The WordPress Gutenberg editor, introduced in WordPress 5.0, revolutionized content creation with its block-based system. For developers building custom blocks, the block.json
file serves as the backbone of block configuration, defining metadata, settings, and attributes. Among its many roles, defining attributes in block.json
is critical to ensuring blocks function correctly, persist data, and provide a seamless user experience. Failing to define attributes properly can lead to significant issues, from lost settings to broken functionality. In this article, we explore why block.json
attribute definitions are essential, the consequences of getting them wrong, and real-world examples to illustrate the impact.
What Are block.json
Attributes?
In WordPress Gutenberg blocks, attributes are the pieces of data that store a block’s configuration and content. They define the customizable properties of a block, such as text content, toggle settings, or selected options, which are saved to the WordPress database as part of a post’s content. The block.json
file is where developers declare these attributes, specifying their type (e.g., string
, boolean
, array
), default value, and other properties.
For example, a custom navigation block might define attributes like this:
{
"attributes": {
"menuId": {
"type": "number",
"default": 0
},
"useCategories": {
"type": "boolean",
"default": false
},
"selectedCategories": {
"type": "array",
"default": []
}
}
}
Here, menuId
stores a numeric menu ID, useCategories
toggles category-based navigation, and selectedCategories
holds an array of category IDs. These attributes are manipulated in the block’s JavaScript (edit.js
) and PHP (render.php
) files and saved to the database to persist user settings.
Why Defining Attributes in block.json
Is Important
Properly defining attributes in block.json
is not just a best practice—it’s a requirement for robust block development. Here’s why it matters:
1. Data Persistence
Attributes defined in block.json
are serialized and saved to the WordPress database (in the wp_posts
table as part of the post content). This ensures that user-configured settings, such as toggle states or selected options, are retained when a page is saved, reloaded, or displayed on the front end. Without a proper definition, WordPress doesn’t know to save the attribute, leading to data loss.
2. Block Validation
WordPress uses the block.json
schema to validate block data when loading or saving a post. If an attribute is used in the block’s code but not defined in block.json
, WordPress may flag the block as invalid or silently discard the undefined attribute’s value, causing unexpected behavior.
3. Improved Developer Experience
Defining attributes in block.json
provides a clear contract for what data the block uses. This makes it easier for developers to understand, maintain, and extend the block. It also enables WordPress to provide better tooling, such as automatic TypeScript support or editor hints, when attributes are properly declared.
4. Front-End Rendering
Attributes are often used in the block’s server-side rendering (via render.php
or a render_callback
). If attributes are not defined in block.json
, the front-end output may fail to reflect user settings, leading to inconsistent or broken displays.
5. Future-Proofing and Compatibility
A well-defined block.json
ensures compatibility with future WordPress updates and tools like the Block Editor’s serialization process. It also supports features like block deprecation and migration, where attributes can be transformed if the block’s structure changes.
Consequences of Not Defining Attributes Correctly
Failing to define attributes in block.json
or defining them incorrectly can lead to a range of issues that frustrate users and developers alike. Here are the most common consequences, with examples to illustrate the impact.
1. Loss of User Settings
If an attribute is used in the block’s JavaScript (e.g., in edit.js
) but not defined in block.json
, WordPress will not save its value to the database. As a result, user-configured settings are lost when the page is refreshed or reloaded.
Example: Custom Navigation Block
Consider a navigation block that allows users to toggle “Hide Empty Categories” and “Show Only Selected Categories.” The edit.js
file might include:
<ToggleControl
label="Hide Empty Categories"
checked={attributes.hideEmpty}
onChange={(value) => setAttributes({ hideEmpty: value })}
/>
<ToggleControl
label="Show Only Selected Categories"
checked={attributes.useSelectedCategoriesOnly}
onChange={(value) => setAttributes({ useSelectedCategoriesOnly: value })}
/>
If hideEmpty
and useSelectedCategoriesOnly
are not defined in block.json
, toggling these settings will appear to work in the editor but will reset to their initial (or undefined) values after a page refresh. This frustrates users who expect their settings to persist.
Fix:
Add the attributes to block.json
:
{
"attributes": {
"hideEmpty": {
"type": "boolean",
"default": false
},
"useSelectedCategoriesOnly": {
"type": "boolean",
"default": false
}
}
}
This ensures WordPress saves the toggle states, preserving user settings.
2. Broken Front-End Rendering
Undefined attributes may not be available in the block’s server-side rendering, leading to incorrect or missing output on the front end.
Example: Category Navigation
Suppose the navigation block uses a selectedCategories
attribute to store an array of category IDs for display. The render.php
file might fetch categories like this:
$categories = get_categories([
'include' => $attributes['selectedCategories'],
'hide_empty' => $attributes['hideEmpty']
]);
If selectedCategories
and hideEmpty
are not defined in block.json
, $attributes['selectedCategories']
and $attributes['hideEmpty']
will be null
or undefined, causing the query to fail or return incorrect results. This could result in no categories being displayed or all categories being shown, ignoring user settings.
Fix:
Define the attributes in block.json
:
{
"attributes": {
"selectedCategories": {
"type": "array",
"default": []
},
"hideEmpty": {
"type": "boolean",
"default": false
}
}
}
This ensures the attributes are available and correctly populated in render.php
.
3. Block Validation Errors
When WordPress loads a post, it validates blocks against their block.json
schema. Undefined attributes can trigger validation errors, causing the block to enter recovery mode or fail to load properly in the editor.
Example: Text Alignment
Imagine a block with a text alignment setting:
setAttributes({ align: 'center' });
If align
is not defined in block.json
(even if supports.align
is enabled), WordPress may mark the block as invalid, prompting users to recover or convert it, which disrupts the editing experience.
Fix:
Explicitly define the attribute, even if it’s supported via supports
:
{
"attributes": {
"align": {
"type": "string",
"default": "left"
}
},
"supports": {
"align": true
}
}
4. Debugging Nightmares
Undefined attributes make debugging difficult, as developers may not immediately realize that missing block.json
definitions are causing issues. This can lead to wasted time chasing symptoms (e.g., settings not saving) instead of addressing the root cause.
Example: Debugging a Toggle
In the navigation block example, a developer might spend hours debugging why hideEmpty
resets, checking edit.js
for state management issues, only to realize the attribute was never defined in block.json
. Proper definitions prevent such confusion.
Best Practices for Defining block.json
Attributes
To avoid these pitfalls, follow these best practices when defining attributes in block.json
:
- Define All Attributes Used:
Ensure every attribute manipulated inedit.js
orrender.php
is declared inblock.json
. Even if an attribute seems minor (e.g., a toggle state), it must be defined to be saved. - Specify Correct Types:
Use the appropriate type (string
,number
,boolean
,array
,object
) to match the attribute’s data. For example, usearray
for lists of values, likeselectedCategories
. - Provide Sensible Defaults:
Set default values that align with the block’s initial behavior. For example, a boolean toggle should default tofalse
if it’s off by default, and arrays should default to[]
if empty. - Document Attributes:
Use comments or a separate documentation file to explain each attribute’s purpose, especially for complex blocks. This helps future developers understand the block’s data model. - Test Attribute Persistence:
After defining attributes, test the block by saving settings, refreshing the editor, and checking the front-end output. Verify that all settings persist and render correctly. - Plan for Migration:
If updating an existing block to add new attributes, use thedeprecated
property inblock.json
to migrate old instances. For example:
{
"deprecated": [
{
"attributes": { /* old attributes */ },
"migrate": (attributes) => ({
...attributes,
newAttribute: false
})
}
]
}
Real-World Example: A Navigation Block
Let’s revisit the navigation block example to tie it all together. A developer creates a block allowing users to display a WordPress menu, custom menu items, or categories. The edit.js
file includes controls for:
- Toggling “Use Categories” (
useCategories
) - Toggling “Hide Empty Categories” (
hideEmpty
) - Toggling “Show Only Selected Categories” (
useSelectedCategoriesOnly
) - Selecting specific categories (
selectedCategories
) - Sorting categories by name, count, or ID (
categoryOrderBy
) - Sorting order (ascending or descending) (
categoryOrder
)
Initially, the block.json
only defines:
{
"attributes": {
"menuId": { "type": "number", "default": 0 },
"useCategories": { "type": "boolean", "default": false },
"categoryCount": { "type": "number", "default": 6 },
"customItems": { "type": "array", "default": [...] },
"className": { "type": "string", "default": "tmenu" }
}
}
Problem: The hideEmpty
, useSelectedCategoriesOnly
, selectedCategories
, categoryOrderBy
, and categoryOrder
attributes are missing. As a result:
- Toggling “Hide Empty Categories” or “Show Only Selected Categories” works in the editor but resets on refresh.
- Selected categories are not saved, so the front-end shows all categories or none.
- Sorting settings default to hardcoded values (e.g.,
name
,asc
) and cannot be customized.
Solution: Update block.json
to include all attributes:
{
"attributes": {
"menuId": { "type": "number", "default": 0 },
"useCategories": { "type": "boolean", "default": false },
"categoryCount": { "type": "number", "default": 6 },
"customItems": { "type": "array", "default": [...] },
"className": { "type": "string", "default": "tmenu" },
"hideEmpty": { "type": "boolean", "default": false },
"useSelectedCategoriesOnly": { "type": "boolean", "default": false },
"selectedCategories": { "type": "array", "default": [] },
"categoryOrderBy": { "type": "string", "default": "name" },
"categoryOrder": { "type": "string", "default": "asc" }
}
}
Outcome:
- User settings for all toggles and selections are saved and persist across page refreshes.
- The
render.php
file can use these attributes to fetch and display the correct categories, respecting user preferences. - The block is more reliable, maintainable, and user-friendly.
Conclusion
Defining attributes in block.json
is a fundamental aspect of WordPress Gutenberg block development. It ensures data persistence, validates block content, supports front-end rendering, and enhances the developer experience. Failing to define attributes correctly can lead to lost settings, broken rendering, validation errors, and debugging headaches, as seen in the navigation block example. By following best practices—defining all attributes, using correct types, providing defaults, and testing thoroughly—developers can create robust, user-friendly blocks that work seamlessly in the Gutenberg editor and beyond.
If you’re building a custom block, take the time to audit your block.json
file and ensure every attribute used in your code is properly declared. Your users (and future self) will thank you for it.