How to automatically create Gutenberg blocks from php classes

Introduction

At Wonderful, Most of the components we use to render elements on a page are php based. We’ve accumulated and consolidated them over the years, and they’re now coupled with many of our administration plugins.

Before the advent of Gutenberg, those components were most of the time hydrated by shortcode values. Shortcodes are mastered by our developers, which means that for them they are easy to declare, quick to implement and to get the administered data out of them to pass those values to the php component for rendering. On the downside, they can be limited for advanced administration needs.

Then Gutenberg came along, with a brand new approach : blocks. Blocks offer a much better editing experience than shortcodes, especially when working with media or complex data structures, (or ones that repeat). But this power comes with a cost : it’s more complex to declare and implement than anything else (including shortcodes). Blocks are also written in JavaScript, preferably in React to be more precise, and this particularity rules out some of our developers from developing blocks at the time, as I explained in detail in this article about how we made the team adopt Gutenberg.

The need for a quick, inclusive and easy option

Like I said in the introduction, we mostly use php components to render our data. The creation of those components is well documented and mastered by the whole team.

Those are the reasons that led us to the conclusion that we needed to retrieve a solution that was quick and easy to implement for everyone in the team to address simple use cases. A solution that would be quick to develop but that would still make the editing experience of our users better by using Gutenberg instead of using shortcodes.

If the solution to a user need is more complex than this, we would build him a full featured Gutenberg block instead.

The plan (also TL;DR)

The idea of our solution is is follows :

  • We’ll add annotations to a component class and its attributes
  • Adding those annotations would automatically make this php based component available as a Gutenberg block in the editor.
  • It would take the form of an admin form to fill, with one field per attribute.
  • Then the administered data would be passed back to the component which will render it.

Essentially we’d like to turn this kind of component :

class CodeComponent extends AbstractComponent
{
    /**
     * @var string
     */
    protected $language;

    /**
     * @var string
     */
    protected $snippet;

//[...]
}

Into this kind of administration form automatically in Gutenberg :

Code snippet admin form screenshot

By adding annotations like so :

/**
 * @Block(title="Code Snippet")
 */
class CodeComponent extends AbstractComponent
{
    /**
     * @var string
     * @BlockAttributes(component="PlainText",type="string",componentAttributes={"placeholder":"Language"})
     */
    protected $language;

    /**
     * @var string
     * @BlockAttributes(component="PlainText",type="string",componentAttributes={"placeholder":"Code"})
     */
    protected $snippet;

//[...]
}

As you can see : we’re settling for a middle line solution. A form to fill is a step below a custom Gutenberg block experience for the end user but still better than shortcodes, and for the developers : it’s quicker to create than original Gutenberg blocks, and it’s still in a language they master. This solution will be chosen when appropriate based on the block end function.

The implementation

Step 1 : A proper php annotations strategy

Originally for us, a php component is a class that has attributes, and a method that renders markup (either directly, or via an associated twig template).

We can take advantage of this existing structure to add annotations at the class level, and at the attributes level.

Here’s an example of the beginning of a an existing component class :

class AddressComponent extends AbstractComponent
{
    /**
     * @var string
     */
    protected $title;

    /**
     * @var string
     */
    protected $address;

//[...]

}

On this code snippet, we will then apply two types of annotations : the class based one and the attributes based one.

Class based annotations

Class based annotations will pilot the block declaration, here’s how they are declared :

/**
 * @Annotation
 * @Target({"CLASS"})
 */
final class Block
{
    /** @var string */
    public $title;
    /** @var string */
    public $icon;
    /** @var string */
    public $category;
}

It allows us to pilot three things :

  • title : the name of the block within the Gutenberg editor. If not set, the class name is used as block title.
  • category : By default the block will be stored under a specific category called molecules, that we created to store those particular blocks. But it can be changed to any existing category.
  • icon : you can specify the name of an icon. By default it will be the abstract block icon.

Attributes based annotations

The attributes based annotations are a way to pilot each attribute behavior and are declared like this :

/**
 * @Annotation
 * @Target({"PROPERTY"})
 */
final class BlockAttributes
{
    /** @var string */
    public $component;
    /** @var string */
    public $type;
    /** @var string */
    public $label;
    /** @var array */
    public $componentAttributes;
}

This case is a bit more complex, we have more things we can parameter for each block attributes, which will become a field to fill in a form on the abstract Gutenberg block. Here’s the detail for each possibility :

  • Component : This is the react component name to use to render to form field. Most popular ones include PlainText, RichText, MediaUpload or InnerBlocks for example.
  • Type : This is the data type for this attribute. You can see the list of accepted types on the official Gutenberg attributes documentation
  • Label : You can specify the form field label. If not set, the attribute name will be used as default.
  • ComponentAttributes: can be an array of options we’ll use to pilot the field behavior when needed.

Let’s now see how we use those annotation capabilities on our example component :

/**
 * @Block(title="Adresse")
 */
class AddressComponent extends AbstractComponent
{
    /**
     * @var string
     * @BlockAttributes(component="PlainText",type="string",componentAttributes={"placeholder":"Titre"})
     */
    protected $title;

    /**
     * @var string
     * @BlockAttributes(component="RichText",type="string",componentAttributes={"placeholder":"Adresse postale"})
     */
    protected $address;

Thanks to the class based annotation, we’ll make the component available in Gutenberg inside the “molecules” category, with the name “Adresse“, and with the default abstract block icon.

Then we specify that the address title will be a string editable via PlainTextComponent.

And finally the address part itself will be a RichText component.

Step 2 : Gathering annotations knowledge and pass it to JavaScript

In Gutenberg, block declaration is a mix of php and JavaScript code. In our scenario, we would like to achieve two things :

  • Automate the php part as much as possible. The only thing we are willing to do is annotate our php components.
  • Completely abstract the JavaScript part so we won’t have to touch it when we would create annotated blocks.

All the rest of the block declaration and logic should work without further action from our developers.

Automating the php part thanks to annotations

We have created a class called the molecule registrator, which role is to take a list of annotated classes (we call them molecules) , and create the corresponding blocks for each of them. The corresponding code looks like this :

    public function createBlocks()
    {
        if (!empty($this->molecules)) {
            foreach ($this->molecules as $moleculeClass) {
                $molecule = new $moleculeClass();
                $block    = $this->createBlock($molecule);
                if (!empty($block)) {
                    $this->addBlock($block);
                }
            }
        }
    }

As you can see we loop on each declared molecule class, and create the block for it thanks to the createBlock method.

Here’s the detail of this createBlock method :

    /**
     * @param AbstractComponent $molecule
     *
     * @return AbstractBlock
     */
    protected function createBlock(AbstractComponent $molecule)
    {
        $abstractBlock = new MoleculeBlock();
        try {
            //Point A : Gather the annotations
            $reflectionClass = new \ReflectionClass($molecule);
            $annotations     = $abstractBlock->extractAnnotationsFromMolecule($reflectionClass, $molecule);
            $shortClassName  = $reflectionClass->getShortName();
        } catch (\Exception $e) {
            $annotations    = [];
            $className      = get_class($molecule);
            $frags          = explode('\\', $className);
            $shortClassName = end($frags);
        }
        if (empty($annotations)) {
            return null;
        }
        $attributes = $annotations['properties'];
        //Point B : Automate the block declaration
        $props      = [
           //Point C : every molecule will share the same JavaScript class : the one able to create abstract blocks
            'editor_script'   => 'wwp-abstract-block',
            'editor_style'    => 'wwp-molecule-block-editor',
            'attributes'      => $attributes,
            //Point D : The render callback is driven back to the molecule
            'render_callback' => [$abstractBlock, 'render'],
        ];
        $blockName  = 'wwp-gutenberg-utils/molecule-' . strtolower($shortClassName) . '-block';
        $abstractBlock->setBlock(new \WP_Block_Type($blockName, $props));
        $abstractBlock->setAnnotations($annotations);

        return $abstractBlock;
    }

Here’s nearly the whole logic in 2 steps : get the annotations near point A, then use them to automate the block creation near point B.

There are two other special things that are notable here :

  • Point C : every molecule will share the same JavaScript class, the one able to create abstract blocks.
  • Point D : The render callback is driven back to the molecule, which means that it will be the annotated class responsibility to render the final markup (which is specifically what we want in our case).

Passing knowledge to the JavaScript

For this, we rely on another method of our molecule registrator service called addBlocksToJsConfig which implementation follows :

/* Point A : in our architecture, $jsConfig is an array driven by php and made available to our JavaScript. 
So what we need to do is add our block knowledge to $jsConfig to be able to locate it in our JS. */
 
public function addBlocksToJsConfig(array $jsConfig)
    {
        if (empty($jsConfig['blocks'])) {
            $jsConfig['blocks'] = [];
        }

        if (!empty($this->blocks)) {
            foreach ($this->blocks as $abstractBlock) {
                $block      = $abstractBlock->getBlock();
                $title      = $this->getInAnnotations($abstractBlock, 'title', __(str_replace('gutenberg-utils-molecule-block/', '', $block->name), WWP_GUTENBERGUTILS_TEXTDOMAIN));
                $cat        = $this->getInAnnotations($abstractBlock, 'category', self::BLOC_CAT);
                $icon       = $this->getInAnnotations($abstractBlock, 'icon', 'archive');
                $attributes = $block->attributes;
                if (!empty($abstractBlock->getAnnotations()['options'])) {
                    $attributes = array_merge($attributes,$abstractBlock->getAnnotations()['options']);
                }
                $jsConfig['blocks'][$block->name] = [
                    'name'       => $block->name,
                    'title'      => $title,
                    'category'   => $cat,
                    'attributes' => $attributes,
                    'icon'       => $icon,
                ];
            }
        }

        return $jsConfig;
    }

As you can see from the code above : we loop on each molecule to build an array of information that we pass to our JS.

Here’s the kind of data structure our addBlocksToJsConfig method produces and passes to the JS:

Screenshot of the data passed to the JavaScript by the addBlocksToJsConfig method (attributes, category, icon, name)

We will now see how our JS uses this array to build abstract blocks automatically on the client side.

Step 3 : Abstracting the block creation on the JavaScript side

On the client side, the aim is to scan and use the blocks definition data passed to the JS to build a corresponding administration form automatically.

We start with the edit method of our block, in which we’ll build and render our administration form. We’ll build this form by looping on each attribute and use its data to create the corresponding form field.

Let’s start by looking at the edit method :

  edit(props) {
    const {className} = props;

//This is where we build the edit form
    const {optionsForm, attributesForm} = this.renderAttributesForm(props);
    const inspectorControls = this.getInspectorControls(optionsForm);

    return (
      {inspectorControls}
      
{this.opts.title} //This is where we render it {attributesForm.map((attributeField) => { return attributeField; })}
); }

This is the detail of the renderAttributesForm method :

 renderAttributesForm(props) {
    const {attributes} = props;

    let attributesForm = [];

    Object.keys(this.opts.attributes).map((key) => {
      if (key !== 'className' && key !== 'layout') {
        let attr = this.opts.attributes[key];

          switch (attr.component) {
            case 'MediaUpload':
              attributesForm.push(this.mediaUploadSubComponent(key, attributes, props));
              break;
            default:
              attributesForm.push(this.defaultSubComponent(key, attributes, props));
              break;
          }
        
        
      }
    })

    return {optionsForm, attributesForm};
  }

Essentially we loop through each attribute and decide which sub component to render for each one. We make a particular case for the media upload component, otherwise we go into the defaultSubComponent method.

Here’s the detail of the mediaUploadSubComponent method :

  mediaUploadSubComponent(key, attributes, props) {
    let attr = this.opts.attributes[key];
    attr.componentAttributes = attr.componentAttributes || {};

    const Component = wp.blockEditor[attr.component];
    return (<Component
      key={key}
      onSelect={this.onMediaSelect.bind(this, key, props)}
      value={attributes[key]}
      render={this.mediaRender.bind(this, key, attributes, props)}
      {...attr.componentAttributes}
    />);
  }

And here’s the detail of the defaultSubComponent one :

  defaultSubComponent(key, attributes, props) {
    let attr = this.opts.attributes[key],
      label = key,
      componentName = attr.component;

    attr.componentAttributes = attr.componentAttributes || {};

    let wrapClasses = componentName + "-wrap " + key + "-wrap";

    let val = attributes[key];
    if (!val && attr.componentAttributes && attr.componentAttributes['data-default-value']) {
      val = attr.componentAttributes['data-default-value'];
    }

    if (componentName === 'HiddenText') {
      componentName = 'PlainText';
      wrapClasses += " hidden";
      this.onChange(key, props, val);
    }

    const Component = wp.blockEditor[componentName];

    return (<div key={key} className={wrapClasses}>
      <label>{label} : </label>
      <Component
        onChange={this.onChange.bind(this, key, props)}
        value={val}
        {...attr.componentAttributes}
      /></div>);
  }

This class is the same for every molecule we create, it doesn’t change when creating a new one. It’s smart enough to adapt to every molecule structure given the fact the molecule is properly annotated. We can focus on the most essential part of the design of a solution for our user without friction. We can also improve this class over time to make it more intelligent.

For example, recently we added the capability to generate options in the Gutenberg sidebar when you select a block, still via our annotation system.

Step 4 : the server side rendering

So far we have on one hand an annotated php class, and on the other one a back office administration form we can fill. We now need to get the administered data and pass it back to our original class for rendering.

If you remember the code snippet of the createBlock method earlier, we had this line in it :

//Point D : The render callback is driven back to the molecule
            'render_callback' => [$abstractBlock, 'render'],

Here’s the detail of this render method :

    public function render(array $attributes = [], $content = null)
    {
        if (empty($attributes['calledClass'])) {
            return (new NotificationComponent('error', 'No molecule class provided'))->getMarkup();
        }

        $moleculeName = $attributes['calledClass'];
        if (!class_exists($moleculeName)) {
            return (new NotificationComponent('error', 'Molecule class provided ' . $moleculeName . ' does not exist.'))->getMarkup();
        }

        try {
//Point A : instanciate the component
            /** @var AbstractComponent $molecule */
            $molecule        = new $moleculeName();
            $reflectionClass = new ReflectionClass($molecule);
//Point B :extract annotations to better know how to use the passed data with this component
            $annotations     = $this->extractAnnotationsFromMolecule($reflectionClass, $molecule);
        } catch (Exception $e) {
            return (new NotificationComponent('error', 'Could not parse annotations from provided molecule : ' . $moleculeName . '.'))->getMarkup();
        }

        $moleculeAttributes = !empty($annotations['properties']) ? $annotations['properties'] : [];
        if (!empty($annotations['options'])) {
            $moleculeAttributes = array_merge($moleculeAttributes, $annotations['options']);
        }

        $fillWith = [];

        if (!empty($moleculeAttributes)) {
            foreach ($moleculeAttributes as $key => $moleculeAttribute) {
//Point C : Loop through each attribute to try to get the best value for each one.
                $key_ = str_replace($moleculeName . ':', '', $key);

                if ($moleculeAttribute['component'] === 'InnerBlocks' && !empty($content)) {
                    $fillWith[$key_] = $content;
                } elseif (isset($attributes[$key])) {
                    $val = $attributes[$key];

                    if ($moleculeAttribute['component'] === 'MediaUpload') {
                        $frags = explode('.', $val);
                        $ext   = strtolower(end($frags));

                        //Est-ce que c'est une image ?
                        if (in_array($ext, ['jpg', 'png', 'gif', 'jpeg', 'avif', 'webp','svg'])) {
                            $size = !empty($moleculeAttribute['componentAttributes']) && !empty($moleculeAttribute['componentAttributes']['size']) ? $moleculeAttribute['componentAttributes']['size']
                                : 'medium';
                            $val = Medias::mediaAtSize($val, $size);
                        }
                    }

                    $fillWith[$key_] = $val;
                }
            }
        }

        $getMarkupOpts = [];

        if (!empty($attributes['className'])) {
            $getMarkupOpts['itemAttributes']['class'] = explode(' ', $attributes['className']);
        }

//Point D : fill the component with the data then get its markup back.
        return $molecule->fillWith($fillWith)->getMarkup($getMarkupOpts);

    }

Here again I’ve commented with notable points the most interesting areas. What this render method essentially does is to retrieve the component to use, then fill it with the administered data, then get its markup back for output.

Conclusion

I faced a real challenge abstracting the JavaScript part as I’m not a React expert. To be honest it’s surely not a flawless implementation. But apart from that, the overall solution does exactly what we wanted at the beginning, and it works like a charm so we’re all pretty happy with the result.

Any member of the team and not just the senior JavaScript developers can now quickly create simple blocks by adding documented annotations on component attributes, and that improved our development speed greatly on those components.

I can’t put a complete code repository of this solution online at the time, but I’m not sure that does matter because what’s important here seems to be the journey more than the final implementation. I’ve tried to provide as much code samples and comments in the article to get you an overall idea of the solution.

Hopefully it will get someone an inspiration to build the same sort of thing in your own organisation if need be.

How we made a php production team adopt Gutenberg

Reaching the limit of wysiwyg

I’ve started working with WordPress in 2009, and it followed me quite closely throughout the last 11 years. During the first 8 or 9 of them, be it me, my colleagues, or most of the community working with this CMS, we did so in PHP. Altogether, we were part of this huge ecosystem made of WordPress profesionnals that built themes and plugins for it, and mostly all of that written in PHP.

Then Gutenberg came along, and for the right reasons in my opinion. The old editor had reached its limits. As an agency building premium websites for our customers, we felt limited by it, and so were our clients. It had reached a point where they couldn’t get high quality layouts without writing HTML themselves or composing with uncomfortable trickery. Well, it had made its time and it was time for a change. And ho boy what a change it was about to be.

First Gutenberg encounter

I remember the excitement I felt when I saw the first Gutenberg demo. It was even mounted on a front office environment, which let us think we might be able to offer live content editing on a page too in the future. Sadly that was just for the demo, Gutenberg is still only a back office editor for now.

Then came the Gutenberg plugin that let us work with it even though it was not part of the WordPress core yet. It was time for a first encounter, and honestly I was shocked. I was shocked because I wasn’t ready, I was shocked because I was surprised, I was shocked because it felt so different, I was shocked because it was hard. I was close to call it a treason. Why ? Because I felt a rupture with why I think WordPress is awesome.

WordPress is in my opinion a very open platform, partly thanks to its hooks and template hierarchy mechanism. Thanks to those two things, you can expand the CMS or rewrite parts of it in really great extends. For example you can use many different rendering methods in the front end, be it the loop by default, or MVC with frameworks, or twig / timber rendering if you want. It’s so open and so many people have produced resources to work with it that you can choose an approach that truly fits the way you’d like to work with it.

At first, I felt we’d lost that with the introduction of Gutenberg. Suddenly I felt I didn’t have the choice anymore, that someone told me and the rest of us : “Learn JavaScript deeply because that’s what the cool kids do now, and we want to be cool again”.

I did think at first that it was a hype driven development and elitist move forcing the use of React upon a php based community. A move that would exclude so many people from the game when WordPress usually tries to get the most on board. I saw a risky and unstable move embracing a technology evolving a high speed, with the frequent introduction of major or breaking changes in its core API, which contrasts with the huge retro compatibility efforts on the PHP core for example.

Unsurprisingly I wasn’t alone feeling this. Many of the WordPress pros I know where shook by this new way of approaching our work.

But don’t get me wrong, I’m not here to bash on Gutenberg. Those last lines summarize a first impression, a first negative impression that needed more time and understanding of the product to mature, that’s why I didn’t share it that much at the time.

Embracing change

Despite my first skepticism regarding the development experience, Gutenberg was going to stay, and was going to be a center piece of the platform I was working the most with, so I had two options : jumping on the train, or stay grumpy on the side.

Furthermore the development experience was not the only thing to take into consideration : what about the webmaster experience? What about the writer experience?

I’ve tried both and I had to admit Gutenberg was a huge improvement compared to what the situation was before it. Writing was smoother, and also quicker. Same for when I put the webmaster hat on : I was able to quickly mount pages, sometimes with complex layouts. The presentation was clear, it distinguished content from the structure, I could rapidly see how the page was mounted, find the area I would want to edit. It felt cleaner with no more HTML visible on the page, just blocks. We awaited an editing improvement and that was clearly the case, it’s a great tool to create content and lay it out, and those are two very good reasons to make a development effort to offer our customers the best editing experience we could.

How can we close such a gap

Let’s summarize the situation we’re in. On one side we’ve got a php production team, with php processes, php plugins, php components accumulated over the years, and on the other hand we’ve got a brand new editor, full of possibilities, promising to break the edition barrier, but requires you to code and even render in react. That made the team defiant.

That’s quite a gap, but the questions are : are those two worlds really incompatible? Do we have to throw away our capitalized efforts in favor of a shiny new tool? Is there a pragmatic migration path ?

First point : server side opening

Remember when I wrote that Gutenberg was removing the luxury of the choice from developers earlier on? It turns out I was wrong (on many parts but let’s focus on this one for now). First choice you have : you can choose to prefer a server side rendering in place of the the react rendering when you register a block on the php side with the register_block_type function. Here’s a more complete example : https://gist.github.com/Shelob9/144055408101e2fdfc4bf34adc85dd04

This opens up a new world of possibilities because in a sever side render callback, you have access to two crucial parameters : the attributes administered on your block, and the administered block content. You can see those two parameters documented on the WP_Block_Type::render method documentation. And honestly with those two parameters you have more than anything you need to render your blocks with a php callback, and more precisely in our case, existing php components like the ones we used to administer via shortcodes for example.

To sum up, with a server side render callback, you can improve the admin experience by coding a react block for the back office, and potentially map the edited attributes and content to an existing php component on the front office, which smoothens the migration path.

Second point : Migrating away from ShortCodes

ShortCodes have been around for so long. They’ve been utterly useful to ease the administration of content inside the wysiwyg editor. They allowed us to separate the content from the HTML structure for example.

But they have limits too, especially when trying to deal with medias for example or complexe structures (even with imbricated shortcodes).

They were quick to create though (thus cheap in cost), and efficient in their task, and most importantly for our subject, they were mastered by everyone, whereas suddenly, we need to produce react blocks, which at the moment of writing eliminates some of our developers from the process, and it’s becoming longer for the others (thus more expansive).

That’s why we’ve decided to invest on a solution that would equilibrate the balance : a php annotation based block abstraction mechanism. I’ll write a dedicated article on this technique soon because it’s a bit long to explain it in detail, but here’s a summary in short : placing php annotations on a php component class (such as ones we use to render content historically), as well as on its attributes, would automatically make this php based component available as a Gutenberg block in the editor, under the form of an admin form, with one field per attribute. Then the render would be delegated back to this component, with all the values for each field passed.

Bascially it allows you to turn the following

/**
 * @Block(title="Chiffre clé")
 *
 */
class ChiffreCleComponent extends AbstractComponent
{
    /**
     * @var string
     * @BlockAttributes(component="PlainText",type="string",componentAttributes={"placeholder":"Titre"})
     */
    protected $title;

    /**
     * @var string
     * @BlockAttributes(component="PlainText",type="string",componentAttributes={"placeholder":"Texte"})
     */
    protected $text;

    /**
     * @var string
     * @BlockAttributes(component="MediaUpload",type="string",componentAttributes={"size":"medium"})
     */
    protected $image;

[...]

Into this :

TL;DR : that worked super well for low level administration needs.

Third point : Becoming comfortable with react Gutenberg blocks in the back office

Indeed that’s the all point of having this new editor : being able to create new blocks for it.

I did a bit of react in the past : a side project to understand the framework, how it worked, and what coding with it felt like. But it was a long time ago and not really in depth, so I had to train again: train on react, train on Gutenberg. I read a lot of tutorials, plenty of documentation, then decided to take on a small challenge : creating a section block (the equivalent of the <section> html tag), and a container block, (which is basically a wrapper div.)

I’ve always found it interesting to try to build something, even small, when learning a new skill : it allows you to learn by doing, and search online for ways to tackle a difficulty you encounter. In this case : focusing on those two blocks helped me to understand how a block should be registered, structured, how changes should be persisted, how the render mechanism works and so on. It was full of discovery and knowledge, and that surely helped me a lot to tackle the abstract block challenge which was a lot more technical.

For those two blocks I opted for a react render, not a server one, it was more appropriate in this case, and it made me discover how you can render inner blocks within a block.

It was not easy at first to build those blocks. Since then, I became more at ease with the process, and the React syntax, even though it moves quickly. But it’s still a lengthier process for us than coding in PHP, so when facing a new block opportunity, we’ve decided to question what the best way of dealing with it would be at the given time instead of having a fixed thinking. Let’s see how in the next chapter.

Conclusion : we have an even greater choice now

To summarize how my thoughts evolved : I was at first afraid that Gutenberg would force us into a closed path, furthermore one that was disconnected from our php reality. After investigation, it turns out it gave us an even wider set of tools to address the needs of our clients by adding new ways to work, and by not necessarily eliminating the existing ones.

Now when we have to develop an administration brick for our clients our webmasters, we ask ourselves the following questions :

  • Is the editing feature we’re looking at complex? (For example a slider, an accordion, a set of rich panels…). And therefore does it deserve the best Gutenberg experience we can offer ?
    • If yes, we should take advantage of the editor’s possibilities, and we should code a native Gutenberg block (in react).
    • We still have the choice to render in PHP or React based on the desired output. Some of our components are even rendered in Twig.
    • It’s more difficult for us to code, but we’re improving block after block. We even abstracted some of the mechanism already to shorten the process of creating of blocks. Our tooling is in place too.
    • For the webmaster : it adds a lot of value to his experience.
    • For now, we rely on more senior profiles to produce those blocks. Those profile took the right training and are now able to create production ready Gutenberg blocks.
  • Is the editing feature we’re looking at basic? Or a shortcode improvement ?
    • If yes, a form to fill should be sufficient in the admin, therefore we’ll provide a php component, made available as a form to fill in Gutenberg thanks to annotations.
    • Thanks to the fact that the process is well documented, these blocks are very quick for the team to code and to make available in Gutenberg.
    • It’s not the full editor experience but it’s still very comfortable for a webmaster, and it’s cheap to produce.
    • Any member of the team knows how to produce such blocks and is able to do so.
  • Do we really need to code a block for this feature? Maybe a set of default Gutenberg blocks could do the trick? Or maybe a pattern ?
    • It’s worth knowing all the default blocks, what you can do with them, and what you can’t. For example, I frequently use the paragraph, columns, gallery, and media ones.

To sump up, in front of a new block opportunity, we try to take the best decision as where to invest time and effort on blocks that are worth it, versus blocks that are not because they are more basic.

Even though adopting Gutenberg was a bit daunting during our first encounter, the time and efforts we put on the tool analysis and the adoption path was clearly worth it. Our webmaster loves it, so do our clients. The team is onboarded, everyone understands the added value and is technically able to contribute at different scale.

Finally, we haven’t lost our historical ecosystem, quite the contrary it’s now enhanced by a much better editing experience. Everyone wins.

Curabitur neque. porta. justo vel, fringilla sit efficitur. risus.