To get the current page URL in WordPress with PHP, combine the global $wp object with home_url(). This works on any page type (posts, pages, archives, categories, tags) and returns a clean, full URL:
global $wp;
$current_url = home_url( add_query_arg( array(), $wp->request ) );
echo esc_url( $current_url );
Below I’ll cover the variations you’ll actually need: just the slug, the URL with query string intact, how to read a URL parameter safely, the WordPress-native alternatives (get_permalink(), home_url(), site_url()), a plain-PHP fallback when you can’t use WordPress functions, just the domain, and a shortcode you can drop into any post or page.
- Use
home_url( add_query_arg( array(), $wp->request ) )for the current URL on any page type. It never touches$_SERVERand works on subdirectory installs. - To keep the query string, pass
$_GETinstead of an empty array. The widely copiedadd_query_arg( null, null )reads the raw request URI and doubles the path on sites installed in a subdirectory. - Read a URL parameter with
sanitize_text_field( wp_unslash( $_GET['key'] ) ), or register it with thequery_varsfilter and useget_query_var(). - For just the domain,
wp_parse_url( home_url(), PHP_URL_HOST )is the safe answer;$_SERVER['HTTP_HOST']comes from the request and can be spoofed. - Always escape on output:
esc_url()for URLs,esc_html()oresc_attr()for parameter values.
Method 1: Get the current URL with the global $wp object
This is the canonical WordPress-native way. It respects your permalink structure, works on any page type, and honors HTTPS and the site’s home setting without hardcoding anything.
function my_get_current_url() {
global $wp;
return home_url( add_query_arg( array(), $wp->request ) );
}
// Usage
echo esc_url( my_get_current_url() );
$wp->request holds the path after your domain (for example, about or blog/my-post). Wrapping it in home_url() prepends your site’s base URL, so the result is always a full, correctly-protocolled URL regardless of the environment.
Always pass output through esc_url() before printing it in HTML. It’s WordPress’s built-in function for escaping URLs safely.
Method 2: Get the current URL with query string
Method 1 strips the query string. If you want the URL including anything after the ? (e.g. ?page=2&s=keyword), use add_query_arg() with no arguments:
global $wp;
$current_url_full = home_url( add_query_arg( $_GET, $wp->request ) );
echo esc_url( $current_url_full );
You will see home_url( add_query_arg( null, null ) ) recommended for this all over the web, and it usually works, but it is worth knowing what it actually does. When add_query_arg() is not given a URL it falls back to $_SERVER['REQUEST_URI'], the raw request path with its query string. That has two consequences. It is the same unsanitised value Method 5 warns about, so the esc_url() on output is doing real work rather than being a formality. And REQUEST_URI includes any subdirectory your site lives in, while home_url() adds it again, so on a site whose address is example.com/blog that snippet hands back example.com/blog/blog/....
The version above avoids both problems. $wp->request is the path with the home directory already stripped and no query string, and $_GET supplies the current parameters, which add_query_arg() re-encodes for you. It comes back without a trailing slash, the same as Method 1.
One more option for single posts and pages: you may not want the current URL at all but the canonical one, which is what search engines and share buttons should be given. wp_get_canonical_url(), available since WordPress 4.6, returns the permalink of the published post, adds the right /2/ style suffix when the post itself is paginated, and returns false for anything that is not a published post.
If you only care about one query parameter, use get_query_var() instead of parsing the URL manually. It works with both pretty permalinks and query-string params.
Get a URL parameter in WordPress
Getting the whole URL is often a detour; what you actually want is one value out of it, like the ref in ?ref=newsletter. There are two sane ways to do that in WordPress, and one common way that is not.
The direct route is PHP’s $_GET, with two WordPress habits applied: wp_unslash(), because WordPress adds slashes to request data, and a sanitiser that matches the type you expect:
// Read ?ref=... safely. Never echo $_GET raw.
$ref = isset( $_GET['ref'] ) ? sanitize_text_field( wp_unslash( $_GET['ref'] ) ) : '';
// Numbers: cast, do not trust.
$page = isset( $_GET['page'] ) ? absint( $_GET['page'] ) : 1;
The WordPress route is to register the parameter as a public query variable, after which get_query_var() will hand it to you anywhere, including inside rewrite rules and pretty permalinks:
// Register the parameter once, then WordPress will parse it for you.
add_filter( 'query_vars', function ( $vars ) {
$vars[] = 'ref';
return $vars;
} );
// Anywhere after the query has run:
$ref = get_query_var( 'ref' ); // '' when the parameter is absent
get_query_var() only knows about registered variables, which is why calling it for an arbitrary ?foo=bar returns an empty string until you add foo through the query_vars filter. Reserve it for parameters your code owns; use the $_GET version for one-offs.
The way that is not sane is echoing $_GET straight into the page, or using a parameter to decide something that matters (which post to edit, whether to run an action) without a nonce and a capability check. A URL parameter is typed by whoever sends the request. Sanitise it on the way in, escape it on the way out, and never let it authorise anything.
Method 3: Get just the current slug
If you only need the path (everything after the domain), the global $wp object has it ready:
global $wp;
$slug = $wp->request; // e.g. "about" or "blog/my-post"
This returns whatever WordPress parsed as the request path, with no leading or trailing slashes. For deeper slug handling (post slug vs. path, terms, pagination), see the dedicated guide on getting the current page slug.
Method 4: Use get_permalink() inside the loop
If you’re inside the WordPress loop or have access to a $post object, get_permalink() returns the URL of the current post or page. It’s the cleanest option for single-post templates:
// Inside the loop
$permalink = get_permalink();
// Or by ID
$permalink = get_permalink( 42 );
echo esc_url( $permalink );
The difference from Method 1: get_permalink() only works for posts and pages, not for archive, category, tag, or search result pages. Use global $wp when you need a universal solution.
Method 5: Plain PHP fallback (no WordPress functions)
Sometimes you need the current URL in a context where WordPress functions aren’t loaded (for example, a stand-alone PHP file you drop into your server). Use $_SERVER:
$is_https = ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] )
|| ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] );
$current_url = ( $is_https ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
// esc_url() is a WordPress function, so it is not available here.
echo htmlspecialchars( $current_url, ENT_QUOTES, 'UTF-8' );
The HTTP_X_FORWARDED_PROTO check matters more than it used to. Behind Cloudflare, a load balancer or most managed hosts, PHP sees a plain HTTP connection from the proxy and $_SERVER['HTTPS'] is empty even though the visitor is on HTTPS; the proxy passes the real scheme in that header instead. When WordPress is loaded, is_ssl() does the first half of this check for you, though behind a proxy you may still need the usual forwarded-proto fix in wp-config.php.
When WordPress is loaded, this is the less-safe option. $_SERVER values can be manipulated by request headers, and you’d need to sanitize them carefully. Stick with Method 1 unless you specifically need a zero-dependency snippet. If WordPress is available in your context, escape with esc_url() instead of htmlspecialchars().
Method 6: Current URL as a shortcode
If you want to drop the current URL into a post or page without writing PHP in a template, register a shortcode. Drop this into your theme’s functions.php or a code snippets plugin:
add_shortcode( 'current_url', function() {
global $wp;
return esc_url( home_url( add_query_arg( array(), $wp->request ) ) );
} );
Now you can type [current_url] inside any post, page, or widget and it’ll output the current URL at render time.
home_url() vs site_url() vs get_permalink()
Three WordPress URL functions that look similar but do different things:
| Function | Returns | Use when |
|---|---|---|
home_url() | The site’s public home address (Settings → General → Site Address) | Building URLs for visitors |
site_url() | Where WordPress itself is installed (WordPress Address) | Building admin, wp-login, or WP core URLs |
get_permalink() | The URL of a specific post or page | Inside the loop or when you have a post ID |
For most themes, home_url() and site_url() return the same value. They diverge when WordPress is installed in a subdirectory but served from the root (for example, WP installed at /wp/ but the site lives at /).
Get just the domain or host name
Sometimes the question is not “what is the current URL” but “what domain is this site on”, for building an email address, a cookie domain, or a comparison. Take it from the site’s own setting rather than from the request:
$host = wp_parse_url( home_url(), PHP_URL_HOST ); // example.com
wp_parse_url() is WordPress’s wrapper around PHP’s parse_url() with more consistent behaviour across PHP versions, and home_url() reflects the Site Address setting, so the result is the domain you configured, not whatever host header the request arrived with. $_SERVER['HTTP_HOST'] gives you the latter, which is fine for logging and wrong for anything security-related, because a client can send any host header it likes. On a multisite network, home_url() already returns the current site’s address, so the same line works there too.
Frequently asked questions
How do I get the current URL in WordPress?
Use the global $wp object with home_url(): $url = home_url( add_query_arg( array(), $wp->request ) );. This returns the full URL of whatever page WordPress is currently rendering (posts, pages, archives, tags, categories). Wrap the output in esc_url() before printing it.
How do I get the current URL in PHP without WordPress?
Use $_SERVER: combine the protocol ($_SERVER['HTTPS']), host ($_SERVER['HTTP_HOST']), and request URI ($_SERVER['REQUEST_URI']). When WordPress is available, prefer the $wp + home_url() approach because $_SERVER values can be manipulated via request headers and need extra sanitization.
What is the difference between home_url() and site_url()?
home_url() returns the public-facing address of your site (Settings → General → Site Address). site_url() returns where WordPress itself is installed (WordPress Address). They’re usually identical, but differ when WordPress lives in a subdirectory like /wp/ while the site is served from /.
How do I get just the slug of the current page?
Use $wp->request. It holds the path after your domain (for example, about or blog/my-post) with no leading or trailing slashes. For more slug variants (post slug alone, parent slug, term slug), see the dedicated slug guide.
How do I get the current URL with query parameters?
Pass the current parameters and the clean path to add_query_arg(): $full = home_url( add_query_arg( $_GET, $wp->request ) );. The common add_query_arg( null, null ) version reads $_SERVER['REQUEST_URI'] and doubles the path on sites installed in a subdirectory. Use get_query_var() or a sanitised $_GET read if you only need one parameter.
How do I get a URL parameter in WordPress?
For a one-off, read it from $_GET and sanitise it: $ref = isset( $_GET['ref'] ) ? sanitize_text_field( wp_unslash( $_GET['ref'] ) ) : '';. For a parameter your code owns, add its name through the query_vars filter and read it with get_query_var( 'ref' ), which returns an empty string when it is absent. Escape the value before printing it, and never let a URL parameter authorise an action on its own.
How do I get the site’s domain name in WordPress?
wp_parse_url( home_url(), PHP_URL_HOST ) returns the host from your Site Address setting, for example example.com. Prefer it over $_SERVER['HTTP_HOST'], which is taken from the incoming request and can be set to anything by the client.
Can I display the current URL in a WordPress shortcode?
Yes. Register a shortcode in functions.php or a code snippets plugin: add_shortcode( 'current_url', function() { global $wp; return esc_url( home_url( add_query_arg( array(), $wp->request ) ) ); } );. Then use [current_url] inside any post, page, or widget.
Why is $_SERVER[‘REQUEST_URI’] not safe in WordPress?
It’s not automatically sanitized, so attackers can inject values via request headers or crafted URLs. If you output it without escaping, you risk reflected XSS. Always pass $_SERVER['REQUEST_URI'] through esc_url() or esc_attr(), or switch to the WordPress-native home_url() + $wp->request approach which handles sanitization for you.
Bottom line
Most of the time you want home_url( add_query_arg( array(), $wp->request ) ) wrapped in esc_url(). That pattern works on every page type, respects your site’s permalink structure and HTTPS settings, and sanitizes cleanly.
Switch to get_permalink() when you’re inside the loop or have a post ID, and drop to plain $_SERVER only when WordPress functions aren’t available. If what you really wanted was one parameter out of the URL, sanitise it from $_GET or register it as a query var, and escape it on the way out.
Related: how to get the current page slug, get the post ID, or browse the full WordPress code snippets library.



One Response
Thank you. Very helpful.