Enterprise content management tools have traditionally been a bit rubbish.

The experience for content authors was often particularly awkward: writing pseudo-HTML by hand and hoping that the results might bear some resemblance to the intended design.

The new WordPress block editor can help us change that. It’s an experience more akin to Medium or Squarespace than the traditional “big empty text box” editor. If you’ve not used WordPress in a while you might be surprised at how capable it’s become.

Here we’ll look at:

  • defining a custom block using modern JavaScript syntax
  • styling the block to create a good experience for content authors
  • making dynamic blocks that display frequently-changing content

We’ll build a “hero” block for our website, which has a headline, introductory content and a background image:

What the finished block could look like

How blocks work

The number one thing to understand is that blocks are just HTML. There’s clever JavaScript-powered magic involved in preparing the HTML for editing, but when the post is saved, the carriage turns back into a pumpkin, and HTML is what gets saved to the database, just as always.

Block technical visual illustration

Unlike most of what happens in WordPress, blocks are defined entirely within JavaScript. The editor itself is powered by React, so the syntax will feel familiar to React developers.

It’s convenient to define our block using JSX, so we’re going to set up the tools to write modern JavaScript and transpile it back to something browsers can understand.

A block file normally does three jobs:

  • list the editable attributes of a block — this is similar to state in React. For every attribute, we can define things like what type of data it is (number, string etc) and how to reconstitute it from stored HTML
  • describe an editor interface for those attributes — it’s sensible to use CSS to make that interface resemble what the block looks like on the front-end, but it doesn’t necessarily have to
  • describe the HTML the block will be saved as — blocks are just HTML, so we need to tell WordPress how to turn the attribute values the author provided into plain HTML for display on the front-end.

Getting set up

Parcel is a good, fast way to write modern JavaScript, including JSX. It does the same job as Webpack and Babel but without any of the messy config. Install it with:

  • npm i -g parcel-bundler

Let’s write our block in our theme directory, under src/js/blocks.js.

Adding the following scripts to package.json. Generate one with npm init -y if you don’t already have one.

  • "scripts": {
    "dev": "parcel watch src/js/blocks.js -d dist/js",
    "build": "parcel build src/js/blocks.js -d dist/js",
    }

Now, npm run dev will start Parcel watching for changes and automatically reload the page as we save our code. Clever!

The last step is to make WordPress load the bundled JavaScript file we’ll be building to. Something like this in functions.php will do the job:

  • <em>function</em> mytheme_load_blocks() { wp_enqueue_script(
    "blocks",
    get_stylesheet_directory_uri() <em>. </em>"/dist/js/blocks.js",
    array(
    "wp-blocks",
    "wp-element",
    "wp-block-editor",
    "wp-components"
    )
    );}add_action("enqueue_block_editor_assets", "mytheme_load_blocks");

Notice the array of dependencies. We’re telling WordPress to load our blocks after those scripts because we’ll be using them in our block definitions. The exact dependencies you need depend on what you’re using.

Okay, let’s write the block.

Creating a block

Our block could look something like this:

  • <em>const</em> { registerBlockType } <em>=</em> window.wp.blocks
    <em>const</em> { MediaUpload, RichText } <em>=</em> window.wp.blockEditor
    <em>const</em> { Button } <em>=</em> window.wp.componentsregisterBlockType( "mytheme/hero", {
    title: "Hero",
    category: common, attributes: {
    title: {
    type: "string",
    source: "text",
    selector: "h1"
    },
    content: {
    type: "string",
    source: "html",
    selector: "section div"
    },
    background: {
    type: "string"
    }
    }, edit: ({ attributes: { title, content, background }, setAttributes, className }) <em>=></em> <div <em>className=</em>{className}>
    <img <em>src=</em>{background} <em>aria-hidden=</em>"true" alt=""/>
    <RichText
    <em>value=</em>{title}
    <em>tagName=</em>"h1"
    <em>placeholder=</em>"Headline..."
    <em>allowedFormats=</em>{[]}
    <em>onChange=</em>{value <em>=></em> setAttributes({title: value})}
    />
    <RichText
    <em>value=</em>{content}
    <em>placeholder=</em>"Introductory content..."
    <em>onChange=</em>{value <em>=> </em>setAttributes({content: value})}
    />
    <MediaUpload
    <em>allowedTypes=</em>{["image"]}
    <em>value=</em>{background}
    <em>onSelect=</em>{media <em>=></em> setAttributes({background: media.url})}
    <em>render=</em>{({ open }) <em>=>
    </em><Button <em>onClick=</em>{open}>Choose background image...</Button>
    }
    />
    </div> , save: ({ attributes: { title, content, background }}) <em>=></em> <section <em>style=</em>{`background-image: url('${background}');`}>.
    <h1>{title}</h1>
    <RichText.Content <em>tagName=</em>"div" <em>value=</em>{content}/>
    </section> ,})

Notice that we’re using some pretty hefty modern JavaScript syntax, especially object destructuring.

Let’s walk through it:

  1. First, we grab the registerBlockType function from window.wp.blocks, one of the dependencies we specified earlier.
  2. Next, we grab some elements to help build our editing interface: MediaUpload, RichText and Button. We could use plain HTML inputs, buttons and textareas, but the WordPress-provided alternatives tend to look nicer from the start and come with extra built-in functionality. It would be pretty tough to build a media editor from scratch, for instance.
  3. At the top of our call to registerBlockType, we give the block a unique name, a human-readable label “Hero” and say what category of the editor we want it to appear in.
  4. Then, we define the editable attributes of the block.
  5. In the edit function we describe the editing interface. This is very React-y. React developers might recognise the controlled component pattern, where we assign a value to form input and a change handler that updates that value.
  6. Lastly, in the save function we provide the actual HTML our users will see on the front-end. We’re using a special <RichText.Content/> component to make sure WordPress saves any rich text formatting properly.

One unusual thing worth mentioning: the background attribute doesn’t have a “source” or “selector” key, so you might wonder how it can be reconstituted from the saved HTML. WordPress has a backup way of storing attribute data in HTML comments. This saves us the effort of manually extracting the URL from its style attribute of the section tag.

You should now see the new block listed under the “common” category. It should be fully working but the editor won’t look the best. Let’s fix that.

Styling blocks

Adding these two lines to functions.php will include styles from the given CSS file on the editor:

  • add_theme_support('editor-styles');
    add_editor_style( "/editor.css" );

In the previous example, we wrapped our editor interface in a div and passed a className attribute. We can use this class to target the block in our CSS. For example, a block called “mytheme/hero” would get a class called “wp-block-mytheme-hero”.

We can get as clever as we want with the styles. It’s a good idea to make the editor as closely resemble the front-end as possible, to give our content authors an easier life.

Getting this right can feel like trial and error at first until you get used to the editor markup WordPress generates.

Adding dynamic server-generated content to blocks

I like to think of blocks as being “baked” into the HTML of a page every time you hit save. This is valuable because the block output persists even if you deactivate the plugin or theme that defined them: they’re just HTML.

The downside of this is that if your block needs to change between the times you hit “save” on the containing page, you need to handle it differently.

A good example is a block that shows the latest posts on the site. We could fetch and “bake” the most recent posts into that block at edit time, but they would be frozen until we next edited the page.

We can fix this by providing a server callback for our block. With it, our block becomes a placeholder: it doesn’t store any HTML to the database by itself, and whenever it’s called on, a PHP function renders the most up-to-date content on its behalf.

In functions.php do something like this:

  • <em>function</em> mytheme_register_dynamic_block() {
    register_block_type("mytheme/dynamic-block", array(
    "render_callback" <em>=></em> "mytheme_render_dynamic_block"
    ));
    }add_action("init", "mytheme_register_dynamic_block");<em>function</em> mytheme_render_dynamic_block($attributes) {
    ob_start();
    ?>

    <!-- Your block content here --> <?php
    $output <em>=</em> ob_get_contents();
    ob_end_clean();
    <em>return</em> $output;
    }

First, we associate a render callback with the name of a block (the block already has to have been registered in JS for this to work).

Then we provide a second function to actually print the block’s content. Using ob_start lets us cleanly write whatever HTML we want, just as if we were working in a normal WP theme file.

Is WordPress a good choice for large enterprise websites?

WordPress started as a blogging platform, but nowadays it’s just as capable and scalable as Drupal, Umbraco or any other traditional enterprise CMS.

It’s almost infinitely extensible through PHP, one of the most common developer skillsets, and also has a modern JSON API as a first-class feature, making it a good fit for organisations trying out “content infrastructure”/API-first architecture.

It also has a considerable edge over some of the recent entrants to the CMS market, especially Contentful, in that it’s open-source and free.

Further reading

Get in touch

We’re always happy to answer any questions you have about FutureGov and discuss how we can work together.

Contact us