Back to Top

URL to Post ID for Custom Post Types

Previous Post:

URL to Post ID for Custom Post Types

Three years ago, when I started using WordPress, I was wondering about one thing: “How should we convert a WordPress’s Post URL to its corresponding ID?”. It would be very useful if we could do such a thing, especially when we have to work with AJAX applications.

Fortunately WordPress has a neat function for such purpose, and it’s called url_to_postid1. That function is located in wp-includes/rewrite.php, which does make some sense as we actually want to rewrite a URL so that it becomes an ID.

The magic function above, though, has one major caveat: It does not work with custom post types, which is a shame, really. I do believe that custom post type support will be added in future releases of WordPress, but until then, it’s no harm creating our custom function, right?

We will simply copy url_to_postid from rewrite.php, rename it, and then add a new block of codes. With that in mind, fire up your .php file of choice and add this new conversion function to it:

/* Post URLs to IDs function, supports custom post types - borrowed and modified from url_to_postid() in wp-includes/rewrite.php */
function bwp_url_to_postid($url)
	global $wp_rewrite;

	$url = apply_filters('url_to_postid', $url);

	// First, check to see if there is a 'p=N' or 'page_id=N' to match against
	if ( preg_match('#[?&](p|page_id|attachment_id)=(\d+)#', $url, $values) )	{
		$id = absint($values[2]);
		if ( $id )
			return $id;

	// Check to see if we are using rewrite rules
	$rewrite = $wp_rewrite->wp_rewrite_rules();

	// Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options
	if ( empty($rewrite) )
		return 0;

	// Get rid of the #anchor
	$url_split = explode('#', $url);
	$url = $url_split[0];

	// Get rid of URL ?query=string
	$url_split = explode('?', $url);
	$url = $url_split[0];

	// Add 'www.' if it is absent and should be there
	if ( false !== strpos(home_url(), '://www.') && false === strpos($url, '://www.') )
		$url = str_replace('://', '://www.', $url);

	// Strip 'www.' if it is present and shouldn't be
	if ( false === strpos(home_url(), '://www.') )
		$url = str_replace('://www.', '://', $url);

	// Strip 'index.php/' if we're not using path info permalinks
	if ( !$wp_rewrite->using_index_permalinks() )
		$url = str_replace('index.php/', '', $url);

	if ( false !== strpos($url, home_url()) ) {
		// Chop off
		$url = str_replace(home_url(), '', $url);
	} else {
		// Chop off /path/to/blog
		$home_path = parse_url(home_url());
		$home_path = isset( $home_path['path'] ) ? $home_path['path'] : '' ;
		$url = str_replace($home_path, '', $url);

	// Trim leading and lagging slashes
	$url = trim($url, '/');

	$request = $url;
	// Look for matches.
	$request_match = $request;
	foreach ( (array)$rewrite as $match => $query) {
		// If the requesting file is the anchor of the match, prepend it
		// to the path info.
		if ( !empty($url) && ($url != $request) && (strpos($match, $url) === 0) )
			$request_match = $url . '/' . $request;

		if ( preg_match("!^$match!", $request_match, $matches) ) {
			// Got a match.
			// Trim the query of everything up to the '?'.
			$query = preg_replace("!^.+\?!", '', $query);

			// Substitute the substring matches into the query.
			$query = addslashes(WP_MatchesMapRegex::apply($query, $matches));

			// Filter out non-public query vars
			global $wp;
			parse_str($query, $query_vars);
			$query = array();
			foreach ( (array) $query_vars as $key => $value ) {
				if ( in_array($key, $wp->public_query_vars) )
					$query[$key] = $value;

		// Taken from class-wp.php
		foreach ( $GLOBALS['wp_post_types'] as $post_type => $t )
			if ( $t->query_var )
				$post_type_query_vars[$t->query_var] = $post_type;

		foreach ( $wp->public_query_vars as $wpvar ) {
			if ( isset( $wp->extra_query_vars[$wpvar] ) )
				$query[$wpvar] = $wp->extra_query_vars[$wpvar];
			elseif ( isset( $_POST[$wpvar] ) )
				$query[$wpvar] = $_POST[$wpvar];
			elseif ( isset( $_GET[$wpvar] ) )
				$query[$wpvar] = $_GET[$wpvar];
			elseif ( isset( $query_vars[$wpvar] ) )
				$query[$wpvar] = $query_vars[$wpvar];

			if ( !empty( $query[$wpvar] ) ) {
				if ( ! is_array( $query[$wpvar] ) ) {
					$query[$wpvar] = (string) $query[$wpvar];
				} else {
					foreach ( $query[$wpvar] as $vkey => $v ) {
						if ( !is_object( $v ) ) {
							$query[$wpvar][$vkey] = (string) $v;

				if ( isset($post_type_query_vars[$wpvar] ) ) {
					$query['post_type'] = $post_type_query_vars[$wpvar];
					$query['name'] = $query[$wpvar];

			// Do the query
			$query = new WP_Query($query);
			if ( !empty($query->posts) && $query->is_singular )
				return $query->post->ID;
				return 0;
	return 0;

As you can see, the new codeblock is from line 81 to line 112, and that’s where the magic happens.

Basically, what we just did was adding some new query vars to the $query variable, which was formerly designed to support only built-in post types, so that WordPress can query for all types of posts through a typical WP_Query2 request later on (line 115) to get the correct post ID.

With the help of this new function, a test like this:

echo bwp_url_to_postid('');

will return 229, which is exactly the ID of this WordPress tip, pretty nice!

Please note that, regardless of what function you would choose to use, custom or original, you should always use it after the init action has been fired, as a lot of plugins and themes will use such action to register post types. If you fail to do so, you will get 0 as the result, obviously.


  1. ... _to_postid []
  2. []

Take Social Sharing to
the Next Level with Monarch!

Take Social Sharing to the Next Level with Monarch!
Print Article Trackback Trackback to this Article   Subscribe to Comments RSS Subscribe to Comments RSS

30 Opinions for URL to Post ID for Custom Post Types (24 Trackbacks)

  1. User's Gravatar
    Luke January 2, 2014 at 7:08 pm – Permalink

    Hi there,

    Do you know if it is possible to return multiple ID’s from multiple URL’s?


    • User's Gravatar
      Khang Minh January 3, 2014 at 1:09 pm – Permalink

      The above function will only take one URL at a time, but you can write a loop that calls it multiple times. Something like this:

      	$urls = array('url1', 'url2', 'url3', .....);
      	foreach ($urls as $url) {
      		echo "Post ID for $url is " .  bwp_url_to_postid($url) . "<br />";
  2. User's Gravatar
    Dan February 11, 2015 at 11:02 pm – Permalink

    2015 and this code is still helping people, thanks a ton!

  3. User's Gravatar
    Mark March 13, 2015 at 8:01 am – Permalink

    This code worked for me. Thank U.

  4. User's Gravatar
    Mike March 24, 2015 at 1:39 am – Permalink

    The code broke my site. Have I done something wrong?

  5. User's Gravatar
    Kevin Provance October 2, 2016 at 2:58 am – Permalink

    Oct 2016: We’ve been using this for years, but recently it stopped working for custom post types. Out of sheer curiosity, I tried the url_to_postid inherent to WP, on a custom post type. It actually kicked back the proper post type. So, did WP actually fix it? Wonder never cease, do they?

    This workaround served us well for many years. 🙂


Speak Up Your Mind!

An asterisk (*) indicates a required field and must be filled.

  • Web page and e-mail addresses turn into links automatically.
  • Wrap codes in: <code lang=""></code> or <pre lang="" extra="">
  • Lines and paragraphs break automatically.

Next Post: