I am trying to retrieve the slug of the current WordPress page outside the loop. The title of the page returns with wp_title (), but how can I get the slug?

  <a href="/slug-of-current-page/">
    <?php wp_title('', true); ?>

12 Answers 12


Use the global variable $post:

    global $post;
    $post_slug = $post->post_name;
  • 4
    Thank you. Your solution works great. Just need to echo the slug: <?php global $post; $post_slug=$post->post_name; echo $post_slug; ?>
    – sarytash
    Commented Feb 13, 2012 at 12:13
  • 2
    Like sarytash said, you need to echo it. So, this'd be ideal: <?php global $post; echo $post->post_name; ?>
    – its_me
    Commented Oct 11, 2013 at 15:59
  • What about $WP_Post? Commented Apr 24, 2019 at 13:00
  • Won't work if you are say, on yourpage.com/search if this is not an existing page but a rewrite from ?s=
    – trainoasis
    Commented Oct 14, 2020 at 12:55
  • 2
    isn't post_name the human readable title (including empty spaces) instead of the slug?
    – abinmorth
    Commented Jul 7, 2021 at 17:01

As per other answers, slug is stored in the post_name property. While it could be accessed directly, I prefer the (underused) get_post_field() function for accessing post properties which have no proper API for them.

It requires post provided explicitly and doesn't default to the current one, so in full for the current post it would be:

$slug = get_post_field( 'post_name', get_post() );
  • 15
    It is worth noting that if you are in the loop you can use get_post_field without second argument (docs)
    – jmarceli
    Commented Jun 16, 2016 at 6:42
  • Small note: I have been using this method in about 8 different websites with my plugin, but it did fail to retrieve the right page slug on the latest website I installed the plugin on. The themes or other plugins itself probably messed up the right reference to the $post object, resulting in the wrong post's slug. Anyway, @Pieter Goosen's answer did fix this problem for me: wordpress.stackexchange.com/a/188945/150100
    – Vasco
    Commented Oct 19, 2020 at 10:07
  • 2
    This should be accepted answer
    – Lovor
    Commented Feb 17, 2021 at 13:13
  • It's probably obvious, but also want to add that the 2nd argument can be a post ID as well.
    – Gavin
    Commented Jun 20, 2021 at 5:57
  • yes this or the answer by Pieter below should be the accepted answer! why? ... if you globalize $post in a function and then inadvertently assign a value to it... you just overwrote the global $post which could break subsequent code... better to use this example or get_queried_object()->post_name
    – aequalsb
    Commented Jul 29, 2021 at 18:15


After digging for more reliability, I ended up doing this answer to the following post which leads to this edit: (Be sure to check it out)

The most reliable method till date I could come up with is the following:

// Get the queried object and sanitize it
$current_page = sanitize_post( $GLOBALS['wp_the_query']->get_queried_object() );
// Get the page slug
$slug = $current_page->post_name;

This way, you are 99.9999% sure that you get the correct data every time.


Another safer alternative to this problem is using get_queried_object() which holds the current queried object to get the page slug which is held by the post_name property. This can be used anywhere in your template.

$post can be used, but it can be unreliable as any custom query or custom code can change the value of $post, so it should be avoided outside of the loop.

Using get_queried_object() to get the current page object is much more reliable and is less likely to be modified, unless you are using the evil query_posts which breaks the main query object, but then that is all up to you.

You can use the above as follow

if ( is_page() )
    $slug = get_queried_object()->post_name;
  • I must say that query_posts is not evil when you want to alter the main query, which however you usually don't and is often misused :)
    – jave.web
    Commented Mar 3, 2018 at 21:12

The simple way to get the slug is with:

<?php echo basename(get_permalink()); ?>
  • 4
    this depends on the permalink settings. If you use the "simple" setting, links will look like http://domain/?p=123, leaving you with ?p=123.
    – Mene
    Commented Oct 14, 2016 at 10:36
  • 1
    @Mene true, but question is how to get slug which, usually, means there is one in the url (GET arg p is not a slug).
    – jave.web
    Commented Feb 17, 2020 at 11:43
  • This is such a neat one liner :D Commented Mar 13, 2020 at 15:46
  • Nice, very nice :)
    – Chaoley
    Commented Oct 21, 2020 at 3:46

Given the code example, it looks like what you really need is a link. In that case, you can use get_permalink(), which can be used outside of the loop. That should do what you need more reliably than using the post slug.

  • 4
    This is the full URL though, not just the slug.
    – Fred
    Commented Nov 21, 2014 at 15:09

You can simply explode the slug from the request.

global $wp;
// Since slugs itself can't contain slashes,
// let's explode on slashes and get just the last portion.
$request_args = explode('/', $wp->request);
$current_slug = end($request_args);

// Given the URL of https://example.com/foo/bar/foo-bar
if ($current_slug === 'foo-bar') {
  // the condition will match.

This works for all posts, pages, custom routes.

  • 3
    This is the best answer to this question - nothing worked until I tried this.
    – Chris
    Commented Aug 14, 2020 at 21:20

Might be an old question, but I created the functions get_the_slug() and the_slug() based on your answers.

if ( !function_exists("get_the_slug") ) {
    * Returns the page or post slug.
    * @param int|WP_Post|null $id (Optional) Post ID or post object. Defaults to global $post.
    * @return string
    function get_the_slug( $id = null ){
        $post = get_post($id);
        if( !empty($post) ) return $post->post_name;
        return ''; // No global $post var or matching ID available.
    * Display the page or post slug
    * Uses get_the_slug() and applies 'the_slug' filter.
    * @param int|WP_Post|null $id (Optional) Post ID or post object. Defaults to global $post.
    function the_slug( $id=null ){
        echo apply_filters( 'the_slug', get_the_slug($id) );

This is the function to use when wanting to retrieve the slug outside of the loop.

get_post_field( 'post_name');

Answer found here: How to Retrieve the Slug of Current Page in WordPress?

  • Indeed, but you need to pass $post or ID of the post as a second argument.
    – trainoasis
    Commented Oct 17, 2019 at 11:29

If you want a more under-the-hood answer, you can use the following SQL query to fetch all of the posts that are either posts, pages, or custom taxonomies at any time, even if no hooks have fired whatsoever as of yet.

Raw SQL:

SELECT `id`, `post_type` AS `type`, `post_author` AS `author`, `post_name` AS 
`slug`, `post_status` AS `status`
FROM wp_posts 
WHERE `post_type` NOT IN ('attachment', 'nav_menu_item', 'revision')
AND `post_status` NOT IN ('draft', 'trash')
ORDER BY `id`;

This works even on the very first line of your functions file, even prior to the mu_plugins_loaded or init hooks.


This is assuming you have a standard database prefix wp_posts. If you need to account for variable prefixes, you can obtain the correct post table through PHP pretty easily by doing the following:

global $wpdb;
$table = $wpdb->posts;
$query = "SELECT `id`, `post_type` AS `type`, `post_author` AS `author`, `post_name` AS 
`slug`, `post_status` AS `status`
FROM " . $table . "
WHERE `post_type` NOT IN ('attachment', 'nav_menu_item', 'revision')
AND `post_status` NOT IN ('draft', 'trash')
ORDER BY `id`;"

Then run with either $wpdb, mysqli, or a PDO instance. Since there is no user input in this query, it is safe to run without a prepared statement as long as you do not inject any variables into it.

I would suggest storing this as a private static value of a class, so it can be accessed without having to fire the query again more than once per page for best performance, something like this:

class Post_Cache
    private static $post_cache;

    public function __construct()
        //This way it skips the operation if it's already set.

    public function get($id, $type = null)
        if ( !(is_int( $id ) && array_key_exists( $id, self::$post_cache ) ) )
            return false;
        if ( !is_null( $type ) )
            //returns the specific column value for the id
            return self::$post_cache[$id][$type];
        //returns the whole row
        return self::$post_cache[$id];

    private function initCache()
        if ( is_null(self::$post_cache) )

            $query = "...";
            $result = some_query_method($query); //Do your query logic here.
            self::$post_cache = $result;


$cache = new \Post_Cache();

//Get the page slug
$slug = $cache->get( get_the_ID(), 'slug');

if ($cache->get( get_the_ID() ))
    //post exists
} else {
    //nope, 404 'em
if ( $cache->get( get_the_ID(), 'status') === 'publish' )
    //it's public
} else {
    //either check current_user_can('whatever_permission') or just 404 it,
    //depending whether you want it visible to the current user or not.
if ( $cache->get( get_the_ID(), 'type') === 'post' )
    //It's a post
if ( $cache->get( get_the_ID(), 'type') === 'page' )
    //It's a page

You get the gist. If you need further details, you can fetch them as per normal with new \WP_Post( get_the_ID() );

This will let your check the posts at any time, even if the wordpress loop has not hit a point where it finds your request agreeable. This is a slightly more optimized version of the same query run by the Wordpress core itself. This one filters out all of the junk you would not want returned, and just gives you a nicely organized list with the relevant author id, post type, slug, and visibility. If you need further details, you can fetch them as per normal with new \WP_Post($id);, or use any of the other native Wordpress functions with any of the relevant table rows, even outside of the loop.

I use a similar setup in a couple of my own custom themes and plugins, and it works pretty great. It's also secure and doesn't leave internal data floating around in the global scope where it can be overridden like most stuff in Wordpress does.


If you are in the loop then the other answers will help you. If not (for example you are hooking on init or plugins_loaded) you can resort to a PHP primitive like parse_url().

Here's a function that works in both cases:

function get_the_slug() {
    global $post;
    $slug = $post->post_name ?? '';

    if ( ! $slug ) {
        $slug = basename( parse_url( $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH ) );

    return $slug;

Please note that this approach only works on posts/pages at root level, because of how basename() works.


Just further on @Matthew Boynes answer, if you're interested in getting the parent slug (if any) also then I've found this function useful:

function mytheme_get_slugs() {
    if ( $link = get_permalink() ) {
        $link = str_replace( home_url( '/' ), '', $link );
        if ( ( $len = strlen( $link ) ) > 0 && $link[$len - 1] == '/' ) {
            $link = substr( $link, 0, -1 );
        return explode( '/', $link );
    return false;

Eg to add the slug(s) to the body class:

function mytheme_body_class( $classes ) {
    if ( $slugs = mytheme_get_slugs() ) {
        $classes = array_merge( $classes, $slugs );
    return $classes;
add_filter( 'body_class', 'mytheme_body_class' );

Dynamic Page calling in WordPress.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.