Skip To Main Content

Creating a Custom Block

In this tutorial, you will install and create a block using @faustwp/blocks. This tutorial assumes you have already manually installed both the @faustwp/blocks and wp-graphql-content-blocks plugin. If you aren’t sure how, head to the Getting Started guide for step-by-step instructions.

This tutorial also utilizes the Faust Scaffold as its repository. Feel free to clone this repository to follow along.

1. Do I have everything I need to create a custom block?

Before beginning, take stock of your activated plugins in your WordPress instance. Faust, WPGraphQL, and WPGraphQL Content Blocks all need to be installed and active before you move forward. Be sure to install the @faustwp/blocks package at your app’s root level as well.

2. Defining your custom block

In this tutorial, we will assume that you do not have a preexisting block in mind to use with your Decoupled site. If you have a block you want to emulate from the WordPress core blocks library, head to our tutorial to learn how to do exactly that. Likewise, if you need to recreate a block used by a third party plugin, we have a tutorial tailored for that process, too.

This tutorial assumes that you begin your block creation from nothing at all, which will therefore require a slightly different set of steps.

NOTE
Check out all the components that are available to you through the @wordpress/components library as you think about what you want your custom block to do and look like.

3. Creating your block using the WordPress block boilerplate code

The @wordpress/create-block package exists to create the necessary block scaffolding to get you started. See the WordPress basic block tutorial documentation for a step-by-step guide.

For this step, we will make a simple block named MyCoolBlock, which has a message that takes text, a radio control input attribute from the @wordpress/components library, and a RichText attribute from the @wordpress/block-editor toolkit applied to another text field. This block should closely match your boilerplate code’s styling:

The custom block as seen in the editor window.

Your boilerplate code from @wordpress/create-block did not include anything in your save file, as not all blocks need a save.js in order to work. For this example, if you did not make a save.js function when you customized this block, you can follow along by looking at the following save function that was made manually while developing our example custom block. Its contents live in the plugin folder:

// wp-content/plugins/mycoolblock/src/save.js

import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function Save({attributes}) {
    return (
        <p { ...useBlockProps.save() }>
            { `The color selected is ${attributes.color}! My favorite color: ${attributes.message}!` }
            <RichText.Content tagName="h4" value={ attributes.content }/>
        </p>
    );
}
Code language: JavaScript (javascript)

Be sure to add the following in your block’s edit.js folder as well if you wish to follow the example, so that the two files can work together to save your data:

// wp-content/plugins/mycoolblock/src/edit.js

import { RadioControl } from '@wordpress/components';
import { RichText } from '@wordpress/block-editor';
...
export default function Edit( { attributes, setAttributes } ) {
    const blockProps = useBlockProps();
    return (
    <div {...blockProps}>
      <TextControl
        label="Which color is your favorite?"
        value={attributes.message}
        onChange={(val) => setAttributes({ message: val })}
      />
      <RadioControl
        selected={attributes.color}
        label="Color:"
        options={[
          { label: "Red", value: "red" },
          { label: "Green", value: "green" },
          { label: "Blue", value: "blue" }
        ]}
        onChange={(val) => setAttributes({ color: val })}
      />
      <RichText
        tagName="h4"
        allowedFormats={["core/bold", "core/italic", "core/strikethrough"]}
        placeholder={"Declared color..."}
        value={attributes.content}
        onChange={(val) => setAttributes({ content: val })}
      />
    </div>
  );
}
Code language: JavaScript (javascript)

Lastly, your index.js file will need an import statement for the save function, and it will need to be added to the second argument object for registerBlockType

// wp-content/plugins/mycoolblock/src/index.js

registerBlockType( metadata.name, {
...
    save: Save,
} );
Code language: JavaScript (javascript)

4. Create the initial block

Inside the codebase of your application, create the basic block structure that simply prints the block’s attributes. The steps required to achieve that are the following:

4.1 Save the block inside a folder called wp-blocks

The file structure of the project, showing the newly created wp-blocks folder at the root level of the Faust Scaffold app and containing a newly created folder called MyCoolBlock.js.
// wp-blocks/MyCoolBlock.js

export default function MyCoolBlock(props) {
  console.log(props.attributes);
  return <div>MyCoolBlock</div>
}

MyCoolBlock.displayName = 'MyCoolBlock';
// This also works
// MyCoolBlock.config.name = 'MyCoolBlock'
Code language: JavaScript (javascript)

NOTE
We added a displayName property here to make sure that the __typename of the block matches this value. For production builds, it is required to use either a displayName="NameOfBlock" or a config.name="NameOfBlock" properties for the block to resolve and render properly.

This component prints the block attributes via the props passed as a parameter. This will help us understand what is happening a little later on.

4.2 Export the block in the index.js

Make another file in wp-blocks, called index.js, and place the following inside:

// wp-blocks/index.js

import MyCoolBlock from './MyCoolBlock';
export default {
  MyCoolBlock
}
Code language: JavaScript (javascript)

By exporting the block in the index.js, we make it available in the WordPressBlocksProvider, which gives the proper context (i.e., which blocks have been defined as apart of your Faust app) to the WordPressBlocksViewer component. Read more about the WordPressBlocksProvider here.

4.3 Create the block fragment

Next, you want to create a fragment that describes the request block fields and attributes. Ideally you want to include all the relevant attributes that this backend block function will need to call to your frontend block:

// wp-blocks/MyCoolBlock.js

import {gql} from '@apollo/client';
...
MyCoolBlock.fragments = {
  key: `MyCoolBlockBlockFragment`,
  entry: gql`
    fragment MyCoolBlockBlockFragment on CreateBlockMycoolblock {
      attributes {
        color
        content
        message
      }
    }
  `,
};
Code language: JavaScript (javascript)

We chose to attach the fragment as a property of the function component itself. This follows best practice for creating colocated fragments.

Before moving forward, check to ensure your block is being queried correctly in the GraphiQL IDE. You may need to use your browser’s search and find method to search all the possible names under editorBlocks.

The GraphiQL IDE is open to the Query Composer, which shows under posts, then nodes, then editorBlocks that the CreateMycoolblock is present, with a selection of attributes visible such as color, content, and message.

4.4 Include the block fragment into the page query string

You want to include the fragment in the page query string and reference the block fragment key so that when the page resolves, the payload includes the data from that block.

In this example we will use it in the single page template provided in the Faust Scaffold. You can learn more about the Faust Template Hierarchy here.

// wp-templates/single.js

import blocks from '../wp-blocks';
export default function Component(props) {
...
}
Component.query = gql`
...
${blocks.MyCoolBlock.fragments.entry}
query GetPost(
...
    post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
        ...
        editorBlocks {
            name
            __typename
            renderedHtml
            id: nodeId
            parentId
            ...${blocks.MyCoolBlock.fragments.key}
        }
    }
...
}
Code language: JavaScript (javascript)

If you navigate to the page that contains this block (here, named localhost:3000/my-cool-block), you should be able to inspect the properties in the console.

Preview of the MyCoolBlock in the console.

5. Porting over the custom block’s style.scss file

While creating your custom block using @wordpress/create-block, the style.scss file was autogenerated in your block’s plugin folder. You can include these in the global styles section of your Decoupled front-end app too. Note that since the boilerplate provided us with an assets folder for Gilbert font-family, we will want to add this assets folder to our front-end site as well.

/MERL-742-Tutorial/styles/globals.css

...
@font-face {
    font-family: Gilbert;
    src: url(../assets/gilbert-color.otf);
    font-weight: 700;
}

.wp-block-create-block-mycoolblock {
    font-family: Gilbert, sans-serif;
    font-size: 64px;
}

6. Implementation of the block

Next, we want to implement the Block based on the save function you created for your custom block. You will need to remove the references of the RichText and useBlockProps imports as you won’t be needing those, but beyond this, it should look similar to what you have in your plugin folder’s save.js file:

// wp-blocks/MyCoolBlock.js

import { gql } from '@apollo/client';
import getStyles from '../utilities/getStyles.js';

export default function MyCoolBlock(props) {
  const className = 'wp-block-code';
  const styles = getStyles(props.attributes)
  return (
    <pre className={className} style={styles}>
      <code>{`${props.attributes?.content}`}</code>
    </pre>
  );
}
MyCoolBlock.displayName = 'MyCoolBlock';
Code language: JavaScript (javascript)

Now that you have the styles ported over, you can navigate into the Headless site’s page and check that the block matches the styling you wish. Make sure to tweak the block in the editor and verify that the changes you want are being reflected here, too.

Further Considerations

Can I style the block differently?

Of course, you can style the block in many ways, choosing to ignore some of the attributes altogether. You can also use an external React Library to style the block, such as Material UI or ChakraUI. Bear in mind though, that this will almost always result in a differences in the editor’s version of the block, versus the rendered result, as the styles in the Editor view won’t match the styles of the rendered page.

What if I want different styling upon saving the block?

save.js: According to the docs, the save function defines the way in which the different attributes should be combined into the final markup, which is then serialized into post_content.

For an example of applying styling to saved content, take a look at our core blocks creation tutorial.

NOTE
Next.js won’t allow you to import the styles.scss directly into the component since it requires all global CSS to be imported from the pages directory only. See the related error message. You will need to either import the styles from a global module like the above, or translate the styles to use Component-Level CSS.