Docs Checklists & Reference

Block Development Checklist: Pre-Flight Before Upload

Run through this checklist before zipping and uploading any block. Every item here maps to a validator rule, a production failure, or a common editor crash.


File Structure

  • [ ] Block folder is directly inside custom-blocks/ with no subfolders
  • [ ] Block folder name is lowercase with hyphens only (e.g. feature-card, not FeatureCard)
  • [ ] Folder contains exactly: block.json, index.js, index.asset.php, style.css, render.php
  • [ ] ZIP file has the block folder at root, not a parent folder containing the block folder
  • [ ] All files saved as UTF-8 without BOM

block.json

  • [ ] name is in namespace/slug format (e.g. wptruss/feature-card)
  • [ ] apiVersion is 3
  • [ ] category is "wptruss"
  • [ ] editorScript is "file:./index.js"
  • [ ] render is "file:./render.php"
  • [ ] wptPanels key is present with at least "structure": true
  • [ ] headingLevel attribute declared with "type": "string" and a valid heading default
  • [ ] semanticRole attribute declared with "type": "string" and a valid tag default
  • [ ] themeMode and backgroundColor attributes declared
  • [ ] elementOverrides attribute declared as "type": "object", "default": {}
  • [ ] All spacing attributes declared: blockPadding, blockGap, spacingBottom
  • [ ] All layout attributes declared: hideOnMobile, hideOnTablet, hideOnDesktop, textAlign

index.js

  • [ ] All code inside a single IIFE: ( function () { 'use strict'; ... }() );
  • [ ] No import, no export, no export default, no JSX
  • [ ] save() returns null
  • [ ] All conditional renders use condition ? el() : null , never condition && el()
  • [ ] All array attributes guarded with Array.isArray() before .map()
  • [ ] No dynamic strings inside __(), static literals only
  • [ ] window.wptElements calls are null-guarded: window.wptElements ? window.wptElements.buildElementClasses(...) : ''
  • [ ] window.wptPanelRegistry falls back to {}: var REGISTRY = window.wptPanelRegistry || {};
  • [ ] Canvas heading uses element classes from buildElementClasses()
  • [ ] Canvas heading has placeholder text for fresh block inserts
  • [ ] TextareaControl used for multi-line text, not raw el('textarea', ...)
  • [ ] Repeater panels extracted to named functions
  • [ ] No .innerHTML, .outerHTML, eval(), fetch(), or XMLHttpRequest

index.asset.php

  • [ ] Returns array with dependencies and version keys
  • [ ] wp-blocks, wp-element, wp-block-editor, wp-components, wp-i18n included
  • [ ] wpt-element-registry included if using any element
  • [ ] One wpt-element-{name} entry per element the block uses
  • [ ] wp-primitives is NOT in the dependencies array
  • [ ] No other unregistered or unknown handles

render.php

  • [ ] defined( 'ABSPATH' ) || exit; is the first line
  • [ ] getblockwrapper_attributes() is called
  • [ ] All output escaped: escattr(), eschtml(), escurl(), wpkses_post()
  • [ ] Spacing goes via CSS custom properties as inline styles (not modifier classes)
  • [ ] Semantic wrapper tag uses $attributes['semanticRole']
  • [ ] Heading tag uses $attributes['headingLevel']

style.css

  • [ ] No hardcoded hex colours, use var(--wpt-*) tokens
  • [ ] Hex values only inside var() as CSS fallbacks
  • [ ] No transition: or transition-* properties
  • [ ] No animation: or animation-* properties
  • [ ] No @keyframes
  • [ ] No @import
  • [ ] Theme modifier classes present: --theme-light, --theme-dark
  • [ ] Background modifier classes present: --bg-light, --bg-surface, --bg-primary, --bg-secondary, --bg-dark
  • [ ] Heading inversion rules for coloured backgrounds
  • [ ] Responsive visibility classes: --hide-mobile, --hide-tablet, --hide-desktop
  • [ ] Root element consumes block-local CSS custom properties for padding and gap