I have a weird problem created by a bad requirement. (I say this because a lot of comments telling me this is a bad thing to want to do are not helpful. I know. I don't want to do it.)

Is it possible to change the permalink separator ( / ) to a dash ( - ), specifically for hierarchical pages?

So that URLs for pages would be like: example.com/page-child-page-grandchild-page





I need the child-parent relationship in order to display breadcrumbs and lists of child and sibling pages.

But the client/boss (for reasons that confuse me) does not want (and i quote) "so many folders."

I think that the site might be compiled out to a static web server when it is done, so that almost makes sense. Please don't tell me this is dumb. I'm aware it is dumb.



Don't use WP's page hierarchy. Use ACF or Posts-to-Posts (or similar) to create an alternative parent-child relationship, and then roll my own Breadcrumb and Child-Page-Listing features.

Comments on whether that is a fundamentally better idea are welcome.


  • 1
    I am honestly not sure if it is possible. And even were you to get the dashes swapped out it might break (my guess is "definitely will break") how WordPress parses the requests to determine what page/post/archive to load, so you've now moved into the job of hacking/rewriting that Core system. – s_ha_dum Dec 19 '15 at 16:43
  • It is possible with a filter on {$posttype}_link. What will not work: two hyphens in a row. – fuxia Dec 19 '15 at 17:08
  • Is it possible to have hierarchical structure NOT show up in the URL? ie: example.com/child-page, example.com/grandchild-page, etc? – Adam Michael Wood Dec 20 '15 at 0:13

You can replace the dash with something else, preferably not a hyphen, because that would be hard to parse back. In the following example, I use a dot.

You have to change two parts: the outgoing permalinks (hook: "{$post_type}_link") and the request (hook: request) parsing for "incoming permalinks".

The latter is harder, because it is very difficult to prevent collisions with other post types and taxonomies.

Here is a simple class with handlers for both cases. I have tested it with the post type page only and with permalink settings that put normal posts (post type post) at the root level.

class Undash_Permalinks
     * What to use instead of /
     * @var string
    private $replacement;

     * Undash_Permalinks constructor.
     * @param string $replacement
    public function __construct( $replacement )
        $this->replacement = $replacement;

     * Change the output URL
     * @wp-hook page_link
     * @param   string $url
     * @return string
    public function output( $url )
        $home     = home_url( '/' );
        $start    = strlen( $home );
        $sub      = substr( $url, $start );
        $replaced = str_replace( '/', $this->replacement, $sub );

        return $home . $replaced;

     * Help WordPress to understand the requests as page requests
     * @wp-hook request
     * @param   array $request
     * @return array
    public function input( array $request )
        if ( empty ( $request[ 'name' ] ) )
            return $request;

        if ( FALSE === strpos( $request[ 'name' ], $this->replacement ) )
            return $request;

        $path = str_replace( $this->replacement, '/', $request[ 'name' ] );
        $page = get_page_by_path( $path );

        if ( ! $page )
            return $request;

        // Convince WP that we really have a page.
        $request[ 'pagename' ] = $path;
        unset( $request[ 'name' ] );

        return $request;

Using this class is pretty simple:

add_action( 'after_setup_theme', function() {
    $undash = new Undash_Permalinks( '.' );
    add_filter( 'page_link', [ $undash, 'output' ] );
    add_filter( 'request', [ $undash, 'input' ] );

This might need some changes for other post types. For attachments you can either use the root directory as upload directory, or just live with the "folder". Requests for them do not pass WordPress, so a filter would help at all.

| improve this answer | |
  • what is the advantage of using an anonymous callback here? I thought that the use of anonymous callbacks was discouraged due to the trouble needed to unhook them. – Dave Romsey Dec 20 '15 at 0:48
  • @goto10 This code belongs to a separate plugin. If you don't want to run it, deactivate the plugin. – fuxia Dec 20 '15 at 1:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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