Building a Custom Block Part 2: Attributes and Supports

By Ian Svoboda on January 13, 2026

In Part 2 of Building a Custom Block, we’ll go through adding block supports and attributes and explore how they work in some more detail.

In Part 1, we created our block’s files and got the block to render in the editor. Now, we need to take the next step to make our block do things based on the content or styles we give it.

We are creating a Notice block, which will display an element on the page that is intended to get someone’s attention and allow them to interact with it (ex: dismissing it) or its contents (such as a button after some text).

From here, we need to tell our block what it’s allowed to do (via configuration and supports) and what data it should store (attributes). It is important to understand the purpose and function of each to build your blocks in a durable and efficient way, so let’s talk about each.

Start by opening up your block’s block.json file and let’s take a look at the “supports” key.

Supports

The supports key in block.json defines the features or capabilities defined in WordPress that our block supports. By default, the generated code only includes "html": false which indicates that our block cannot be edited as custom HTML.

By adding certain keys and values here, we can opt in (or out) of various editor features and capabilities. A great example is something like an HTML ID. WordPress provides blocks a way to use this feature without having to define their own attribute to do so by setting "anchor": true. By default, all blocks have a className attribute as well, which you could turn off, but in practice there’s not much reason to.

When you’re considering what supports you need, try to think about the things you want the block to do or how you want to use the block. You may decide to allow users to change things like the colors, padding, font-sizes, etc. You may want to be able to give it an ID, and so on.

In our case, we can start with adding support for a few built-in features like an ID, background and text colors, spacing, alignment, and layout. To declare support, you just need to add the appropriate key and value to the top level “supports” key in block.json.

src/blocks/notice/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "lwpd/notice",
	"version": "0.1.0",
	"title": "Notice",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"supports": {
		"align": ["wide", "full"],
		"anchor": true,
		"color": {
			"background": true,
			"text": true,
			"link": true
		},
		"html": false,
		"layout": true,
		"spacing": {
			"margin": true,
			"padding": true,
			"blockGap": true
		},
		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalFontStyle": true,
			"__experimentalFontWeight": true,
		}
	},
	"textdomain": "notice",
	"render": "file:./render.php",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}
JSON

The start task should still be watching for changes, so once you save the block.json file, if you go back to the editor, you should see a number of additional options that weren’t there before including the HTML ID which is located in the Advanced panel.

Screenshot of the block editor showing the sidebar for the Notice block
Screenshot of the block editor with the Notice block styles panel visible.

So by declaring those support keys, the block opts into various core behaviors that we don’t have to set up ourselves. The attributes and UI to change them are all added for us in a way that is perfectly consistent with WordPress core. Whenever you’re building a custom block, it really helps to keep the block as close to core as reasonably possible. This helps users (and yourself) move efficiently through the UI and reduces the possibility of you building a thing that already exists.

Here’s a quick explanation of what each of those main supports keys are for:

  • color – Set various color styles for the block and its children
  • typography – Set various font/typography styles for the block.
  • spacing – Set spacing styles for the block and its children
  • layout – Allows the block’s children to use alignments
  • align – Allows the block to specify an alignment
  • anchor – Adds HTML ID support

Note: The availability of some of these options depends on the theme you’re using. I’m using a default install with the twentytwentyfive theme active here.

You might be looking at this list and saying: shouldn’t there be more things? The answer there kind of depends on what you’re doing, but there’s a few things to consider. Generally speaking you should ask yourself: should I allow someone (or myself) to change this style or setting? If the answer is no, then you shouldn’t declare support for it or give someone the ability to change it.

The idea here is to provide the right level of options without overwhelming users. It doesn’t need to do everything it needs to do the right things. For instance, if we’re setting the background and text colors for the entire notice block, we probably want the other blocks inside of it to use the same text color and not have to set it on each block inside. This is the same approach you’d take if you were writing this CSS by hand: set common styles on a shared parent, but let the children override them if needed.

Blocks have the ability to use a “default” or inherited style. So if you want to be able to easily set the font-size for everything and then override it only where needed, this makes it very easy to do that. Things like typography and color settings are often very handy to set at the top level so the child blocks can inherit them and you only have to set them in one place.

You can read more about block supports in the Block Editor Handbook.

Cleaning up the UI

So after we declared these, the styles panel became a bit cramped. The block editor will show you all of the controls by default, but there’s an experimental key you can add that will only show some by default: __experimentalDefaultControls. This key accepts an object that allows you to say what controls should be visible even if they’re not set or not. Let’s go ahead and update this to hide some of the options and only show specific options by default. The others will be available inside the styles panel by click the three vertical dots to the right of the panel label.

src/blocks/notice/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "lwpd/notice",
	"version": "0.1.0",
	"title": "Notice",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"supports": {
		"align": ["wide", "full"],
		"anchor": true,
		"color": {
			"background": true,
			"text": true,
			"link": true
		},
		"html": false,
		"layout": true,
		"spacing": {
			"margin": true,
			"padding": true,
			"blockGap": true,
			"__experimentalDefaultControls": {
				"blockGap": true,
				"margin": false,
				"padding": false
			}
		},
		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalFontStyle": true,
			"__experimentalFontWeight": true,
			"__experimentalDefaultControls": {
				"fontSize": true,
				"lineHeight": true
			}
		}
	},
	"textdomain": "notice",
	"render": "file:./render.php",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}
JSON

Now if we refresh, the panel looks a lot tidier out of the box:

Screenshot of the block editor showing the streamlined styles panel for the Notice Block

If you try setting these styles you can see them apply in real time right there in the editor. However we still have these gross default styles from the generated block files. We will eventually need the style.scss file, but we may not need the editor.scss file (we’ll see!). So for now we can remove the editor.scss file and just comment out the styles in style.scss.

To remove the editor.scss file, you can delete the file, but then edit block.json so remove the “editorStyles” key and its value:

src/blocks/notice/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "lwpd/notice",
	"version": "0.1.0",
	"title": "Notice",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"supports": {
		"align": ["wide", "full"],
		"anchor": true,
		"color": {
			"background": true,
			"text": true,
			"link": true
		},
		"html": false,
		"layout": true,
		"spacing": {
			"margin": true,
			"padding": true,
			"blockGap": true,
			"__experimentalDefaultControls": {
				"blockGap": true,
				"margin": false,
				"padding": false
			}
		},
		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalFontStyle": true,
			"__experimentalFontWeight": true,
			"__experimentalDefaultControls": {
				"fontSize": true,
				"lineHeight": true
			}
		}
	},
	"textdomain": "notice",
	"render": "file:./render.php",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}
JSON

Also, we need to remove the import statement and the comment above it from in block’s edit.js file:

src/blocks/notice/edit.js
/**
 * Retrieves the translation of text.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
 */
import { __ } from "@wordpress/i18n";

/**
 * React hook that is used to mark the block wrapper element.
 * It provides all the necessary props like the class name.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
 */
import { useBlockProps } from "@wordpress/block-editor";

/**
 * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
 * Those files can contain any CSS code that gets applied to the editor.
 *
 * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
 */
import "./editor.scss";

/**
 * The edit function describes the structure of your block in the context of the
 * editor. This represents what the editor will render when the block is used.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
 *
 * @return {Element} Element to render.
 */
export default function Edit({ attributes, setAttributes }) {
	console.log(attributes);
	return (
		<p {...useBlockProps()}>
			{__("Notice – hello from the editor!", "notice")}
		</p>
	);
}
JavaScript

From there, just go to style.scss and erase the rules inside the wp-block-lwpd-notice selector, then comment out the entire selector to leave it for later.

src/blocks/notice/style.scss
/**
 * The following styles get applied both on the front of your site
 * and in the editor.
 *
 * Replace them with your own styles or remove the file completely.
 */

// .wp-block-lwpd-notice {
// }
SCSS

We’ll come back to the styling aspect of things in a future part and we’ll need this file for later, which is why we’re saving it and not removing it like the editor one.

Next let’s talk about what data the block needs to store: block attributes.

Block attributes

Blocks attributes are used to store structured data about a block. Almost all blocks have at least one attribute and our block now has several thanks to the block supports we just declared. However, there are a few special things that the block may need to do that we’ll be building over the course of this series and those will require their own special attributes.

All block attributes have a few configuration options in common:

  • type – The data type (string, array, boolean, etc)
  • source – Where the attribute’s value is saved. This is in either in JSON object stored in an HTML comment (the comment delimiter), or in the markup itself.
  • default – The default value (if any) for the attribute.

By default, blocks have no “source” value and store the block values in the block comment delimiter. Most of the time this is what you want when you’re building a custom block. In some cases it might make more sense get the attribute’s value from the block’s markup, but it’s a more specific use case.

When do I want to use a different source value?

A great example is a block like the core/paragraph or core/heading block. The “content” attribute just represents the text inside the block. So if the block saves the <p> tag and its text content to the database, when the block loads up the block just tells WordPress “the stuff inside the <p> tag is the value of the content attribute”.

block-library/paragraph/block.json
		"content": {
			"type": "rich-text",
			"source": "rich-text",
			"selector": "p",
			"role": "content"
		},
JSON

Similarly, the core/image tag uses the src attribute from its <img> tag to determine the value of the url attribute:

JSON
		"url": {
			"type": "string",
			"source": "attribute",
			"selector": "img",
			"attribute": "src",
			"role": "content"
		},
JSON

You can read more about the source options in the Block Editor Handbook.

In our case we’re making a notice block. Sometimes we want to show a notice on the page and allow a user to click an “X” button to make it go away. To allow this, we need to start by adding a block attribute called “isDismissible” that will determine if the notice can be dismissed or not.

Since this is a true or false attribute, we’ll naturally want to make it a boolean. We can add the “attributes” key to the block.json file and then include the following object as its value:

src/blocks/notice/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "lwpd/notice",
	"version": "0.1.0",
	"title": "Notice",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"attributes": {
		"isDismissible": {
			"type": "boolean",
			"default": false
		}
	},
	"supports": {
		"align": ["wide", "full"],
		"anchor": true,
		"color": {
			"background": true,
			"text": true,
			"link": true
		},
		"html": false,
		"layout": true,
		"spacing": {
			"margin": true,
			"padding": true,
			"blockGap": true,
			"__experimentalDefaultControls": {
				"blockGap": true,
				"margin": false,
				"padding": false
			}
		},
		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalFontStyle": true,
			"__experimentalFontWeight": true,
			"__experimentalDefaultControls": {
				"fontSize": true,
				"lineHeight": true
			}
		}
	},
	"textdomain": "notice",
	"render": "file:./render.php",
	"editorScript": "file:./index.js",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}
JSON

After you save this, the block won’t have any new UI controls. We’ll need to add in our own InspectorControls which we can do using some React components from WordPress core, which we’ll be covering in Part 3.

Block attributes can have several different types including:

  • null
  • boolean
  • object
  • array
  • string
  • integer
  • number

Type Validation

When a block attribute is saved, the value being saved will be validated against the attribute type. So if you specify an attribute as an integer but then try to save a string to it, the value will not save. This is a common “rookie mistake” that even an experienced developer can run into. Certain admin components like the TextControl return the new value as a string by default, so it helps to consider the type and make sure you’re setting it accordingly.

When your attribute uses things like an object or array for its value, the type definition may include some schema or structure about how the value should be. For instance, it should be an object that has X, Y, and Z keys, an array of strings, etc. You do this by setting the “properties” (for objects) or “items” (for arrays) key when the block attribute has one of those types:

src/blocks/notice/block.json
"attributes": {
  "arrayExample": {
    "type": "array",
    "items": { "type": "string" }
  },
  "objectExample": {
    "type": "object",
    "properties": {
      "X": { "type": "string" },
      "Y": { "type": "number" },
      "Z": { "type": "boolean" }            
    }
  }
}
JSON

If you’re looking at the above and wondering: do I really need to do that for each array or object attribute? the answer is (thankfully) no. If the data you’re adding into the array or object has “mixed types” where the values could be all kinds of things, it might not be very practical or worthwhile to add all of these.

Do I need to define the schema for array or object attributes?

Thankfully, no. It’s not actually required, but if your data is structured and specific, it can be beneficial to make sure the block stores data the way you want. If the attribute’s value is something like an API response, it might make sense to define the schema of what that response should include.

If you’re dealing with complex nested data, you may find defining things this way is quite cumbersome or even too restrictive. There are many commercial block plugins out there that don’t define these things for their own block attributes even.

My advice: if you reasonably can, go for it. But don’t feel obligated especially if you end up fighting with the validation or the definition becomes absurdly long. If that attribute is only being set by you, you can use good coding practices to ensure the data is set correctly and largely eliminate the need to do this at all.

Additionally, you can also specify that the attribute’s value must be one of a few possible choices. This is referred to as an enum value. One common use case is a string attribute whose value can be one of a few specific choices like “yes”, “no”, or “maybe”. You could define that attribute like so:

src/blocks/notice/block.json
"attributes": {
  "stringExample": {
    "type": "string",
    "enum": [ "yes", "no", "maybe" ]
  }
}
JSON

If you are picturing a dropdown, radio, or checklist of possible choices, you probably need an enum if the value of the attribute is either a string or number/integer.

You can read more about block attributes in the Block Editor Handbook.

Next Steps

Now that we have our attributes and supports sorted out, we’ll need to start updating our edit component to support adding inner blocks (aka child blocks) and our own Inspector Controls in Part 3.

Read Part 3

Further Reading



Leave a Reply

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