Docs Block Validator

Block Validator: The Governance Rules


Rule 1 – block.json Must Exist

Severity: Error – blocks fails to register

block.json must exist in the block folder root. If it is missing, the validator stops immediately, no further checks run.

If block.json exists but contains invalid JSON, this rule also fails. Validate your JSON at jsonlint.com before uploading.

✅ PASS: feature-card/block.json exists and is valid JSON
❌ FAIL: block.json is missing from the folder
❌ FAIL: block.json exists but contains a syntax error

Rule 2 – Required Keys in block.json

Severity: Error – block fails to register

block.json must contain all five required top-level keys: name, title, category, editorScript, attributes.

// ✅ PASS
{
    "name":         "wptruss/my-block",
    "title":        "My Block",
    "category":     "wptruss",
    "editorScript": "file:./index.js",
    "attributes":   {}
}

// ❌ FAIL — missing category
{
    "name":         "wptruss/my-block",
    "title":        "My Block",
    "editorScript": "file:./index.js",
    "attributes":   {}
}

Rule 3 – No core/ Namespace

Severity: Error – block fails to register

The block name must not start with core/. This prevents overriding WordPress core blocks.

// ✅ PASS
{ "name": "wptruss/feature-card" }
{ "name": "my-brand/hero" }

// ❌ FAIL
{ "name": "core/paragraph" }
{ "name": "core/custom-hero" }

Use wptruss/ as the namespace for all blocks built for this platform.


Rule 4 – Required Attributes: headingLevel and semanticRole

Severity: Error – block fails to register

Both headingLevel and semanticRole must be declared in the attributes object. Without them, the block cannot participate in the heading governance system or semantic structure control.

// ✅ PASS
"attributes": {
    "headingLevel": { "type": "string", "default": "h2" },
    "semanticRole": { "type": "string", "default": "section" }
}

// ❌ FAIL — headingLevel missing
"attributes": {
    "semanticRole": { "type": "string", "default": "section" }
}

Your index.js must then use attr.headingLevel as the heading tag on the canvas, never hardcode h2 or any other heading level directly.


Rule 5 – editorScript File Must Exist

Severity: Error – block fails to register

The file declared in editorScript must exist in the block folder. The validator resolves the file:./ prefix and checks the filesystem.

// ✅ PASS
block.json: "editorScript": "file:./index.js"
index.js exists in the folder

// ❌ FAIL
block.json: "editorScript": "file:./index.js"
index.js is missing from the folder

Common cause: uploading a project folder that has a build output in a subfolder, rather than the block folder itself.


Rule 6 – index.asset.php Must Exist

Severity: Error – block fails to register

WordPress requires an .asset.php file alongside every registered editor script. The validator checks that index.asset.php exists next to index.js.

If you are not using a build tool, create the file manually:

<?php
return array(
    'dependencies' => array(
        'wp-blocks',
        'wp-element',
        'wp-block-editor',
        'wp-components',
        'wp-i18n',
    ),
    'version' => '1.0.0',
);

Rule 7 – JavaScript Security Scan

Severity: Error – block fails to register

The block’s editor JavaScript is scanned for forbidden patterns. Comments are stripped before scanning.

Forbidden pattern Why Use instead
eval( Arbitrary code execution Remove. Use JSON.parse() for data.
new Function( Arbitrary code execution Use named functions.
document.write( DOM injection wp.element.createElement()
.innerHTML XSS risk wp.element.createElement() or wp_kses()
.outerHTML XSS risk wp.element.createElement()
atob( Base64 obfuscation Serve data plainly.
btoa( Base64 encoding Not permitted in block code.
window.fetch( Unapproved network request wp.apiFetch()
XMLHttpRequest Unapproved network request wp.apiFetch()
import( Dynamic import not supported Declare deps in .asset.php
require( Not available in browser Declare deps in .asset.php

Note: .innerHTML is checked with the leading dot so that the word innerHTML appearing in a comment or documentation string does not trigger a false positive. Real DOM assignments (element.innerHTML = ...) always have the dot.

Similarly, window.fetch( is the pattern rather than just fetch( to avoid flagging wp.apiFetch() calls, which contain the substring fetch(.

// ✅ PASS
var p = wp.element.createElement( 'p', null, text );

// ❌ FAIL
container.innerHTML = '<p>' + text + '</p>';

Rule 8 – V1 Motion Compliance

Severity: Error – block fails to register

Block CSS must not contain any transition, animation, or @keyframes declarations. Motion is reserved for the V2 Motion Layer, where it will be governed by motion.json contracts that allow consistent, auditable animation across the platform.

This is a platform governance rule, not a stylistic preference.

Forbidden CSS patterns:

Pattern Notes
transition: The shorthand
transition- Sub-properties (transition-duration, transition-property, etc.)
animation: The shorthand
animation- Sub-properties
@keyframes Keyframe definitions
/* ✅ PASS */
.wpt-my-block__button:hover {
    background-color: var(--wpt-color-primary);
}

/* ❌ FAIL */
.wpt-my-block__button {
    transition: background-color 0.2s ease;
}

/* ❌ FAIL */
@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

The --wpt-transition-* and easing tokens exist in the token ABI but may not be used yet. They are reserved for when the V2 Motion Layer ships.


Rule 9 – Token Compliance

Severity: Warning – block registers but violation is logged

CSS files are scanned for bare hex colour values (#rgb or #rrggbb) outside of var() calls. Each one found is reported as a warning. The block registers, but violations appear in the debug panel.

/* ✅ PASS — hex value is inside var() as a fallback */
.wpt-my-block {
    background-color: var(--wpt-color-surface, #F8F9FA);
    color: var(--wpt-color-text, #1A1A1D);
}

/* ❌ WARNING — bare hex outside var() */
.wpt-my-block {
    background-color: #F8F9FA;
    color: #1A1A1D;
}

Replace every bare hex value with its nearest --wpt-color-* token. If no exact token exists, use the nearest token as the primary value and keep the hex only as a CSS fallback inside var().


Rule 10 – Forbidden Dependency Handles

Severity: Warning – block registers but violation is logged

index.asset.php is scanned for dependency handles that cause blocks to silently fail. Currently one handle is on the forbidden list: wp-primitives.

wp-primitives is an internal Gutenberg build toolchain package, not an exposed WordPress script handle. Declaring it as a dependency causes WordPress to attempt to enqueue a script that does not exist, resulting in the entire block script failing to load with no visible error.

// ✅ PASS
'dependencies' => array(
    'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n',
),

// ❌ WARNING
'dependencies' => array(
    'wp-blocks', 'wp-element', 'wp-primitives',  // causes silent block load failure
),

Valid dependency handles:

  • wp-blocks
  • wp-element
  • wp-block-editor
  • wp-components
  • wp-i18n
  • wpt-element-registry (when using any element)
  • wpt-element-{name} (one per element used)
  • wpt-icon-picker (only when using the icon picker component)