wppaste
WordPress

add_query_arg( $args ): string

Since
1.5.0, 5.3.0
Source
wp-includes/functions.php:1132

Builds a URL with one or more query string parameters added, updated, or removed by passing false as a value. Works with either a single key/value pair or an associative array, and falls back to the current request URI ($_SERVER['REQUEST_URI']) when no URL is supplied. Pair it with esc_url() before printing, since the returned string is not escaped.

Retrieves a modified URL query string.

Description

You can rebuild the URL and append query variables to the URL query by using this function.
There are two ways to use this function; either a single key and value, or an associative array.

Using a single key and value:

add_query_arg( 'key', 'value', 'http://example.com' );

Using an associative array:

add_query_arg( array(
 'key1' => 'value1',
 'key2' => 'value2',
), 'http://example.com' );

Omitting the URL from either use results in the current URL being used (the value of $_SERVER['REQUEST_URI']).

Values are expected to be encoded appropriately with urlencode() or rawurlencode().

Setting any query variable's value to boolean false removes the key (see remove_query_arg()).

Important: The return value of add_query_arg() is not escaped by default. Output should be late-escaped with esc_url() or similar to help prevent vulnerability to cross-site scripting (XSS) attacks.

Compatibility

WordPress
since 5.3.0
PHP
7.4–8.6-dev
  • 6.7.7
  • 6.8.8
  • 6.9.7
  • 7.0.4
  • 7.1.0

Present in every tracked release (6.7.7 to 7.1.0), and compiles on PHP 7.4 through 8.6-dev.

Parameters

$args

Return value

string
New URL query string (unescaped).

Code examples

Every example is editable and runs in a real WordPress booted in your browser by WordPress Playground. Press Run, then edit the code: clicking away re-runs it. Nothing is sent anywhere until you do.

Add pagination and post type query args to an admin list table URL

Build a link back to the post list screen filtered to a given post type and page number.

$base_url = admin_url( 'edit.php' );

$paged_url = add_query_arg(
	array(
		'post_type' => 'post',
		'paged'     => 2,
	),
	$base_url
);

echo esc_html( $paged_url );

The array form lets you set several query variables in one call instead of chaining add_query_arg() calls.

Remove a query variable while adding another in the same call

Strip an existing 'featured' flag from a URL and replace it with a tag filter using the site's 'featured' tag.

$listing_url = 'http://example.com/blog/?cat=news&featured=1';

$updated_url = add_query_arg(
	array(
		'featured' => false,
		'tag'      => 'featured',
	),
	$listing_url
);

echo esc_html( $updated_url );

Setting a value to boolean false removes that key from the query string instead of writing 'key=' or 'key=0'.

Common problems and fixes · 4

Why is my add_query_arg() URL vulnerable when I print it directly?

The function returns the rebuilt query string unescaped, it does no output escaping on its own, so any characters from the query values or the original URL pass straight through.

Why does add_query_arg() give me a URL based on the wrong page?

When the URL argument is omitted, or explicitly passed as false, the function reads $_SERVER['REQUEST_URI'] instead of a URL you control, which is unreliable outside a normal front-end or admin request (cron, CLI, AJAX handlers).

How do I delete a query parameter instead of setting it?

add_query_arg() loops over the merged query args after adding your key(s) and unsets any whose value is exactly boolean false, so passing false as the value removes that key entirely rather than writing an empty value.

Why did my URL argument get treated as a query value instead of the target URL?

Because the function is variadic and inspects is_array($args[0]) to decide whether the URL is the second or third argument, calling it with only two arguments in single key/value form (key, value) makes it fall back to $_SERVER['REQUEST_URI'] instead of using your intended value as the URL.

Alternatives and related functions

remove_query_arg
When you only need to delete one or more query variables from a URL and don't want to bother constructing a false-valued array for add_query_arg().
esc_url
When the URL returned by add_query_arg() is going to be echoed into HTML, since add_query_arg() does not escape its return value itself.
build_query
When you already have a clean associative array and just need to turn it into a query string without any URL parsing or merging logic.
home_url
When you need a base site URL to pass as the URL argument instead of relying on the current request's REQUEST_URI.

Performance profile

How much work a call to add_query_arg() does, and what it touches: the algorithmic scaling, the Zend instruction count per call across PHP versions, the hooks it hands control to, and the core code that calls it. Measured from the compiled opcodes, not a stopwatch, so every number is identical on any machine running the same PHP version, and every function in core is ranked by cost.

Cost class
Light

Touches nothing outside its own arguments.

Scaling
Scales with input

The body loops, so the work grows with what you pass in.

Instructions
68–90

Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 129.

Plugin surface
None

Nothing here hands control to plugin code.

Called by
50

50 places in core call this, so the cost is paid more often than your own code shows.

What it touches

  • hookthird-party callbacksapply_filters()one call below add_query_arg()

What one call costs · 4 distinct outcomes

One number would be a lie: the work depends on which branch runs. These are every distinct cost add_query_arg() can have, taken from its control-flow graph on PHP 8.5.

WhenInstructionsCalls it makes
!$uri68–80stripos(), wp_parse_str(), urlencode_deep(), build_query(), rtrim()
!$uri71–86stripos(), stripos(), wp_parse_str(), urlencode_deep(), build_query(), rtrim()
$uri74–84stripos(), explode(), wp_parse_str(), urlencode_deep(), build_query(), rtrim()
$uri77–90stripos(), stripos(), explode(), wp_parse_str(), urlencode_deep(), build_query(), rtrim()

Across PHP versions

PHPCompiledExecutedBranchesNotes
8.6-dev12968–9017
8.512968–9017
8.412968–901728 fewer instructions than PHP 8.3
8.315787–11217
8.215787–11217
8.115787–11217
7.415787–11217

An instruction is not a fixed amount of time, so a matching count is not necessarily the same speed; what it rules out is a difference in the work itself.

Uses · 5

  • stripos()
  • str_contains()Polyfill for `str_contains()` function added in PHP 8.0.
  • wp_parse_str()Parses a string into variables to be stored in an array.
  • urlencode_deep()Navigates through an array, object, or scalar, and encodes the values to be used in a URL.
  • build_query()Builds URL query based on an associative and, or indexed array.

Used by · 50

Show all 50

Source code

function add_query_arg( ...$args ) {	if ( is_array( $args[0] ) ) {		if ( count( $args ) < 2 || false === $args[1] ) {			$uri = $_SERVER['REQUEST_URI'];		} else {			$uri = $args[1];		}	} else {		if ( count( $args ) < 3 || false === $args[2] ) {			$uri = $_SERVER['REQUEST_URI'];		} else {			$uri = $args[2];		}	} 	$frag = strstr( $uri, '#' );	if ( $frag ) {		$uri = substr( $uri, 0, -strlen( $frag ) );	} else {		$frag = '';	} 	if ( 0 === stripos( $uri, 'http://' ) ) {		$protocol = 'http://';		$uri      = substr( $uri, 7 );	} elseif ( 0 === stripos( $uri, 'https://' ) ) {		$protocol = 'https://';		$uri      = substr( $uri, 8 );	} else {		$protocol = '';	} 	if ( str_contains( $uri, '?' ) ) {		list( $base, $query ) = explode( '?', $uri, 2 );		$base                .= '?';	} elseif ( $protocol || ! str_contains( $uri, '=' ) ) {		$base  = $uri . '?';		$query = '';	} else {		$base  = '';		$query = $uri;	} 	wp_parse_str( $query, $qs );	$qs = urlencode_deep( $qs ); // This re-URL-encodes things that were already in the query string.	if ( is_array( $args[0] ) ) {		foreach ( $args[0] as $k => $v ) {			$qs[ $k ] = $v;		}	} else {		$qs[ $args[0] ] = $args[1];	} 	foreach ( $qs as $k => $v ) {		if ( false === $v ) {			unset( $qs[ $k ] );		}	} 	$ret = build_query( $qs );	$ret = trim( $ret, '?' );	$ret = preg_replace( '#=(&|$)#', '$1', $ret );	$ret = $protocol . $base . $ret . $frag;	$ret = rtrim( $ret, '?' );	$ret = str_replace( '?#', '#', $ret );	return $ret;}

Changelog

Introduced in 1.5.0. Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

5.3.0
Formalized the existing and already documented parameters by adding ...$args to the function signature.from the docblock
1.5.0
Introduced.from the docblock

About this page

Parsed data
Generated from the wordpress-develop 6.7.7 tag, from src/wp-includes/functions.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.
Corrections
Something wrong on this page? Report it and it gets fixed in the next regeneration.