This WordPress plugin adds additional governance capabilities to the block editor. This is accomplished via two dimensions:
- Insertion: restricts what kind of blocks can be inserted into the block editor. Only what’s allowed can be inserted, and nothing else. This means that even if new core blocks are introduced they would not be permitted.
- Interaction: This adds the ability to control the styling available for blocks at any level.
We have approached this plugin from an opt-in standpoint. In other words, enabling this plugin without any rules will severely limit the editing experience. The goal is to create a stable editor with new blocks and features being enabled explicitly via rules, rather than implicitly via updates.
This plugin is currently developed for use on WordPress sites hosted on the VIP Platform.
Try out the VIP Governance plugin in your browser with WordPress Playground.
To use the WordPress VIP Block Governance plugin after activation, skip to Usage.
The WordPress VIP Block Governance plugin is authored and maintained by WordPress VIP, and made available to all WordPress sites by VIP MU plugins. Customers who host on WordPress VIP or use vip dev-env
to develop locally have access to this plugin automatically. We recommend this activation method for WordPress VIP customers.
Enable the plugin by adding the method shown below to your application's [client-mu-plugins/plugin-loader.php
][vip-go-skeleton-plugin-loader-example]:
// client-mu-plugins/plugin-loader.php
\Automattic\VIP\Integrations\activate( 'vip-governance' );
Create this path in your WordPress VIP site if it does not yet exist.
This will automatically install and activate the latest mu-plugins release of the WordPress VIP Block Governance plugin. Remove this line to deactivate the plugin.
The latest version of the plugin can be downloaded from the repository's Releases page. Unzip the downloaded plugin and add it to the plugins/
directory of your site's GitHub repository.
Your governance rules are saved in governance-rules.json
in your private folder. Before diving into how it's used, a quick run down of the schema will shed light on how it works.
Note: The private folder is only supported on VIP sites, or while using vip dev-env
locally.
You can find the schema definition used for the rules here. You can use https://api.wpvip.com/schemas/plugins/governance.json
as the schema entry in your rules, to take advantage of code completion in most editors.
We have allowed significant space for customization. This means it is also possible to create unintended rule interactions. We recommend making rule changes one or two at a time to troubleshoot these interactions.
Each rule is an object in an array. The one required property is type
, which can be default
, role
, or postType
. Your rules should only have one entry of the default
type, as described below, and it is the only type that is required in your rule set.
Rules not of type default
require an additional field. These are broken down below, along with examples of their possible values:
Rule Type | Required Field | Possible Values |
---|---|---|
role |
roles |
name/slug of any default or custom roles |
postType |
postTypes |
name/slug of any default or custom post types |
Each rule can have any one of the following properties.
allowedFeatures
: This is an array of the features that are allowed in the block editor. This list will expand with time, but we currently support two values:codeEditor
(viewing the content of your post as code in the editor) andlockBlocks
(ability to lock/unlock blocks that will restrict movement/deletion). If you do not want to enable these features, omit them from the array.blockSettings
: These are specific settings related to the styling available for a block. They match the settings available in theme.json under the keyblocks
. The definition for that can be found here. Unlike theme.json, you can nest these rules under a block name to apply different settings depending on the parent of a particular block.allowedBlocks
: These are the blocks allowed to be inserted into the block editor. Additionally, you can useallowedBlocks
inblockSettings
rules to restrict what blocks can be nested under a parent.
Non-default rule types will be merged with the default rule. This is done intentionally to avoid needless repetition of your default properties. If multiple non-default rule types are provided, they will be applied in the following ascending priority:
- Post Type
- Role
So if a matching postType
and role
rule is found, the role
rule will be applied, and the postType
rule will be ignored. The best analogy is the CSS cascade where more specific rules overwrite less specific rules. We are making a choice that Role-based rules should overwrite Post Type rules. We will introduce a filter in the near future to allow this priority to be customized.
The wildcard *
can be used within allowedBlocks
and within blockSettings
to target more than 1 block. The intention is that it will limit repeated rules, and allow greater flexibility in controlling the editor experience.
For an example of this feature, refer to the example file here.
Note: allowedBlocks
are not respected when a parent blockSettings
also has a wildcard. For example, this will not work:
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "default",
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "core/*" ],
"blockSettings": {
"core/*": {
"allowedBlocks": [ "core/paragraph", "core/heading" ], // ← Not allowed under "core/*"
"color": {
"text": true,
}
}
}
}
]
}
Instead, only apply block settings to wildcards, and specify allowedBlocks
to individual parent blocks:
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "default",
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "core/*" ],
"blockSettings": {
"core/*": {
"color": {
"text": true
}
},
"core/quote": {
"allowedBlocks": [ "core/paragraph", "core/heading" ]
},
"core/media-text": {
"allowedBlocks": [ "core/paragraph", "core/heading" ]
}
}
}
]
}
By default, the plugin uses this governance-rules.json
. To start using the plugin with your own rules, you'll need to create your own governance-rules.json
in your private folder. We recommend duplicating one of the starter rule sets provided below, and adapting it for your needs. In order to take advantage of the rules schema for in-editor support, use https://api.wpvip.com/schemas/plugins/governance.json
.
With this default rule set, all blocks and all features are enabled. It is sensible to set your default rule to the settings you want for your least privileged user then add capabilities with role and/or post type-specific rules.
Below is some rule sets that you can use to build your governance-rules.json
. They cover a wide range of use cases.
This is the default rule set used by the plugin.
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "default",
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "*" ]
}
]
}
With this rule set, the following rules will apply:
- All blocks can be inserted across all the roles.
- No restrictions apply for what's allowed under a block.
- The code editor is accessible for everyone.
- Blocks can be locked and unlocked.
This expands the default rule set by adding restrictions for all users and post types.
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "default",
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "core/group", "core/heading", "core/paragraph", "core/image" ],
"blockSettings": {
"core/group": {
"spacing": {
"spacingSizes": [
{
"size": "clamp(2.5rem, 6vw, 3rem)",
"slug": "300",
"name": "12"
}
]
}
},
"core/heading": {
"color": {
"palette": [
{
"color": "#ff0000",
"name": "Custom red",
"slug": "custom-red"
},
{
"color": "#00FF00",
"name": "Custom green",
"slug": "custom-green"
},
{
"color": "#FFFF00",
"name": "Custom yellow",
"slug": "custom-yellow"
}
],
"gradients": [
{
"slug": "vertical-red-to-green",
"gradient": "linear-gradient(to bottom,#ff0000 0%,#00FF00 100%)",
"name": "Vertical red to green"
}
]
},
"typography": {
"fontFamilies": [
{
"fontFamily": "Consolas, Fira Code, monospace",
"slug": "code-font",
"name": "Code Font"
}
],
"fontSizes": [
{
"name": "Large",
"size": "2.75rem",
"slug": "large"
},
{
"name": "X-Large",
"size": "3.75rem",
"slug": "x-large"
}
]
}
}
}
}
]
}
With this rule set, the following rules will apply:
- Default: Rules that apply to everyone as a baseline:
- The only blocks allowed are group, heading, paragraph and image. Under the group block, only heading, paragraph, image and group can be inserted.
- The code editor is accessible, and blocks can be locked/unlocked or moved.
- For a heading at the root level, there are 3 custom colors as well as a custom gradient that will show up in the color palette. In addition, a custom font called Code Font as well as 2 custom font sizes will show up in the typography panel.
- For a group block, there will be only one option for a spacing size available in padding/margin and block spacing.
This example focuses on providing a restrictive default rule set, and expanded permissions for a specific user role.
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "role",
"roles": [ "administrator" ],
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "core/quote", "core/media-text", "core/image" ],
"blockSettings": {
"core/media-text": {
"core/heading": {
"color": {
"text": true,
"palette": [
{
"color": "#ff0000",
"name": "Custom red",
"slug": "custom-red"
}
]
}
}
},
"core/quote": {
"core/paragraph": {
"color": {
"text": true,
"palette": [
{
"color": "#00FF00",
"name": "Custom green",
"slug": "custom-green"
}
]
}
}
}
}
},
{
"type": "default",
"allowedBlocks": [ "core/heading", "core/paragraph" ],
"blockSettings": {
"core/heading": {
"color": {
"text": true,
"palette": [
{
"color": "#FFFF00",
"name": "Custom yellow",
"slug": "custom-yellow"
}
]
}
}
}
}
]
}
With this rule set, the following rules will apply:
- Default: Rules that apply to everyone as a baseline:
- Heading/paragraph blocks are allowed
- For a heading at the root level, a custom yellow color will appear as a possible text color option.
- Blocks cannot be locked/unlocked or moved.
- The code editor is not accessible.
- Administrator role: Role-specific rules combined with the default set of rules:
- In addition to the default allowed blocks, quote/media-text and image blocks are allowed as well. Both the quote, and media-text blocks are allowed to have heading, paragraph, and image blocks inserted under it.
- A heading at the root level is a custom yellow color as a possible text color option.
- A heading inside a media-text is allowed to have a custom red color.
- A paragraph inside a quote is allowed to have a custom green color.
- The code editor is accessible.
- Blocks can be locked, unlocked, and moved.
This example focuses on providing a restrictive default rule set, and expanded permissions for a specific post type.
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "postType",
"postTypes": [ "post" ],
"allowedFeatures": [ "lockBlocks" ],
"allowedBlocks": [ "core/quote", "core/image" ],
"blockSettings": {
"core/quote": {
"allowedBlocks": [ "core/paragraph", "core/heading" ],
"core/paragraph": {
"color": {
"text": true,
"palette": [
{
"color": "#00FF00",
"name": "Custom green",
"slug": "custom-green"
}
]
}
}
}
}
},
{
"type": "default",
"allowedFeatures": [ "codeEditor" ],
"allowedBlocks": [ "core/heading", "core/paragraph" ],
"blockSettings": {
"core/heading": {
"color": {
"text": true,
"palette": [
{
"color": "#FFFF00",
"name": "Custom yellow",
"slug": "custom-yellow"
}
]
}
}
}
}
]
}
With this rule set, the following rules will apply:
- Default: Rules that apply to everyone as a baseline:
- Heading/paragraph blocks are allowed
- For a heading at the root level, a custom yellow color will appear as a possible text color option.
- Blocks cannot be locked/unlocked or moved.
- The code editor is accessible.
- Posts: Post specific rules combined with the default set of rules:
- In addition to the default allowed blocks, quote and image blocks are allowed as well. A quote block is allowed to have heading, paragraph and if cascading mode is enabled then an image block as well.
- A heading at the root level is a custom yellow color as a possible text color option.
- A paragraph inside a quote is allowed to have a custom green color.
- The code editor is accessible.
- Blocks can be locked, unlocked and moved.
This example focuses on providing a default rule set, using wildcards within the blockSettings
and allowedBlocks
. The use of a wildcard is helpful in targetting a wide variety of blocks, with minimal configuration.
{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "default",
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "core/*" ],
"blockSettings": {
"core/heading": {
"color": {
"text": true,
"palette": [
{
"color": "#FFFF00",
"name": "Custom yellow",
"slug": "custom-yellow"
}
]
}
},
"core/quote": {
"allowedBlocks": [ "core/paragraph", "core/heading" ],
"core/*": {
"color": {
"text": true,
"palette": [
{
"color": "#00FF00",
"name": "Custom green",
"slug": "custom-green"
}
]
}
}
}
}
}
]
}
With this rule set, the following rules will apply:
- Default: Rules that apply to everyone as a baseline:
- All core blocks are allowed
- Within a quote block, only heading and paragraph is allowed
- For a heading at the root level, a custom yellow color will appear as a possible text color option.
- For a heading or paragraph within the quote block, a custom green color will appear as a possible text color option.
- Blocks can be locked/unlocked or moved.
- The code editor is accessible.
-
We highly recommend including
core/paragraph
inallowedBlocks
for thedefault
rule so that all users have access to use paragraph blocks. There are some limitations with the editor that make this necessary:- The Gutenberg editor uses
core/paragraph
blocks as an insertion primitive. If a user is unable to insert paragraph blocks, then they will also be unable to insert any other block in the same place. - Some
core
blocks automatically insertcore/paragraph
blocks that can not be blocked by plugin code. For example, thecore/quote
block has a childcore/paragraph
block built-in to block output. Even if a user hascore/paragraph
blocks disabled, they may still be able to access built-in child blocks.
It is possible to disable
core/paragraph
blocks for a role if it makes sense for your workflow but keep in mind these limitations when doing so. - The Gutenberg editor uses
-
Support for
color.duotone
has not been implemented. -
wp-env.json
within this plugin is intended for tests only, it doesn't work locally. -
Currently, the plugin is restricted to new and existing posts/pages only. So it won't work on the site-editor, widgets, etc. We have support coming in the future to make this possible.
There are filters in place that can be applied to change the behavior for what's allowed and what's not allowed.
Change the governance rules file that's used by the plugin, based on a variety of filter options that are available. By default, it is set to the path to governance-rules.json
in the private directory in a VIP site. For non-vip sites, it is set to the path to governance-rules.json
in the plugin directory.
/**
* Filter the governance file path, based on the filter options provided.
*
* Currently supported keys:
*
* site_id: The site ID for the current site.
*
* @param string $governance_file_path Path to the governance file.
* @param array $filter_options Options that can be used as a filter for determining the right file.
*/
apply_filters( 'vip_governance__governance_file_path', $governance_file_path, $filter_options );
For example, this filter can be used to customize the rules file used for a network site:
add_filter( 'vip_governance__governance_file_path', function ( $governance_file_path, $filter_options ) {
if ( isset( $filter_options['site_id'] ) && $filter_options['site_id'] === 2 ) {
return WPCOM_VIP_PRIVATE_DIR . '/site/2/' . WPCOMVIP_GOVERNANCE_RULES_FILENAME;
}
return $governance_file_path;
}, 10, 2 );
Change, or programmatically set the governance rules used by the plugin, based on a variety of filter options that are available. By default, the rules read from the governance-rules.json
are set regardless of a site being VIP or non-vip.
/**
* Filter the governance rules, based on the filter options provided.
*
* Currently supported keys:
*
* site_id: The site ID for the current site.
*
* This filter can be used to either modify the governance rules content before it's parsed, or to generate the content dynamically.
*
* @param string $governance_rules_json Governance rules content.
* @param array $filter_options Options that can be used as a filter for determining the right rules.
*/
apply_filters( 'vip_governance__governance_rules_json', $governance_rules_json, $filter_options );
For example, this filter can be used to programmatically set the rules used by the plugin instead of using the default rule set provided by the plugin:
add_filter( 'vip_governance__governance_rules_json', function ( $governance_rules_json, $filter_options ) {
return '{
"$schema": "https://api.wpvip.com/schemas/plugins/governance.json",
"version": "1.0.0",
"rules": [
{
"type": "default",
"allowedFeatures": [ "codeEditor", "lockBlocks" ],
"allowedBlocks": [ "core/heading", "core/paragraph" ],
"blockSettings": {
"core/heading": {
"typography": {
"fontFamilies": [
{
"name": "Arial",
"slug": "arial",
"css": "Arial, sans-serif"
}
]
}
}
}
}
]
}';
}, 10, 2 );
This way the governance-rules.json
no longer needs to be created outside the plugin.
Change what blocks are allowed to be inserted in the block editor. By default, root level and children blocks are compared against the governance rules, and then a decision is made to allow or reject them. This filter will allow you to override the default logic for insertion.
/**
* Change what blocks are allowed to be inserted in the block editor.
*
* @param {bool} isAllowed Whether or not the block will be allowed.
* @param {string} blockName The name of the block to be inserted.
* @param {string[]} parentBlockNames An array of zero or more parent block names,
* starting with the most recent parent ancestor.
* @param {Object} governanceRules An object containing the full set of governance
* rules for the current user.
*/
return applyFilters(
'vip_governance__is_block_allowed_for_insertion',
isAllowed,
blockType.name,
parentBlockNames,
governanceRules
);
For example, this filter can be used to allow the insertion of a custom block even if it's not allowed by the governance rules:
addFilter(
'vip_governance__is_block_allowed_for_insertion',
'example/allow-custom-block-insertion',
( isAllowed, blockName, parentBlockNames, governanceRules ) => {
if ( blockName === 'custom/my-amazing-block' ) {
return true;
}
return isAllowed;
}
);
Change what blocks are allowed to be edited in the block editor. Disabled blocks will display with a grey border and will not be editable. By default, root level and children blocks are compared against the governance rules, and then a decision is made to allow or reject them. This filter will allow you to override the default logic for editing.
/**
* Change what blocks are allowed to be edited in the block editor.
*
* @param {bool} isAllowed Whether or not the block will be allowed.
* @param {string} blockName The name of the block to be edited.
* @param {string[]} parentBlockNames An array of zero or more parent block names,
* starting with the most recent parent ancestor.
* @param {Object} governanceRules An object containing the full set of governance
* rules for the current user.
*/
applyFilters(
'vip_governance__is_block_allowed_for_editing',
isAllowed,
blockName,
parentBlockNames,
governanceRules
);
For example, this filter can be used to allow the editing of a custom block type even if it is disabled by governance rules:
addFilter(
'vip_governance__is_block_allowed_for_editing',
'example/allow-custom-block-editing',
( isAllowed, blockName, parentBlockNames, governanceRules ) => {
if ( blockName === 'custom/my-amazing-block' ) {
return true;
}
return isAllowed;
}
);
Select the mode that's used for determining if a block should be allowed or not, between cascading and restrictive. Cascading works similarly to CSS in that, the rules of the parent are looked up first, followed by the root-level rules for determining if the block is to be allowed or not. On the other hand, restrictive only looks up the rules under the parent. If there are no rules under a parent or a block is not allowed under a parent, then that block cannot be inserted. Cascading allows for a simpler rule file avoiding excessive repetition of blocks under a parent. Restrictive does result in more repetition in the rules file, but it results in a more locked-down editor experience. By default, the filter is set to cascading mode. Note that, you have access to the parent block names, block name, and the governance rules in order to decide what mode should be used. So you can fine tune the mode based on any of these values.
/**
* Select the mode used to determine if a block should be allowed or not, between cascading and restrictive.
*
* @param {bool} True, if cascading mode is to be used or false if restrictive is to be used.
* @param {string} blockName The name of the block to be edited.
* @param {string[]} parentBlockNames An array of zero or more parent block names,
* starting with the most recent parent ancestor.
* @param {Object} governanceRules An object containing the full set of governance
* rules for the current user.
*/
applyFilters(
'vip_governance__is_block_allowed_in_hierarchy',
true,
blockName,
parentBlockNames,
governanceRules
);
There is an admin settings menu titled VIP Block Governance
that's created with the use of this plugin. This page offers:
- Turning on and off the plugin quickly, without re-deploying.
- View all the rules at once, and also any errors if the schema is invalid.
- View combined rules as a specific user role and/or for a specific post type.
This endpoint is used to return the combined rules for a given role. This API is utilized by the settings page to visualize merged default and role rules for a selected role. It's only available to users with the manage_options
permission.
It has only three root level keys: allowedBlocks
, blockSettings
, and allowedFeatures
.
This example involves making a call to http://my.site/wp-json/vip-governance/v1/editor/rules
for an editor
role, while using this rule file found in the starter rule sets:
{
"allowedBlocks": [ "core/heading", "core/paragraph" ],
"blockSettings": {
"core/heading": {
"color": {
"text": true,
"palette": [
{
"color": "#FFFF00",
"name": "Custom yellow",
"slug": "custom-yellow"
}
]
}
}
},
"allowedFeatures": []
}
Please note this is for VIP sites only. Analytics are disabled if this plugin is not being run on VIP sites.
The plugin records two data points for analytics, on VIP sites:
-
A usage metric when the block editor is loaded with the WordPress VIP Block Governance plugin activated. This analytic data simply is a counter, and includes no information about the post's content or metadata. It will only include the customer site ID to associate the usage.
-
When an error occurs from within the plugin on the WordPress VIP platform. This is used to identify issues with customers for private follow-up.
Both of these data points are a counter that is incremented and do not contain any other telemetry or sensitive data. You can see what's being collected in code here.
In order to ensure no dev dependencies are installed, the following can be done while installing the packages:
composer install --no-dev
We currently have unit, and e2e tests to ensure thorough code coverage of the plugin. These tests can be run locally with wp-env
and Docker.
For the PHP unit tests:
wp-env start
composer install
composer run test
For the JS unit tests:
npm install
npm run test:js
For the e2e tests:
wp-env start
composer install
npm install
npx playwright install chromium --with-deps
npx playwright test