How to Create a Block from a Third-Party Plugin
In this tutorial you will install and create a block using @faustwp/blocks
that is using data from a blocks plugin that you downloaded in your WordPress site. It assumes you have already installed both the @faustwp/blocks
and wp-graphql-content-blocks
plugin.
1. Set up a @faustwp/blocks
If you have not already done so, you will need to follow the Getting Started guide so that you are ready to create new blocks.
2. Download a third-party plugin that provides Gutenberg Blocks
You need some real working Gutenberg blocks so you can implement them in the Faust application. For this tutorial, we are using Ultimate Blocks which contain a nice selection of blocks we can use.
3. Pick a block that you want to implement
The Ultimate blocks package contains a few interesting blocks. You want to have blocks that have a complete block.json
with defined attributes, since the wp-graphql-content-blocks
plugin will automatically expose those as GraphQL fields.
For this tutorial, we are going to implement the Call To Action Block.
4. Review the Block design in the Editor
Before you try to implement the block, it’s useful to get an idea of how the block is configured within the editor. You want to create a few sample blocks and test their options.
Navigate to the editor by creating a new Post and insert the Block into the page:
You can expand the block sidebar options to change some of its styling. For example, you can configure the Color, Headline, Content, Button and Link settings for that block. Once you get familiar with the block’s look and feel, let’s explore its attributes from the GraphQL IDE.
5. Review the Block data and attributes in the GraphQL IDE
Within the GraphQL IDE, use the Docs to find the type of block to display its type and properties. For our example, there should be a UBCallToActionBlock
that contains all the important fields we can query.
Expand the UbCallToActionBlockAttributes
to see the block attributes as well:
There are a lot of attributes associated with that block. You can use the following query to inspect the block data:
{
post(id: "/posts/test", idType: URI) {
editorBlocks {
renderedHtml
...on UbCallToActionBlock {
attributes {
url
openInNewTab
blockID
ubCallToActionHeadlineText
ubCtaButtonText
ubCtaContentText
}
}
}
}
}
Code language: JavaScript (javascript)
This will resolve to:
"data": {
"post": {
"editorBlocks": [
{
"renderedHtml": "<div class=\"ub_call_to_action\" id=\"ub_call_to_action_37aa7f1a-5d3a-4077-9c2b-a29b3672896c\">\n <div class=\"ub_call_to_action_headline\">\n <p class=\"ub_call_to_action_headline_text\">Call To Action</p></div>\n <div class=\"ub_call_to_action_content\">\n <p class=\"ub_cta_content_text\">Help us achieve our goals</p></div>\n <div class=\"ub_call_to_action_button\">\n <a href=\"http://locahost:3000/donate\" target=\"_self\" rel=\"noopener noreferrer\"\n class=\"ub_cta_button\">\n <p class=\"ub_cta_button_text\">Donate Now</p></a></div></div>",
"attributes": {
"url": "http://locahost:3000/donate",
"openInNewTab": false,
"blockID": "37aa7f1a-5d3a-4077-9c2b-a29b3672896c",
"ubCallToActionHeadlineText": "Call To Action",
"ubCtaButtonText": "Donate Now",
"ubCtaContentText": "Help us achieve our goals"
}
}
]
}
}
Code language: HTML, XML (xml)
There are also attributes that control the style of the individual elements so you can query their attributes as well. Once you get familiar with how to query the necessary data from that block, let’s see now how to implement the associated block in the Decoupled site.
6. 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:
6.1 Create the block and save it inside the wp-blocks
folder
In wp-blocks/UbCallToActionBlock.js:
export default function UbCallToActionBlock(props) {
console.log(props.attributes);
return <div>UbCallToActionBlock</div>
}
UbCallToActionBlock.displayName = 'UbCallToActionBlock';
// This also works
// UbCallToActionBlock.config.name = 'UbCallToActionBlock'
Code language: JavaScript (javascript)
This component prints the block attributes via the props passed as a parameter.
NOTE
We added a displayName
property here to make sure that the __typename
field in the editorBlocks
query matches this value. For production builds, it is required to use either a displayName="NameOfBlock"
or a config.name="NameOfBlock"
properties for theWordPressBlocksViewer
component to resolve and render the block properly.
6.2 Export the block in the index.js
In wp-blocks/index.js:
import UbCallToActionBlock from './UbCallToActionBlock';
export default {
UbCallToActionBlock
}
Code language: JavaScript (javascript)
By exporting the block in the index.js
we make it available in the WordPressBlocksProvider
. You can also use a different name as long as it matches either the Block name
or the __typename
fields.
This also works:
// wp-blocks/index.js:
import UbCallToActionBlock from './UbCallToActionBlock';
export default {
'ub/call-to-action-block': UbCallToActionBlock
}
Code language: JavaScript (javascript)
6.3 Define the block fragment
You want to create a fragment that describes the request block fields and attributes. Ideally you want to include all the relevant fields for the implementation part:
// wp-blocks/UbCallToActionBlock.js
import {gql} from '@apollo/client';
...
UbCallToActionBlock.fragments = {
key: `UbCallToActionBlockFragment`,
entry: gql`
fragment UbCallToActionBlockFragment on UbCallToActionBlock {
attributes {
ctaBackgroundColor
ctaBorderSize
ctaBorderColor
headFontSize
headColor
headAlign
ubCallToActionHeadlineText
contentFontSize
contentColor
contentAlign
ubCtaContentText
buttonColor
buttonWidth
url
buttonTextColor
buttonFontSize
ubCtaButtonText
addNofollow
openInNewTab
}
}
`,
};
Code language: JavaScript (javascript)
We chose to attach the fragment as a property of the function component itself. This follows a good practice for creating colocated fragments.
6.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 would include the data from that block.
In our example we use it in both the Posts and Pages templates when using the Faust Template Hierarchy:
In your wp-templates/single.js:
import blocks from '../wp-blocks';
export default function Component(props) {
...
}
Component.query = gql`
...
${blocks.UbCallToActionBlock.fragments.entry}
query GetPost(
...
post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
...
editorBlocks {
name
__typename
renderedHtml
id: clientId
parentClientId
...${blocks.UbCallToActionBlock.fragments.key}
}
}
...
}
Code language: JavaScript (javascript)
If you navigate to the page that contains this block, you should be able to inspect the properties in the console:
Now you are ready to design the block based on the original block’s look and feel.
7. Implement the Block using the provided block attributes and the Editor view
The last part is to implement the Block by using the block attributes and the view in the editor to construct the final HTML markup.
Since the block you want to implement already consists of React elements that the Editor uses, you can review the plugin implementation of this block and try to port the parts that render the block the block in the frontend.
If you take a look at the wp-content/plugins
folder inside the ultimate-blocks/src/blocks/call-to-action
folder there are some important files there that you want to review:
$ tree -L 2 wp-content/plugins/ultimate-blocks/src/blocks/call-to-action
wp-content/plugins/ultimate-blocks/src/blocks/call-to-action
├── block.js
├── block.php
├── components.js
├── editor.css
├── editor.scss
├── icons
│ └── icon.js
├── oldVersions.js
├── style.css
└── style.scss
Code language: Bash (bash)
You are interested in the following files:
style.scss
: Contains the front-end block styles. You can definitely include those into our global styles.
In your style.scss:
/**
* #.# Styles
*
* CSS for both Frontend+Backend.
*/
.wp-block-ub-block-call-to-action {
margin: 0 auto;
max-width: 100%;
}
...
Code language: CSS (css)
block.js
: Contains both the block attributes specification and the save
function. 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
.
Most of the time, the save
function contains the actual implementation of the block, so you can take this markup and adopt it. This way the design of the block in the editor will match the design in the Decoupled site.
NOTE
In our case we were lucky. Sometimes the save
function is not provided or it returns null
. The component is considered then to be dynamic
, aka it renders in the server. So you want to check if the register_block_type provides an implementation for rendering the block. In that case, the render_callback
will provide the markup for the block using PHP. You can take this implementation and translate it to React.
Since the save
function is provided with that block we have a viable strategy that will save us time without having to figure out the actual implementation.
So go ahead and provide the implementation of the block based on the save
function:
// wp-blocks/UbCallToActionBlock.js
export default function UbCallToActionBlock(props) {
const {
ctaBackgroundColor,
ctaBorderSize,
ctaBorderColor,
headFontSize,
headColor,
headAlign,
ubCallToActionHeadlineText,
contentFontSize,
contentColor,
contentAlign,
ubCtaContentText,
buttonColor,
buttonWidth,
url,
buttonTextColor,
buttonFontSize,
ubCtaButtonText,
addNofollow,
openInNewTab,
} = props.attributes;
return (
<div className={props.className}>
<div
className="ub_call_to_action"
style={{
backgroundColor: ctaBackgroundColor,
border: ctaBorderSize + 'px solid',
borderColor: ctaBorderColor,
}}>
<div className="ub_call_to_action_headline">
<p
className="ub_call_to_action_headline_text"
style={{
fontSize: headFontSize + 'px',
color: headColor,
textAlign: headAlign,
}}>
{ubCallToActionHeadlineText}
</p>
</div>
<div className="ub_call_to_action_content">
<p
className="ub_cta_content_text"
style={{
fontSize: contentFontSize + 'px',
color: contentColor,
textAlign: contentAlign,
}}>
{ubCtaContentText}
</p>
</div>
<div className="ub_call_to_action_button">
<a
href={url}
target={openInNewTab ? '_blank' : '_self'}
rel={`${addNofollow ? 'nofollow ' : ''}noopener noreferrer`}
className={`wp-block-button ub_cta_button`}
style={{
backgroundColor: buttonColor,
width: buttonWidth + 'px',
}}>
<p
className="ub_cta_button_text"
style={{
color: buttonTextColor,
fontSize: buttonFontSize + 'px',
}}>
{ubCtaButtonText}
</p>
</a>
</div>
</div>
</div>
);
}
UbCallToActionBlock.displayName = 'UbCallToActionBlock';
Code language: JavaScript (javascript)
Don’t forget to include the global scss for the block so that the right styles are applied. You can create a new folder for block styles inside the styles
folder and import it there:
styles/blocks.scss
// WordPress block styles.
...
@import './blocks/UbCallToActionBlock' // import block styles here
Code language: JavaScript (javascript)
Not that you have the styles configured correctly, you can navigate into the Decoupled site’s page and check that the block matches the styles with the editor view:
Make sure to tweak the block in the editor and inspect the changes reflected in the website.
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. So you need to either import the styles from a global module or translate the styles to use Component-Level CSS.
Further Considerations
What if the block is missing some attributes?
If the block does not have any attributes or has only a few of them declared in the block.json, you can still try to extend the block API by declaring additional attributes for that block. Follow the filters reference guide, to create a block that uses the additional_block_attributes
property. The attributes will be available to query from that block.
class UbCallToActionBlock extends WPGraphQL\ContentBlocks\Blocks\Block
{
protected ?array $additional_block_attributes = array(
// Write your attributes here
);
}
Code language: PHP (php)
NOTE
If you include those extensions in a custom plugin, it means also that your Decoupled application is dependent on the inclusion of this plugin. You need to make sure you bundle them together otherwise the queries you do in the application will fail at runtime.
Can I style the block differently?
Yes, 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. For example, use Material UI or ChakraUI. Bear in mind that this will almost always result in degraded user editing experience as the styles of the Editor view won’t match the styles of the rendered page.
What if the block contains custom js assets?
Some blocks include JavaScript assets that are injected into the WordPress page so that they can run in the front view. Typically a lot of Dynamic Blocks use that functionality since they have no way to include user interactivity. Since React is not normally bundled as a dependency in the browser, the JavaScript code usually consists of good old jQuery and document selectors.
In that case you want to be able to review the bundled JavaScript assets and choose one of the following options:
- Include them in your code: This is not recommended as React does not play well with plain JavaScript and jQuery so you can expect lots of problems.
- Rewrite them as React components: You can attempt to rewrite the code into React. If the bundled code can be understood and rewritten with low effort then you can make that commitment.
- Use an equivalent React Component from a library: A simpler alternative is to find a compatible React Package and use that instead of trying to replicate the blocks interactivity. Sometimes this is a viable strategy as it frees the developer from implementing the functionality from scratch.
Inevitably this is a common pain point when trying to leverage the power of Blocks in the Decoupled Website so it’s up to you as to how you want to handle the pros and cons of each approach.