There is a lot of great documentation out there for creating custom WordPress blocks, and I admit I’m a bit late to the party, but when I finally started learning how to do this it was a little difficult to find a single tutorial that gave me all the steps I needed. In this article I will attempt to break down the process in an easy to follow tutorial for those of you that are eager to dive into this brave new world. We will create a very simple block that allows the user to input some text and a link that displays like a button. You can create much more complex blocks, but since we’re just getting started, we’ll keep it simple to learn the basics.
Obviously, every single bit of knowledge and skill that is needed to get started can’t be covered in one article, so I am assuming you have at least basic experience with HTML, CSS, JavaScript, and React. If not, then it’s time to learn, after which you can make your way back here.
Let’s get started!
Again, there are a lot of tools to get you going with this. I suggest starting with Local, a tool that will get a local WordPress site up and running with pretty much a single click. You can download it here and find installation and set up instructions here.
Once WordPress is set up, you’ll need to install Node.js and npm (if not already installed). You can download Node here. Once you’ve installed Node.js, run the command node -v in your terminal to confirm installation. You can also run the npm -v command to confirm that you have the npm package available.
This was a little confusing to me at first. Create a plugin for a block? But then I realized this makes it portable so you can use it on any of your WordPress sites.
Thankfully, there’s a dev-tool that will create all of the files and folders you need to create the plugin scaffolding and register a new block.
/wp-content/plugins/ directory
.npx @wordpress/create-block name-of-your-block
(obviously changing that last bit to your own custom block name).y
if/when asked to confirmIt will take a few minutes to finish, but then you can navigate to the Plugins screen in your WordPress admin dashboard and see your new plugin! Go ahead and activate it, then:
cd name-of-your-block
npm start
(This will enable you to run the plugin in development mode and will compile all of your changes as you make them.)Now that we’re all set up, we’re going to start customizing the new block. All of your work will be done in /wp-content/plugins/name-of-your-block/src/
, so navigate to that directory in your code editor of choice and:
block.json
.icon
property expects any Dashicon name as a string, see list of available icons. The category
specified is a string and must be one of: “common, formatting, layout, widgets, or embed”. You can create your own custom category name, see documentation for details.name-of-your-block.php
, you can edit the Plugin Name, Description, Version, Author, etc. to match what you’ve done in block.json
. This will change what you see on the Plugins screen in the admin dashboard.edit.js
.import { useBlockProps, RichText } from '@wordpress/block-editor';
@wordpress/block-editor
package, which allows developers to render a contenteditable input, providing users with the option to format block content to make it bold, italics, linked, or use other formatting.export default function Edit() {
include the corresponding useBlockProps
element by addingconst blockProps = useBlockProps();
It should now be as follows:export default function Edit() {
const blockProps = useBlockProps();
return (
<p { ...useBlockProps() }>
{ __( 'Test Block – hello from the editor!', 'test-block' ) }
</p>
);
}
<p { ...useBlockProps() }> { __( 'Test Block – hello from the editor!', 'test-block' ) } </p>{ __( 'Test Block – hello from the editor!', 'test-block' ) }
Replace with:
<div
{ ...blockProps }
>
<RichText
tagName="span"
onChange={ onChangeContent }
allowedFormats={ [ 'core/bold', 'core/italic' ] }
value={ __( content ) }
placeholder={ __( 'Write your text...', 'name-of-your-block' ) }
style={ { textAlign: align } }
/>
<ExternalLink
href={ affiliateLink }
className="btn btn-external-link"
rel={ hasLinkNofollow ? "nofollow" : "" }
>
{ linkLabel }
</ExternalLink>
</div>
With this we’ve started creating our layout of the block, with a span that has a placeholder of “Write your text” and a button with the classes “btn btn-external-link”. It won’t actually work yet and will throw an error if you try to use it, because we still have more code to add.
block.json
, add the following code in a new line 17 directly under "style": "file:./style-index.css"
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "span"
},
"align": {
"type": "string",
"default": "none"
},
"affiliateLink": {
"type": "string",
"default": "https://"
},
"linkLabel": {
"type": "string",
"default": "Your Button Label"
},
"hasLinkNofollow": {
"type": "boolean",
"default": false
}
}
Attributes provide information about the data stored by a block, such as rich content, background color, URLs, etc. For example, we added the span element as an HTML string in the first few lines. We’ve also added the text alignment, the URL field for the button, the label for the button, and the rel=nofollow attribute for the link.
edit.js
, we need to pass the Edit
function an object of properties, so change line 32 from:export default function Edit() {
to:
export default function Edit( { attributes, setAttributes } ) {
const { content, align, affiliateLink, linkLabel, hasLinkNofollow } = attributes;
const onChangeContent = ( newContent ) => {
setAttributes( { content: newContent } )
}
const onChangeAlign = ( newAlign ) => {
setAttributes( {
align: newAlign === undefined ? 'none' : newAlign,
} )
}
const onChangeAffiliateLink = ( newAffiliateLink ) => {
setAttributes( { affiliateLink: newAffiliateLink === undefined ? '' : newAffiliateLink } )
}
const onChangeLinkLabel = ( newLinkLabel ) => {
setAttributes( { linkLabel: newLinkLabel === undefined ? '' : newLinkLabel } )
}
const toggleNofollow = () => {
setAttributes( { hasLinkNofollow: ! hasLinkNofollow } )
}
So now everything we just changed in your edit.js file should look like this:
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
const onChangeContent = ( newContent ) => {
setAttributes( { content: newContent } )
}
const onChangeAlign = ( newAlign ) => {
setAttributes( {
align: newAlign === undefined ? 'none' : newAlign,
} )
}
const onChangeAffiliateLink = ( newAffiliateLink ) => {
setAttributes( { affiliateLink: newAffiliateLink === undefined ? '' : newAffiliateLink } )
}
const onChangeLinkLabel = ( newLinkLabel ) => {
setAttributes( { linkLabel: newLinkLabel === undefined ? '' : newLinkLabel } )
}
const toggleNofollow = () => {
setAttributes( { hasLinkNofollow: ! hasLinkNofollow } )
}
return (
<div
{ ...blockProps }
>
<RichText
tagName="span"
onChange={ onChangeContent }
allowedFormats={ [ 'core/bold', 'core/italic' ] }
value={ __( content ) }
placeholder={ __( 'Write your text...', 'name-of-your-block' ) }
style={ { textAlign: align } }
/>
<ExternalLink
href={ affiliateLink }
className="btn btn-external-link"
rel={ hasLinkNofollow ? "nofollow" : "" }
>
{ linkLabel }
</ExternalLink>
</div>
);
}
import { useBlockProps, RichText } from '@wordpress/block-editor';
to:
import {
useBlockProps,
RichText,
AlignmentControl,
BlockControls,
InspectorControls
} from '@wordpress/block-editor';
This adds various components to the block toolbar.
import {
TextControl,
PanelBody,
PanelRow,
ToggleControl,
ExternalLink
} from '@wordpress/components';
This adds the controls we need for the Block Settings Sidebar.
return ( <> <InspectorControls> <PanelBody title={ __( 'Link Settings', 'name-of-your-block' )} initialOpen={true} > <PanelRow> <fieldset> <TextControl label={__( 'External link', 'name-of-your-block' )} value={ affiliateLink } onChange={ onChangeAffiliateLink } help={ __( 'Add your external link', 'name-of-your-block' )} /> </fieldset> </PanelRow> <PanelRow> <fieldset> <TextControl label={__( 'Link label', 'name-of-your-block' )} value={ linkLabel } onChange={ onChangeLinkLabel } help={ __( 'Add link label', 'name-of-your-block' )} /> </fieldset> </PanelRow> <PanelRow> <fieldset> <ToggleControl label="Add rel = nofollow" help={ hasLinkNofollow ? 'Has rel nofollow.' : 'No rel nofollow.' } checked={ hasLinkNofollow } onChange={ toggleNofollow } /> </fieldset> </PanelRow> </PanelBody> </InspectorControls> <BlockControls> <AlignmentControl value={ attributes.align } onChange={ onChangeAlign } /> </BlockControls> <div { ...blockProps } > <RichText tagName="span" onChange={ onChangeContent } allowedFormats={ [ 'core/bold', 'core/italic' ] } value={ __( content ) } placeholder={ __( 'Write your text...', 'name-of-your-block' ) } style={ { textAlign: align } } /> <ExternalLink href={ affiliateLink } className="btn btn-external-link" rel={ hasLinkNofollow ? "nofollow" : "" } > { linkLabel } </ExternalLink> </div> </> );
With this code we’ve added all of our sidebar settings within Inspector Controls, and the alignment settings in the toolbar.
At this point, you should be able to create a new page and test the block. Since we haven’t styled it yet, it will look a bit funky, but it will have all of the elements we added so far. Now we have to modify the save function to store everything in the database.
save.js
import { useBlockProps } from '@wordpress/block-editor';
with
import { useBlockProps, RichText} from '@wordpress/block-editor';
export default function save() {
return (
<p { ...useBlockProps.save() }>
{ __( 'Test Block – hello from the saved content!', 'test-block' ) }
</p>
);
}
with
export default function save( { attributes } ) { const { content, align, affiliateLink, linkLabel, hasLinkNofollow } = attributes; const blockProps = useBlockProps.save(); return ( <div { ...blockProps }> <RichText.Content tagName="span" value={ content } style={ { textAlign: align } } /> <a href={ affiliateLink } className="btn btn-external-link" rel={ hasLinkNofollow ? "nofollow" : "noopener noreferrer" } > { linkLabel } <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="components-external-link__icon css-bqq7t3 etxm6pv0" role="img" aria-hidden="true" focusable="false"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg> </a> </div> ); }
Now the values entered into the block will be saved in the database and rendered on the front end.
We now have a fully functioning block! Only problem is that it’s not very pretty, so let’s fix that.
I’ll provide you with some sample CSS styling, but you can edit as you see fit. Basically, you should style the block to look the way you want it to look within the context of the front end, but know that it will also look this way on the back end edit screen for your users. The SASS is compiled all at once for both!
style.scss
.wp-block-create-block-name-of-your-block {
align-items:center;
border:none !important;
display:flex;
font-size:1em;
.rich-text {
width:40%;
}
.btn-external-link {
align-items:center;
background:#191919;
border:none;
border-radius:3px;
color:#fff;
display:flex;
font-size:13px;
margin-left:15px;
padding:11px 15px;
text-decoration: none;
transition:background 0.15s linear;
svg {
margin-left:2px;
path {
fill:#fff;
}
}
&:hover {
background:#31ACE3;
}
}
}
And that’s it! As you can see, there is a LOT that goes into creating the simplest of custom blocks, but hopefully this tutorial has eliminated any hesitancy you may have had with getting started and shown you that, although it’s a bit complex, there really isn’t much that’s difficult to understand. So go forth and create your own custom WordPress blocks!