How to limit the editor to certain blocks by role in PHP in WordPress

Contents

Overview

This article explains, in full detail, how to limit the block editor (Gutenberg) to a specific set of blocks per user role using PHP. It covers the available filters, practical examples, per-post-type rules, wildcard matching, dynamic configuration, where to place the code (theme vs plugin), and troubleshooting tips. All code examples are ready to paste into a themes functions.php or into a small plugin file.

What you need to know first

  • Goal: Allow only certain block types for certain user roles (for example: allow editors only paragraph and image blocks).
  • Primary hooks:
    • allowed_block_types_all — recommended when available: receives the current block whitelist and an editor context array (post_type, post, etc.).
    • allowed_block_types — older filter that receives the allowed list and the post object (fallback if the _all filter is not available in your environment).
  • Block names: Block identifiers are strings like core/paragraph, core/image, my-plugin/my-block. Use exact names when returning allowed lists, or compute names from the block registry.
  • Where to add the code: Themes functions.php or create a small plugin (recommended for portability and persistence across themes).

Key implementation concepts

  1. Detect user role: Use wp_get_current_user() and check user->roles. Do not rely on display names — roles are stored in the user object.
  2. Be defensive: The incoming allowed list may be null or boolean true — detect that and produce an explicit array of strings (or return true to allow everything).
  3. Per-post-type filtering: Use the editor context (post_type) if available to limit blocks on a per-post-type basis (e.g., pages vs posts vs custom post types).
  4. Intersect with registered blocks: If you declare block names that are not registered, they will simply be ignored for robustness, compute intersection with WP_Block_Type_Registry to avoid typos.
  5. Wildcard/prefix: You can allow all blocks from a namespace (e.g., all core blocks core/) by matching prefixes against registered block names.

Simple example: allow only a few blocks for the Editor role

This example uses the modern filter allowed_block_types_all (preferred). For backward compatibility you may also register an allowed_block_types fallback (shown later).

lt?php
/
  Limit allowed blocks for the editor role.
 
  Return an explicit array of block names for editors, otherwise return default.
 /
function my_allowed_blocks_by_role( allowed_blocks, editor_context ) {
    // Get current user
    user = wp_get_current_user()
    if ( ! user  empty( user->roles ) ) {
        return allowed_blocks
    }

    // If user is an editor, restrict blocks
    if ( in_array( editor, (array) user->roles, true ) ) {
        // Explicit allowed blocks for editors
        return array(
            core/paragraph,
            core/image,
            core/heading,
            core/list,
        )
    }

    // For all other roles, do not change the allowed blocks
    return allowed_blocks
}
add_filter( allowed_block_types_all, my_allowed_blocks_by_role, 10, 2 )
?gt

Notes

  • If the editor_context is available you can check editor_context[post_type] to apply different rules for pages vs posts.
  • Return an array of names. Returning true allows all registered blocks returning false prevents all blocks (not typically useful).

Per-role and per-post-type configuration (practical approach)

Use a configuration array mapping roles to allowed block lists, and optionally add post type rules. This scales better than multiple functions.

lt?php
/
  Config-driven allowed blocks per role and per post type.
 /
function config_allowed_blocks_by_role( allowed_blocks, editor_context ) {
    user = wp_get_current_user()
    if ( ! user ) {
        return allowed_blocks
    }

    // Example config: role => [ post_type => [blocks] ] or role => [blocks] for global per-role
    config = array(
        administrator => true, // true means allow all registered blocks
        editor => array(
            post => array( core/paragraph, core/image, core/heading ),
            page => array( core/paragraph, core/heading ), // pages less media
        ),
        author => array( core/paragraph ), // authors only get paragraph globally
    )

    // Determine role (first matched role)
    role = null
    foreach ( (array) user->roles as r ) {
        if ( isset( config[ r ] ) ) {
            role = r
            break
        }
    }

    // If no special role config, leave defaults
    if ( ! role ) {
        return allowed_blocks
    }

    role_setting = config[ role ]

    // true means allow all
    if ( role_setting === true ) {
        return true
    }

    // If post type context is present and a mapping exists
    post_type = isset( editor_context[post_type] ) ? editor_context[post_type] : null
    if ( post_type  is_array( role_setting )  isset( role_setting[ post_type ] ) ) {
        desired = role_setting[ post_type ]
    } else {
        // Fallback to global list for role (if config is direct array)
        desired = is_array( role_setting ) ? role_setting : allowed_blocks
    }

    // Ensure we only return names that are actually registered
    registered = array_keys( WP_Block_Type_Registry::get_instance()->get_all_registered() )
    final_allowed = array_intersect( registered, desired )

    return final_allowed
}
add_filter( allowed_block_types_all, config_allowed_blocks_by_role, 10, 2 )
?gt

Why intersect with registered blocks?

To avoid typos or references to blocks that are not registered on the site. Returning a block name that does not exist is silently ignored by the editor, but producing a final list that only contains valid block names is cleaner and helps you detect configuration mistakes during development.

Fallback for older environments: allowed_block_types

Some environments or plugin stacks may not expose allowed_block_types_all. Use a fallback that supports the older signature. Note the older filter passes the post object rather than an editor context.

lt?php
/
  Fallback using allowed_block_types signature.
 /
function fallback_allowed_block_types( allowed_blocks, post ) {
    user = wp_get_current_user()

    if ( ! user ) {
        return allowed_blocks
    }

    if ( in_array( editor, (array) user->roles, true ) ) {
        // Example: editors allowed only paragraph and image on posts
        if ( post  isset( post->post_type )  post->post_type === post ) {
            return array( core/paragraph, core/image )
        }
        return array( core/paragraph )
    }

    return allowed_blocks
}
add_filter( allowed_block_types, fallback_allowed_block_types, 10, 2 )
?gt

Wildcard or namespace matching (allow all core/)

If you want to allow all blocks within a namespace (e.g., all core blocks) without listing each block individually, compute the allowed set by filtering the registered blocks for a prefix match.

lt?php
/
  Allow all core/ blocks for a role (wildcard prefix match).
 /
function allow_core_namespace_for_role( allowed_blocks, editor_context ) {
    user = wp_get_current_user()
    if ( ! user ) {
        return allowed_blocks
    }

    if ( in_array( contributor, (array) user->roles, true ) ) {
        all_registered = WP_Block_Type_Registry::get_instance()->get_all_registered()
        names = array_keys( all_registered )

        core_allowed = array_filter( names, function( name ) {
            return strpos( name, core/ ) === 0
        } )

        // array_values to reindex if desired
        return array_values( core_allowed )
    }

    return allowed_blocks
}
add_filter( allowed_block_types_all, allow_core_namespace_for_role, 10, 2 )
?gt

Common extra examples

  • Allow a custom block only for editors: Use the block name my-plugin/my-block in the allowed array.
  • Allow different sets for multiple roles: Use a mapping array (role => blocks) as shown earlier.
  • Allow all blocks for administrators: Return true for administrators so they are not restricted.

Where to place this code

  • Theme: functions.php — quick and simple, but role restrictions will disappear if you switch themes.
  • Plugin: Create a small plugin and put the code in the main plugin file. This is recommended for portability and upgrades.

Example plugin header to create a small plugin file (place this at top of file in wp-content/plugins/my-block-limiter/my-block-limiter.php):

lt?php
/
  Plugin Name: Block Limiter by Role
  Description: Limit allowed block types in the editor per user role.
  Version: 1.0
  Author: Your Name
 /
 
// (Add the filter function code here)
?gt

Testing and debugging tips

  1. Log available registered block names to debug what to allow:
    lt?php
    registered = WP_Block_Type_Registry::get_instance()->get_all_registered()
    error_log( Registered blocks:  . implode( , , array_keys( registered ) ) )
    ?gt
        
  2. Test with users assigned the target roles. Create test accounts for each role.
  3. Clear caches (object cache and any admin caching plugins). Some hosts aggressively cache admin output.
  4. When testing on multisite, ensure network activation rules and per-site block registration do not change available block types unexpectedly.
  5. If the editor still shows blocks you expected to hide, ensure no other plugin re-adds them, and check filter priority conflicts — set a later or earlier priority as required.

Troubleshooting common problems

  • Block not disappearing: Confirm the block name is correct. Use registered names from the block registry rather than guessing.
  • Filter not firing: Ensure the filter is added at plugin load time (no conditional that stops it), and check for typos in the hook name.
  • Role check failing: Remember a user can have multiple roles. Use in_array on user->roles as shown.
  • Third-party blocks still visible: They may be registered after your code runs in rare setups. You can increase priority or call your filter at a later stage, but allowed_block_types_all is normally applied at the right moment for the editor.

Security and best practices

  • Do not rely on client-side enforcement only. The allowed_block_types filters change the editor configuration in PHP which is the authoritative place to limit blocks. There are no security implications beyond correct capability checks unless you use custom server-side rendering that depends on block content — in those cases validate block content when saving.
  • Prefer returning explicit arrays rather than lightweight client-side JS hacks PHP-side filters will be applied consistently.
  • Document your configuration array, and keep block names in a single place within your plugin or theme to avoid duplicated magic strings.

Advanced: dynamic administration UI (quick outline)

If you want a UI to configure which roles can use which blocks, simple steps:

  1. Build an options page with roles and a multi-select of registered blocks (use WP_Block_Type_Registry to list blocks).
  2. Save the mapping into an option (e.g., my_block_limiter_settings).
  3. Use the saved mapping inside your allowed_block_types_all filter to return the selected blocks per role.

Compatibility notes

  • Both the allowed_block_types and allowed_block_types_all filters operate on server-side configuration used by the block editor. Use allowed_block_types_all when available for better context (post_type, post), otherwise fallback to allowed_block_types.
  • Custom block editors or specialized plugin editors may behave differently test with the exact editor configuration used on your site.

Useful references

Complete sample plugin (compact)

A compact, self-contained plugin that enforces different block sets for administrators, editors and authors:

lt?php
/
  Plugin Name: Compact Block Limiter
  Description: Example: limit blocks per role (admin: all, editor: paragraph image, author: paragraph).
  Version: 1.0
  Author: Example
 /

function compact_block_limiter( allowed_blocks, editor_context ) {
    user = wp_get_current_user()
    if ( ! user ) {
        return allowed_blocks
    }

    // Admins get everything
    if ( in_array( administrator, (array) user->roles, true ) ) {
        return true
    }

    // Editors restricted on posts
    if ( in_array( editor, (array) user->roles, true ) ) {
        // You can use post_type context if needed
        if ( isset( editor_context[post_type] )  editor_context[post_type] === post ) {
            return array( core/paragraph, core/image )
        }
        return array( core/paragraph, core/heading )
    }

    // Authors get only paragraphs
    if ( in_array( author, (array) user->roles, true ) ) {
        return array( core/paragraph )
    }

    return allowed_blocks
}
add_filter( allowed_block_types_all, compact_block_limiter, 10, 2 )
?gt

Final checklist before deploying

  • Test with at least one test user per role.
  • Confirm block names are correct and available on the site.
  • Decide whether the change should be theme-scoped (functions.php) or site-scoped (plugin).
  • Document behavior for site editors and train them on the allowed blocks.
  • Consider an option to temporarily disable the restrictions for troubleshooting.


Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *