Skip to content
@aploon

Custom Post Types and Taxonomies: Modeling Structured Content in WordPress

  • #wordpress
  • #php
  • #contentmodeling
  • #cms

WordPress is often introduced as a blogging platform, but its content model can support much more than posts and pages.

A directory of professionals, a library of courses, a product catalog, or a portfolio can all be modeled with WordPress's content APIs.

Two features are especially important:

  • Custom post types define distinct kinds of content.
  • Taxonomies classify that content using terms and relationships.

Using them appropriately produces a more structured content model and a better editing experience.

1. When should you create a custom post type?

A custom post type is useful when a content entity has its own lifecycle, editing interface, permissions, or presentation requirements.

For example, a portfolio website might distinguish between standard blog posts and projects.

add_action('init', 'myplugin_register_project_type');
 
function myplugin_register_project_type(): void {
    register_post_type('project', [
        'labels' => [
            'name'          => 'Projects',
            'singular_name' => 'Project',
        ],
        'public'       => true,
        'show_in_rest' => true,
        'has_archive'  => true,
        'rewrite'      => ['slug' => 'projects'],
        'supports'     => [
            'title',
            'editor',
            'excerpt',
            'thumbnail',
        ],
    ]);
}

This illustrative example registers a project post type.

The show_in_rest option enables REST API support, which is particularly useful for block-editor integration and headless WordPress applications.

The supports array controls which standard editing features are available. Additional capabilities may be needed depending on the project's requirements.

2. Use taxonomies to classify content

Suppose projects belong to categories such as web development, mobile applications, or data engineering.

A taxonomy provides a reusable classification mechanism.

add_action('init', 'myplugin_register_project_taxonomy');
 
function myplugin_register_project_taxonomy(): void {
    register_taxonomy('project_category', ['project'], [
        'labels' => [
            'name'          => 'Project Categories',
            'singular_name' => 'Project Category',
        ],
        'public'       => true,
        'hierarchical' => true,
        'show_in_rest' => true,
        'rewrite'      => ['slug' => 'project-category'],
    ]);
}

A hierarchical taxonomy behaves similarly to categories: terms can have parent-child relationships.

A non-hierarchical taxonomy behaves more like tags.

For example, a project might belong to the category Web Development while also having tags such as Laravel, React, and API.

Taxonomies are not limited to labels shown on the front end. They can also support filtering, navigation, and structured content discovery.

3. Distinguish content types, taxonomies, and metadata

These concepts solve different problems.

WordPress featurePurposeExample
Post typeDefines a content entityProject
TaxonomyClassifies entitiesProject category
Post metadataStores additional attributesRepository URL
Post contentStores primary editorial contentProject description

Consider a project record:

  • Title: Inventory Management Platform
  • Category: Business Applications
  • Technology metadata: Laravel
  • Repository URL metadata: A project repository address
  • Description: The project's overview

The category is a classification, whereas the repository URL is an attribute of the individual project.

Choosing the right mechanism makes querying and maintaining the content easier.

4. Store custom attributes carefully

WordPress post metadata is suitable for additional values associated with a post.

For example, a plugin might store a project's repository URL:

update_post_meta(
    $project_id,
    '_project_repository_url',
    esc_url_raw($repository_url)
);

This is an illustrative write operation. In a real implementation, validate the submitted URL, check user capabilities, verify the request nonce where appropriate, and ensure the field is registered or handled consistently.

When exposing metadata through the REST API, register it with an appropriate schema and permissions. Do not expose sensitive information simply because a field exists in post metadata.

For a small number of attributes, post metadata is practical. For complex relationships, frequent filtering on many custom fields, or highly structured records, consider whether a dedicated data model or custom database tables would be more appropriate.

5. Query structured content

Once a post type and taxonomy exist, WordPress can retrieve content through WP_Query.

For example, the following illustrative query retrieves published projects in the web-development category:

$projects = new WP_Query([
    'post_type'      => 'project',
    'post_status'    => 'publish',
    'posts_per_page' => 10,
    'tax_query'      => [
        [
            'taxonomy' => 'project_category',
            'field'    => 'slug',
            'terms'    => ['web-development'],
        ],
    ],
]);

Always escape output when rendering values in HTML, and follow WordPress's normal loop and query-cleanup practices where applicable.

For large catalogs, pay attention to pagination, query performance, and the cost of filtering on metadata. A data model that is convenient for editing is not automatically optimal for every search requirement.

6. Think about URL design early

Custom post types and taxonomies can define their public URL structures.

For example:

  • /projects/ for the project archive.
  • /projects/inventory-platform/ for an individual project.
  • /project-category/web-development/ for a taxonomy archive.

These paths are illustrative and depend on registration settings, permalink configuration, and any custom routing.

After changing rewrite rules or registration settings, WordPress may need its rewrite rules refreshed. Avoid calling flush_rewrite_rules() on every request; doing so repeatedly can introduce unnecessary overhead. A plugin should generally flush rules during activation and deactivation when appropriate.

URL design should also account for content migration, redirects, and the possibility of future taxonomy or post-type changes.

7. Keep the content model portable

A custom post type can be registered by a theme, but a content type that represents a business entity is often better registered by a plugin.

If the user changes themes, the underlying project data should not become dependent on the old theme to remain recognizable and manageable.

Keep responsibilities clear:

  • Plugins define durable content structures and business behavior.
  • Themes control presentation.
  • Templates display content without unnecessarily owning the data model.

This separation improves portability and reduces the risk of losing essential editing functionality during a redesign.

Custom post types and taxonomies allow WordPress to represent structured content beyond traditional articles and pages.

Define a separate post type when the entity deserves its own identity and workflow. Use taxonomies for reusable classifications, metadata for additional attributes, and queries that reflect real application requirements.

The best content model is not necessarily the most complex one. It is the simplest structure that accurately represents the domain and remains maintainable as the application grows.