Skip to content

Get the Current WordPress URL: Permalink or Requested URL?

Tested November 2026 on WordPress 7.1.4 (PHP 8.3).

If you use get_permalink() to get the current WordPress page URL, it can look correct on a single post and return a post’s URL on your homepage. The first choice is what you actually need: the queried post’s permalink, or the requested path with its query string.

I tested this on a fresh WordPress 7.1.4 site with PHP 8.3.36, MariaDB 11.8.9 and Twenty Twenty-Five 1.5. I decided to keep these two jobs separate, rather than use one function whose meaning changes between a post, an archive and a search page.

The snippet below gives you both options. For the requested URL, it uses the site’s home_url() origin instead of building the hostname from the incoming Host header.

The short answer

  • Use tp_singular_permalink() for the queried singular post or page. It calls get_permalink( get_queried_object_id() ) only when is_singular() is true.
  • Use tp_requested_url() for the requested path and query string on the site’s configured host, including pagination and tracking parameters. This is not a canonical-permalink helper.
  • Call the helpers after the main query is available. When inserting either result into an HTML href, escape it with esc_url() and skip the link if the result is empty.

1. Add the two URL helpers

Copy & paste the following code snippet:

<?php
function tp_requested_url() {
    $home = wp_parse_url( home_url( '/' ) );
    $uri  = $_SERVER['REQUEST_URI'] ?? '/';
    if ( ! str_starts_with( $uri, '/' ) ) {
        return '';
    }
    return $home['scheme'] . '://' . $home['host']
        . ( isset( $home['port'] ) ? ':' . $home['port'] : '' )
        . $uri;
}

function tp_singular_permalink() {
    return is_singular() ? get_permalink( get_queried_object_id() ) : '';
}Code language: PHP (php)

Where to add this code

Add the snippet with a code snippets plugin instead of your theme’s functions.php, so it survives theme updates and you can switch it off without FTP. My guide to adding code snippets safely shows how, using the free Code Snippets plugin. On my own sites I use WPCodeBox, which keeps snippets in a cloud library so the same snippet can go on several sites. Not sure which plugin to choose? I compared Code Snippets, WPCode and WPCodeBox by feeding each one broken snippets. (Marius)

Disclosure: the WPCodeBox link is an affiliate link. If you buy through it, I earn a commission at no extra cost to you.

The snippet defines the helpers only. Adding it does not automatically print a URL or add a link to your pages.

There are multiple ways to build a current URL, but I prefer these separate names because they make the choice clear where the function is called.

For the queried post or page

tp_singular_permalink() checks is_singular() first, then passes get_queried_object_id() to get_permalink().

That explicit ID matters. It identifies the requested singular content instead of relying on whichever post is currently in the global post variable.

In the pretty-permalink test, the requested address included /my-first-post/?utm_source=newsletter&color=blue. The singular helper returned http://example.com/my-first-post/, without the tracking parameters.

For the requested path and query string

tp_requested_url() takes the scheme, host and optional port from home_url( '/' ). It then appends REQUEST_URI, which supplies the requested path and query string.

For the same request, it returned http://example.com/my-first-post/?utm_source=newsletter&color=blue.

I would use this when the query arguments are part of what I need to keep, not as a replacement for the post’s permanent link.

2. Call the helper after WordPress knows the request

Next, call the helper from your template or another point after the main query is available. In the singular rendering test, both the implicit get_permalink() call and the explicit queried-object version worked at the wp hook, before template rendering.

If you need a permalink link, get the result from tp_singular_permalink(), check that it is not empty, then pass it through esc_url() when placing it in the link’s href.

For a requested-URL link, follow the same pattern with tp_requested_url().

Don’t skip the empty-result check. The singular helper intentionally returns an empty string on non-singular requests. The requested-URL helper also returns an empty string when REQUEST_URI does not start with /.

I wouldn’t use this without checking an archive and a missing post, not just a successful single-post request.

On the posts homepage, the singular helper returned an empty string. On a category archive, page 2, it also returned an empty string, while the requested-URL helper kept /category/news/page/2/?utm_source=newsletter.

The missing Plain post request, /?p=999999&utm_source=newsletter, returned HTTP 404. It was not singular, and no singular permalink link was printed.

That is the difference I want: no post permalink when the request is not a singular post or page. The requested-URL helper can still construct a URL for a missing resource, so its result is not an existence check.

4. What I tried and why I didn’t pick it

Several suggested solutions reconstruct the URL from WordPress’s request path or query variables. These were the useful differences in the test:

MethodWhat happenedWhy I didn’t pick it for the requested URL
get_permalink() without an IDOn the posts homepage, it returned a post URL before the loop and the last iterated post URL afterwards.Its result followed the post context, not the homepage request.
home_url( $wp->request )On the pretty single-post request, it omitted the query string and trailing slash.It did not preserve the requested address.
add_query_arg( $wp->query_vars, home_url( $wp->request ) )On the pretty single-post request, it appended routing variables instead of recovering the tracking arguments.WordPress query variables were not a copy of the original query string.
home_url( add_query_arg( array(), $wp->request ) )It also omitted the tracking query and trailing slash.It did not return the full requested URL in that test.
home_url( $_SERVER['REQUEST_URI'] )It preserved the query on the root-site request, but duplicated /journal in a CLI construction check.The request path can already contain the home subdirectory.

The older advice that get_permalink() only works inside the loop did not hold in this test. It worked on a singular request at the wp hook before template rendering.

The more useful distinction was which post it used.

During a secondary loop, the implicit call changed to http://example.com/summer-sale/. Both the explicit queried-object ID and tp_singular_permalink() retained http://example.com/my-first-post/.

On the posts homepage, resetting post data returned the first post URL, not the homepage URL. Moving or resetting the loop therefore did not turn get_permalink() into a requested-page URL function.

With Plain permalinks, the singular helper returned http://example.com/?p=5.

The query-variable methods kept the post selector but dropped the arbitrary utm_source and color arguments. Both request-based methods kept those arguments.

On a successful Plain search request with pagination, the query-variable methods retained s and paged but lost utm_source. The requested-URL helper preserved all three, while the singular helper returned an empty string.

5. Keep the configured host and escape the output

With a hostile Host: attacker.invalid header, tp_requested_url() retained example.com. Direct concatenation using HTTP_HOST produced a URL on attacker.invalid.

The helper’s scheme follows home_url() in the current environment. With X-Forwarded-Proto: https, the test returned an HTTPS URL even though the stored home option used HTTP. “Configured host” does not mean the scheme is always fixed to the stored option.

I also checked a configured port and home subdirectory through CLI construction checks. The helper retained port 8080 and included /journal once. Passing the same request URI directly to home_url() duplicated /journal. This was a URL-construction check, not a running subdirectory installation.

Last, but not least, escape the URL at output. For the encoded attribute payload sent over HTTP, the emitted href retained percent-encoded quotes and spaces, and esc_url() escaped the query separator as &#038;.

The final round’s PHP diagnostics contained no logged notices, warnings, errors or deprecation messages.

Limitations

  • tp_requested_url() is not a canonical URL function and does not confirm that the requested resource exists.
  • It preserves query arguments, including potentially sensitive values. Don’t automatically publish every requested URL into share links, public caches or analytics.
  • Guard empty results before printing a link, otherwise you can still produce an empty href.
  • The escaping check covered server-generated HTML, not browser execution.
  • Reverse-proxy trust settings need their own review. The forwarded-scheme result does not validate a production proxy configuration.
  • No older WordPress or PHP release was tested.

Leave a Reply

Your email address will not be published. Required fields are marked *