How to Create a Custom Post Type in WordPress
WordPress was originally designed around publishing posts and pages, but modern websites often need to manage different types of content. A business website might need Services, Products, Team Members, Testimonials, or Case Studies, while a directory website may require listings or locations.
Instead of storing all this information as regular posts, WordPress allows developers to create Custom Post Types (CPTs).
A custom post type gives you a dedicated area in the WordPress Dashboard for managing a specific type of content. In this guide, we’ll explore what custom post types are, why they are useful, and several ways to create one safely.
What Is a Custom Post Type?
A custom post type is a content type that extends WordPress beyond its default Posts and Pages.
For example, imagine a web hosting company wants to publish information about its hosting services. Instead of putting everything under regular blog posts, it could create a custom post type called:
Services
The Dashboard could then contain:
Dashboard
├── Posts
├── Pages
├── Media
├── Services
│ ├── Add New
│ ├── All Services
│ └── Service Categories
└── Settings
Each service could have its own title, description, featured image, and other custom information.
Why Use Custom Post Types?
Custom post types are useful when your website contains content that has a different purpose or structure from normal blog posts.
Common examples include:
- Products
- Services
- Team members
- Testimonials
- Portfolio projects
- Case studies
- Events
- Courses
- Properties
- Job listings
- Locations
- Books
- Recipes
For example:
Posts → Blog articles
Pages → Static website content
Products → Items offered for sale
Services → Services provided by a business
Projects → Portfolio work
This separation makes content easier to manage and organize.
Custom Post Type vs Regular Post
A regular WordPress post is designed primarily for chronological content such as blog articles.
A custom post type is designed for a specific content category.
| Feature | Regular Post | Custom Post Type |
|---|---|---|
| Blog articles | Yes | Possible |
| Custom content structure | Limited | Yes |
| Separate Dashboard section | No | Yes |
| Custom taxonomies | Yes | Yes |
| Custom fields | Yes | Yes |
| Custom templates | Yes | Yes |
| Custom URLs | Yes | Yes |
Method 1: Create a Custom Post Type Using PHP
One of the most flexible ways to create a custom post type is with PHP.
For example, let’s create a Services post type.
The code can be placed in a custom plugin or, for theme-specific functionality, a child theme.
function my_register_services_post_type() {
register_post_type('services', array(
'labels' => array(
'name' => 'Services',
'singular_name' => 'Service',
'add_new' => 'Add New Service',
'add_new_item' => 'Add New Service',
'edit_item' => 'Edit Service',
'new_item' => 'New Service',
'view_item' => 'View Service',
'search_items' => 'Search Services',
'not_found' => 'No services found',
'menu_name' => 'Services'
),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-admin-tools',
'supports' => array(
'title',
'editor',
'thumbnail',
'excerpt'
),
'rewrite' => array(
'slug' => 'services'
),
'show_in_rest' => true
));
}
add_action('init', 'my_register_services_post_type');
After adding the code, visit your WordPress Dashboard.
You should see a new:
Services
section.
Understanding register_post_type()
The primary WordPress function used to create a custom post type is:
register_post_type()
It tells WordPress what your new content type should be called and how it should behave.
For example:
register_post_type('services', $args);
Here:
services
is the post type’s internal identifier.
Choosing a Post Type Slug
The first parameter should be a suitable identifier.
For example:
register_post_type('services', ...)
Good identifiers are:
services
products
projects
events
team
Avoid unnecessarily long or confusing identifiers.
The post type key should also follow WordPress’s naming restrictions and should not conflict with an existing post type.
Creating Custom Labels
Labels control the text displayed throughout the WordPress Dashboard.
For example:
'labels' => array(
'name' => 'Services',
'singular_name' => 'Service',
'add_new' => 'Add New',
'add_new_item' => 'Add New Service',
'edit_item' => 'Edit Service',
'view_item' => 'View Service',
'search_items' => 'Search Services',
)
You can customize these labels to match your content type.
For a product post type, for example:
Products
Add New Product
Edit Product
View Product
Search Products
Making the Post Type Public
This option controls whether the post type is generally available on the frontend.
'public' => true,
Setting it to true makes the content publicly accessible, subject to the other registration settings.
For an internal content type that should not have public pages, you can configure the post type differently.
Adding an Archive Page
You can enable an archive page using:
'has_archive' => true,
For example, if the post type uses the slug:
services
WordPress can provide an archive URL such as:
example.com/services/
The exact URL depends on your site’s permalink and rewrite configuration.
Adding a Featured Image
If you want each custom post to support a featured image, add:
'supports' => array(
'title',
'editor',
'thumbnail'
),
The thumbnail option enables the featured image functionality.
Other commonly used options include:
title
editor
author
thumbnail
excerpt
comments
revisions
custom-fields
page-attributes
Only enable the features your content actually needs.
Enabling the WordPress Block Editor
To make the custom post type compatible with the WordPress block editor and REST API, include:
'show_in_rest' => true,
This is particularly important for modern WordPress workflows.
Adding a Custom Menu Icon
You can customize the Dashboard icon:
'menu_icon' => 'dashicons-admin-tools',
WordPress includes a collection of Dashicons that can be used for administrative menus.
Choose an icon that makes sense for the content type.
Creating Custom URLs
You can specify the URL structure using:
'rewrite' => array(
'slug' => 'services'
),
This can produce URLs such as:
example.com/services/web-hosting/
instead of exposing the internal post type name in an undesirable format.
Flush Permalinks After Creating a Post Type
Sometimes your custom post type URL may produce a 404 error immediately after registration.
Go to:
Settings → Permalinks
Then click:
Save Changes
You don’t necessarily need to change any settings. Saving the permalink settings refreshes WordPress’s rewrite rules.
For plugins, you can also handle rewrite-rule flushing during activation/deactivation rather than flushing them on every request.
Method 2: Create a Custom Post Type With a Plugin
If you don’t want to write PHP, you can use a plugin that provides a graphical interface for creating custom post types.
The general process is:
- Open Plugins → Add New Plugin.
- Search for a reputable custom-post-type management plugin.
- Install and activate it.
- Open its post-type management interface.
- Choose Add New Post Type.
- Enter the post type name and labels.
- Select supported features.
- Configure visibility and URLs.
- Save the post type.
- Test it from the WordPress Dashboard.
This approach can be useful for site owners who aren’t comfortable editing PHP.
Adding a Custom Taxonomy
A custom post type can also have its own taxonomy.
For example, a Services post type could have categories such as:
Web Hosting
VPS Hosting
Dedicated Servers
WordPress Hosting
Domain Services
You can register a custom taxonomy using:
function my_register_service_taxonomy() {
register_taxonomy(
'service_category',
'services',
array(
'label' => 'Service Categories',
'public' => true,
'hierarchical' => true,
'show_in_rest' => true
)
);
}
add_action('init', 'my_register_service_taxonomy');
Because hierarchical is set to true, the taxonomy behaves similarly to standard WordPress categories.
Categories vs Tags for Custom Post Types
You can also use non-hierarchical taxonomies, which behave more like tags.
For example:
'hierarchical' => false,
This is useful for characteristics such as:
Linux
SSD
Managed
Cloud
Beginner
Business
A simple distinction is:
Hierarchical taxonomy: categories and parent/child relationships.
Non-hierarchical taxonomy: tags or labels without a hierarchy.
Adding Custom Fields
Sometimes a title and editor aren’t enough.
A service might need additional information such as:
- Price
- Server location
- Storage
- RAM
- Bandwidth
- Service duration
- Call-to-action URL
These additional pieces of information can be stored as custom fields or managed using a custom-fields solution.
For example:
Service Name: VPS Basic
Price: ₹499/month
Storage: 50 GB
RAM: 4 GB
Location: India
A custom post type combined with custom fields can create a structured content-management system inside WordPress.
Creating a Custom Post Type Template
WordPress supports specialized templates for custom post types.
For a post type called:
services
you can create:
single-services.php
for individual service pages.
You can also create:
archive-services.php
for the service archive page.
A typical structure might look like:
your-theme/
├── single-services.php
├── archive-services.php
└── style.css
If you’re customizing a theme, use a child theme when appropriate so that your changes aren’t overwritten by parent-theme updates.
Example single-services.php
A simple template could look like:
<?php get_header(); ?>
<main class="services-content">
<?php
while (have_posts()) :
the_post();
?>
<article>
<h1><?php the_title(); ?></h1>
<?php if (has_post_thumbnail()) : ?>
<?php the_post_thumbnail('large'); ?>
<?php endif; ?>
<div class="service-description">
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
</main>
<?php get_footer(); ?>
This gives you control over how individual service pages are displayed.
Custom Post Type and SEO
Custom post types can also be optimized for search engines.
Important areas include:
- SEO-friendly URLs
- Unique page titles
- Useful descriptions
- Structured content
- Internal links
- Optimized images
- Appropriate taxonomy pages
- XML sitemap inclusion
Many SEO plugins can recognize custom post types and provide settings for controlling their search visibility.
Before enabling indexing, make sure the content provides useful information and isn’t simply duplicate content from other pages.
Custom Post Types and WordPress REST API
If:
'show_in_rest' => true
is enabled, the post type can generally be exposed through the WordPress REST API.
This can be useful for:
- JavaScript applications
- Headless WordPress
- Mobile applications
- External integrations
- Custom frontend interfaces
A custom post type can therefore serve as a structured content source beyond the traditional WordPress theme.
Common Custom Post Type Mistakes
1. Using an Existing Post Type Name
Don’t accidentally register a post type with a name already used by WordPress or another plugin.
Choose a unique identifier.
2. Using an Unclear Slug
A slug such as:
abc123
doesn’t provide much meaning.
Use descriptive identifiers such as:
services
products
projects
3. Forgetting show_in_rest
If you want block-editor and REST API compatibility, remember:
'show_in_rest' => true,
4. Forgetting to Refresh Permalinks
If custom post URLs return 404 errors, visit:
Settings → Permalinks → Save Changes
5. Registering the Post Type in the Wrong Place
If the content should remain available when the theme changes, registering the post type in a custom plugin is generally preferable to putting it directly in the theme.
Otherwise, changing themes can make the custom content management interface disappear.
6. Creating Too Many Post Types
Not every type of content needs its own post type.
Before creating one, consider whether a normal post, page, taxonomy, or custom field would solve the problem more simply.
Custom Post Type Best Practices
Follow these guidelines when creating custom post types:
- Choose a clear and unique post type key.
- Use descriptive labels.
- Enable only the features you need.
- Use
show_in_restfor modern WordPress workflows where appropriate. - Use custom taxonomies when content needs additional organization.
- Use custom fields for structured information.
- Create dedicated templates when necessary.
- Keep custom functionality in a plugin if it shouldn’t depend on the theme.
- Refresh rewrite rules after changing URL structures.
- Test archive and individual URLs.
- Consider SEO before making the content publicly indexable.
- Back up the website before making structural changes.
When Should You Use a Custom Post Type?
A custom post type is a good choice when your website contains a distinct type of content that needs its own:
- Dashboard section
- URL structure
- Templates
- Taxonomies
- Custom fields
- Search behavior
- Permissions
- Workflow
For example, a web hosting company could organize its website like this:
Posts
└── Hosting Guides
Services
├── Shared Hosting
├── VPS Hosting
├── Dedicated Servers
└── WordPress Hosting
Projects
├── Project A
├── Project B
└── Project C
This is much easier to manage than putting unrelated content into one generic post list.
Final Thoughts
Custom post types are one of WordPress’s most useful features for building structured websites. They allow you to create dedicated content types that behave independently from standard posts and pages while still using the familiar WordPress editing environment.
For developers, registering a custom post type with register_post_type() provides extensive control over labels, URLs, templates, editor support, archives, and visibility. For non-developers, a reputable custom-post-type plugin can provide a simpler interface.
The key is to create custom post types only when they provide a genuine organizational or functional benefit. Combined with custom taxonomies, custom fields, dedicated templates, and appropriate SEO settings, they can turn WordPress into a flexible content-management platform for almost any type of website.