How to Create a Custom Gutenberg Gravatar Block From Scratch

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.

Gravatar Custom Block Example

This article is quite long, so here’s a Table of Contents:

Table of Contents

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

Screenshot of a MAMP Pro Interface
MAMP Pro Hosts Screen

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

Command line screenshot of a node version
Node 18 using Warp Terminal

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)
Plugin Creation Commands Using Warp Terminal
Plugin Creation Commands Using Warp Terminal

Using your favorite code editor, open up dlx-gravatar-block.php.

Shortcut Command for Opening File
Shortcut Command for Opening File

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.

DLX Gravatar Block on the Plugins Screen
DLX Gravatar Block on the Plugins Screen

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 initCode language: Bash (bash)

You’ll see several prompts. You can just press Enter all the way through.

Running npm init In Terminal
Running npm init In Terminal

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

NPM Run Start
NPM Run Start Example

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.jsonCode 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.scssCode 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)
Running npm run build in terminal
Running `npm run build` in Terminal

You should now see a build directory like this:

.
├── build/
│   └── blocks/
│       └── gravatar-block/
│           └── block.json
├── index.asset.php
├── index.css
└── index.jsCode 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.

Gravatar Block is Selectable
Gravatar Block is Selectable

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.

Author Avatar Interface
Author Avatar Interface

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)
ToggleControl for Using the Author Avatar
ToggleControl for Using the Author Avatar

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.

Animation of Block Interface With Toggle Working
Animation of Block Interface With Toggle Working

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.

Get Avatar REST Flowchart
Get Avatar REST Flowchart

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.

Animation of a Loading Spinner When a Gravatar is Loading
Animation of a Loading Spinner When a Gravatar is Loading

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)
Animation Showing Disabled Button on Validation Error
Animation Showing Disabled Button on Validation Error

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:

Animation of Avatar Loading
Animation of Avatar Loading

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.

Animation of Gravatar Block Refreshing
Animation of Gravatar Block Refreshing

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)

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.

Animation of a Change Button in the Toolbar
Animation of a Change Button in the Toolbar

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

Gravatar Block on the Frontend
Gravatar Block on the Frontend

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.

Ronald Huereca
By: Ronald Huereca
Published On: on April 28, 2024

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.

Leave Your Valuable Feedback

Your email address will not be published. Required fields are marked *

Shopping Cart
  • Your cart is empty.
Scroll to Top