add_query_arg( $args ): string
- Since
- 1.5.0, 5.3.0
- Source
wp-includes/functions.php:1138
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.
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?
- Why does add_query_arg() give me a URL based on the wrong page?
- How do I delete a query parameter instead of setting it?
- Why did my URL argument get treated as a query value instead of the target URL?
Why is my add_query_arg() URL vulnerable when I print it directly?
Why does add_query_arg() give me a URL based on the wrong page?
How do I delete a query parameter instead of setting it?
Why did my URL argument get treated as a query value instead of the target 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
- Scaling
- Scales with input
- Instructions
- 68–90
- Plugin surface
- None
- Called by
- 50
Touches nothing outside its own arguments.
The body loops, so the work grows with what you pass in.
Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 129.
Nothing here hands control to plugin code.
50 places in core call this, so the cost is paid more often than your own code shows.
What it touches
- hookthird-party callbacks
apply_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.
| When | Instructions | Calls it makes |
|---|---|---|
!$uri | 68–80 | stripos(), wp_parse_str(), urlencode_deep(), build_query(), rtrim() |
!$uri | 71–86 | stripos(), stripos(), wp_parse_str(), urlencode_deep(), build_query(), rtrim() |
$uri | 74–84 | stripos(), explode(), wp_parse_str(), urlencode_deep(), build_query(), rtrim() |
$uri | 77–90 | stripos(), stripos(), explode(), wp_parse_str(), urlencode_deep(), build_query(), rtrim() |
Across PHP versions
| PHP | Compiled | Executed | Branches | Notes |
|---|---|---|---|---|
| 8.6-dev | 129 | 68–90 | 17 | |
| 8.5 | 129 | 68–90 | 17 | |
| 8.4 | 129 | 68–90 | 17 | 28 fewer instructions than PHP 8.3 |
| 8.3 | 157 | 87–112 | 17 | |
| 8.2 | 157 | 87–112 | 17 | |
| 8.1 | 157 | 87–112 | 17 | |
| 7.4 | 157 | 87–112 | 17 |
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
- Custom_Image_Header::step_1()Displays first step of custom header image page.
- Custom_Image_Header::step_2()Displays second step of custom header image page.
- Featured_Content::customize_register()Add settings to the Customizer.
- Plugin_Installer_Skin::do_overwrite()Checks if the plugin can be overwritten and outputs the HTML for overwriting a plugin on upload.
- Theme_Installer_Skin::after()Performs an action following a single theme install.
- Theme_Installer_Skin::do_overwrite()Checks if the theme can be overwritten and outputs the HTML for overwriting a theme on upload.
- Theme_Upgrader_Skin::after()Performs an action following a single theme update.
- WP_Automatic_Updater::has_fatal_error()Performs a loopback request to check for potential fatal errors.
- WP_Comments_List_Table::column_author()
- WP_Comments_List_Table::get_views()
- WP_Customize_Manager::add_state_query_params()Adds customize state query params to a given URL if preview is allowed.
- WP_Customize_Manager::customize_pane_settings()Prints JavaScript settings for parent window.
Show all 50
- WP_Customize_Manager::handle_load_themes_request()Loads themes into the theme browsing/installation UI.
- WP_List_Table::comments_bubble()Displays a comment count bubble.
- WP_List_Table::pagination()Displays the pagination.
- WP_List_Table::print_column_headers()Prints column headers, accounting for hidden and sortable columns.
- WP_List_Table::set_pagination_args()Sets all the necessary pagination arguments.
- WP_List_Table::view_switcher()Displays a view switcher.
- WP_MS_Sites_List_Table::get_views()Gets links to filter sites by status.
- WP_MS_Themes_List_Table::column_autoupdates()Handles the auto-updates column output.
- WP_MS_Themes_List_Table::column_name()Handles the name column output.
- WP_MS_Themes_List_Table::get_views()
- WP_MS_Users_List_Table::column_username()Handles the username column output.
- WP_MS_Users_List_Table::handle_row_actions()Generates and displays row action links.
- WP_Media_List_Table::column_author()Handles the author column output.
- WP_Media_List_Table::column_default()Handles output for the default column.
- WP_Media_List_Table::column_parent()Handles the parent column output.
- WP_Plugin_Dependencies::check_plugin_dependencies_during_ajax()Checks plugin dependencies after a plugin is installed via AJAX.
- WP_Plugin_Install_List_Table::get_more_details_link()Creates a 'More details' link for the plugin.
- WP_Plugins_List_Table::__construct()Constructor.
- WP_Plugins_List_Table::get_view_details_link()Returns a 'View details' link for the plugin.
- WP_Plugins_List_Table::get_views()
- WP_Plugins_List_Table::single_row()
- WP_Posts_List_Table::get_edit_link()Creates a link to edit.php with params.
- WP_Posts_List_Table::get_views()
- WP_Privacy_Data_Export_Requests_List_Table::column_email()Actions column.
- WP_Privacy_Data_Export_Requests_List_Table::column_next_steps()Displays the next steps column.
- WP_Privacy_Data_Removal_Requests_List_Table::column_email()Outputs the Actions column.
- WP_Privacy_Data_Removal_Requests_List_Table::column_next_steps()Outputs the Next steps column.
- WP_Privacy_Requests_Table::get_views()Gets an associative array ( id => link ) with the list of views available on this table.
- WP_REST_Block_Directory_Controller::prepare_links()Generates a list of links to include in the response for the plugin.
- WP_REST_Block_Types_Controller::prepare_links()Prepares links for the request.
- WP_REST_Comments_Controller::get_items()Retrieves a list of comment items.
- WP_REST_Comments_Controller::prepare_links()Prepares links for the request.
- WP_REST_Font_Collections_Controller::get_items()Gets the font collections available.
- WP_REST_Global_Styles_Revisions_Controller::get_items()Returns paginated revisions of the given global styles config custom post type.
- WP_REST_Post_Statuses_Controller::prepare_item_for_response()Prepares a post status object for serialization.
- WP_REST_Posts_Controller::get_items()Retrieves a collection of posts.
- WP_REST_Posts_Controller::prepare_links()Prepares links for the request.
- WP_REST_Revisions_Controller::get_items()Gets a collection of revisions.
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.
Signature, return type and hooks compared across 5 parsed releases.
...$args to the function signature.from the docblockAbout this page
- Parsed data
- Generated from the wordpress-develop 6.8.8 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.