Protect custom post types by registering them with 'capability_type' and 'map_meta_cap' => true, then assigning the resulting capabilities to specific roles using add_cap(). This gives you WordPress-native, role-level access control with no plugin overhead.
What Are Custom Post Types and Why Protect Them?
Custom Post Types (CPTs) extend WordPress beyond its default post and page structures. A CPT might represent courses in an LMS, properties in a real estate platform, case studies in a portfolio site, or confidential reports in a client portal. Because CPTs can contain sensitive or premium content, controlling who can view, edit, and publish them is a core requirement for any serious WordPress application.
By default, when you register a new CPT with register_post_type(), it inherits capabilities from the built-in post capability structure. This means anyone who can edit posts can also edit your CPT, and depending on your visibility settings, the CPT’s archive and single pages may be publicly accessible without any authentication. That’s rarely what you want on a membership site or client portal.
The good news is that WordPress’s capability system is well-designed and gives you fine-grained control. With the right registration arguments, you can create CPT-specific capabilities, assign them to exactly the right roles, and build an access control layer that’s both secure and maintainable.
Setting Capabilities When Registering a Custom Post Type
The key to native CPT protection is the capability_type and map_meta_cap arguments in register_post_type(). Setting capability_type to a custom string (like 'course') tells WordPress to generate a unique set of capabilities for this post type: edit_course, read_course, delete_course, edit_courses, edit_others_courses, and so on.
function wpdev_register_course_cpt() {
$labels = array(
'name' => 'Courses',
'singular_name' => 'Course',
'menu_name' => 'Courses',
);
$args = array(
'labels' => $labels,
'public' => false, // Not publicly queryable.
'publicly_queryable' => true, // Allow front-end queries when user has caps.
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'courses' ),
'capability_type' => 'course', // Creates custom capabilities.
'map_meta_cap' => true, // Let WordPress handle meta capabilities.
'has_archive' => true,
'hierarchical' => false,
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
);
register_post_type( 'course', $args );
}
add_action( 'init', 'wpdev_register_course_cpt' );
/**
* Grant course capabilities to the 'instructor' role.
* Run once on plugin/theme activation — not on every page load.
*/
function wpdev_add_course_caps() {
$instructor = get_role( 'instructor' );
if ( $instructor ) {
$instructor->add_cap( 'edit_course' );
$instructor->add_cap( 'read_course' );
$instructor->add_cap( 'delete_course' );
$instructor->add_cap( 'edit_courses' );
$instructor->add_cap( 'publish_courses' );
}
// Grant read-only access to subscribers (enrolled students).
$subscriber = get_role( 'subscriber' );
if ( $subscriber ) {
$subscriber->add_cap( 'read_course' );
}
}
register_activation_hook( __FILE__, 'wpdev_add_course_caps' );
With this setup, only users with the read_course capability can view individual course posts. Everyone else gets a 404 or is redirected to your login page — whatever your theme handles for unauthorized access.
Role-Based Access Control for CPTs
Role-based access control (RBAC) for CPTs works by assigning capabilities to roles, then letting WordPress’s native permission checks handle the rest. When a user attempts to view a CPT post, WordPress checks whether that user’s role has the appropriate capability. If not, the access is denied.
The critical distinction is between front-end visibility and back-end (admin) access. Setting 'public' => false on your CPT hides it from the front-end REST API and XML sitemaps by default, but setting 'publicly_queryable' => true still allows front-end templates to query and display the content for authorized users. This is the right combination for most protected content: not discoverable by search engines or unauthenticated API calls, but accessible to logged-in users with the right role.
For front-end access gates, add a check in your single CPT template using current_user_can('read_course', $post->ID). If it returns false, display your upsell or login prompt instead of the content. This is a belt-and-suspenders approach that works even if a direct URL is accessed.
Password-Protecting Individual CPT Posts
WordPress’s built-in post password feature works with custom post types if your CPT is registered with 'public' => true or 'publicly_queryable' => true. In the post editor, set Visibility to “Password Protected” and enter a password. WordPress will show a password form on the front end before revealing the content.
This is a lightweight solution for occasional document protection or preview-before-purchase scenarios. It’s not suitable for full membership sites because passwords are shared and can’t be revoked per-user. For proper per-user access control, use the capability-based approach described above.
Hiding CPTs from Non-Logged-In Users
To redirect unauthenticated users away from protected CPT pages, hook into template_redirect and check both the post type and the user’s login status. This fires before any template output, so you can safely redirect without causing header conflicts.
You can also use the pre_get_posts filter to exclude your protected CPT from the main query entirely for non-logged-in users. This prevents protected content from appearing in search results, category pages, or any other archive that uses the main WordPress query — a particularly important consideration for sites with internal search functionality.
Combining CPT Protection with Membership Plugins
For complex multi-tier membership sites, combining the native capability approach with a plugin like MemberPress, Restrict Content Pro, or LearnDash gives you the best of both worlds. The plugin handles payment processing and subscription management; your native capability code handles the actual access control logic.
Use the plugin’s webhooks or action hooks (like MemberPress’s mepr-event-member-signup-completed) to call your wpdev_add_course_caps() function when a user upgrades to a paid tier. On cancellation or expiry, a corresponding function removes those capabilities. This approach keeps your access logic portable and independent of the membership plugin’s own content restriction UI, which gives you much more flexibility in your front-end design.
Register CPTs with a custom capability_type and 'map_meta_cap' => true to get WordPress-native, role-granular access control. Assign capabilities on role creation or membership event, not on every request. Add a template_redirect check as a front-end gate.
Frequently Asked Questions
Yes. WordPress natively supports password-protected posts and pages via the Visibility setting in the post editor. This works for custom post types as well, provided the CPT is registered with public or publicly_queryable enabled. For per-user access control (where different users have different access), use the capability-based approach described in this article instead of a shared password.
Add a front-end gate with the template_redirect hook: check is_singular(‘your_cpt’) and ! is_user_logged_in(), then redirect to login with auth_redirect() or wp_safe_redirect(wp_login_url()). For list and archive visibility, also filter pre_get_posts so the CPT’s entries don’t appear in queries for logged-out visitors. The redirect handles direct URL access; the query filter handles listings — you usually want both.
Register the CPT with a custom ‘capability_type’ and ‘map_meta_cap’ => true, which generates granular capabilities like edit_courses, publish_courses, and read_private_courses. Then grant them to the role with $role->add_cap(‘edit_courses’) — run that once on activation or when the role is created, not on every page load. Only roles holding those caps can then manage that post type.
Setting ‘map_meta_cap’ => true tells WordPress to map the meta capabilities (edit_post, delete_post, read_post) to your CPT’s primitive capabilities based on ownership and status — so ‘who can edit this specific course’ resolves correctly instead of falling back to generic post capabilities. Without it, custom capability_type post types don’t enforce per-object permissions properly, which is the most common reason CPT protection silently fails.
CPT content (posts) is stored in the wp_posts table, distinguished from standard posts and pages by the post_type column. Custom field data for CPT posts lives in wp_postmeta. The CPT registration itself (the PHP code defining it) is not stored in the database — it runs on each page load via the init action hook, typically in functions.php or a site-specific plugin.
Yes — capabilities plus a template_redirect gate give you WordPress-native, role-based protection with no plugin overhead, which is the approach this guide uses. Reach for a membership plugin only when you need drip content, paid access tiers, an admin UI for non-developers, or payment integration. For straightforward role-gating, code is lighter, faster, and fully under your control.
Exclude categories from the main query using the pre_get_posts filter with ‘category__not_in’ set to an array of term IDs. For CPT custom taxonomies, filter terms in your archive template or use get_terms() with an ‘exclude’ argument. Membership plugins can hide entire taxonomy terms based on subscription level, which is useful for tiered content access.
Some developers explore alternatives for more modern JS-first workflows or better performance defaults. However, WordPress still powers over 43% of the web in 2026 and offers an unmatched plugin ecosystem and content management experience. For most businesses, a professionally built custom WordPress site addresses performance and flexibility concerns without the migration cost of switching platforms.
Related Resources
Custom WordPress Development
Membership platforms and access-controlled portals built on WordPress.
Custom WordPress DevelopmentCustom PHP Development
PHP-powered access control and subscription management systems.
Custom PHP DevelopmentWordPress Login Redirects
Role-based post-login redirects — pairs perfectly with CPT protection.
WordPress Login Redirects