WordPress

Custom Post Types and Taxonomies That Survive a Redesign

How to structure WordPress custom post types and taxonomies so a redesign changes the templates and nothing else: registration, naming, fields and archives.

5 min read
WordPressACFArchitecture
824 words5 min read

The test of a content model

A business website gets redesigned every three to five years. The content does not change shape that often. Services are still services, staff are still staff, locations are still locations. A good content model recognises that and separates the data from its presentation, so a redesign swaps the templates and leaves the data alone.

A bad content model stores the services as pages with hand-built layouts, the staff as a gallery block, and the prices as a table typed into the about page. Every redesign is then a content migration too, and something is always lost.

Here is how we structure custom post types and taxonomies so the data survives.

Register in a plugin, not the theme

Post types and taxonomies registered in functions.php disappear when the theme is switched, taking the admin menus with them and leaving the content orphaned in the database. Register them in a small site-specific plugin, or a must-use plugin, that has no dependency on the theme. The theme renders; the plugin defines.

PHP
// wp-content/mu-plugins/site-content-model.php
add_action( 'init', function () {
  register_post_type( 'service', [
    'labels'       => [ 'name' => 'Services', 'singular_name' => 'Service' ],
    'public'       => true,
    'has_archive'  => true,
    'rewrite'      => [ 'slug' => 'services' ],
    'show_in_rest' => true,
    'supports'     => [ 'title', 'editor', 'thumbnail', 'excerpt', 'revisions' ],
    'menu_icon'    => 'dashicons-clipboard',
  ] );

  register_taxonomy( 'service_category', 'service', [
    'labels'       => [ 'name' => 'Service categories' ],
    'hierarchical' => true,
    'show_in_rest' => true,
    'rewrite'      => [ 'slug' => 'service-category' ],
  ] );
} );

show_in_rest is not optional: it is what makes the type editable in the block editor and readable by anything headless later.

Model the business, not the page

Ask what the business has, not what the site shows. For a booking-led business the recurring set is:

  • Service. Name, short description, duration, from-price, booking URL or ID, category. One per thing a client can book.
  • Team member. Name, role, bio, photo, services they perform, registration number, booking URL.
  • Location. Address, hours, phone, map, staff at that location.
  • Testimonial or Case study, where the sector allows them.
  • FAQ, with a taxonomy for which page it appears on.

Each is a post type. Each field that other things need to read, price, duration, registration number, is a meta field, not text in the body. The body is for prose.

Taxonomies for relationships, meta for attributes

A taxonomy answers "which group is this in": service category, location, treatment area. It is queryable, filterable and gives you archive pages for free.

Meta answers "what is true about this one": price, duration, phone number. It is attached to the single post.

The mistake is using one for the other. A "price" taxonomy with terms for every price is unqueryable nonsense; a "category" meta field cannot generate an archive. Relationships between types, which staff perform which service, are a relationship field in ACF or a shared taxonomy, depending on whether you need to query from both sides.

Name things for the business, not the design

Post type slugs and field names get baked into URLs, queries and integrations. service survives; homepage_card_item does not. duration_minutes survives; right_column_text does not. If a field's name describes where it appears rather than what it is, it will be wrong after the redesign.

Keep the template out of the data

Every time a designer wants "a service page with a different layout for premium services", the temptation is a new post type or a layout field. Resist it. Add a tier taxonomy term and let the template decide how to render tier. The data says what the service is; the theme decides how a premium service looks this year.

Plan for the archive and the single

Every post type needs two decisions: does it get its own URL and archive, and what appears there. Services usually do: /services/ lists them, /services/deep-tissue-massage/ is a page. Testimonials usually do not: they render inside other pages. Set public and has_archive accordingly, and give types without their own URL publicly_queryable => false so they do not create thin pages Google indexes.

Export and document

Field groups in ACF export to JSON; keep that JSON in the plugin so the model is in version control. Write a one-page document: each type, what it represents, its fields, its taxonomies, and which templates read it. That document is what the next developer opens first, and it is what makes the redesign a template job.

What survives

Done this way, the redesign is: new theme, new templates that read the same fields, same URLs. The client's fifty services, twelve staff and three locations do not move, are not re-entered, and do not lose their rankings. That is the whole point of the model, and it is why we build the content layer before the first pixel of the design when we take on a WordPress project.

All writingHire me for this