I’m posting this slightly before the presentation I will be giving Saturday at WordCamp Miami 2019. If all goes well, this post will be updated after the event with a video replay and additional notes.

This presentation is built around the frustrations and payoffs of updating RSVPMaker and the WordPress for Toastmasters extension to RSVPMaker, as well as starting to create new functionality around Gutenberg, such as the My Marginalia plugin I created to help a client with the “show notes” she posts with recorded videos. Whether you’re creating a plugin for the repository of a hack for your own website, Gutenberg may be able to help you make it better.

(For anyone not grokking the terminology, Gutenberg refers to the new editor that comes standard with WordPress 5+ and the more JavaScript-centric programming model that comes with it).


Gutenberg Handbook


Gutenberg Hub

Create Guten Block
Tutorials: CSS Tricks and Tuts+

Demo Code

Here’s simple example showing edit and save functions, plus an “inspector” component for the sidebar.

All this does is create a block in the editor and a save mechanism on the front end, with a custom headline and a line of text you can change using the sidebar component.


import './style.scss';
import './editor.scss';
const { __ } = wp.i18n; 
const { registerBlockType } = wp.blocks; 
const { Component } = wp.element;
const { InspectorControls } = wp.editor;
const { PanelBody, TextControl } = wp.components;

class DemoInspector extends Component {
render() {
	const { attributes, setAttributes, className } = this.props;
	return (
		 setAttributes( { message } ) }
}//end render


registerBlockType( 'wcmia/demo', {
	title: __( 'WordCamp Demo' ), 
	icon: 'shield', 
	category: 'common', 
	keywords: [__( 'Demo' )],
	attributes: {
	message: {
		type: 'string',
		default: '',
	edit: function( props ) { // UI for editor
	const { attributes, setAttributes, className } = props;
		return (

My custom block (editor)


); }, save: function( props ) { // format for storage / retrieval const { attributes, setAttributes, className } = props; return (

My custom block (editor)


); }, } );

We can give it formatting that will be applied both in the editor and on the front end with a style.scss file like this:

.wp-block-wcmia-demo  {
border: medium solid red;
padding: 5px;

More practical examples:

  • A custom format you define for product descriptions, executive bios, recipes or other content employed frequently in the context of your specific uses for WordPress. For example, I created a Floating Callout plugin for a callout / sidebar format used as an accent within a post or page. I had essentially been doing the same thing for years with snippets of custom HTML / CSS — but the plugin makes it easier.
  • A block of content that will exhibit custom behavior when viewed on your site, in combination with either front end JavaScript or server-side processing in PHP (more on that below).

Fussy Code

One important thing to understand is that the “save” function doesn’t merely save content to the HTML content of the post, as it will be stored in the WordPress database. When you retrieve the content, Gutenberg expects it to be organized the same way. If not, you will see an error.

Here’s an example that popped up while I was writing this post.

The unexpected or invalid code warning.

This apparently happened because I was having trouble with the display of my JavaScript code examples, which I recorded here using the Gutenberg component for displaying code. Ironically, Gutenberg seemed to be confused by the JSX examples. All the < symbols in HTML or component tags were being displayed as entity codes (<).

To get the code to display properly, I had to do a search-and-replace in Windows Notepad. That worked in terms of front-end display, except that Gutenberg would no longer allow me to edit those blocks as blocks in the editor.

Wrapping a Block Around Other Blocks

When you define Edit and Save functions using the InnerBlocks component, you can define a block that wraps around other content blocks (paragraphs, images, etc.). The code below is from My Marginalia, which applies special fomatting to the wrapped content. The Floating Callout plugin I mentioned does the same thing.

There’s also a “Limited Time” block in RSVPMaker that uses an InnerBlocks component in combination with server-side rendering to suppress the display of content that is past its expiration date.

    edit: function( props ) {	
	const { attributes, className, setAttributes, isSelected } = props;
	const marginstyle = {width: attributes.width + 'em'};
	const innerstyle = {marginLeft: (attributes.width + 2) + 'em'};

	return (
Nested inside another Marginalia block
setAttributes({ content })} />
); }, save: function( { attributes, className } ) { const marginstyle = {}; const innerstyle = {}; if(attributes.position == 'left') { marginstyle['float'] = 'left;' marginstyle['width'] = attributes.width + 'em'; innerstyle['marginLeft'] = (attributes.width + 2) + 'em'; } else { marginstyle['float'] = 'right;' marginstyle['width'] = attributes.width + 'em'; innerstyle['marginRight'] = (attributes.width + 2) + 'em'; } return
; } });

Displaying a Notification

Displaying a notification in the WordPress admin following an event like a post being saved used to be fairly easy in PHP.

add_action('admin_notices', 'rsvpmaker_template_notice');

function rsvpmaker_template_notice () {
	global $post;
	if(isset($_POST['rsvpmaker_template_data'])) //detect some trigger
	echo '

'.__('After updating this template, click','rsvpmaker').' ID).'" >'.__('create / update events','rsvpmaker').'

'; }

It took me months of research to figure out how to do essentially the same thing in Gutenberg because those PHP-generated notifications aren’t shown on the editor screen when Gutenberg is active. The following code is based on a tip from a collaborator on Github and my own study of internal WordPress code to figure out how it detects such events (the example I found was related to saving the content of metaboxes on post save).

wp.plugins.registerPlugin( 'rsvpmaker-sidebar-postpublish', {
	render: RSVPPluginPostPublishPanel,
} );

}// end initial test that rsvpmaker is set

if((typeof rsvpmaker_json !== 'undefined' ) && rsvpmaker_json.projected_url) {

		let wasSavingPost = 'core/editor' ).isSavingPost();
		let wasAutosavingPost = 'core/editor' ).isAutosavingPost();
		let wasPreviewingPost = 'core/editor' ).isPreviewingPost();
		// determine whether to show notice
		subscribe( () => {
			const isSavingPost = 'core/editor' ).isSavingPost();
			const isAutosavingPost = 'core/editor' ).isAutosavingPost();
			const isPreviewingPost = 'core/editor' ).isPreviewingPost();
			const hasActiveMetaBoxes = 'core/edit-post' ).hasMetaBoxes();

			// Save metaboxes on save completion, except for autosaves that are not a post preview.
			const shouldTriggerTemplateNotice = (
					( wasSavingPost && ! isSavingPost && ! wasAutosavingPost ) ||
					( wasAutosavingPost && wasPreviewingPost && ! isPreviewingPost )

			// Save current state for next inspection.
			wasSavingPost = isSavingPost;
			wasAutosavingPost = isAutosavingPost;
			wasPreviewingPost = isPreviewingPost;

			if ( shouldTriggerTemplateNotice ) {'core/notices').createNotice(
		'info', // Can be one of: success, info, warning, error.
		__('After updating this template, click'), // Text string to display.
			isDismissible: true, // Whether the user can dismiss the notice.
			// Any actions the user can perform.
			actions: [
					url: rsvpmaker_json.projected_url,
					label: __('create / update events')
			/* placeholder for logic to remove notice
			else {
				console.log('remove notice');
} );	

Blocks versus Shortcodes

Here are some shortcode examples, one where the output is based on the attributes added to the shortcode and another that wraps around a piece of content (the shortcode used in the Classic Editor to format image captions and allow them to be treated as a unit with the accompanying image).

The PHP code to process your shortcode can then be something like this:


function my_shortcode_function($atts = array()) {
	$title = $atts['title'];
	$size = $atts['size'];
	$content = sprintf('


',$size,$content); return $content; } function my_shortcode_wrapper($atts = array(), $content = '') { //apply some extra formatting or logic to $content return $content; }

You can register a Gutenberg block to get run through a server-side routine in much the same way:

function wpt_server_block_render(){
	register_block_type('wp4toastmasters/role', ['render_callback' => 'toastmaster_short']);


function toastmasters_short($atts) {
	global $post;
	$role = $atts['role'];
	$count = (int) $atts['count'];
	$content = '';
	for($i =1; $i <= $count; $i++) {
		$slug = $role+$i;
		$assigned = get_post_meta($post->ID,$slug,true);
			$content .= get_agenda_content($slug,$assigned);
			$content .= get_signup_button($slug,$assigned);
	return $content;

Escape from Gutenberg

Although I like many aspects of Gutenberg, I wasn’t prepared to refactor every bit of PHP into JavaScript. In particularly, I had a lot of functionality built into metaboxes displayed beneath the classic editor, and metaboxes are discouraged in the Gutenberg framework. Mine included some JQuery interactivity that seemed to clash with the Gutenberg code.

The coping mechanism I devised for RSVPMaker was to make a few basic options like setting the date and turning RSVP registrations on or off available within the sidebar to the editor. The more elaborate functionality like setting event pricing and customizing the registration form moved to a separate screen.

Here’s how you can do that, in pseudocode simplified from the actual code used in RSVPMaker.

add_action( 'admin_bar_menu', 'my_rsvpmaker', 99 );

function my_toolbar($wp_admin_bar) {
	global $post;
	if(isset($post->post_type) && ($post->post_type == 'rsvpmaker') )
	//a new menu
	$args = array(
		'id'    => 'rsvpmaker_options',
		'title' => 'RSVP / Event Options',
		'href'  => admin_url('edit.php?post_type=rsvpmaker&page=rsvpmaker_details&post_id='.$post->ID),
		'meta'  => array( 'class' => 'edit-rsvpmaker-options')
	$wp_admin_bar->add_node( $args );
	//a subnew menu, parent set to id of item above
	$args = array(
		'parent'    => 'rsvpmaker_options',
		'id' => 'my_form',
		'title' => 'My Form Page',
		'href'  => admin_url('edit.php?post_type=rsvpmaker&page=my_form_page&post_id='.$post->ID),
		'meta'  => array( 'class' => 'my_form')
	$wp_admin_bar->add_node( $args );

add_action('admin_menu', 'rsvpmaker_admin_menu');

function rsvpmaker_admin_menu() {
//submenu menu under post type
add_submenu_page('edit.php?post_type=rsvpmaker', __("Event Options",'rsvpmaker'), __("Event Options",'rsvpmaker'), 'edit_rsvpmakers', "my_form_page", "my_form_page" );
//standalone menu
add_menu_page(__("Event Options",'rsvpmaker'), __("Event Options",'rsvpmaker'), 'edit_rsvpmakers', "my_form_page", "my_form_page" );

function my_form_page() {
	$post_id = (isset($_REQUEST['post_id'])) ? (int) $_REQUEST['post_id'] : false;
		echo '

Updated my variable

'; } printf('
',admin_url('edit.php?post_type=rsvpmaker&page=rsvpmaker_details&post_id='.$post_id)); printf('',get_post_meta($post_id,'my_variable',true)); submit_button(); echo '
'; } else { $o = ''; $future = get_future_events(); foreach($future as $event) { $o .= sprintf('',$event->ID,$event->post_title,$event->date); } printf('
',admin_url('edit.php'),$o); } }

This shows hooking into the action for display of the admin bar, getting access to the $wp_admin_bar object, and calling add_node methods that add our custom menu and submenu.

We register a custom admin page and define a function for displaying it. If the user clicks through from the admin bar (while viewing a post on the website or in the editor), that link will have the post id added to the URL query.

If the post ID is set, we display a form with options specific to that post (an RSVPMaker event in this case). Otherwise, we retrieve a list of posts and give the user a form they can fill out to display the options for the post they are interested in.

Check back on Sunday or Monday for updates to this post.


Unsubscribe *|EMAIL|* from this list *|UNSUB|*
Our mailing address is:
Copyright (C) *|CURRENT_YEAR|* *|LIST:COMPANY|* All rights reserved.