WordPress Custom Post Type Generator

Generate register_post_type() code with every label, REST and block-editor support, rewrite rules and menu icons — as a snippet or plugin.

Runs in your browser

Post type

Lowercase, max 20 characters. Singular is conventional: book, event, team_member.

Output
Supports

Built-in taxonomies

Visibility

Required for the block editor and the REST API.

Endpoint: /wp-json/wp/v2/book

Admin menu
URLs

Single URL: /book/sample-book/

Prefix URLs with the static part of your permalink structure, e.g. /blog/.

Capabilities & data

functions.php snippet

// Register Custom Post Type: Books.
// After adding this code, visit Settings → Permalinks and click Save to flush rewrite rules.
if ( ! function_exists( 'myplugin_register_book_post_type' ) ) {
	/**
	 * Registers the "book" post type.
	 */
	function myplugin_register_book_post_type() {
		$labels = array(
			'name'                     => _x( 'Books', 'post type general name', 'my-plugin' ),
			'singular_name'            => _x( 'Book', 'post type singular name', 'my-plugin' ),
			'menu_name'                => _x( 'Books', 'admin menu', 'my-plugin' ),
			'name_admin_bar'           => _x( 'Book', 'add new on admin bar', 'my-plugin' ),
			'add_new'                  => __( 'Add New Book', 'my-plugin' ),
			'add_new_item'             => __( 'Add New Book', 'my-plugin' ),
			'new_item'                 => __( 'New Book', 'my-plugin' ),
			'edit_item'                => __( 'Edit Book', 'my-plugin' ),
			'view_item'                => __( 'View Book', 'my-plugin' ),
			'view_items'               => __( 'View Books', 'my-plugin' ),
			'all_items'                => __( 'All Books', 'my-plugin' ),
			'search_items'             => __( 'Search Books', 'my-plugin' ),
			'parent_item_colon'        => __( 'Parent Book:', 'my-plugin' ),
			'not_found'                => __( 'No books found.', 'my-plugin' ),
			'not_found_in_trash'       => __( 'No books found in Trash.', 'my-plugin' ),
			'featured_image'           => __( 'Book Featured Image', 'my-plugin' ),
			'set_featured_image'       => __( 'Set featured image', 'my-plugin' ),
			'remove_featured_image'    => __( 'Remove featured image', 'my-plugin' ),
			'use_featured_image'       => __( 'Use as featured image', 'my-plugin' ),
			'archives'                 => __( 'Book Archives', 'my-plugin' ),
			'attributes'               => __( 'Book Attributes', 'my-plugin' ),
			'insert_into_item'         => __( 'Insert into book', 'my-plugin' ),
			'uploaded_to_this_item'    => __( 'Uploaded to this book', 'my-plugin' ),
			'filter_items_list'        => __( 'Filter books list', 'my-plugin' ),
			'filter_by_date'           => __( 'Filter by date', 'my-plugin' ),
			'items_list_navigation'    => __( 'Books list navigation', 'my-plugin' ),
			'items_list'               => __( 'Books list', 'my-plugin' ),
			'item_published'           => __( 'Book published.', 'my-plugin' ),
			'item_published_privately' => __( 'Book published privately.', 'my-plugin' ),
			'item_reverted_to_draft'   => __( 'Book reverted to draft.', 'my-plugin' ),
			'item_trashed'             => __( 'Book trashed.', 'my-plugin' ),
			'item_scheduled'           => __( 'Book scheduled.', 'my-plugin' ),
			'item_updated'             => __( 'Book updated.', 'my-plugin' ),
			'item_link'                => _x( 'Book Link', 'navigation link block title', 'my-plugin' ),
			'item_link_description'    => _x( 'A link to a book.', 'navigation link block description', 'my-plugin' ),
		);

		$args = array(
			'labels'              => $labels,
			'public'              => true,
			'publicly_queryable'  => true,
			'exclude_from_search' => false,
			'show_ui'             => true,
			'show_in_menu'        => true,
			'show_in_nav_menus'   => true,
			'show_in_admin_bar'   => true,
			'show_in_rest'        => true,
			'menu_position'       => 20,
			'menu_icon'           => 'dashicons-book',
			'capability_type'     => 'post',
			'hierarchical'        => false,
			'supports'            => array( 'title', 'editor', 'thumbnail', 'excerpt', 'revisions' ),
			'has_archive'         => true,
			'rewrite'             => array(
				'slug'       => 'book',
				'with_front' => false,
			),
			'query_var'           => true,
			'can_export'          => true,
			'delete_with_user'    => false,
		);

		register_post_type( 'book', $args );
	}
}
add_action( 'init', 'myplugin_register_book_post_type' );
Flush permalinks
After adding the snippet, go to Settings → Permalinks and click Save once — otherwise the new URLs return 404.

What is a WordPress custom post type?

WordPress stores almost all content in one table, wp_posts, and the post_type column decides what each row is: a post, a page, a menu item, a reusable block. A custom post type (CPT) adds your own type — books, events, team members, case studies, properties — with its own admin menu, editor screen, URLs, archive page and REST endpoint.

CPTs are registered with register_post_type() on the init hook. Most of the work is in the arguments: around 35 labels that make the admin read naturally (“Add New Book”, “No books found in Trash”), visibility flags, show_in_rest for the block editor, supports for the editor features, rewrite rules for pretty URLs and capabilities for permissions. Getting one of them wrong leads to the classic problems — the block editor not showing, archive pages returning 404, or editors losing access.

This generator writes the complete, translation-ready code following the WordPress Coding Standards: every label wrapped in __() or _x() with your text domain, prefixed function names, and either a functions.php snippet or a standalone plugin that flushes rewrite rules on activation.

How to use the WordPress Custom Post Type Generator

  1. Enter the post type key (singular, lowercase, max 20 characters). Singular and plural names are suggested automatically — adjust them if the plural is irregular.
  2. Set your text domain and a unique function prefix, then choose snippet or plugin output.
  3. Tick the editor features under Supports, and keep show_in_rest on for the block editor.
  4. Configure the menu icon, archive and rewrite slug. Hints show the resulting URLs.
  5. Copy or download the code, add it to a plugin or child theme, then flush permalinks by saving Settings → Permalinks.

Example

The core of the generated code for a “book” post type (labels shortened):

function myplugin_register_book_post_type() {
	$labels = array(
		'name'          => _x( 'Books', 'post type general name', 'my-plugin' ),
		'singular_name' => _x( 'Book', 'post type singular name', 'my-plugin' ),
		// …33 more labels
	);

	register_post_type(
		'book',
		array(
			'labels'       => $labels,
			'public'       => true,
			'show_in_rest' => true,
			'has_archive'  => true,
			'rewrite'      => array( 'slug' => 'books', 'with_front' => false ),
			'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
		)
	);
}
add_action( 'init', 'myplugin_register_book_post_type' );

Plugin or theme?

Register post types in a plugin (or a must-use plugin), not in a theme. Content outlives designs: if a CPT lives in functions.php and the theme is switched, the posts are still in the database but disappear from the admin and their URLs return 404. The plugin output also flushes rewrite rules on activation, which is the only safe moment to call flush_rewrite_rules() — never call it on every init.

Next steps

Frequently Asked Questions

Why does my custom post type show a 404?

The rewrite rules have not been regenerated. Visit Settings → Permalinks and click Save Changes. Also check that no page uses the same slug as the post type’s rewrite or archive slug.

Why is the block editor not showing for my post type?

The block editor needs show_in_rest => true and editor in supports. Without REST, WordPress falls back to the Classic Editor.

What is the difference between public and publicly_queryable?

public is a shortcut that sets defaults for several flags (search, admin UI, nav menus). publicly_queryable alone controls whether single entries and archives can be viewed on the front end — set it to false for internal data like testimonials shown only via a block.

Why is the post type key limited to 20 characters?

The post_type column in wp_posts is varchar(20). register_post_type() refuses longer keys with a “doing it wrong” notice. Keys are permanent — renaming later means migrating the database.

Should I use capability_type “post” or custom capabilities?

Use post unless different roles need different access. Custom capabilities (with map_meta_cap) let you, for example, allow a “Shop Manager” to edit products but not posts — but you must add the new capabilities to roles yourself.

How do I show custom post types on the blog page?

Hook pre_get_posts, check $query->is_main_query() && $query->is_home(), and set post_type to an array such as array( 'post', 'book' ). More examples are in functions.php Snippets.