There’s more to attributes than might initially meet the eye. A block can have as many attributes as you like, and these are defined in the attributes property of the block’s block.json
file.
Block attributes are essentially variables that store data or content for the block. Users can modify and update the values stored there. But where does the data or content come from?
An attribute can get its value from a defined default
, or from user input. It can also be populated with a value retrieved from the stored content. The values retrieved in this last way can, in fact, come from a variety of different places in the stored content and can be retrieved in various ways. Let’s dig in.
Table of contents
The Anatomy of a simple block
Consider this simple block. It has a single attribute, namely content
:
"attributes": {
"content": {
"type": "string"
}
}
The Edit()
function implements a RichText
component that displays and updates the content
attribute:
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Edit( { attributes, setAttributes } ) {
return (
<RichText
{ ...useBlockProps() }
placeholder="Type something here..."
tagName="div"
value={ attributes.content }
onChange={ ( val ) => setAttributes( { content: val } ) }
/>
);
}
The save()
function does what you expect it to do and merely saves the content:
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function save( { attributes } ) {
return (
<RichText.Content
{ ...useBlockProps.save() }
tagName="div"
value={ attributes.content }
/>
);
}
You’ll see this in your editor:
Add some content and save your post and this is what you’ll see in the front end of your site:
All well and good, and no surprises there. But let’s take a look at the code that this block generates.
Click on the three dots in top right of the editor and select “Code editor” – or use the handy shortcut key combination to toggle between the visual editor and the code editor.
This is what the code for this block looks like:
<!-- wp:mb/attributes-test {"content":"My cool content!"} -->
<div class="wp-block-mb-attributes-test">My cool content!</div>
<!-- /wp:mb/attributes-test -->
Notice something here. The text you entered into the RichText
component’s text area appears twice – once in the attribute in the block delimiter, and then again in the actual block content.
You may think that this is a bit redundant, which of course it is!
By default block attributes are stored in the block delimiter. But this needn’t be the case. They are only stored in the delimiter if no source
property is provided in the attribute definition.
The source
and selector
properties
The source
and selector
properties can be used in tandem with each other to target part of the saved content of the block so that the attribute can be populated with a value derived from the targeted area.
Let’s look at an example to see how these two properties work together. Add source
and selector
properties to the definition of the content
attribute:
"attributes": {
"content": {
"type": "string",
"source": "text",
"selector": "div"
}
}
Refresh the post in the editor and re-examine the code. Now you’ll see that the text only occurs once, in the block content, and it no longer appears in the block delimiter:
<!-- wp:mb/attributes-test -->
<div class="wp-block-mb-attributes-test">My even cooler content!</div>
<!-- /wp:mb/attributes-test -->
Yet, if you add a console.log
statement to the Edit()
function to view the attributes:
console.log( attributes );
You’ll see that the content
attribute is still being populated:
The content
attribute is in fact getting its value from the text, as defined by the source
property, within the <div>
element, as defined by the selector
property – so when you reload the post in the editor the value
property of the RichText
component is populated with the text within the <div>
element retrieved from the saved content.
It’s important to ensure that you’re using the correct value for the source
property of your attribute. Let’s see why.
Use the RichText
component’s toolbar to make part of the text bold:
If you inspect the code for this block now you’ll clearly see the <strong>
tag wrapping the emboldened text:
<!-- wp:mb/attributes-test -->
<div class="wp-block-mb-attributes-test">My <strong>even cooler</strong> content!</div>
<!-- /wp:mb/attributes-test -->
Save the post, and check that it’s all looking good in the front-end.
Great! Now reload the post in the editor and…. oh yikes, that’s unexpected!
Check in the browser console and you’ll see an error something like this:
Content generated by `save` function:
<div class="wp-block-mb-attributes-test">My even cooler content!</div>
Content retrieved from post body:
<div class="wp-block-mb-attributes-test">My <strong>even cooler</strong> content!</div>
This is because you now have markup embedded in the content, namely the <strong></strong>
tags and so it’s not pure text anymore. Change the value of the source
property in the attribute from text
to html
:
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "div"
}
}
Now when you save the post and then reload it in the editor all will be well.
In effect the text
value works akin to HTML’s textContent
property, and the html
value works akin to HTML’s innerHTML
property.
Using HTML attributes as the source
As well as text
and html
, the source
property can also take a value of an HTML attribute. In this case an additional attribute
property must also be supplied.
Add a link to your text in the block:
If you now look at the block’s code you’ll see additional markup, namely the <a>
tag with the URL you chose in the href
attribute:
<!-- wp:mb/attributes-test -->
<div class="wp-block-mb-attributes-test">My <strong>even cooler</strong> content - powered by <a href="https://wordpress.org">WordPress</a>.</div>
<!-- /wp:mb/attributes-test -->
Let’s get that URL into a block attribute.
Add a new attribute to block.json
and call it embedded-link
:
"embedded-link": {
"type": "string",
"source": "attribute",
"selector": "a",
"attribute": "href"
}
As mentioned earlier, when you specify attribute
as the value of source
then you must supply an attribute
property, in addition to the selector
property.
So here we’re telling the block’s embedded-link
attribute that its source is an HTML attribute, and that it should select the <a>
element and get its value from the href
attribute of that element.
Note: in the foregoing paragraph it’s important to distinguish between what is a block attribute and what is an HTML attribute. Don’t confuse the two!
Make sure you’ve still got the console.log( attributes )
statement in your Edit()
function and reload the post containing your block in the editor.
You should now see that the attributes
object has two properties, content
and embedded-link
:
content
contains the markup entered by the user into theRichText
component in the blockembedded-link
contains the URL retrieved from thehref
attribute in the<a>
element
{content: 'My <strong>even cooler</strong> content - powered by <a href=\"https://wordpress.org\">WordPress</a>.', embedded-link: 'https://wordpress.org'}
It should be noted that the value of selector
can be any HTML tag or any CSS selector queryable with querySelector. So if in the markup of your block the <a
> element had a class:
<a class="wp-link" href=\"https://wordpress.org\">WordPress</a>
Then you could instead target it thus:
"embedded-link": {
"type": "string",
"source": "attribute",
"selector": ".wp-link",
"attribute": "href"
}
Likewise, if the <a>
element instead had a name
attribute:
<a name="wp-link" href=\"https://wordpress.org\">WordPress</a>
It could then be targeted like this:
"embedded-link": {
"type": "string",
"source": "attribute",
"selector": "a[name='wp-link']",
"attribute": "href"
}
This is all well and good for targeting single items of content, but what if you want to get multiple items? For example, what if you want to get all the URLs from all the <a>
elements?
Using query
as the source
At the moment there’s only a single <a>
element, so add an additional link to your block’s content:
Now your block’s code contains two links and looks something like this:
<!-- wp:mb/attributes-test -->
<div class="wp-block-mb-attributes-test">My <strong>even cooler</strong> content - powered by <a href="https://wordpress.org">WordPress</a>, made with <a href="https://en.wikipedia.org/wiki/Love">love</a>.</div>
<!-- /wp:mb/attributes-test -->
Change the embedded-link
attribute in block.json
to instead be called embedded-links
, as this is more meaningful for storing multiple values. Define it as follows:
"embedded-links": {
"type": "array",
"source": "query",
"selector": "a",
"query": {
"link": {
"type": "string",
"source": "attribute",
"attribute": "href"
}
}
}
Here we’ve changed the value of the type
property to array
, as we’re going to be storing multiple values. We’ve also changed the source
to query
, while the selector
remains as a
.
Just as when the source
was set to attribute
it was necessary to define an attribute
property, when the source
is set as query
you must define a query
property.
The query yields an array of objects and it is in the definition of the query
property that you define the structure of the object. Here, each object in the array will have just one property, namely link
which will store a string, and it will use as its source the href
attribute of each <a>
element iterated over.
Refresh the post in the editor and, assuming you still have the console.log( attributes )
statement in your Edit()
function, if you look in the browser console you should see that both the URLs have been captured.
embedded-links: Array(2)
0: {link: 'https://wordpress.org'}
1: {link: 'https://en.wikipedia.org/wiki/Love'}
Let’s also try getting the text content of the links. Add a link-content
property to the query:
"embedded-links": {
"type": "array",
"source": "query",
"selector": "a",
"query": {
"link": {
"type": "string",
"source": "attribute",
"attribute": "href"
},
"link-content": {
"type": "string",
"source": "text"
}
}
}
And you should see something like this in the console:
embedded-links: Array(2)
0: {
link: 'https://wordpress.org'.
link-content: "WordPress"
}
1: {
link: 'https://en.wikipedia.org/wiki/Love',
link-content: "love"
}
Conclusion
Attributes provide a means for users to change the content and other data for the block. If you can retrieve attribute values from within the block’s saved content then you should do so by defining a source
property to specify where you want to retrieve the value from.
However, not all values can be retrieved from the content. It is still perfectly legitimate to store attributes in the block’s delimiter. Values stored in the delimiter would generally come from fields that you provide for the user, for example fields within an <InspectorControls>
component that appear in the settings sidebar.
For the full low-down on working with attributes see the Attributes page in the Block Editor Handbook.
Kudos for reviewing this post go to: @bph, @juanmaguitar, and @thatdevgirl
Leave a Reply