Custom Post Type as Page parent

WordPress uses a set of rewrite rules to be able to convert an url into a database query.

The regex that handle urls for pages is very general, IIRC it is something like (.+.?)/?, it mathes pratically everything has not be already matched by other rules.

For this reason is not possible to write a rewrite rule that will work in your case: because you can’t distinguish via regex that in a url like example.com/my-portal/sample-page the ‘my-portal’ part is a CPT and sample-page is a page.

Things become more complex if you have more levels of nesting: my-portal/my-portal-child/sample-page.

To handle this kind of urls, WordPress uses get_page_by_path() function: it explodes the page url by /, obtaining page slugs, then queries database for all the pages that have those slugs.

E.g if you have a page whose slug is “sample-page” and you set as parent for it the CPT “my-portal” WordPress calls:

get_page_by_path('my-portal/sample-page')

but it doesn’t return any result because it looks for a page with slug ‘sample-page’ whose parent is another page with slug ‘my-portal’. That page doesn’t exists, so you get the 404 error.

However, get_page_by_path() accepts as 3rd argument an array of post types: if you set it to array('page', 'portal') then the function will be able to correctly find the page.

So you can solve the issue by manually setting the page id (retrieved as explained above) into WP query vars.

The 'parse_request' hook is perfect for the scope:

  • it runs after the url has been parsed
  • it passes to hooking callbacks the instance of $wp object you can use to set query vars

Code:

add_action('parse_request', function ($wp) {
  // only if WP found a page
  if (isset($wp->query_vars['pagename']) && ! empty($wp->query_vars['pagename'])) {
    $page = get_page_by_path( // let's find the page object
        $wp->query_vars['pagename'],
        OBJECT,
        array('page', 'portal') // we need to set both post types
    );
    if ($page instanceof WP_Post) { // if we find a page
        unset($wp->query_vars['pagename']); // remove pagename var
        $wp->query_vars['page_id'] = $page->ID; // replace with page_id query var
    }
  }
});

This code, in combination with the filter in OP, is all you need.

Note that code works even with nested hierarchical portals.

Leave a Comment