Custom Post Types and Taxonomies: Structuring WordPress Content
When to create a custom post type, how taxonomies differ from fields, the registration settings that matter, and the mistakes that are painful to reverse.

The content model is the decision that shapes everything downstream: your templates, your queries, your editor experience and how much a redesign costs in three years. It's also usually made in the first hour of a project, quickly, and never revisited.
Here's how to make it deliberately.
When you need a custom post type
The test is whether the content is a different kind of thing, not whether it looks different. A custom post type is right when the answer to most of these is yes:
- Does it need its own URL and its own page?
- Does it have fields that posts and pages don't have — a venue, a price, a client name, a date that isn't the publication date?
- Does it need its own archive, or to be queried independently?
- Should it have its own item in the admin menu, so editors find it without hunting?
- Would it be confusing to see it mixed in with blog posts?
When you don't
- It's a page with a different layout. That's a page template, not a post type.
- It's a blog post with a different category. That's a category.
- There will only ever be one of them. That's a page, or an options screen.
- It's a component inside another piece of content — a testimonial shown on a service page. That's often a repeater field or a block.
- You want it to appear in the same feed as blog posts. Mixing post types in one archive is possible but fiddly; consider whether a category would do.
Taxonomies versus fields
This is where most content models go wrong, and it's a genuinely subtle distinction.
A taxonomy is for grouping: many items share the same value, and you want to browse everything with that value. Product category, project industry, event type. Taxonomies get their own archive pages and their own URLs, and they're indexed efficiently for querying.
A field is for describing: the value belongs to this one item and nobody browses by it. A price, a client's name, a date, a URL, a phone number.
The test: would a visitor plausibly want a page listing everything with this value? If yes, taxonomy. If no, field.
Getting this wrong is expensive in both directions. Using fields where you needed a taxonomy means slow meta queries and no archive pages. Using taxonomies for unique values means thousands of one-item terms cluttering the admin and generating thin archive pages that hurt your SEO.
Registration settings that matter
- public, and the more specific flags underneath it. A post type can be publicly queryable but excluded from search, or admin-only with no front end at all.
- has_archive — whether /projects/ exists as a listing page. Say yes if you want one, and remember to flush permalinks after changing it.
- rewrite with a slug, which controls the URL. Choose it carefully; changing it later means redirecting every item.
- supports — which editor panels appear. Turning off the ones a post type doesn't need makes the editing screen much clearer.
- show_in_rest, which is required for the block editor and for the REST API. A post type without it gets the classic editor and is invisible to any headless frontend.
- capability_type and map_meta_cap if the post type needs its own permissions rather than inheriting post permissions.
- menu_position and menu_icon, which are cosmetic but make a real difference to whether editors find things.
Register in a plugin, not the theme
This is the rule people break most often and regret most sharply. If your post types are registered in the theme's functions.php, then switching themes makes every one of those items vanish from the admin. The content is still in the database — it's just unreachable, because nothing is registering the type any more.
That turns a redesign into a data migration, and it means the client is locked to a theme for reasons that have nothing to do with design.
Put registration in a small site-specific plugin. The rule of thumb: if it would still need to be true after a redesign, it isn't presentation and it doesn't belong in the theme.
Naming, which you only get one shot at
- Prefix the post type key to avoid collisions: acme_project, not project. A plugin registering the same key is a genuinely confusing failure.
- Keep the key under twenty characters — that's a hard limit.
- Use singular for the key and be consistent about it.
- Choose the rewrite slug for readers, not for the database. The key and the slug don't have to match.
- Think about the slug before launch. Changing it afterwards means redirecting every item's URL.
The editor experience
A post type is only as good as the screen editors see. Once the type exists, spend a little time on how it's edited.
Turn off supports for panels that don't apply — an excerpt field on a post type with no excerpts is just noise. Set the placeholder text on the title field to say what should go there. Customise the admin columns so the list view shows the fields that matter rather than just the title and date. Add the taxonomy filters editors will actually use.
Write the labels properly. The default labels say 'Add New Post' on every post type, and 'Add New Project' costs one line to set and makes the admin feel built rather than bolted on.
Common mistakes
- Registering in the theme, so the content vanishes on a redesign.
- Forgetting to flush permalinks after registering, then concluding the archive is broken.
- Using a taxonomy for values that are unique per item, generating thousands of thin term archives.
- Using meta fields for values you need to filter by at scale, which makes archive queries slow.
- Making everything public when some post types should be internal.
- Not setting show_in_rest, then wondering why the block editor won't load.
- Creating a post type for content that is genuinely one page.
Changing it later
Post type keys and taxonomy slugs are painful to change once there's content, because they're stored on every row and baked into every URL. It's doable — a migration script plus redirects — but it's real work, and it's work nobody budgets for.
Which is the argument for spending an hour on the model before writing a template. Sketch the content types, their fields, their relationships and their URLs on paper first. An hour there saves days later.
Frequently asked questions
Should I use a plugin or code to register custom post types?
Either, as long as it isn't the theme. A UI plugin is fine and fast for simple cases; code in a site-specific plugin gives you version control and precise settings. What matters is that registration survives a theme change, because otherwise a redesign makes all that content disappear from the admin.
What's the difference between a taxonomy and a custom field?
A taxonomy groups many items under a shared value and gets its own archive page. A field describes one item and nobody browses by it. The test: would a visitor plausibly want a page listing everything with this value? Yes means taxonomy, no means field.
Why is my custom post type archive showing a 404?
Almost always because permalinks haven't been flushed since the post type was registered, or because has_archive is false. Visit Settings → Permalinks and save — that flushes the rewrite rules. Never call flush_rewrite_rules on every page load; it's expensive.
How many custom post types is too many?
There's no technical limit that matters, but there's an editorial one. If editors can't tell which type something belongs in, you have too many or the boundaries are wrong. Five well-defined types beat twelve overlapping ones, and the admin menu gets unusable past a certain point.
Can I change a custom post type's slug after launch?
You can, but every existing item's URL changes, so you need a 301 redirect from each old URL to its new one. Changing the post type key itself is worse — it's stored on every row and needs a database migration. Decide both before launch if you possibly can.
Topics
- custom post types
- WordPress taxonomy
- register_post_type
- WordPress content model