In this tutorial, I’m going to show you how to create a custom block from scratch, meaning we won’t be using a plugin or block generator such as @wordpress/create-block.
Here’s what we’re building: a Gravatar block.
This article is quite long, so here’s a Table of Contents:
Table of Contents
- Prerequisites
- Creating the plugin
- Creating a package.json file
- Install the @wordpress/scripts package
- Create an src folder
- Add the build commands to package.json
- Creating the block files
- Editing block.json for the block
- Adding the block interface
- Initializing the block via JS
- Add the block to src/index.js
- Registering the block via PHP
- Enqueueing the block scripts and styles
- Block planning
- Building the block interface
- Creating the REST API endpoint for retrieving the Gravatar
- Creating the REST API Callback
- Setting up a loading screen
- Validating the email input
- Getting the post author Gravatar
- Remembering the Gravatar email
- Changing the Gravatar
- Outputting the block on the frontend
- The end result
- Conclusion and example code
Prerequisites
We’ll be diving into Node, React, block.json, and a host of other items. This tutorial is fairly advanced, so let’s dive into the prerequisites.
You have a local environment
You need an environment to develop in, particularly with WordPress. I recommend MAMP Pro and Local.
Your favorite command line tool is good to go and ready
We’ll be doing some command line stuff, so please get your favorite command line tool ready. For Mac users, I highly recommend Warp.
Node is installed
We’ll be doing some npm
stuff, so you’ll need a copy of Node installed. I recommend installing Node via NVM, as different projects constantly need different Node versions. For this tutorial, I’ll be using Node 16 to avoid having to append every other command with --legacy-peer-deps
.
Creating the plugin
Navigate to your WordPress plugin directory via the command line. Let’s create the plugin folder.
mkdir dlx-gravatar-block
cd dlx-gravatar-block
touch dlx-gravatar-block.php
Code language: Bash (bash)
Using your favorite code editor, open up dlx-gravatar-block.php
.
Let’s add some plugin headers in order to make the plugin visible on the plugin’s screen.
<?php
/**
* Plugin Name: DLX Gravatar Block
* Plugin URI: https://dlxplugins.com/tutorials/
* Description: A sample Gravatar block for the block editor.
* Version: 1.0.0
* Requires at least: 6.3
* Requires PHP: 7.2
* Author: DLX Plugins
* Author URI: https://dlxplugins.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*
* @package DLXGravatarBlock
*/
Code language: PHP (php)
You should now be able to activate the plugin.
Creating a package.json file
We’ll be making use of the npm repository to install several packages, and we need a file to keep a record of all of these dependencies. This is where package.json comes in, which acts as a blueprint for npm projects.
Let’s create our package.json
file via command line. In the project’s directory, type in:
npm init
Code language: Bash (bash)
You’ll see several prompts. You can just press Enter all the way through.
If you open up the package.json, you should see the following:
{
"name": "dlx-gravatar-block",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Code language: JSON / JSON with Comments (json)
Install the @wordpress/scripts package
Next, let’s install the @wordpress/scripts package. We’ll need this package in order to build our block.
npm install --save-dev @wordpress/scripts
Code language: Bash (bash)
Our package.json
file should now look like this:
{
"name": "dlx-gravatar-block",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@wordpress/scripts": "^27.6.0"
}
}
Code language: JSON / JSON with Comments (json)
Create an src
folder
Let’s create an src
folder and place an empty index.js
file inside of it. The @wordpress/scripts package assumes an entry point of /src/index.js
.
mkdir src
cd src
touch index.js
cd ..
Code language: Bash (bash)
Add the build commands to package.json
We need to be able to run some commands in order to initiate the @wordpress/scripts package. These would be:
npm run start
– Start a development and watch build of our plugin’s scripts.npm run build
– Build a production build of our plugin’s scripts.npm run zip
– Build a shippable ZIP file of our block.
This corresponds to the entries we’ll have to add to package.json
. Modify your package.json
file and add the script commands.
{
"name": "dlx-gravatar-block",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "wp-scripts start --env mode=development",
"build": "wp-scripts build --env mode=production",
"zip": "wp-scripts plugin-zip"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@wordpress/scripts": "^27.6.0"
}
}
Code language: JSON / JSON with Comments (json)
You should now be able to run: npm run start
This will start what’s known as a “watch.” Any changes in the src/index.js
file will be compiled to a build
directory.
The default file for @wordpress/scripts that is generated is a build/index.js
file.
Check your file structure after running npm run start
. It should be similar to the following:
dlx-gravatar-block
├── build
├── node_modules
├── src
├── dlx-gravatar-block.php
├── package-lock.json
└── package.json
Code language: AsciiDoc (asciidoc)
Creating the block files
Navigate to your src
folder either using command line or your favorite code editor. We’ll be creating a directory called blocks
.
cd src
mkdir blocks
cd blocks
Code language: Bash (bash)
We’ll be using this structure just in case we add more blocks in the future.
Inside blocks
, we’ll create another folder called gravatar-block
.
mkdir gravatar-block
cd gravatar-block
Code language: Bash (bash)
Inside the gravatar-block
folder, we’ll create four files:
- block.js (this is our main interface file)
- index.js (this is our block initialization file)
- editor.scss (this is our block’s stylesheet file)
- block.json (this is our block’s metadata file)
touch block.js
touch index.js
touch editor.scss
touch block.json
Code language: Bash (bash)
We should now have an src
folder with this structure:
src
├── index.js
└── blocks
└── gravatar-block
├── block.json
├── block.js
├── index.js
└── editor.scss
Code language: AsciiDoc (asciidoc)
Editing block.json for the block
First, let’s add to the block.json
file. Open this file in your favorite code editor and add the following base code:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"title": "Gravatar Block",
"apiVersion": 3,
"name": "dlxplugins/gravatar-block",
"category": "media",
"icon": "admin-users",
"description": "An Gravatar block.",
"keywords": [
"avatar",
"gravatar"
],
"version": "1.0.0",
"textdomain": "dlx-gravatar-block",
"attributes": {
},
"supports": {
"anchor": true,
"align": true,
"className": true
}
}
Code language: JSON / JSON with Comments (json)
This is a basic block.json
file that is enough for a “Hello World.”
For more about block.json, please read my guide on block.json and the developer article on metadata in blocks. Later in this tutorial, we’ll be adding attributes (i.e., block variables) that we’ll need for the block.
Adding the block interface
Open block.js
in your favorite code editor.
For this next step, we’ll be coding out the main block interface. It’ll just be a “Hello World” for the time being.
import './editor.scss';
import { useBlockProps } from '@wordpress/block-editor';
const GravatarBlock = ( props ) => {
const { attributes, setAttributes, clientId } = props;
const blockProps = useBlockProps(
{
className: 'dlx-gravatar-block',
}
);
return (
<div { ...blockProps }>
Hello World from Gravatar Block!
</div>
);
};
export default GravatarBlock;
Code language: JavaScript (javascript)
This is a very basic block interface framework, but it demonstrates the minimum needed to get a block to display. You may be unfamiliar with useBlockProps, a WordPress helper hook for initializing a block. It’s used to add various attributes to a container element, so it should always go on the outermost container for a block.
Initializing the block via JS
In the main index.js
file, we’ll use function registerBlockType
to initialize our block via the already-built block.json
file.
import { registerBlockType } from '@wordpress/blocks';
import metadata from './block.json';
import Block from './block';
registerBlockType( metadata, {
edit: Block,
save() {
return null;
}
} );
Code language: JavaScript (javascript)
The function registerBlockType
function can take the JSON metadata and initialize the block via that mechanism. For the edit
function, we’re just rendering what’s in block.js
.
For more on registerBlockType
, see the block editor registration documentation.
For the save
function, I’m returning null
because this block will be dynamic. Please read more about dynamic blocks.
Add the block to src/index.js
If you remember, the @wordpress/scripts package assumes a start path of src/index.js
. So far we’ve built the block in isolation, but now it’s time to hook it up. Open up src/index.js
and add in the following:
import './blocks/gravatar-block';
Code language: JavaScript (javascript)
Back in terminal, head to the root of the plugin and run:
npm run build
Code language: Bash (bash)
You should now see a build
directory like this:
.
├── build/
│ └── blocks/
│ └── gravatar-block/
│ └── block.json
├── index.asset.php
├── index.css
└── index.js
Code language: AsciiDoc (asciidoc)
We’ll need these files when we register the block via PHP.
Registering the block via PHP
It’s still necessary to register your block via PHP, so let’s head back to our main dlx-gravatar-block.php
file. First, we’ll add a namespace below the plugin initialization file so we don’t have to worry about naming collisions. After that, an ABSPATH check in case of direct file access.
Here’s what we have so far:
<?php
/**
* Plugin Name: DLX Gravatar Block
* Plugin URI: https://dlxplugins.com/tutorials/
* Description: A sample Gravatar block for the block editor.
* Version: 1.0.0
* Requires at least: 6.3
* Requires PHP: 7.2
* Author: DLX Plugins
* Author URI: https://dlxplugins.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*
* @package DLXGravatarBlock
*/
namespace DLXPlugins\DLXGravatarBlock;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
Code language: PHP (php)
We’ll be using the init
action to register the block.
add_action( 'init', __NAMESPACE__ . '\register_block' );
/**
* Register the block.
*/
function register_block() {
register_block_type(
plugin_dir_path( __FILE__ ) . 'build/blocks/gravatar-block/block.json',
array(
'render_callback' => __NAMESPACE__ . '\render_block',
)
);
}
/**
* Render the block.
*
* @param array $attributes The block attributes.
*
* @return string The block output.
*/
function render_block( $attributes ) {
return '';
};
Code language: PHP (php)
Function register_block_type can take in the path of the block.json
file. We provide it with a render_callback
so we can render the block via PHP later.
Enqueueing the block scripts and styles
Enqueueing the block’s scripts and styles involves a little bit of PHP and JS. We’ll use action enqueue_block_editor_assets
to enqueue our JS.
// Enqueue the block editor JavaScript.
add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\register_block_editor_js' );
/**
* Enqueue the block editor JavaScript.
*/
function register_block_editor_js() {
$deps = require plugin_dir_path( __FILE__ ) . 'build/index.asset.php';
wp_register_script(
'dlx-gravatar-block-editor',
plugins_url( 'build/index.js', __FILE__ ),
$deps['dependencies'],
$deps['version'],
true,
);
}
Code language: PHP (php)
You may have noticed we’re grabbing a PHP file from the build
directory. This file is built automatically for you and contains any found dependencies that are enqueueable. For example, here’s an example of what’s inside index.asset.php
.
<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks'), 'version' => '44fe9f2b973c24b4d44f');
Code language: PHP (php)
Since it’s just returning an array, you can assign it to a PHP variable. You can then properly enqueue using the dependencies
and version
keys.
We’ll need a localized variable in order to store some variables we’ll need in JavaScript later.
- apiUrl – The path to the REST API endpoint we’ll be creating later to retrieve the Gravatar
- nonce – A nonce that allows a permissions check when doing REST requests.
- avatarSizes – A list of available avatar sizes for use with the Gravatar service.
We’ll be adding this after wp_register_script
:
/**
* Enqueue the block editor JavaScript.
*/
function register_block_editor_js() {
$deps = require plugin_dir_path( __FILE__ ) . 'build/index.asset.php';
wp_register_script(
'dlx-gravatar-block-editor',
plugins_url( 'build/index.js', __FILE__ ),
$deps['dependencies'],
$deps['version'],
true,
);
// Get avatar sizes.
$avatar_sizes = rest_get_avatar_sizes();
// Map to label/value pair.
$avatar_sizes = array_map(
function ( $size ) {
return array(
'label' => $size,
'value' => $size,
);
},
$avatar_sizes
);
// Add localized vars we'll need.
wp_localize_script(
'dlx-gravatar-block-editor',
'dlxGravatarBlock',
array(
'apiUrl' => rest_url( 'dlx-gravatar-block/v1/gravatar/' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'avatarSizes' => $avatar_sizes,
)
);
}
Code language: PHP (php)
Now when the block script loads, we’ll have access to global variable dlxGravatarBlock
in our JavaScript.
To load in the styles for the block, we’ll use enqueue_block_assets
. The action allows styles to be enqueued properly in the block and site editors, which could potentially be loaded in an iframe if all blocks use block.json API version 3.
// Enqueue the block editor CSS.
add_action( 'enqueue_block_assets', __NAMESPACE__ . '\register_block_css' );
/**
* Enqueue the block editor CSS.
*/
function register_block_css() {
$deps = require plugin_dir_path( __FILE__ ) . 'build/index.asset.php';
wp_enqueue_style(
'dlx-gravatar-block-editor',
plugins_url( 'build/index.css', __FILE__ ),
array(),
$deps['version'],
'all'
);
}
Code language: PHP (php)
We’ll now need to add the script and style handles to block.json
via keys editorScript
and editorStyle
:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"title": "Gravatar Block",
"apiVersion": 3,
"name": "dlxplugins/gravatar-block",
"category": "media",
"icon": "admin-users",
"description": "An Gravatar block.",
"keywords": [
"avatar",
"gravatar"
],
"version": "1.0.0",
"textdomain": "dlx-gravatar-block",
"attributes": {
},
"supports": {
"anchor": true,
"align": true,
"className": true
},
"editorScript": "dlx-gravatar-block-editor",
"editorStyle": "dlx-gravatar-block-editor"
}
Code language: JSON / JSON with Comments (json)
You can read more about how to load scripts and styles in the block editor using block.json.
Now we should have a block we can insert into any block-enabled page.
Block planning
Now that we have a Hello World, we can start imagining what features we would want in our new Gravatar block. For this, we have to anticipate who the end user is. Chances are, the user who is using this block is trying to build some sort of directory or perhaps needs an author avatar.
To keep the scope down, we’ll limit the Gravatar search to an email address field. For an author avatar, we can grab the current post-author ID and then grab their Gravatar.
To accommodate both cases, we’ll add a toggle above the email field that allows you to search by post author.
Clicking on Get Avatar will try to load the avatar image. For security reasons, we won’t store the emails as attributes and instead use a Gravatar hash as an indicator that the avatar block shouldn’t show the initial screen.
To follow this specification and wireframe, we’ll need some state variables to handle certain aspects:
- isAuthorAvatar – This will store whether the Use Author Avatar is checked.
- email – This will store the text entered In the Username, ID, or Email field.
- size – This will store the state of the combobox where users can select a size.
We’ll need a block.json attribute for storing the email hash (when retrieved).
Building the block interface
Right now our block.js
file looks like the following:
import './editor.scss';
import { useBlockProps } from '@wordpress/block-editor';
const GravatarBlock = ( props ) => {
const { attributes, setAttributes, clientId } = props;
const blockProps = useBlockProps(
{
className: 'dlx-gravatar-block',
}
);
return (
<div { ...blockProps }>
Hello World from Gravatar Block!
</div>
);
};
export default GravatarBlock;
Code language: JavaScript (javascript)
We’ll need to add in a few dependencies, namely @wordpress/i18n and @wordpress/components, which are part of the wp-scripts package already installed. We’ll also need the useState
hook from React, which we’ll use in a few places to handle the state of the block.
import './editor.scss';
import { useState } from 'react';
import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import {
ToggleControl,
} from '@wordpress/components';
Code language: JavaScript (javascript)
We’ll add some state to detect when the ‘Use Author Avatar’ toggle is set.
const { attributes, setAttributes, clientId } = props;
const [ isAuthorAvatar, setIsAuthorAvatar ] = useState( false );
const blockProps = useBlockProps(
{
className: 'dlx-gravatar-block',
}
);
Code language: JavaScript (javascript)
And finally render out a Toggle box using the ToggleControl component.
return (
<div { ...blockProps }>
<ToggleControl
label={ __( 'Use author avatar', 'dlx-gravatar-block' ) }
checked={ isAuthorAvatar }
onChange={ ( value ) => {
setIsAuthorAvatar( value );
} }
help={ __( 'Use the post author\'s Gravatar instead of a custom Gravatar.', 'dlx-gravatar-block' )}
/>
</div>
);
Code language: JavaScript (javascript)
Let’s add the remaining components, which will be a TextControl, SelectControl, and Button component.
import {
ToggleControl,
TextControl,
SelectControl,
Button,
} from '@wordpress/components';
Code language: JavaScript (javascript)
Here are the components in use.
return (
<div { ...blockProps }>
<ToggleControl
label={ __( 'Use author avatar', 'dlx-gravatar-block' ) }
checked={ isAuthorAvatar }
onChange={ ( value ) => {
setIsAuthorAvatar( value );
} }
help={ __( 'Use the post author\'s Gravatar instead of a custom Gravatar.', 'dlx-gravatar-block' )}
/>
{ ! isAuthorAvatar && (
<>
<TextControl
label={ __( 'Username, ID, or email', 'dlx-gravatar-block' ) }
value={ email }
onChange={ ( value ) => {
setEmail( value );
} }
/>
</>
) }
<SelectControl
label={ __( 'Avatar Size', 'dlx-gravatar-block' ) }
value={ size }
options={ dlxGravatarBlock.avatarSizes }
onChange={ ( value ) => {
setSize( value );
} }
/>
<Button
variant="primary"
onClick={() => {
// Get avatar and do some validation.
} }
>
{ __( 'Get Avatar', 'dlx-gravatar-block' ) }
</Button>
</div>
);
Code language: JavaScript (javascript)
Now we have a functional block interface.
Creating the REST API endpoint for retrieving the Gravatar
I couldn’t find a native RESTy WordPress way of retrieving a Gravatar, so for this tutorial I created my own endpoint.
Back in dlx-gravatar-block.php
, I’ve added in the following to create the REST endpoint to retrieve a Gravatar.
/**
* Start REST.
*/
add_action( 'rest_api_init', __NAMESPACE__ . '\register_rest_fields' );
/**
* Register REST fields.
*/
function register_rest_fields() {
register_rest_route(
'dlx-gravatar-block/v1',
'/gravatar/(?P<id>.+)',
array(
'methods' => 'GET',
'callback' => __NAMESPACE__ . '\rest_get_gravatar',
'sanitize_callback' => 'sanitize_text_field',
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
),
);
}
Code language: PHP (php)
I’m using hook rest_api_init
to register the REST route using WordPress function register_rest_route.
I have a callback function of rest_get_gravatar
, which will return any found Gravatars.
A URL to the endpoint would be something like: https://domain.local/wp-json/dlx-gravatar-block/v1/gravatar/id_or_email
Since I have a permissions_callback
set to a capability check, only authors or greater should be able to search for someone’s Gravatar.
Creating the REST API Callback
The REST callback will have to assume that any passed parameter could be an ID, username, email, or email hash. So we’ll need to be careful when validating the input.
/**
* Get the Gravatar.
*
* @param \WP_REST_Request $request The REST request.
*
* @return \WP_REST_Response The REST response.
*/
function rest_get_gravatar( $request ) {
$maybe_id_or_email = sanitize_text_field( urldecode( $request->get_param( 'id' ) ) ); //Accepts a user ID, Gravatar MD5 hash, username, user email. Assumes urlencoded.
// Call local plugin function get_user to get a user object.
$maybe_user = get_user( $maybe_id_or_email );
// Get avatar URLs.
$avatar_urls = rest_get_avatar_urls( $maybe_id_or_email );
// Format email into hash.
$email_hash = false;
if ( $maybe_user ) {
$maybe_id_or_email = $maybe_user->user_email;
$email_hash = hash( 'sha256', strtolower( $maybe_id_or_email ) ) . '@md5.gravatar.com';
} elseif ( is_email( $maybe_id_or_email ) ) {
// Make sure email isn't a gravatar email.
if ( strpos( $maybe_id_or_email, '@md5.gravatar.com' ) === false ) {
$email_hash = hash( 'sha256', strtolower( $maybe_id_or_email ) ) . '@md5.gravatar.com';
}
}
if ( ! empty( $avatar_urls ) ) {
$return = array(
'avatarUrls' => $avatar_urls,
'emailHash' => $email_hash,
);
return rest_ensure_response( $return );
}
return rest_ensure_response(
new \WP_Error(
'no_avatar',
__( 'No avatar found.', 'dlx-gravatar-block' ),
)
);
}
Code language: PHP (php)
The callback attempts to get a user, or email and returns any found avatars. If a valid email is found, via function is_email, then a hash is returned that we can store for the Gravatar.
Here’s a flowchart of the logic flow for easier parsing.
Within the callback we call local function get_users
, which I’ll show below:
/**
* Get the user.
*
* @param int|string|object $id_or_email The user ID, email address, or comment object.
*
* @return false|\WP_User The user object.
*/
function get_user( $id_or_email ) {
$id_or_email = sanitize_text_field( $id_or_email );
$user = false;
// Get user data.
if ( is_numeric( $id_or_email ) ) {
$user = get_user_by( 'id', (int) $id_or_email );
} elseif ( is_object( $id_or_email ) ) {
$comment = $id_or_email;
if ( empty( $comment->user_id ) ) {
$user = get_user_by( 'id', $comment->user_id );
} else {
$user = get_user_by( 'email', $comment->comment_author_email );
}
} elseif ( is_string( $id_or_email ) ) {
if ( is_email( $id_or_email ) ) {
$user = get_user_by( 'email', $id_or_email );
} else {
$user = get_user_by( 'login', $id_or_email );
}
}
return $user;
}
Code language: PHP (php)
This code attempts to get a user via several means, and if it can’t find one, it returns false. It borrows heavily from WordPress function get_avatar_data.
We now have a functional REST API endpoint. Let’s go back into the block and get an avatar for display.
Setting up a loading screen
Back in the block.js
file, we’ll add a loading screen.
Normally I’d separate each screen into a separate component, but for the sake of brevity, I’ll include everything in the main block file.
Let’s set up a loading screen that’ll display when a user is searching for a Gravatar.
I’ll first add the Spinner component to show a loading animation:
import {
ToggleControl,
TextControl,
SelectControl,
Button,
Spinner,
} from '@wordpress/components';
Code language: JavaScript (javascript)
I’ll set a loading state that I can read later:
// Set loading state for when a Gravatar request is being made.
const [ isLoading, setIsLoading ] = useState( false );
Code language: JavaScript (javascript)
Above the return
statement, I’ll just return a loading mechanism if we are in a loading state.
if ( isLoading ) {
return (
<div { ...blockProps }>
<Spinner /> { __( 'Loading Avatar...', 'dlx-gravatar-block' ) }
</div>
);
}
Code language: JavaScript (javascript)
I’ll then wire up the Button component and set the loading state through a timer for demonstration purposes.
<Button
variant="primary"
onClick={() => {
// todo: validate email field.
// todo: get gravatar.
setIsLoading( true );
setTimeout(
() => {
setIsLoading( false );
},
1500
);
} }
>
{ __( 'Get Avatar', 'dlx-gravatar-block' ) }
</Button>
Code language: JavaScript (javascript)
The result is a loading animation when the Get Avatar button is clicked.
Validating the email input
In summary, we must accept a valid ID, email, username, or email hash. Validating for one particular item will be intensive, so it’s best to encode the data in URL format and let server validation take care of the rest. We can do some basic validation, however, for example, making sure the value isn’t empty.
We’ll start by creating a new function in block.js
that’ll return whether the ‘Get Avatar’ button should be disabled or not. It’ll be disabled if the text input is empty and the ‘Use author avatar’ option isn’t checked.
/**
* Whether the Get Avatar button should be disabled.
*
* @returns {boolean} Whether the button should be disabled.
*/
const isButtonDisabled = () => {
if ( isAuthorAvatar ) {
return false;
}
const newEmail = email.trim();
if ( newEmail.length === 0 ) {
return true;
}
return false;
}
Code language: JavaScript (javascript)
And we’ll wire up the button so that it uses the disabled
prop to update automatically.
<Button
variant="primary"
onClick={() => {
// todo: validate email field.
// todo: get gravatar.
} }
disabled={ isButtonDisabled() }
>
{ __( 'Get Avatar', 'dlx-gravatar-block' ) }
</Button>
Code language: JavaScript (javascript)
Getting the post author Gravatar
In order to get the post author’s avatar, we need to first get the post author. We’ll do do using useSelect
from the @wordpress/data package.
import {
useSelect,
} from '@wordpress/data';
Code language: JavaScript (javascript)
This supplies a React hook that we can use to get the post author’s user ID.
We’ll also need apiFetch
from the @wordpress/api-fetch package.
import apiFetch from '@wordpress/api-fetch';
Code language: JavaScript (javascript)
Next, in the body, we’ll use useSelect
to retrieve the current post author’s ID.
// Get the post author ID.
const authorId = useSelect(
( select ) => select( 'core/editor' ).getCurrentPostAttribute( 'author' ),
[]
);
Code language: JavaScript (javascript)
Let’s now create a new function for retrieving the Gravatar. We’ll be using the WordPress API Fetch API.
For now, we’ll be pinging our REST API, and doing a console.log
of the output.
const getGravatar = () => {
// Set the loading state to true.
setIsLoading( true );
let maybeIdorEmail = authorId;
if ( ! isAuthorAvatar ) {
maybeIdorEmail = email;
}
// Make the request to get the Gravatar.
apiFetch( {
path: `dlx-gravatar-block/v1/gravatar/${ encodeURIComponent( maybeIdorEmail ) }`,
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': dlxGravatarBlock.nonce,
},
} ).then( ( response ) => {
console.log( response );
} ).catch( ( error ) => {
console.error( error );
} ).finally( () => {
// Set the loading state to false.
setIsLoading( false );
} );
}
Code language: JavaScript (javascript)
Next is wiring up the button.
<Button
variant="primary"
onClick={() => {
getGravatar();
} }
disabled={ isButtonDisabled() }
>
{ __( 'Get Avatar', 'dlx-gravatar-block' ) }
</Button>
Code language: JavaScript (javascript)
We should now have a semi-functional block that retrieves our avatar in JSON format.
If the response is a success, we should expect:
{
"avatarUrls": {
"24": "https:\/\/secure.gravatar.com\/avatar\/aca52b16e7...mm&r=g",
"48": "https:\/\/secure.gravatar.com\/avatar\/aca52b16e7...d=mm&r=g",
"96": "https:\/\/secure.gravatar.com\/avatar\/aca52b16e7..?s=96&d=mm&r=g"
},
"emailHash": "43730967b075e1682e1e2..."
}
Code language: JSON / JSON with Comments (json)
The avatarUrls
parameter will come in handy for displaying the Gravatar in the block editor, whereas the emailHash
we can store in block attributes so we don’t have to look up the user again later.
If the response is an error, then we should expect:
{"code":"no_avatar","message":"No avatar found.","data":null}
Code language: JSON / JSON with Comments (json)
In order to handle the response, I need to update the size
state to select the highest size.
// Set input state.
const [ isAuthorAvatar, setIsAuthorAvatar ] = useState( false );
const [ email, setEmail ] = useState( '' );
const [ size, setSize ] = useState( dlxGravatarBlock.avatarSizes[ dlxGravatarBlock.avatarSizes.length - 1 ].value );
Code language: JavaScript (javascript)
I’ll need a state variable to hold the URL of the Gravatar:
// Set the Avatar URL.
const [ avatarUrl, setAvatarUrl ] = useState( '' );
Code language: JavaScript (javascript)
Back in function getGravatar
, I’ll update the response to put in an error placeholder, save the email hash, and update the avatarUrl
state variable.
/**
* Get the Gravatar based on the email or ID.
*
* @param {string} emailHash The email hash.
*
* @returns {void}
*/
const getGravatar = ( emailHash = '' ) => {
// Set the loading state to true.
setIsLoading( true );
let maybeIdorEmail = authorId;
if ( ! isAuthorAvatar ) {
maybeIdorEmail = email;
if ( emailHash.length > 0 ) {
maybeIdorEmail = emailHash;
}
}
// Make the request to get the Gravatar.
apiFetch( {
path: `dlx-gravatar-block/v1/gravatar/${ encodeURIComponent( maybeIdorEmail ) }`,
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': dlxGravatarBlock.nonce,
},
} ).then( ( response ) => {
const isError = Object.keys( response ).includes( 'code' );
if ( isError ) {
// todo - display error.
return;
} else {
const maybeHash = response.emailHash;
// If there's a hash, update it. Else, ignore.
if ( maybeHash ) {
setAttributes( {
gravatarHash: maybeHash,
} );
}
// Get avatar based on size.
const avatarUrls = response.avatarUrls;
const avatarUrl = avatarUrls[ size ];
setAvatarUrl( avatarUrl );
}
} ).catch( ( error ) => {
console.error( error );
} ).finally( () => {
// Set the loading state to false.
setIsLoading( false );
} );
}
Code language: JavaScript (javascript)
To output the author’s Gravatar, we’ll first check for an avatar. If there is one, we’ll save the block attributes and update the avatar state.
In order to save the block attributes, we’ll have to declare them in our block.json
file. Attributes can get a bit messy, so I’m keeping this simple.
Here’s the block.json
with attributes added.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"title": "Gravatar Block",
"apiVersion": 3,
"name": "dlxplugins/gravatar-block",
"category": "media",
"icon": "admin-users",
"description": "An Gravatar block.",
"keywords": [
"avatar",
"gravatar"
],
"version": "1.0.0",
"textdomain": "dlx-gravatar-block",
"attributes": {
"gravatarHash": {
"type": "string",
"default": ""
},
"gravatarSize": {
"type": "string",
"default": ""
},
"align": {
"type": "string",
"default": "none"
}
},
"supports": {
"anchor": true,
"align": true,
"className": true
},
"editorScript": "dlx-gravatar-block-editor",
"editorStyle": "dlx-gravatar-block-editor"
}
Code language: JSON / JSON with Comments (json)
For more on block attributes, this guide on attributes in block.json is a must-read.
Basically, there are three attributes, each of type string
.
- gravatarHash – This will contain the email hash of the original email, and can be used to retrieve Gravatars later.
- gravatarSize – This will contain the desired size of the Gravatar to display.
- align – This will contain the desired alignment of the resulting image.
For display purposes, we can check if the state variable avatarUrl
is set.
Back in block.js
, I’ll add a return statement just after the loading animation check.
if ( isLoading ) {
return (
<div { ...blockProps }>
<Spinner /> { __( 'Loading Avatar...', 'dlx-gravatar-block' ) }
</div>
);
}
// If there's an avatar URL, display it.
if ( avatarUrl ) {
return (
<div { ...blockProps }>
<img
src={ avatarUrl }
alt={ __( 'Gravatar', 'dlx-gravatar-block' ) }
width={ gravatarSize || size }
height={ gravatarSize || size }
/>
</div>
);
}
Code language: JavaScript (javascript)
Here’s the result:
We should be able to save the post at this point and our attributes should save as well.
Remembering the Gravatar email
We’ll need to “remember” the avatar the next time the page refreshes, so for that we’ll use the saved Gravatar hash attribute. We’ll use an effect to load in an avatar immediately if a hash is detected.
For that, we’ll import useEffect
:
import { useState, useEffect } from 'react';
Code language: JavaScript (javascript)
I’ll need to read in the gravatarHash
and gravatarSize
attributes:
const { attributes, setAttributes, clientId } = props;
// Get block attributes.
const { gravatarHash, gravatarSize } = attributes;
Code language: JavaScript (javascript)
I’ll update the loading state to show if there is a hash detected.
// Set loading state for when a Gravatar request is being made.
const [ isLoading, setIsLoading ] = useState( gravatarHash.length > 0 );
Code language: JavaScript (javascript)
Finally, we’ll add in the effect:
/**
* Retrieve avatar onload or when the author id changes.
*/
useEffect( () => {
if ( gravatarHash.length > 0 ) {
getGravatar( gravatarHash );
}
}, [] );
Code language: JavaScript (javascript)
The result is being able to see the saved avatar on a page refresh.
Changing the Gravatar
We’ll need a way to change the Gravatar. To do this, we’ll add a “Change” button to the block toolbar and will need a few new components.
import {
ToggleControl,
TextControl,
SelectControl,
Button,
Spinner,
ToolbarGroup,
ToolbarButton,
} from '@wordpress/components';
import {
BlockControls,
} from '@wordpress/block-editor';
Code language: JavaScript (javascript)
- BlockControls – This is a wrapper for any block controls, including toolbars.
- ToolbarGroup component – Wrapper for our “Change” button.
- ToolbarButton component – The toolbar item for the “Change” button.
Using these components, let’s create a toolbar that’ll display when the avatar is showing.
// Begin toolbar.
const toolbar = (
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon="edit"
title={ __( 'Change', 'dlx-gravatar-block' ) }
onClick={ () => {
setAvatarUrl( '' );
setEmail( '' );
} }
>
{ __( 'Change', 'dlx-gravatar-block' ) }
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
);
// If there's an avatar URL, display it.
if ( avatarUrl ) {
return (
<div { ...blockProps }>
{ toolbar }
<img
src={ avatarUrl }
alt={ __( 'Gravatar', 'dlx-gravatar-block' ) }
width={ gravatarSize || size }
height={ gravatarSize || size }
/>
</div>
);
}
Code language: JavaScript (javascript)
I’ve highlighted the area where the toolbar outputs.
We are now able to change the avatar.
Outputting the block on the frontend
You may remember we set a few attributes using block.json
. We can now use those attributes to grab the avatar on the frontend.
Let’s go back into the main PHP file dlx-gravatar-block.php
and begin rendering the output.
We’ll have three attributes available to us:
- gravatarHash
- gravatarSize
- align
We can use those to generate an avatar image.
/**
* Render the block.
*
* @param array $attributes The block attributes.
*
* @return string The block output.
*/
function render_block( $attributes ) {
$gravatar_hash = $attributes['gravatarHash'] ?? '';
$gravatar_size = $attributes['gravatarSize'] ?? 96;
$alignment = $attributes['align'] ?? 'none';
// Return if no hash is found.
if ( empty( $gravatar_hash ) ) {
return '';
}
// Get CSS classes.
$classes = array(
'wp-block-dlx-gravatar-block',
'align' . $alignment,
);
// Get the avatar based on hash.
$avatar = get_avatar(
$gravatar_hash,
$gravatar_size,
'',
'',
array( 'class' => implode( ' ', $classes ) )
);
return $avatar;
}
Code language: PHP (php)
The end result
We now have a Gravatar block we can add to a block-enabled post, have it remember the Gravatar email, and output the avatar to the frontend.
Conclusion and example code
This block can be expanded further to remember the author ID and update the Gravatar each time that changes, but I will leave that as an exercise for the reader. Better error checking and validation are also needed.
I hope this helped you on your block journey. Thank you so much for reading.
Please comment or @me on Twitter if you have any questions.
Like this tutorial? There's more like it. Subscribe today!
Ronald Huereca founded DLX Plugins in 2022 with the goal of providing deluxe plugins available for download. Find out more about DLX Plugins, check out some tutorials, and check out our plugins.