URL manipulation is a recurring task in WordPress development: adding a status parameter to an admin redirect after a form submission, appending a pagination variable to a REST API request, stripping UTM parameters from a URL before storing it, or building a “clear filters” link that removes specific query string keys while preserving others. Doing this correctly with raw PHP string concatenation and parse_url() is error-prone — you have to handle existing query strings, URL encoding, and edge cases around empty values manually. WordPress provides two helper functions for this: add_query_arg() adds or updates query string parameters in a URL, and remove_query_arg() strips named parameters. Both functions handle encoding correctly, work with the current URL when no base URL is supplied, and are safe to chain. This article covers both functions and the critical security consideration that makes their output require escaping before output.
Problem: After processing an admin form, you need to redirect back to the same admin page with a status=updated query parameter added and a stale error parameter removed, without hardcoding the page URL.
Solution: Use add_query_arg() to append the status, remove_query_arg() to strip the error key, and wp_safe_redirect() to perform the redirect. Always escape the URL with esc_url() when outputting it in HTML.
<?php
// ── add_query_arg examples ─────────────────────────────────────────────
// Add a single parameter (uses current URL when second arg omitted)
$url = add_query_arg( 'status', 'updated' );
// https://example.com/wp-admin/options-general.php?page=my-plugin&status=updated
// Add multiple parameters at once
$url = add_query_arg( [
'status' => 'updated',
'section' => 'general',
], admin_url( 'options-general.php' ) );
// Update an existing parameter (overwrites old value)
$url = add_query_arg( 'paged', 3, 'https://example.com/shop/?paged=1' );
// → https://example.com/shop/?paged=3
// ── remove_query_arg examples ──────────────────────────────────────────
// Remove a single parameter
$clean_url = remove_query_arg( 'error', $url );
// Remove multiple parameters
$clean_url = remove_query_arg( [ 'utm_source', 'utm_medium', 'utm_campaign' ], $url );
// ── Practical: redirect after form processing ──────────────────────────
function handle_admin_form_submission() {
if ( ! isset( $_POST['my_nonce'] ) || ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) {
wp_die( 'Security check failed.' );
}
// Process form…
// Remove old error, add success status, redirect
$redirect = remove_query_arg( 'error',
add_query_arg( 'status', 'saved', wp_get_referer() ?: admin_url() )
);
wp_safe_redirect( $redirect );
exit;
}
// ── ALWAYS escape before HTML output ──────────────────────────────────
$clear_link = remove_query_arg( [ 'filter_cat', 'filter_tag' ] );
echo '<a href="' . esc_url( $clear_link ) . '">' . esc_html__( 'Clear filters', 'textdomain' ) . '</a>';
NOTE: add_query_arg() output is not automatically escaped — if you echo the return value directly into HTML without esc_url(), it is an XSS vulnerability. This is a very common mistake because the function looks like a utility function, not a user-supplied input function. The URL it builds could contain characters that break HTML attribute context. Always wrap the output in esc_url() for HTML output or use esc_url_raw() for database storage or HTTP header values.