Standard WordPress roles are often too broad. But you don’t need a heavy permissions plugin to fix this.
Why Restrict the Editor Role? (The “Principle of Least Privilege”)
By default, a WordPress Editor can edit every page and post on your site. For many clients, this is a recipe for disaster. They want to hand-over some of the website updates to a consultant or the front-desk team, but not provide all access. Here’s a solution that uses PHP and can be added directly to your site using the Free Snippets plugin (or your functions.php).
Limit Editor Permissions PHP CodeSnippet for WordPress
While many “Role Editor” plugins exist, they are often overkill—bloating your database and requiring constant manual updates. This “Restrict Editors” script is a lightweight, set-it-and-forget-it solution. Here’s how it works.
Step 1: Find Your Page IDs
Before we can lock the doors, we need to know the address of the pages we want to leave open. There are two quick ways to find a Page ID in WordPress:
Method A: The “Hover” Trick
Go to Pages > All Pages. Hover your mouse over the title of the page you want to allow. Look at the very bottom-left corner of your browser (the status bar). You’ll see a URL that contains post=XXXX. That number is your ID.
Method B: The “Edit” Method
Click to Edit the page. Once the editor loads, look at your browser’s address bar at the top. The URL will look something like yoursite.com/wp-admin/post.php?post=1623&action=edit. 1623 is your ID.
Step 2: Implementation
You can add this code to your child theme’s functions.php file, but for a cleaner, safer workflow, I recommend using the WP Code Snippets plugin. It allows you to toggle the code on and off without risking a “White Screen of Death” if you make a typo. Make sure to set your script to “Run Everywhere” in Snippets, which is the default, and it will easily work to limit what EDITOR roles on your WordPress website can edit.
Copy the code below exactly. You will be editing lines 9 and 10 to fit your needs.
Step 3: Customizing the Logic (Lines 9 & 10)
This script is built to be flexible. Here is how to configure it for your specific project:
- To Allow a “Directory” (Parent and Children): If you have a main “Gallery” page (ID
2133) and you want every single page nested under it to be editable, put that ID on Line 10. If you don’t need any other random pages, you can comment out or delete Line 9. - To Allow a Single Page: On Line 9, replace
1623with your ID. If you don’t have a “directory” or parent page, simply delete Line 10 or turn it into a comment by adding//at the very beginning of the line. - To Allow Multiple Pages: You can add as many IDs as you want to the brackets, separated by commas. Example:
$allowed_individual_ids = [1623, 1624, 1700];
The “Editor Permissions” Script
PHP
/**
* Restrict Editors to Specific Pages & Directories
* Author: DesignerToFullStack.com
*/
add_action('init', function() {
// --- CONFIGURATION: DROP YOUR IDS HERE ---
$allowed_individual_ids = [1623]; // Line 9: Individual Pages
$allowed_directory_ids = [2133]; // // Line 10: Parent Pages (Directories)
// -----------------------------------------
// 1. PERMISSIONS: The Hard Lockdown
add_filter( 'map_meta_cap', function( $caps, $cap, $user_id, $args ) use ($allowed_individual_ids, $allowed_directory_ids) {
$edit_caps = ['edit_post', 'delete_post', 'edit_page', 'delete_page', 'edit_others_pages', 'edit_published_pages'];
if ( ! in_array( $cap, $edit_caps, true ) ) return $caps;
$user = get_userdata( $user_id );
if ( ! $user || ! in_array( 'editor', (array) $user->roles, true ) ) return $caps;
$post_id = isset( $args[0] ) ? intval( $args[0] ) : 0;
if ( ! $post_id ) return $caps;
// Allow if in individual list
if ( in_array( $post_id, $allowed_individual_ids ) ) return $caps;
// Allow if in directory list or is a child/descendant
if ( in_array( $post_id, $allowed_directory_ids ) ) return $caps;
$ancestors = get_post_ancestors( $post_id );
foreach ( $allowed_directory_ids as $dir_id ) {
if ( in_array( $dir_id, $ancestors ) ) return $caps;
}
// Slam the door on everything else
return [ 'do_not_allow' ];
}, 10, 4 );
// 2. VISIBILITY: The "All Pages" Filter
add_filter( 'pre_get_posts', function( $query ) use ($allowed_individual_ids, $allowed_directory_ids) {
if ( ! is_admin() || ! $query->is_main_query() ) return;
global $pagenow;
if ( $pagenow !== 'edit.php' || $query->get('post_type') !== 'page' ) return;
if ( ! current_user_can('editor') ) return;
// Dynamically find all children of allowed directories
$all_allowed = $allowed_individual_ids;
foreach ( $allowed_directory_ids as $parent_id ) {
$all_allowed[] = $parent_id;
$children = get_posts([
'post_type' => 'page',
'post_parent' => $parent_id,
'posts_per_page' => -1,
'post_status' => 'any',
'fields' => 'ids',
]);
$all_allowed = array_merge($all_allowed, (array)$children);
}
$query->set( 'post__in', array_unique($all_allowed) );
$query->set( 'hierarchical', false ); // Force flat list so children show up
});
});
FAQ: Common Permission Questions
Can I limit a WordPress user to just one page?
Yes. By using the allowed_individual_ids array in our script, you can lock a user down to a single ID. They won’t even see the other pages in their list.
How do I restrict access to child pages automatically?
Our script uses get_post_ancestors. This means if you allow a “Parent” page, the Editor automatically gets permission for every child and grandchild page added under that parent.
Will this hide the pages from the Admin menu?
This script filters the “All Pages” list. While the “Pages” link still exists in the sidebar, clicking it will only show the authorized pages. To hide the sidebar menu items entirely, you can use remove_menu_page().
Why Most Plugins are Overkill
1. Capability vs. Context (The “What” vs. The “Which”)
Most WordPress permission plugins (like User Role Editor or Members) are built around Capabilities. They answer the question: “Can this user edit pages?”
They are not naturally built to answer the question: “Can this user edit this specific page ID?”
- The Plugin Way: You often have to buy a “Pro” version to get “Content Restriction” or “Post-Level” permissions.
- The Full Stack Way: Using the
map_meta_caphook allows us to intercept the request at the exact moment WordPress asks, “Is this allowed?” and check the Context (the ID) rather than just the Role.
2. The “New Page” Maintenance Trap
This is the biggest reason standard plugins fail for “Directories” (like your Before-and-After section).
- With a plugin, every time the client adds a new “child” page, you (the dev) usually have to log back in and manually check a box to give the Editor permission to that new ID.
- Our script is dynamic. By checking for the
post_parentorancestors, the code automatically “adopts” any new pages the client creates. It’s a “set and forget” solution.
3. Performance & Technical Debt
Every plugin you install is another “cook in the kitchen.”
- Database Bloat: Large permission plugins often create their own custom tables or add hundreds of rows to your
wp_optionstable to track granular settings. - Loading Overhead: These plugins run complex logic checks on every page load, even for visitors on the front end who aren’t even logged in.
- The Update Cycle: Plugins are a liability. Every time WordPress updates, a complex plugin has a chance of breaking. A 30-line PHP snippet using core WordPress hooks is virtually future-proof.
4. The “Hidden Page” UX Conflict
As you discovered, many plugins might stop someone from editing a page, but they are terrible at hiding the pages from the list. The client ends up seeing a list of 50 pages they “can’t touch,” which is confusing and makes the dashboard feel cluttered. By using pre_get_posts in our script, we aren’t just locking the door; we’re making the other houses on the street invisible. This creates a much cleaner “SaaS-like” experience for the client.
Many developers reach for plugins like “User Role Editor” for this task. The problem? Those plugins are designed to manage Capabilities (the what), not Context (the which).
With a plugin, you often have to manually check a box every time the client adds a new child page. With our code-first approach, the script automatically “adopts” any new children added to your directory ID. It’s cleaner, faster, and doesn’t add any extra weight to your database.

