How to change a custom post type's URL slug in WordPress

A custom post type's URL base comes from the rewrite slug in its registration. To change /portfolio/ to /work/, set a new rewrite slug, in your own register_post_type() call or through the register_post_type_args filter, then save Settings → Permalinks once and 301 the old addresses with a server rule.

Three kinds of URL carry the base: single entries (/portfolio/acme-rebrand/), the archive (/portfolio/) and its paged and feed variants. All of them move together.

Step by step

1. Find where the post type is registered

If your theme or your own plugin registers the type, you will edit that code. If a third-party plugin does, check its settings first: many expose a "slug" or "permalink base" field, and using it survives updates. Only when there is no setting should you override the registration from outside with a filter.

2. Change the slug in your own registration

In the register_post_type() call, set the rewrite argument:

php
register_post_type( 'portfolio', array(
    'public'      => true,
    'has_archive' => 'work',
    'rewrite'     => array( 'slug' => 'work', 'with_front' => false ),
    // ...the rest of your arguments.
) );

slug sets the base for single entries. has_archive accepts a string, which sets the archive address separately. true uses the rewrite slug. with_front set to false stops a prefix from your permalink structure, such as /blog/, being added in front.

3. Or override another plugin's registration

Core passes every registration through the register_post_type_args filter, with the arguments and the post type name:

php
add_filter( 'register_post_type_args', function ( $args, $post_type ) {
    if ( 'portfolio' === $post_type ) {
        $args['rewrite']     = array( 'slug' => 'work', 'with_front' => false );
        $args['has_archive'] = 'work';
    }
    return $args;
}, 10, 2 );

Put this in a small plugin or a child theme so it loads before the type is registered on init. The post type name itself stays portfolio. Only the URL changes, so existing entries, queries and templates are unaffected.

4. Refresh the rewrite rules

WordPress stores its URL rules in the database and does not rebuild them when code changes. Open Settings → Permalinks and click Save Changes. With WP-CLI, wp rewrite flush does the same. Until you do, the new addresses return 404 and the old ones still work.

5. Redirect the old base

WordPress's own old-slug redirect tracks an entry's slug, not the base in front of it, so nothing forwards /portfolio/acme-rebrand/. Add a pattern rule. Apache or LiteSpeed, above # BEGIN WordPress:

apache
RedirectMatch 301 ^/portfolio/(.*)$ /work/$1
RedirectMatch 301 ^/portfolio$ /work/

nginx:

nginx
rewrite ^/portfolio(/.*)?$ /work$1 permanent;

If you cannot edit server config, use Hydrogen SEO → Redirections and add an Exact Match 301 for the archive and for each entry that has traffic or links. A Starts With rule is not a substitute here, because it sends every matching URL to one fixed target instead of to its own new address.

6. Test all three URL kinds

bash
curl -sI https://example.com/portfolio/acme-rebrand/ | grep -iE "^HTTP|^location"
curl -sI https://example.com/work/acme-rebrand/ | grep -iE "^HTTP"
curl -sI https://example.com/work/ | grep -iE "^HTTP"

Expect 301 to the new single URL, then 200 twice. Open the sitemap from Hydrogen SEO → Sitemap and check the post type's sitemap lists /work/ addresses.

Menu items added as Custom Links and links typed into content still use the old base. Update them. In Search Console, the old URLs move to Page with redirect as they are recrawled. Inspect the new archive URL and request indexing to prompt the first crawl.

Slug collisions

The new base must not equal the slug of an existing page. If a page already lives at /work/, the post type's rules usually win and the page becomes unreachable. Rename one of them. The same applies to a taxonomy registered with the same slug.

When it still shows 404

Work through this list in order: rules not flushed (save Permalinks again); the filter runs too late (a theme functions.php loads in time, a snippet hooked to a late action does not); a caching plugin serving stored 404 responses (purge it); or publicly_queryable set to false on the type, which removes single URLs regardless of slug.

Common questions

Does changing the rewrite slug change the post type's name?

No. The registered name, used in the database and in queries, stays the same. Only the public URL base changes.

Do I need to flush rewrite rules every time?

Only after the slug or other rewrite arguments change. Saving the Permalinks screen once is enough. Flushing on every page load slows the site.

Will entries keep their own slugs?

Yes. Each entry's slug is untouched. `/portfolio/acme-rebrand/` becomes `/work/acme-rebrand/`.