Contents
Introduction
This article is a complete, practical, and detailed tutorial on how to register a Custom Post Type (CPT) in WordPress with editor support using PHP. It covers everything you need to know: arguments and their meaning, enabling the classic and block editors (Gutenberg), block templates, permissions and capabilities, rewrite rules and archives, registering REST support, adding/removing editor support after registration, common pitfalls, and production-ready patterns (activation hooks, capability assignment). All examples are ready-to-use and include activation/flush examples, role capability setup, and meta registration for REST-enabled custom fields.
Prerequisites
- WordPress installation (5.0 recommended for block editor support).
- Basic knowledge of PHP and where to place code: a custom plugin (recommended) or your themes functions.php (for testing only).
- Ability to add/activate plugins or edit theme files and access to WP admin.
Why editor support matters
The editor support flag controls whether a post type has the main post content editor box (the large content area) in the edit screen:
- For classic editor users, it shows the TinyMCE or Classic Editor area.
- For block editor (Gutenberg) users, the same flag allows block content editing—but the post type must also expose itself to the REST API (show_in_rest => true) to fully enable Gutenberg.
Core steps at a glance
- Create a function that calls register_post_type() with proper arguments.
- Hook that function to the init action.
- Ensure show_in_rest is set to true for Gutenberg/block editor.
- Flush rewrite rules on plugin activation (do not flush on every pageload).
- Optionally set capabilities and assign them to roles on activation for granular control.
Minimal working example
Below is a minimal example that registers a CPT named book and enables the editor.
lt?php / Register book custom post type. / function myplugin_register_post_type_book() { labels = array( name =gt Books, singular_name =gt Book, menu_name =gt Books, name_admin_bar =gt Book, add_new =gt Add New, add_new_item =gt Add New Book, new_item =gt New Book, edit_item =gt Edit Book, view_item =gt View Book, all_items =gt All Books, search_items =gt Search Books, parent_item_colon =gt Parent Books:, not_found =gt No books found., not_found_in_trash =gt No books found in Trash., ) args = array( labels =gt labels, public =gt true, publicly_queryable =gt true, show_ui =gt true, show_in_menu =gt true, query_var =gt true, rewrite =gt array( slug =gt books ), capability_type =gt post, has_archive =gt true, hierarchical =gt false, menu_position =gt 5, menu_icon =gt dashicons-book, supports =gt array( title, editor, thumbnail, excerpt, revisions ), show_in_rest =gt true, // enables Gutenberg editor rest_base =gt books, ) register_post_type( book, args ) } add_action( init, myplugin_register_post_type_book )
Explanation of important arguments
- labels — An array of human-readable labels used in the admin UI.
- public — Whether the post type is intended to be used publicly (affects multiple defaults).
- show_in_rest — If true, the post type is accessible in the REST API (required for Gutenberg/block editor).
- supports — An array that defines which meta features the edit screen supports. Include editor to enable the content editor.
- has_archive — Whether to generate an archive page for the post type (enable for collection pages).
- rewrite — Controls rewrite rules (slug, with_front, custom endpoints).
- capability_type and map_meta_cap — For custom capability mapping if you want custom permissions per post type.
- rest_base — The REST route base for the post type (defaults to post type name).
- hierarchical — Whether the post type behaves like pages (true) or posts (false).
Available support features (common values)
- title — title input box.
- editor — main content editor (post content).
- excerpt — excerpt field.
- author — author metabox.
- thumbnail — featured image.
- comments — comments support.
- revisions — store revisions.
- page-attributes — menu order, parent/child (useful for hierarchical taxonomies/pages).
- custom-fields — legacy custom fields UI.
- trackbacks, post-formats — other features.
Enabling Gutenberg (block editor) specifically
To make sure the block editor is available you must do two things:
- Include editor in supports.
- Set show_in_rest => true so the block editor can access the post via the REST API.
Without show_in_rest the classic editor will show (if active) or the block editor will be unable to edit content blocks properly.
Advanced example with block template and template lock (predefined blocks)
You can define a block template for your CPT to preset blocks for new posts of this type. The template and template_lock arguments allow this.
lt?php function myplugin_register_cpt_with_template() { labels = array( name => Recipes, singular_name => Recipe, ) args = array( labels => labels, public => true, supports => array( title, editor, thumbnail ), show_in_rest => true, has_archive => true, // Define a block template made of core/paragraph and core/image blocks template => array( array( core/paragraph, array( placeholder =gt Write an introduction..., ) ), array( core/image, array() ), array( core/paragraph, array( placeholder =gt Write instructions for the recipe..., ) ), ), // all locks structure completely insert prevents removing/dragging of template blocks, // false allows full editing template_lock => insert, ) register_post_type( recipe, args ) } add_action( init, myplugin_register_cpt_with_template )
Flushing rewrite rules properly (activation hook)
Never call flush_rewrite_rules() on every page load. Do it only on plugin activation or theme switch. Example using a plugin activation hook:
lt?php function myplugin_activate() { // Register the post type so rewrite rules exist myplugin_register_post_type_book() // Flush rewrite rules once on activation flush_rewrite_rules() } register_activation_hook( __FILE__, myplugin_activate ) function myplugin_deactivate() { // Optional: flush rewrite rules on deactivation to remove CPT rules flush_rewrite_rules() } register_deactivation_hook( __FILE__, myplugin_deactivate )
Custom capabilities for granular control
If you want to give specific capabilities (separate from default posts), define a capabilities array and set map_meta_cap => true. Then on activation give those capabilities to roles (administrator/editor).
lt?php function myplugin_register_cpt_with_caps() { labels = array( name =gt Magazines, singular_name =gt Magazine ) capabilities = array( edit_post =gt edit_magazine, read_post =gt read_magazine, delete_post =gt delete_magazine, edit_posts =gt edit_magazines, edit_others_posts =gt edit_others_magazines, publish_posts =gt publish_magazines, read_private_posts =gt read_private_magazines, ) args = array( labels => labels, public => true, supports => array( title, editor ), capability_type => array( magazine, magazines ), // singular, plural map_meta_cap => true, capabilities => capabilities, ) register_post_type( magazine, args ) } add_action( init, myplugin_register_cpt_with_caps ) / On activation, add capabilities to administrator role (example). / function myplugin_add_caps_on_activation() { roles = array( administrator, editor ) caps = array( edit_magazine,read_magazine,delete_magazine, edit_magazines,edit_others_magazines,publish_magazines,read_private_magazines ) foreach ( roles as role_name ) { role = get_role( role_name ) if ( ! role ) { continue } foreach ( caps as cap ) { role->add_cap( cap ) } } } register_activation_hook( __FILE__, myplugin_add_caps_on_activation )
Removing or toggling editor support after registration
Sometimes you may want to remove the editor from a CPT conditionally or add it later. Use add_post_type_support() and remove_post_type_support().
lt?php // Add editor support after registration (if needed) add_action( init, function() { add_post_type_support( book, editor ) } ) // Remove editor support add_action( init, function() { remove_post_type_support( book, editor ) } )
Registering meta fields that are available in REST (for block editor)
If you want custom meta to be available to Gutenberg, register post meta with show_in_rest => true or use register_post_meta(). This makes meta editable via block editor components and accessible in the REST API.
lt?php function myplugin_register_meta() { register_post_meta( book, subtitle, array( type =gt string, description =gt Book subtitle, single =gt true, show_in_rest =gt true, auth_callback =gt function() { return current_user_can( edit_posts ) }, ) ) } add_action( init, myplugin_register_meta )
Adding a classic meta box and saving content (if using the content editor with supporting meta)
You can add custom meta boxes while keeping the editor intact. Save data with a save_post callback, sanitizing user input.
lt?php // Add meta box function myplugin_add_meta_boxes() { add_meta_box( myplugin_book_details, Book Details, myplugin_book_details_callback, book, side ) } add_action( add_meta_boxes, myplugin_add_meta_boxes ) function myplugin_book_details_callback( post ) { wp_nonce_field( myplugin_book_details_save, myplugin_book_details_nonce ) value = get_post_meta( post-gtID, _book_subtitle, true ) echo ltlabelgtSubtitle:lt/labelgt echo ltinput type=text name=book_subtitle value= . esc_attr( value ) . style=width:100% /gt } // Save meta function myplugin_save_book_meta( post_id ) { if ( ! isset( _POST[myplugin_book_details_nonce] ) ) { return } if ( ! wp_verify_nonce( _POST[myplugin_book_details_nonce], myplugin_book_details_save ) ) { return } if ( defined( DOING_AUTOSAVE ) DOING_AUTOSAVE ) { return } if ( ! current_user_can( edit_post, post_id ) ) { return } if ( isset( _POST[book_subtitle] ) ) { update_post_meta( post_id, _book_subtitle, sanitize_text_field( _POST[book_subtitle] ) ) } } add_action( save_post, myplugin_save_book_meta )
Common pitfalls and best practices
- Flushing rules too often: Dont call flush_rewrite_rules() on init during normal runtime—use activation/deactivation hooks.
- Gutenberg not available: If the block editor does not load for your CPT, ensure show_in_rest is true and the REST routes work. Also check for plugins that force the Classic Editor.
- Capabilities: When you use custom capability_type, remember to create/grant those capabilities to roles on activation.
- Admin menu placement: Use menu_position and menu_icon to make the post type easier to find.
- Translations: Use translation functions (e.g., _x(), __()) for labels when building production-ready plugins or themes. In examples above labels are simple strings for clarity.
- REST performance/security: When exposing meta via show_in_rest, ensure auth callbacks and proper sanitization to avoid data leaks.
- Testing: Test CPT rewrite rules, archives, single templates (single-{post_type}.php), and REST endpoints.
Example: Full plugin-ready file (concise)
Below is a compact plugin-style example combining registration, activation flush, and REST-enabled editor support.
lt?php / Plugin Name: My Books CPT Description: Registers a book CPT with editor (supports Gutenberg). / // Register CPT function mybooks_register_cpt() { labels = array( name => __( Books, mybooks ), singular_name => __( Book, mybooks ), ) args = array( labels => labels, public => true, show_in_rest => true, rest_base => books, rewrite => array( slug =gt books ), has_archive => true, supports => array( title, editor, thumbnail, excerpt, revisions ), ) register_post_type( book, args ) } add_action( init, mybooks_register_cpt ) // Activation hook to flush rewrite rules function mybooks_activate() { mybooks_register_cpt() flush_rewrite_rules() } register_activation_hook( __FILE__, mybooks_activate ) // Deactivation cleanup function mybooks_deactivate() { flush_rewrite_rules() } register_deactivation_hook( __FILE__, mybooks_deactivate )
Troubleshooting checklist
- If CPT archive 404s appear after registering, activate the plugin (or run flush rewrite) to refresh rules.
- If Gutenberg shows a spinner or fails: confirm show_in_rest => true and that REST endpoints for the CPT return expected JSON (check /wp-json/wp/v2/your_post_type).
- If editor is missing: ensure editor is in supports and you have no conflicting code that removes it (remove_post_type_support).
- Check for plugin conflicts (Classic Editor plugin, custom admin filters, capability filters).
Checklist before going to production
- Use textdomain and translation functions for labels.
- Only flush rewrite rules on activation/deactivation.
- Sanitize and escape all input/output (use register_post_meta with appropriate sanitization or custom save_post handlers with sanitize_ functions).
- Give capabilities to roles responsibly avoid granting dangerous capabilities to non-admins by default.
- Test REST endpoints and front-end templates (archive-{post_type}.php, single-{post_type}.php).
Summary
To register a Custom Post Type with editor support in WordPress using PHP: call register_post_type() with supports including editor, hook to init, set show_in_rest => true to enable Gutenberg, and flush rewrite rules on activation. Optionally define block templates, customize capabilities, and register meta to expose custom fields to the REST API. The code snippets above provide multiple real-world patterns to suit themes, plugins, and advanced use cases.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |