Skip to content
Back to all articles
WordPress6 min readSeptember 30, 2026

WordPress Custom Post Types Without a Plugin: A Complete Guide

When a custom post type is the right choice, how to register one properly with register_post_type(), and the templates, taxonomies and pitfalls that come after.

WordPress Custom Post Types Without a Plugin: A Complete Guide article
In this guide
  1. When a custom post type is the right choice
  2. Registering a post type
  3. Writing it without the boilerplate
  4. The 404 trap
  5. Adding a taxonomy
  6. Templates: controlling how it looks
  7. Displaying items anywhere
  8. Adding extra fields
  9. Theme or plugin?
  10. Pitfalls to avoid
  11. A solid foundation

At some point, every growing WordPress site outgrows "Posts" and "Pages". You are building a site for a restaurant and need Menu Items. A property agent needs Listings. A training company needs Courses. You could squeeze them into blog posts with categories, and many people do, but it never feels right: the menus get cluttered, the fields do not fit, and your client keeps accidentally publishing a course in the blog feed.

Custom post types are WordPress's answer. They let you create a new kind of content with its own admin menu, its own URLs and its own templates. Plugins can create them, but the code is short and worth understanding. This guide explains when a custom post type is the right choice, how to register one properly, and the templates, taxonomies and pitfalls that come after.

When a custom post type is the right choice

Use a custom post type when the content:

  • Has its own structure, such as a product with a price or an event with a date.
  • Should be listed separately from the blog, with its own archive page.
  • Is managed differently, often by different people.
  • Would clutter posts or pages if mixed in with them.

If you only need to group blog posts, a category or tag is usually enough. If you need one special page, a normal page is fine. Custom post types shine when you have many items of the same kind.

Registering a post type

Everything happens through one function, register_post_type(), called on the init hook:

php
function acme_register_book_post_type() {
    register_post_type( 'book', array(
        'labels'       => array(
            'name'          => __( 'Books', 'acme' ),
            'singular_name' => __( 'Book', 'acme' ),
            'add_new_item'  => __( 'Add New Book', 'acme' ),
            'edit_item'     => __( 'Edit Book', 'acme' ),
        ),
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'menu_icon'    => 'dashicons-book',
        'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'rewrite'      => array( 'slug' => 'books' ),
    ) );
}
add_action( 'init', 'acme_register_book_post_type' );

That is a complete, working post type. The important settings are worth understanding one by one.

The key

book is the post type key, used in code and queries. It must be 20 characters or fewer, lowercase, and should be specific enough to avoid clashes with other plugins; acme_book is safer than book on a site with many plugins. Some names, like post, page, attachment and order, are reserved by WordPress and must never be used.

public, has_archive and rewrite

public makes the content visible on the front end with its own URLs. has_archive creates a listing page, and rewrite sets the URL base, so books live at /books/book-title/ and the archive at /books/.

show_in_rest

This one catches many people out. Without show_in_rest => true, your post type uses the old classic editor instead of the block editor, and it is not available in the REST API. Leave it on unless you have a reason not to.

supports

Chooses which editing features appear: title, editor, featured image (thumbnail), excerpt, custom fields, comments, revisions and page attributes. Include only what editors need.

Writing it without the boilerplate

The full set of labels runs to twenty lines, and it is easy to make a typo in one of them. The WordPress CPT Generator writes the complete code as you fill in a form, updating live as you type. It fills in the key and URL slug from your labels, escapes labels safely so names like "Doctor's Notes" do not break the PHP, and warns you straight away about keys that are too long or reserved. It can also generate a matching taxonomy in the same file.

The 404 trap

You add the code, create your first book, click View, and get a 404 page. Almost everyone hits this once. WordPress stores its URL rules in a cache, and new post types are not in it yet.

The fix: go to Settings → Permalinks and click Save Changes. You do not need to change anything; saving rebuilds the rules. If you ship a post type in a plugin, flush the rules once on activation instead of on every page load, because flushing is slow.

Adding a taxonomy

A taxonomy groups your items, like Genres for Books. It can be hierarchical, like categories, or flat, like tags:

php
register_taxonomy( 'genre', 'book', array(
    'label'             => __( 'Genres', 'acme' ),
    'hierarchical'      => true,
    'show_in_rest'      => true,
    'show_admin_column' => true,
    'rewrite'           => array( 'slug' => 'genres' ),
) );

Register it on init as well. show_admin_column adds a handy column to the admin list, and show_in_rest makes it appear in the block editor sidebar.

Templates: controlling how it looks

WordPress looks for template files named after your post type. In a classic theme:

TemplateUsed for
single-book.phpOne book
archive-book.phpThe book listing at /books/
taxonomy-genre.phpA genre listing

If these files do not exist, WordPress falls back to the general single.php and archive.php. Start by copying those and adjusting them. If you are building a theme from a starter, our guide to building a custom WordPress theme explains the template hierarchy in detail.

Displaying items anywhere

To show the latest books on the homepage, use a custom query:

php
$books = new WP_Query( array(
    'post_type'      => 'book',
    'posts_per_page' => 6,
) );

while ( $books->have_posts() ) {
    $books->the_post();
    the_title( '<h3>', '</h3>' );
}
wp_reset_postdata();

Always call wp_reset_postdata() afterwards so the rest of the page uses the main query again. The WordPress Functions Reference has copy-ready examples of WP_Query and related functions.

Adding extra fields

Most custom content needs more than a title and text: a price, an author, an event date. Enable custom-fields in supports to use WordPress's built-in fields box, or use a fields plugin such as Advanced Custom Fields to build friendlier forms. Either way, the values are stored as post meta and read with get_post_meta().

Theme or plugin?

This is the most important decision, and the answer is almost always a plugin. Content registered in a theme's functions.php disappears from the admin the moment someone switches theme. The books are still in the database, but nobody can see or edit them.

Putting the code in a small plugin keeps the content type independent of the design. The WordPress Plugin Generator creates a clean plugin structure to drop it into, and our guide to creating your first WordPress plugin walks through the basics.

Pitfalls to avoid

  • Generic keys like product or event can clash with plugins that register the same thing.
  • Forgetting show_in_rest, then wondering why the block editor is missing.
  • Flushing rewrite rules on every page load, which slows the whole site.
  • Registering in the theme for content that must outlive the design.
  • Public types you meant to keep private. Testimonials or team members shown only through a query can be non-public, so they do not get their own URLs or appear in search.

A solid foundation

Custom post types turn WordPress from a blog into a proper content system, and they are simpler than they look: one function, a handful of settings, a permalink refresh and a couple of templates. Generate the code with the WordPress CPT Generator, put it in a plugin, add templates for the single and archive views, and your editors get a clean, dedicated place for their content.

Was this article helpful?

Found this guide useful?

Share it with someone who is struggling with the same problem.

Free tool · No sign-up

WordPress Custom Post Type Generator

Generate register_post_type() code for WordPress as you type. Set labels, URL slug, editor features, block editor support, menu icon and an optional custom taxonomy, with instant checks for invalid keys.

Open tool
Toolyconvert Team profile photo

Written by

Toolyconvert Team

Web Development & Software Engineering Team

The Toolyconvert Team builds, maintains, and improves free online tools designed to make everyday digital tasks faster and easier. Our team works across web development, software engineering, APIs, performance optimization, testing, and user experience to create reliable and practical tools.