How To Build A Block – From Scratch

So far we’ve been looking at different plugins that help give us a set of build tools to quickly and to be honest, more easily build a custom block. There are times though when you may have to roll up your sleeves and build your own block from scratch. To accomplish this we are going to the officially supported build tool of WordPress, Create Block.

via GIPHY

The first step is navigate to our plugins folder with the command line. Once there you will run the following commands:

$ npx @wordpress/create-block review
$ cd review
$ npm start

It will take a couple minutes for all of the assets to download and your scaffolding plugin to be generated.

via GIPHY

Once created your new plugin folder will look something like this

and you should see the plugin in your WordPress install.

At this point we do have an example block we can review. It’s a simple block, with just some text. The text is a bit different from the editor to what is displayed on the front-end to show the process in editing and the final saved content.

Example block in the editor
The same example block from the front-end

Our code for compiling the block comes from the src folder in our project.

The editor view comes from edit.js, the front-end view comes from save.js. block.json is where we add some of our block register settings, editor.scss is where we will add our styles that are displayed in the editor, style.scss controls our front-end styles, and index.js put’s it all together for us.

We’ll start by updating our block.json file to add the attributes for our fields, all together this is now our json file.

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "create-block/review",
	"version": "0.1.0",
	"title": "Review",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"supports": {
		"html": false
	},
	"textdomain": "review",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"attributes": {
		"name": {
			"type": "string",
			"source": "text",
			"selector": ".review-name",
			"default": ""
		},
		"description": {
			"type": "string",
			"source": "text",
			"selector": ".review-description",
			"default": ""
		},
		"stars": {
			"type": "number",
			"default": 5
		}
	}
}

Once that is done we can begin working on our edit.js file. Adding the needed fields requires that we import a few things at the top of our file.

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { TextControl,TextareaControl,RadioControl} from '@wordpress/components';

We are importing our internationalization function to be able to support multiple languages with our block, useBlockProps will allow us to save our attributes, a text, textarea, and radio control to build out our fields.

The next step is to build out our edit function. I’m going to take each field a piece at a time and then show it all together. First up is our review description textarea field.

<TextareaControl
  label={ __( 'Description', 'review' ) }
  value={ attributes.description }
  onChange={ ( val ) => setAttributes( { description: val } ) }
  class="review-description"
/>
  • Our label will tell the users which field they are editing
  • value gets the current value of our description from our attribute that was set in our block.json file.
  • onChange basically says when this field changes, update the value of our description attribute
  • class gives our designated class name.

We’ll repeat basically the same setup for our review name, changing the attribute value, and field type.

<TextControl
  label={ __( 'Name', 'review' ) }
  value={ attributes.name }
  onChange={ ( val ) => setAttributes( { name: val } ) }
  class="review-name"
/>

Last we set up our radio control for our stars

<RadioControl
  label="Stars"
  selected={ attributes.stars }
  options={ [
    { label: '1', value: 1 },
    { label: '2', value: 2 },
    { label: '3', value: 3 },
    { label: '4', value: 4 },
    { label: '5', value: 5 },
  ] }
  onChange={ ( val ) => setAttributes( { stars: parseInt(val) } ) }
/>

Our radio control has an options array that displays our available radio control options. Instead of a value, we tell it which option is selected from our stars attribute.

We’ll need to pass our attributes and setAttributes to our edit function, and wrap our fields with a div and useBlockProps(). All together our edit function looks like this.

export default function Edit({ attributes, setAttributes }) {
  return (
    <div {...useBlockProps()}>
      <TextareaControl
        label={__("Description", "review")}
        value={attributes.description}
        onChange={(val) => setAttributes({ description: val })}
        class="review-description"
      />
      <TextControl
        label={__("Name", "review")}
        value={attributes.name}
        onChange={(val) => setAttributes({ name: val })}
        class="review-name"
      />
      <RadioControl
        label="Stars"
        selected={attributes.stars}
        options={[
          { label: "1", value: 1 },
          { label: "2", value: 2 },
          { label: "3", value: 3 },
          { label: "4", value: 4 },
          { label: "5", value: 5 }
        ]}
        onChange={(val) => setAttributes({ stars: parseInt(val) })}
      />
    </div>
  );
}

From here we can now build our save function within save.js. We’ll start by importing useBlockProps at the top of the file.

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

Next we’ll need to pass our attributes to the save function and set up our blockProps variable.

const blockProps = useBlockProps.save();

Displaying our review name and description is pretty straightforward, using dot notation and pulling from our attributes they can displayed like this.

{ attributes.description }

Displaying our stars requires a bit more work since we need to write a loop. We’ll create a function to get our star count, loop through and output the number of stars we need.

let starsDisp = (stars) => {
  stars = parseInt(stars);
  let content = '';
  for (let i = 0; i < stars; i++) {
    content += '★';
  }
  return content;
};

With our function written, it can now be used in our tempalte

{starsDisp(attributes.stars)}

All together this is our save function.

export default function save({ attributes }) {
  const blockProps = useBlockProps.save();

  let starsDisp = (stars) => {
    stars = parseInt(stars);
    let content = "";
    for (let i = 0; i < stars; i++) {
      content += "★";
    }
    return content;
  };

  return (
    <div {...useBlockProps.save()}>
      <blockquote class="review-blockquote">
        <p class="review-description">{attributes.description}</p>
        <p class="review-name">{attributes.name}</p>
        <div class="review-stars">{starsDisp(attributes.stars)}</div>
      </blockquote>
    </div>
  );
}

Now at this point we should have a working block. So let’s check it out.

If we go back into the editor we can see our review block inside the block inserter.

Once selected we have our fields, but because we haven’t written any styles yet our form has very minimal formatting to it.

Once we add some content to our fields and save, we can see we have a working, custom built block from scratch.

Awesome! We’ve now written our own block without the use of a third-party plugin! To wrap up this project it would be a good idea to add some styles to clean up our form a bit, the way those styles will look will probably depend on your project. You could even go further and build in a preview within the editor for your block, you can do anything really because by building it custom you’re not limiting yourself to the constrains of a prebuilt plugin. So dive in and see what you can come up with.