Custom Post Types: When Posts and Pages Are Not Enough
· 5 min read
You've seen Posts and Pages, and you know roughly when to reach for each.
You've seen Posts and Pages, and you know roughly when to reach for each: Posts for time-ordered content like a blog, Pages for standalone content like an About page. But plenty of real sites need to manage content that isn't really either one — a portfolio of projects, a list of client testimonials, a catalog of team members, a set of events with dates. WordPress's answer to that gap is custom post types.
What A Custom Post Type Is
A custom post type is, structurally, exactly like Posts and Pages — WordPress stores it in the same posts database table, gives it the same kind of edit screen, and lets it have a title, content, a featured image, and custom fields if you add them. What's different is the label and the behavior around it. Instead of a generic "Post," you get something named for what it actually is: "Project," "Testimonial," "Team Member," "Event." That naming isn't just cosmetic — it's what keeps a site's admin area understandable once it's managing several different kinds of content, rather than dumping everything into one long list of generic Posts that all look the same until you open them.
When You Actually Need One
The question beginners usually get stuck on is: when do I actually need one, instead of just using Posts with a category? A good rule of thumb is to ask whether the content has its own distinct shape and its own reason to be displayed outside the normal blog flow. A "Testimonials" post type, for example, probably needs a client name, a company, and a star rating — fields that make no sense on a regular blog post — and you likely want to show testimonials in a sidebar widget or a dedicated page, not mixed into a chronological blog feed. A "Projects" post type likely needs its own fields too (client, completion date, technologies used) and its own archive page separate from the blog. If you just need to group ordinary blog posts into topics, a category or tag on the regular Post type is the right tool, and reaching for a custom post type would be overkill. Registering one is done with a function called register_post_type(), usually placed inside a custom plugin or in your theme's functions.php, hooked to WordPress's init action so it runs at the right point in the loading process.
function register_project_post_type() {
register_post_type('project', array(
'labels' => array(
'name' => 'Projects',
'singular_name' => 'Project',
),
'public' => true,
'has_archive' => true,
'supports' => array('title', 'editor', 'thumbnail'),
'menu_icon' => 'dashicons-portfolio',
));
}
add_action('init', 'register_project_post_type');
Breaking Down The Code
Reading this piece by piece: the first argument, 'project', is the post type's internal name — short, lowercase, no spaces, used internally by WordPress and in URLs. The labels array controls what shows up in the admin menu, so editors see "Projects" rather than the internal slug. 'public' => true makes it visible on the front end of the site and searchable, rather than hidden away for internal use only. 'has_archive' => true gives it an automatic listing page, much like the blog's main page lists regular Posts. 'supports' controls which editing features show up on the edit screen — here, a title field, the main content editor, and a featured image, but you could add more or fewer depending on what a Project actually needs. Once that code runs, a new "Projects" menu appears in the WordPress admin sidebar, right alongside Posts and Pages, and it behaves the same way: you click Add New, fill in a title and content, and publish it. The difference is purely organizational — Projects live separately from your blog Posts, get their own archive page, and can have their own template files in a theme (an archive-project.php and single-project.php, following the same naming pattern as the regular archive.php and single.php template files from the Loop lesson) so they can look and behave differently from a blog post if needed.
A Common Registration Pitfall
One detail that trips people up: registering a custom post type through code like this, rather than through a plugin's settings screen, means the registration only exists while that code is active. If it's in a theme's functions.php and someone switches themes later, the post type (and the admin menu for it) disappears, even though the actual content rows are still sitting in the database, just with nowhere to manage them. That's why many real sites register custom post types inside a small dedicated plugin rather than inside the theme — a plugin stays active independent of whatever theme is being used, so the content management stays available even after a redesign. Plugins like Custom Post Type UI exist specifically so you can register one through a settings screen instead of writing register_post_type() yourself, which is a reasonable shortcut once you understand what that screen is actually generating behind the scenes. It's worth remembering, too, that a custom post type on its own is just a content container — it doesn't automatically know how to display its custom data anywhere special. Showing a star rating or a client logo on the front end still needs either custom fields (often added through a plugin like Advanced Custom Fields) and some template code that reads them, or page-builder blocks that do the same job visually. Registering the post type is the first step that makes the rest possible, not the whole solution by itself.
Small Challenge
A small challenge: think of one type of content your own site (or a site you're imagining) would need that isn't really a blog Post or a static Page — testimonials, recipes, events, portfolio pieces, whatever fits. Write out, on paper or in a notes app, what the register_post_type() labels and supports array would look like for it, using the Project example above as a template.
KEEP READING