When I first started building custom blocks with Gutenberg I’d always start with the edit function. Then I’d move to the save function and make sure the right kind of HTML was saved to the database. Attributes were basically an afterthought for me.
I had a vague idea that they defined some of the data in my block, but not much else. When I started exploring attributes I quickly realized that attributes aren’t just essential, but they’re an excellent starting point for a new block, and they’re worth exploring further.
You should probably have a basic understanding of what Gutenberg is and how to develop with it before you read ahead. This is not a step by step guide to developing blocks for Gutenberg (there’s some great tutorials for that). This is not a breakdown of all the different components of block.
What Are Gutenberg Attributes?
I’m going to start out with an example from the very excellent Gutenberg boilerplate. It’s a fairly straightforward block – a single text field that saves a paragraph. (It’s been edited a bit for clarity):
registerBlockType( 'gb/block-editable-03', { title: __( 'GB Editable', 'GB' ), // Block title. icon: 'edit', category: 'common', // This is the important bit. // Our attributes help define what our block's data looks // like. attributes: { content: { type: 'array', source: 'children', selector: 'p', default: [], }, }, // The "edit" property must be a valid function. edit: function( props ) { var content = props.attributes.content; var focus = props.focus; function onChangeContent( newContent ) { props.setAttributes( { content: newContent } ); } // The editable content. return el( Editable, { tagName: 'p', className: props.className, onChange: onChangeContent, value: content, focus: focus, onFocus: props.setFocus } ); }, // Save function. save: function( props ) { var content = props.attributes.content; return el( 'p', { className: props.className, }, content, ); }, } );
Attributes Section
This basic block has one line of editable content, which we’ll get to in a moment. First I really want to zoom in on one part of that big configuration object that we see in registerBlockType
: the attributes
section:
attributes: { content: { type: 'array', source: 'children', selector: 'p', default: [], }, },
The best way to think of attributes is a Javascript object that holds the state (data) for your entire block. They are the glue that connects your edit
and save
functions, so they’re easily accessible from both parts of the block. Technically this object can have anything you want in it – it’s just a way of keeping a central store for your block’s data.
The attributes object can hold any kind of data. It doesn’t necessarily have to be related to your block’s outputs. The only thing that’s absolutely required for an attribute is the type
parameter, which defines what kind of data you will be dealing with. It can be an array
, string
, number
, object
or any of React’s proptypes.
So I could have an attributes array that just looked like this:
attributes: { numberAttribute: { type: 'number', }, textAttribute: { type: 'string', }, },
Storing Attributes in the Database
The important bit is how this is actually saved to the database. Remember, everything is saved as HTML in Gutenberg, so if you ever have an attribute like this that isn’t actually tied to the output of a block, it will be stored in an HTML comment instead. Let’s say I make a change to that textAttribute
I listed above. When I save the block, this will be stored in the database:
<!-- wp:cgb/block-test-block {"textAttribute":"that"} --> <!-- /wp:cgb/block-test-block —>
As the block is loaded in the admin, Gutenberg parses that HTML, sees that there’s some data embedded in the comment, and pulls it out as a string that can be referenced in your edit
function. When that data hits your save
function, you can make any changes you want to it, and the process repeats itself.
This cycle is going to be important. Chances are you want to do something more complicated than just save some static data; you’re going to want to save some actual structured markup. That’s where attribute sources come in.
Attribute Sources
Sources are a guide for the inner workings of Gutenberg. The real “aha” moment for me was realizing that sources really just tell Gutenberg how to pull data out of a block of HTML and convert them into a plain Javascript object. That’s why you can use attributes like this in your edit function:
edit: function( props ) { var imgSrc = props.img.src; }
But what’s saved to the database is actually a block of HTML like this:
<!-- wp:cgb/block-test-block—> <img src="http://path/to/img.jpg" /> <!-- /wp:cgb/block-test-block —>
Let’s work backwards a bit. We’re going to take a look at the HTML and walk through how Gutenberg uses attributes and attribute sources to parse that HTML out and extract data from it.
The example above is just an img
tag with a single src
. What I want to do is make that src
editable in the block itself via a text field. That means Gutenberg is going to have to extract that src
as a single string from the HTML, and pass that data down to the edit method.
HPQ Library
In order to do this, Gutenberg makes use of the hpq library, which is a helper that does exactly this kind of parsing. The documentation for that library, by the way, is worth reading. It will help you understand exactly what is going on behind the scenes.
But you still need to tell Gutenberg what to look for in the HTML, which is exactly what you use source
for.
Gutenberg’s documentation includes a list of possible sources. Each of these describes a different function from hpq that’s used to parse an HTML block. From the example above, we want to target our img
tag, find the src
attribute and make it available as a string. Our attribute would look like this:
imgAttribute: { type: 'string', source: 'attribute', selector: 'img', attribute: 'src', }
The source type here is “attribute”. This tells Gutenberg to take my selector (in this case, the img
tag) and start searching for its attributes. Specifically, I want to grab the src
attribute, so I add that as an additional property to the object.
Selectors
The selector doesn’t have to be an HTML element. It can be anything queryable, like a class or an ID. So if, for instance, our img
tag had a class of myImg
, we could use that instead. Our HTML would look like this:
<!-- wp:cgb/block-test-block—> <img class="myImg" src="http://path/to/img.jpg" /> <!-- /wp:cgb/block-test-block —>
And we would use this attribute:
imgAttribute: { type: 'string', source: 'attribute', selector: '.myImg', attribute: 'src', }
In certain situations, this is all you need to do. This whole process though gets a bit more complicated for more advanced blocks.
The children source
Most of the time, we’ll want something more than just a simple string. A really common use case is a series of paragraphs inside of a single HTML element. This is represented in hpq not as a string, but as an array of DOM nodes, with each element representing a new index in the array. Otherwise, each paragraph wouldn’t be independently editable. Fortunately, Gutenberg gives us the children
attribute source for just this reason.
Sample HTML
Let’s again take a look at sample HTML saved to the database:
<!-- wp:cgb/block-test-block—> <section class="myContent"> <p>Line number 1</p> <p>Line number 2</p> </section> <!-- /wp:cgb/block-test-block —>
Here, I want to take everything that’s inside of my section tag and make it available to edit as an array. This can be more than just paragraph tags. It might include formatting elements (the <strong>
tag), or images. An array ensures that we can iterate over all these elements in our Javascript one by one. Coincidentally, the Editable
component, which you can use to make editable text in a block, accepts an array of this type.
So we know our attribute’s type. It’s an array. Our selector is going to be our section
element. The only thing left to add is our source. For that, we will use children
. Children will parse all of the elements inside of your selector and convert it to an array automatically. So our attribute looks like this:
contentAttribute: { type: 'array', source: 'children', selector: 'section', }
And just like that, you can start extracting complex HTML from your block. This is what you’ll be using most of the time, and it’s the same attribute source used in the first example of this post. But just for fun, we’re going to step it up one more time.
The query source
Probably the most complicated of the source types is query
. You can think of query
as almost a low level implementation of hpq which lets you grab multiple pieces of data from a single element. A simple example of this might be an image tag were you need to grab both a src
attribute and an alt
attribute. This is the example that’s provided in Gutenberg’s documentation.
Another example might be when you have a structured module that needs to be repeated multiple times inside of a single parent element. For instance, let’s say we had a card block with two columns in it. Each card has a single headline and description. In our edit component, however, we want to have this available to us as an array of cards, each card represented as a Javascript object with the properties headline
and description
. That way we can easily iterate through our data, and make it all editable.
Sample HTML
Starting with our HTML, this is what things will look like when it’s saved to the database:
<!-- wp:cgb/block-test-block --> <section class="row"> <div class="card-block"> <h3>Headline 1</h3> <div class="card-content"> <p>Description</p> </div> </div> <div class="card-block"> <h3>Headline 2</h3> <div class="card-content"> <p>Description</p> </div> </div> </section> <!-- /wp:cgb/block-test-block -->
What we want to do here is parse the row
element, find each card-block
, and pull the headline
and description
out, each as a separate key inside of a larger object. Using query
we can define multiple datapoints for the same element.
Using query, I can actually map multiple selectors all inside of the same object. I use the selector just like I have in all my other blocks, targeting child-block
. But I also need to add a query
object which defines exactly what data to look for inside of that target. The query parameter in our attribute is simply a list of other attributes to pull from inside of a single element. In this instance:
content: { type: 'array', source: 'query', default: [], selector: 'section .card-block', query: { headline: { type: 'array', selector: 'h3', source: 'children', }, description: { type: 'array', selector: '.card-content', source: 'children', }, }, }
As you can see, query can be very powerful (and also a little confusing). It won’t always be necessary, but it is extremely useful when you want to intelligently group a nested block of HTML. That’s why I’ve been starting with HTML in these examples. It’s sometimes easiest to start with how the HTML will be rendered out, and then work backwards to figure out exactly how the data should be extracted.
Getting used to Gutenberg attributes
As you can hopefully see by now, there is a lot that can be done with attributes. It’s one of the more powerful, yet less talked about features inside of Gutenberg. But a good understanding of how they work opens up all kinds of possibilities for your blocks.