get_page_by_path( string $page_path, string $output = OBJECT, string|array $post_type = ‘page’ ): WP_Post|array|null

Retrieves a page given its path.

Parameters

$page_pathstringrequired
Page path.
$outputstringoptional
The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to a WP_Post object, an associative array, or a numeric array, respectively.

Default:OBJECT

$post_typestring|arrayoptional
Post type or array of post types. Default 'page'.

Default:'page'

Return

WP_Post|array|null WP_Post (or array) on success, or null on failure.

Source

function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
	global $wpdb;

	$last_changed = wp_cache_get_last_changed( 'posts' );

	$hash      = md5( $page_path . serialize( $post_type ) );
	$cache_key = "get_page_by_path:$hash:$last_changed";
	$cached    = wp_cache_get( $cache_key, 'post-queries' );
	if ( false !== $cached ) {
		// Special case: '0' is a bad `$page_path`.
		if ( '0' === $cached || 0 === $cached ) {
			return;
		} else {
			return get_post( $cached, $output );
		}
	}

	$page_path     = rawurlencode( urldecode( $page_path ) );
	$page_path     = str_replace( '%2F', '/', $page_path );
	$page_path     = str_replace( '%20', ' ', $page_path );
	$parts         = explode( '/', trim( $page_path, '/' ) );
	$parts         = array_map( 'sanitize_title_for_query', $parts );
	$escaped_parts = esc_sql( $parts );

	$in_string = "'" . implode( "','", $escaped_parts ) . "'";

	if ( is_array( $post_type ) ) {
		$post_types = $post_type;
	} else {
		$post_types = array( $post_type, 'attachment' );
	}

	$post_types          = esc_sql( $post_types );
	$post_type_in_string = "'" . implode( "','", $post_types ) . "'";
	$sql                 = "
		SELECT ID, post_name, post_parent, post_type
		FROM $wpdb->posts
		WHERE post_name IN ($in_string)
		AND post_type IN ($post_type_in_string)
	";

	$pages = $wpdb->get_results( $sql, OBJECT_K );

	$revparts = array_reverse( $parts );

	$foundid = 0;
	foreach ( (array) $pages as $page ) {
		if ( $page->post_name == $revparts[0] ) {
			$count = 0;
			$p     = $page;

			/*
			 * Loop through the given path parts from right to left,
			 * ensuring each matches the post ancestry.
			 */
			while ( 0 != $p->post_parent && isset( $pages[ $p->post_parent ] ) ) {
				++$count;
				$parent = $pages[ $p->post_parent ];
				if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] ) {
					break;
				}
				$p = $parent;
			}

			if ( 0 == $p->post_parent && count( $revparts ) === $count + 1 && $p->post_name == $revparts[ $count ] ) {
				$foundid = $page->ID;
				if ( $page->post_type == $post_type ) {
					break;
				}
			}
		}
	}

	// We cache misses as well as hits.
	wp_cache_set( $cache_key, $foundid, 'post-queries' );

	if ( $foundid ) {
		return get_post( $foundid, $output );
	}

	return null;
}

Changelog

VersionDescription
2.1.0Introduced.

User Contributed Notes

  1. Skip to note 4 content

    Page Path
    This is the equivalent of the pagename query, as in: index.php?pagename=parent-page/sub-page.

    Code for the above could be written as (assuming parent-page/sub-page is actually the path to a page):

    get_page_by_path('parent-page/sub-page');

    For non-hierarchical custom post types, you need to use just the slug in tandem with the post_type parameter.

    //Returns nothing, assumes animals is the rewrite slug for the animal CPT
    get_page_by_path('animals/cat', OBJECT, 'animal');
    
    //Returns the animal with the slug 'cat'
    get_page_by_path('cat', OBJECT, 'animal');

    The functions basename() and untrailingslashit() are handy for grabbing the last part of the URL for this:

    $page_path = 'animals/cat/';
    get_page_by_path( basename( untrailingslashit( $page_path ) ) , OBJECT, 'animal');
  2. Skip to note 5 content

    As per scottb79, it is correct that the function checks for the post type supplied (Page by default) and Attachment by default.

    This function will add the Attachment post type to any instances where the post type is passed as a string. Due to the following code:

    if ( is_array( $post_type ) ) {
      $post_types = $post_type;
    } else {
      $post_types = array( $post_type, 'attachment' );
    }

    If you truly only want to return the item path from only the post type supplied.
    It needs to be passed as an array like this:

    $page = get_page_by_path( 'example', OBJECT, [ 'page' ] );

    This issue was found when an attachment sotred in the database had the same path as the page we wanted to retrieve.
    The attachment having a lower Post ID it is returned first.

  3. Skip to note 6 content

    If you don’t specify a post_type, it searches both page and attachment.

    If you want only page, pass it via the third parameter.:

    get_page_by_path( '/about/', OBJECT, 'page' );

You must log in before being able to contribute a note or feedback.