Custom Post Types & Taxonomies

WordPress Intermediate/Advanced

Chapter 4 · Custom Post Types & Taxonomies

WordPress Fundamentals 3 made a promise: since Posts and Pages are really just two values of the same underlying post_type column, nothing stops a developer from defining a third. This chapter delivers on that promise directly, plus its exact counterpart for Categories and Tags.

register_post_type() — Defining a Third Content Category

<?php function register_portfolio_post_type() { register_post_type( 'portfolio', array( 'labels' => array( 'name' => 'Portfolio Items', 'singular_name' => 'Portfolio Item', ), 'public' => true, 'has_archive' => true, 'supports' => array( 'title', 'editor', 'thumbnail' ), 'menu_icon' => 'dashicons-portfolio', ) ); } add_action( 'init', 'register_portfolio_post_type' ); ?>
This is the exact reveal WordPress Fundamentals 3 previewed
Once this runs, portfolio becomes a real, valid value for the very same post_type column already holding post and page. A Portfolio Item isn't stored in some separate, new system — it's an ordinary row in the exact same database table, distinguished the same way Posts and Pages always have been.
  • public — whether this post type is queryable and has its own front-end pages at all
  • has_archive — whether an automatic archive listing page exists for it
  • supports — which built-in editor features this post type gets (title, the main content editor, a featured image, and more)
Notice the hook
register_post_type() is called inside a function hooked to WordPress's own init action — the same actions/filters mechanism WordPress Fundamentals 6 previewed as a black box. WordPress Intermediate/Advanced 5 formally covers this mechanism in full; for now, just recognize the pattern — WordPress runs your function at the right moment, rather than your code calling WordPress directly at the top level of a file.

What This Does to the Template Hierarchy

Registering portfolio makes single-portfolio.php and archive-portfolio.php real, meaningful filenames in WordPress Intermediate/Advanced 2's own hierarchy — the exact same lookup system already covered, now simply extended with one more post type to check against.

register_taxonomy() — Custom Classification, Beyond Categories and Tags

A taxonomy is a classification system — and per WordPress Fundamentals 7, Categories and Tags were always just two pre-built taxonomies with different configurations. This chapter's own function lets you build your own.

<?php function register_project_type_taxonomy() { register_taxonomy( 'project_type', 'portfolio', array( 'labels' => array( 'name' => 'Project Types' ), 'hierarchical' => true, 'public' => true, ) ); } add_action( 'init', 'register_project_type_taxonomy' ); ?>
hierarchical settingBehaves like
trueCategories, per WordPress Fundamentals 7 — parent/child structure allowed
falseTags, per WordPress Fundamentals 7 — flat, no hierarchy
Categories and Tags were never special
"Category" and "Tag" are simply the names of two taxonomies WordPress core happens to register automatically, using exactly this same register_taxonomy() mechanism. Setting hierarchical to true or false on a custom taxonomy is, mechanically, the same choice that separates Categories from Tags in the first place.

Bringing It Together

A portfolio custom post type combined with a project_type custom taxonomy gives a real, working content model: individual portfolio items, each classified into a hierarchical set of project types — the same pattern behind products, testimonials, team members, or events on a real WordPress site.

An honest alternative worth knowing
Plugins like Custom Post Type UI exist to configure exactly this — post types and taxonomies — entirely through the dashboard, with no PHP at all, echoing WordPress Fundamentals 6's own plugin material. Writing it by hand, as this chapter does, gives full control and keeps the definition in version-controlled code rather than database settings — a real trade-off, not a right-or-wrong choice.

Hands-On Exercises

Exercise 1

Write the register_post_type() call for a "Testimonial" custom post type that supports only a title and the main editor, with no automatic archive page. Explain each argument you chose.

📄 View solution
Exercise 2

Explain why registering a "portfolio" post type doesn't require creating any new database table, connecting your answer directly to WordPress Fundamentals 3's own material.

📄 View solution
Exercise 3

A developer registers a custom taxonomy called "skill_level" with hierarchical set to false. Explain what this taxonomy will behave like, using this chapter's own comparison to Categories and Tags.

📄 View solution

Chapter 4 Quick Reference

  • register_post_type(), hooked to init — defines a new value for the same post_type column Posts and Pages already share
  • public, has_archive, and supports control queryability, archive pages, and which editor features the new post type gets
  • A registered post type gets its own real, meaningful entries in the template hierarchy — single-{type}.php, archive-{type}.php
  • register_taxonomy() — Categories and Tags were always just two pre-built taxonomies; hierarchical true/false is the exact setting separating them
  • Custom Post Type UI exists as a no-code, plugin-based alternative to writing this by hand — a real trade-off, not a wrong approach
  • Next chapter: Plugin Development Fundamentals