Block Attributes & Supports

Where a block's data actually lives — the comment delimiter or the saved HTML — and what supports gives you without writing UI.

Syntax"attributes": { "url": { "type": "string", "source": "attribute", "selector": "img", "attribute": "src" } }

Attributes are a block's data. Each entry in the attributes object of block.json declares a type, an optional default, and optionally a source saying where the value is read back from when saved markup is parsed.

Sources

sourceValue comes from
omittedThe JSON inside the block's comment delimiter
attributeThe HTML attribute named by attribute, on selector
textThe textContent of selector
htmlThe innerHTML of selector
queryAn array, one entry per match, shaped by a nested query

The meta source is deprecated; read post meta through the entity APIs instead.

supports

supports hands a block whole panels of editor UI without you writing any: color (background, text, gradients, link), spacing (padding, margin, blockGap), typography (fontSize, lineHeight), border, dimensions (6.2), shadow (6.5), anchor, align. Each adds controls, stores its value under a reserved attribute, and generates the classes emitted by useBlockProps() and get_block_wrapper_attributes().

"align": true is shorthand for "align": [ "left", "center", "right", "wide", "full" ]; the array form is how you offer only some of them. className and html both default to true.

Your own controls

Whatever supports does not cover goes in <InspectorControls> from @wordpress/block-editor, which renders into the settings sidebar. Adding group="styles" moves the panel to the Styles tab.

Example

Example · php
/**
 * Render callback for sitekit/callout.
 *
 * Attributes arrive already validated against the schema in block.json, and
 * anything the user never touched arrives as its declared default.
 */
function sitekit_render_callout( array $attributes, string $content, WP_Block $block ): string {
	$heading = (string) ( $attributes['heading'] ?? '' );

	// Clamp anything that lands in the markup: the attribute schema checks the
	// type, not the range.
	$level = max( 2, min( 6, (int) ( $attributes['level'] ?? 2 ) ) );

	// supports.color populates $attributes['backgroundColor'] / ['textColor'],
	// supports.spacing populates $attributes['style']. Do not read those and
	// build classes yourself — get_block_wrapper_attributes() emits every class
	// and inline style those supports generated, plus the wp-block-* class.
	$wrapper = get_block_wrapper_attributes( array( 'role' => 'note' ) );

	return sprintf(
		'<div %1$s><h%2$d>%3$s</h%2$d>%4$s</div>',
		$wrapper,                 // Escaped by core.
		$level,
		esc_html( $heading ),
		$content                  // Inner blocks, already rendered.
	);
}

When to use it

  • Deciding whether a value belongs in the comment delimiter or in the saved HTML
  • Giving authors colour and spacing controls without building the UI
  • Adding a single toggle or number field to a block's settings sidebar

More examples

Attributes with every source shape

level has no source, so it is stored as JSON in the comment and stays readable even if the markup is edited. The rest are parsed back out of the saved HTML, which keeps the comment small and the markup self-describing. The query source is how a repeater works: selector picks the elements, query describes one entry, and the attribute comes back as an array of objects.

Example · json
{
  "attributes": {
    "level": {
      "type": "number",
      "default": 2
    },
    "caption": {
      "type": "string",
      "source": "html",
      "selector": "figcaption",
      "default": ""
    },
    "url": {
      "type": "string",
      "source": "attribute",
      "selector": "img",
      "attribute": "src"
    },
    "alt": {
      "type": "string",
      "source": "attribute",
      "selector": "img",
      "attribute": "alt",
      "default": ""
    },
    "links": {
      "type": "array",
      "source": "query",
      "selector": "a",
      "query": {
        "href": { "type": "string", "source": "attribute", "attribute": "href" },
        "label": { "type": "string", "source": "text" }
      }
    }
  }
}

supports doing the work

Every panel a user expects, with no JavaScript written. align takes an array to offer only wide and full rather than "align": true, which would offer all five. html: false removes the "Edit as HTML" option, worth setting on blocks whose markup must stay intact. shadow is WordPress 6.5 and dimensions is 6.2, so check your minimum supported version before relying on either.

Example · json
{
  "supports": {
    "anchor": true,
    "align": [ "wide", "full" ],
    "html": false,
    "color": {
      "background": true,
      "text": true,
      "gradients": true,
      "link": true
    },
    "spacing": {
      "padding": true,
      "margin": [ "top", "bottom" ],
      "blockGap": true
    },
    "typography": {
      "fontSize": true,
      "lineHeight": true
    },
    "shadow": true,
    "dimensions": { "minHeight": true }
  }
}

A custom control in the sidebar

InspectorControls is a slot fill: the JSX inside it is rendered in the settings sidebar whenever this block is selected, so it can sit anywhere in the returned tree. Wrap related fields in a PanelBody so they collapse together. Pass group="styles" to InspectorControls when the control is visual rather than functional, and it moves to the Styles tab beside the supports panels.

Example · javascript
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, ToggleControl, RangeControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

export default function Edit( { attributes, setAttributes } ) {
	const { showDate, count } = attributes;

	return (
		<>
			{ /* Renders into the sidebar, not into the block itself. */ }
			<InspectorControls>
				<PanelBody title={ __( 'Feed settings', 'sitekit' ) }>
					<ToggleControl
						__nextHasNoMarginBottom
						label={ __( 'Show date', 'sitekit' ) }
						checked={ !! showDate }
						onChange={ ( value ) => setAttributes( { showDate: value } ) }
					/>
					<RangeControl
						__nextHasNoMarginBottom
						label={ __( 'Posts to show', 'sitekit' ) }
						value={ count }
						min={ 1 }
						max={ 20 }
						onChange={ ( value ) => setAttributes( { count: value } ) }
					/>
				</PanelBody>
			</InspectorControls>

			<div { ...useBlockProps() }>{ /* preview */ }</div>
		</>
	);
}

Discussion

  • Be the first to comment on this lesson.