Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: gutenberg

  • Editor Sidebar Madness and Gutenberg

    Editor Sidebar Madness and Gutenberg

    Way back when WP was simple and adding a sidebar to the editor was a simple metabox, I had a very straightforward setup with a box that, on page load, would tell you if the data in the post matched the remote API, and if not would tell you what to update.

    My plan was to have it update on refresh, and then auto-correct if you press a button (because sometimes the API would be wrong, or grab the wrong account — loose searching on people’s names is always rough). But my plan was ripped asunder by this new editor thingy, Gutenberg.

    I quickly ported over my simple solution and added a note “This does not refresh on page save, sorry.” and moved on.

    Years later brings us to 2024 and November being my ‘funenployment’ month, where I worked on little things to keep myself sharp before I started at AwesomeMotive. Most of the work was fixing security issues, moving the plugin into the theme so there was less to manage, modernizing processes, upgrading libraries, and so on.

    But one of those things was also making a real Gutenbergized sidebar that autoupdates (mostly).

    What Are We Doing?

    On LezWatch.TV, we collect actor information that is public and use it to generate our pages. So if you wanted to add in an actor, you put in their name, a bio, an image, and then all this extra data like websites, social media, birthdates, and so on. WikiData actually uses us to help determine gender and sexuality, so we pride ourselves on being accurate and regularly updated.

    In return, we use WikiData to help ensure we’re showing the data for the right person! We do that via a simple search based on either their WikiData ID (QID), IMDb ID, or their name. The last one is pretty loose since actors can have the same name now (oh for the days when SAG didn’t allow that…). We use the QID to override the search in cases where it grabs the wrong person.

    I built a CLI command that, once a week, checks actors for data validity. It makes sure the IMDb IDs and socials are formatted properly, it makes sure the dates are valid, and it pings WikiData to make sure the birth/death etc data is also correct.

    With that already in place, all I needed was to call it.

    You Need an API

    The first thing you need to know about this, is that Gutenberg uses the JSON API to pull in data. You can have it pull in everything by custom post meta, but as I already have a CLI tool run by cron to generate that information, making a custom API call was actually going to be faster.

    I went ahead and made it work in a few different ways (you can call it by IMDb ID, post ID, QID, and the slug) because I planned for the future. But really all any of them are doing is a search like this:

    	/**
    	 * Get Wikidata by Post ID
    	 *
    	 * @param int $post_id
    	 * @return array
    	 */
    	private function get_wikidata_by_post_id( $post_id ): array {
    		if ( get_post_type( $post_id ) !== 'post_type_actors' ) {
    			return array(
    				'error' => 'Invalid post ID',
    			);
    		}
    
    		$wikidata = ( new Debug_Actors() )->check_actors_wikidata( $post_id );
    
    		return array( $wikidata );
    	}
    

    The return array is a list of the data we check for, and it either is a string of ‘matches’ /true, or it’s an array with WikiData’s value and our value.

    Making a Sidebar

    Since we have our API already, we can jump to making a sidebar. Traditionally in Gutenberg, we make a sidebar panel for the block we’re adding in. If you want a custom panel, you can add in one with an icon on the Publish Bar:

    A screenshot of the Gutenberg Publish bar, with the Jetpack and YoastSEO icons

    While that’s great and all, I wanted this to be on the side by default for the actor, like Categories and Tags. Since YoastSEO (among others) can do this, I knew it had to be possible:

    Screenshot of the Gutenberg Sidebar, with YoastSEO's custom example.

    But when I started to search around, all anyone told me was how I had to use a block to make that show.

    I knew it was bullshit.

    Making a Sidebar – The Basics

    The secret sauce I was looking for is decidedly simple.

    	const MetadataPanel = () => (
    		<PluginDocumentSettingPanel
    			name="lwtv-wikidata-panel"
    			title="WikiData Checker"
    			className="lwtv-wikidata-panel"
    		>
    			<PanelRow>
    				<div>
    					[PANEL STUFF HERE]
    				</div>
    			</PanelRow>
    		</PluginDocumentSettingPanel>
    	);
    

    I knew about PanelRow but finding PluginDocumentSettingPanel took me far longer than it should have! The documentation doesn’t actually tell you ‘You can use this to make a panel on the Document settings!’ but it is obvious once you’ve done it.

    Making it Refresh

    This is a pared down version of the code, which I will link to at the end.

    The short and simple way is I’m using UseEffect to refresh:

    useEffect(() => {
    		if (
    			postId &&
    			postType === 'post_type_actors' &&
    			postStatus !== 'auto-draft'
    		) {
    			const fetchData = async () => {
    				setIsLoading(true);
    				try {
    					const response = await fetch(
    						`${siteURL}/wp-json/lwtv/v1/wikidata/${postId}`
    					);
    					if (!response.ok) {
    						throw new Error(
    							`HTTP error! status: ${response.status}`
    						);
    					}
    					const data = await response.json();
    					setApiData(data);
    					setError(null);
    				} catch (err) {
    					setError(err.message);
    					setApiData(null);
    				} finally {
    					setIsLoading(false);
    				}
    			};
    			fetchData();
    		}
    	}, [postId, postType, postStatus, siteURL, refreshCounter]);
    

    The reason I’m checking post type and status, is that I don’t want to try and run this if it’s not an actor, and if it’s not at least a real draft.

    The constants are as follows:

    	const [apiData, setApiData] = useState(null);
    	const [isLoading, setIsLoading] = useState(true);
    	const [error, setError] = useState(null);
    

    Right below this I have a second check:

    	if (postType !== 'post_type_actors') {
    		return null;
    	}
    

    That simply prevents the rest of the code from trying to run. You have to have it after the UseEffect because JS is weird and does things in an order. If you have a return before it, it fails to pass a lint (and I enforce linting on this project).

    How it works is on page load of an auto-draft, it tells you to save the post before it will check. As soon as you do save the post (with a title), it refreshes and tells you what it found, speeding up initial data entry!

    But then there’s the issue of refreshing on demand.

    HeartBeat Flatline – Use a Button

    I did, at one point, have a functioning heartbeat checker. That can get pretty expensive and it calls the API too many times if you leave a window open. Instead, I made a button that uses a constant:

    const [refreshCounter, setRefreshCounter] = useState(0);
    

    and a handler:

    	const handleRefresh = () => {
    		setRefreshCounter((prevCounter) => prevCounter + 1);
    	};
    

    Then the button itself:

    &lt;Button
    	variant="secondary"
    	onClick={handleRefresh}
    	isBusy={isLoading}
    >
    	{isLoading ? 'Refreshing...' : 'Refresh'}
    &lt;/Button>
    

    Works like a champ.

    Output the Data

    The data output is the interesting bit, because I’m still not fully satisfied with how it looks.

    I set up a filter to process the raw data:

    	const filteredPersonData = (personData) => {
    		const filteredEntries = Object.entries(personData).filter(
    			([key, value]) => {
    				const lowerCaseValue = String(value).toLowerCase();
    				return (
    					lowerCaseValue !== 'match' &&
    					lowerCaseValue !== 'n/a' &&
    					!['wikidata', 'id', 'name'].includes(key.toLowerCase())
    				);
    			}
    		);
    		return Object.fromEntries(filteredEntries);
    	};
    

    The API returns the WikiData ID, the post ID, and the name, none of which need to be checked here, so I remove them. Otherwise it capitalizes things so they look grown up.

    Then there’s a massive amount of code in the panel itself:

    <div>
    	{isLoading && <Spinner />}
    	{error && <p>Error: {error}</p>}
    	{!isLoading && !error && apiData && (
    		<>
    			{apiData.map((item) => {
    				const [key, personData] = Object.entries(item)[0];
    				const filteredData = filteredPersonData(personData);
    				return (
    					<div key={key}>
    						<h3>{personData.name}</h3>
    						{Object.keys(filteredData).length ===
    						0 ? (
    							<p>[All data matches]</p>
    						) : (
    							<div>
    								{Object.entries( filteredData ).map(([subKey, value]) => (
    									<div key={subKey}>
    										<h4>{subKey}</h4>
    										{value && (
    											<ul>
    												{Object.entries( value ).map( ([ innerKey, innerValue, ]) => (
    													<li key={ innerKey }>
    														<strong>{innerKey}</strong>:{' '} <code>{innerValue || 'empty'}</code>
    													</li>
    												)
    										)}
    											</ul>
    										)}
    										{!value && 'empty'}
    									</div>
    								))}
    							</div>
    						)}
    					</div> );
    			})}
    		</>
    	)}
    
    	{!isLoading && !error && !apiData && (
    		<p>No data found for this post.</p>
    	)}
    	<Button />
    </div>
    

    <Spinner /> is from '@wordpress/components' and is a default component.

    Now, innerKey is actually not a simple output. I wanted to capitalize the first letter and unlike PHP, there’s no ucfirst() function, so it looks like this:

    {innerKey .charAt( 0 ).toUpperCase() + innerKey.slice( 1 )}

    Sometimes JavaScript makes me want to drink.

    The Whole Code

    You can find the whole block, with some extra bits I didn’t mention but I do for quality of life, on our GitHub repo for LezWatch.TV. We use the @wordpress/scripts tooling to generate the blocks.

    The source code is located in folders within /src/ – that’s where most (if not all) of your work will happen. Each new block gets a folder and in each folder there must be a block.json file that stores all the metadata. Read Metadata in block.json if this is your first rodeo.

    The blocks will automagically build anytime anyone runs npm run build from the main folder. You can also run npm run build from the blocks folder.

    All JS and CSS from blocks defined in blocks/*/block.json get pushed to the blocks/build/ folder via the build process. PHP scans this directory and registers blocks in php/class-blocks.php. The overall code is called from the /blocks/src/blocks.php file.

    The build subfolders are NOT stored in Git, because they’re not needed to be. We run the build via actions on deploy.

    What It Looks Like

    Gif showing how it auto-loads the data on save.

    One of the things I want to do is have a way to say “use WikiData” or “use ours” to fill in each individual data point. Sadly sometimes it gets confused and uses the wrong person (there’s a Katherine with an E Hepburn!) so we do have a QID override, but even so there can be incorrect data.

    WikiData often lists socials and websites that are defunct. Mostly that’s X these days.

    Takeaways

    It’s a little frustrating that I either have to do a complex ‘normal’ custom meta box with a lot of extra JS, or make an API. Since I already had the API, it’s no big, but sometimes I wish Gutenberg was a little more obvious with refreshing.

    Also finding the right component to use for the sidebar panel was absolutely maddening. Every single document was about doing it with a block, and we weren’t adding blocks.

    Finally, errors in Javascript remain the worst. Because I’m compiling code for Gutenberg, I have to hunt down the likely culprit, which is hard when you’re still newish to the code! Thankfully, JJ from XWP was an angel and taught me tons in my 2 years there. I adore her.

  • Interlude: Gutenberg Moves Fast

    Interlude: Gutenberg Moves Fast

    I’m taking a pause on my plugin posts to talk about Gutenberg.

    I really love Gutenberg. I’m not kidding! I find it far more enjoyable to write (stories) in plain apps (I used Apple Pages because it syncs between laptop and iPad, yes, I am often using my iPad to write my novel, yes, I will let the world know when it’s done). But when I write for the web, it’s a more visual medium, and Gutenberg is fantastic to represent what I’m writing as it will be properly seen by all!

    But.

    Gutenberg moves fast. Hella fast. So fast it can leave you in the dust, and it has a critical flaw that I feel has been stifling it’s growth and usage among developers.

    JS isn’t your Momma’s PHP

    This is obvious. Javascript ain’t PHP. PHP is a simple language that can be coerced into doing complex things if you understand basic algebra. Surprise! Everyone who considers themselves good at PHP? You’ve mastered the concepts of algebra! Alegrba is one of the easier ‘complex’ mathematic concepts to wrap your head areoud. You get “if a + b = c and a = 10 and c = 11 then b = 1” and you win!

    Javascript though, it’s a little more like calculus and trig, in that you have to understand the formulas a little deeper, and they have that thing where not just numbers and letters appear, but weird symbols.

    [nb: Like all analogies, this falls apart at scale, don’t read too much into it.]

    For the thousands of developers who whet their teeth on PHP, jumping into JS feels like you’re a first-year high schooler in senior maths! It’s scary, it’s complicated, and worst of all … it isn’t actually documented at the micro level, because it’s generally compiled.

    Micro vs Macro / Interpreted vs Compiled

    The macro scale is, more or less, the big picture of what your code is supposed to do. Micro would be each individual element. For PHP, you can clearly identify both the macro (the overall function) and the micro (each teeny process in that function). This is less so for JS because the languages are different.

    There are two primary types of code languages. PHP is what we call an interpreted language, because while the PHP binary is a compiled app, what you write is interpreted by the compiler. Basic JS (like jQuery) is also an interpreted language!

    Compiled languages need a “build” step – they need to be manually compiled first. And if that suddenly made you think “Wait, Gutenberg is JS but I have to build it!” then you have spotted the quirk! The JS we use in Gutenberg is actually JSX!

    JSX was designed for React (which is what we use to build in Gutenberg) and while it may contain some plain Javascript, it’s impossible to use the code without React. That’s why we have the build process, it takes the JSX, compiles it into JS, and saves it to a file.

    The Compilation Downfall

    This is where it gets messy … messier.

    When there’s an error in PHP, we get the error message either on the page or in our logs, depending on how we set up our environment. I personally pipe things to debug.log and just keep that file up as I bash on things. Those errors tend to be incredibly helpful!

    $mastodon not defined on /path/to/file.php:123

    In that example, I know “Ooops, I’m calling the variable $mastodon on line 123 of file.php and forgot to declare it!” Either I need an isset() check or (in this case) I brain farted and copied a line but forgot to rename the variable so I was setting $tumblr twice. Mea culpa, pop in, edit, save, done.

    On the other hand, I was testing out some blocks and modernizing them a little when suddenly … the block didn’t load. I got the WP notice of the block had an error. You’ve probably seen this if you’re a dev:

    Example of an error which says "This block has encountered an error and cannot be previewed"

    or this:

    An error: This block contains unexpected or invalid content.

    And if you’re like me, you used foul language and wondered ‘well… now what.’

    Enter the Console

    Unlike PHP, the errors don’t go to a nice debug.log file, it goes to your in-browser console. This is because, again, PHP is being directly interpreted on the server, and the server happily converts the PHP to HTML and Bob’s your uncle.

    JS (and JSX in this case) aren’t processed by the server. They’re processed on the fly in the browser. If you’ve ever wondered why too much JS, or bad JS, cause your browser to hang, that’s why. We moved the processing from the server (PHP) to the browser. On top of that, it’s also why JS content isn’t really cachable by traditional methods! But that’s another story.

    In this case, I got the first error (cannot be previewed) and being somewhat savvy with the world of Gutes, I popped open the console and saw this gem:

    wp.blockEditor.RichText value prop as children type is deprecated

    The rest of the message was warning me that the thingy would be removed in WP 6.3, and it had a link to ‘help’ resolve it. Spoilers? It didn’t. But take a deep breath. Let’s debug.

    Debugging Gutenberg

    The first issue was that the error came on a page with multiple blocks. I happened to be using a custom plugin I wrote that contains about 6 blocks, you see, so I opened a new page on localhost and added each block, one at a time, until I determined the issue was my incredibly simple spoiler block.

    How simple is this block? It’s basically a custom formatted paragraph, so everyone could use the same design without having to remember the exact colours. I could have made it a ‘reusable block’ on the site but, at the time, I wanted the practice.

    Next I went to that link, which was for “Introducing Attributes and Editable Fields“. I admit, I was a little confused, since I was already using attributes and editable fields! But I did the logical thing and searched that page for the word ‘children.’ My thought process was that if something was being deprecated, it would have a warning right?

    Gif from Blazing Saddles, where Dom DeLuise is a director and walks up to an actor who made a mistake. He uses his bullhorn to scream WRONG! at the man, and bops him in the head with the bullhorn.

    Okay, maybe I was looking in the wrong place. This error is specific to RichText so I clicked on the link to read the RichText Reference and again, looked for “children.” Nothing. Zip. Nada. I followed the link for the more in-depth details on GitHub and still nothing.

    At this point, I ranted on Mastodon because I was chapped off. I also popped open the Gutenberg Deprecations page, and looked for “children” but all I could find was a message to use children!

    RichText explicit element format removed. Please use the compatible children format instead.

    Logically there should be a note that “children is deprecated, please use…” but there is not.

    Now, here is where I accidentally stumbled on a fix, but after I made my fix is when I found the Github issue about this!

    If you are still using “children” or “node” sources in the block attribute definition, like so:

    content: {
     	type: 'array',
     	source: 'children',
     	selector: 'p',
     }
    

    Then change it to use the “html” source instead to get rid of the deprecation warning:

    content: {
     	type: 'string',
     	source: 'html',
     	selector: 'p',
     }
    

    And in fact, that was the correct fix.

    Here’s the Flaw

    None of that was properly documented.

    The link to ‘help’ fix the error didn’t mention the specific error, it talked about attributes at the MACRO level. I was (obviously) already using attributes, else I wouldn’t have had that error at all.

    There is no proper documentation that could help someone fix the issue on their own UNLESS they happened to be trawling through all the issues on GitHub.

    As I put it to my buddy, the reasons developers are salty about Gutenberg are:

    1. It changes pretty much every release
    2. There’s no real way to tell people if it impacts you so you have to check every release and read the console logs, which is not what devs are used to
    3. The JS console won’t tell you (I don’t know if it can) what file caused the warning, so finding it is a crap shoot
    4. The documentation is high level, which is not helpful when you get micro level errors

    Okay, can we fix it?

    At this point, if you’ve made a WordPress Block for Gutenberg, make sure you test every single release with the JS console open. If you don’t do this, you will have a rude awakening until things are made a little better.

    How can things be made better? It will have to begin with a culture shift. Traditionally WordPress has used a “release and iterate” model. With Gutenberg, we’ve become “move fast and break things,” but that is only sustainable if everything broken can be documented for a fix.

    That means I see only one way to correct this, and it’s to slow down Gutenberg enough that deprecations AND THEIR CORRECTIONS are properly documented, and the error messages link to a page about deprecations.

    We need to not link to the general “here’s how attributes work” page, but instead to a specific page that lists those deprecations along side the WordPress versions impacted.

    Another matter is we should be posting in the Field Guide about these things. Currently the 6.3 field guide links to all the various pages where you can find information, but that means you have to click and open each one and hopefully find your exact usage. In my case, those links to the ten Gutenberg versions being ported to core never mention the issue I had.

    If we don’t start slowing down and paving the road for developers, we will begin haemorrhaging the very people who make WordPress a success.

  • Blocks: Another Way to Adjust Settings

    Blocks: Another Way to Adjust Settings

    As I’ve mentioned a few times, one of the ways you can adjust settings on a block in Gutenberg is via the sidebar. In fact, this is the default way most people will interact with block settings.

    If you make a paragraph today, you can see it like this:

    This is the default paragraph sidebar from Gutenberg. No customizations.

    But. I don’t actually like it very much. I appreciate that I have it, and I love that I can get it out of the way. But sometimes I like settings to be a little more contextual.

    It’s Easy to Add Sidebar Settings

    One of the reasons we all use sidebar settings is that, well, they’re easy. When I built out my listicles plugin, I could use the inspector controls and automagically I have my own settings.

    This:

    <InspectorControls>
    	<PanelBody title={ 'Listicle Settings' }>
    		<RangeControl
    			label={ 'Items' }
    			value={ items }
    			onChange={ ( value ) => setAttributes( { items: value } ) }
    			min={ 1 }
    			max={ MAX_ITEMS }
    		/>
    		<ToggleControl
    			label={ 'Reversed' }
    			help={ ( checked ) => checked ? 'Reversed order (10 - 1)' : 'Numerical order (1 - 10)' }
    			checked={ props.attributes.reversed }
    			onChange={ () => props.setAttributes( { reversed: ! props.attributes.reversed } ) }
    		/>
    	</PanelBody>
    </InspectorControls>

    Looks like this:

    A screenshot of the listicle settings, which are a slider for the items and a toggle to reverse the order display.

    But like I said, I don’t like it very much. It’s clunky, it’s touchy, if you delete the number and type in a new one it’ll wipe all your data. And worst of all, my partner in crime, Tracy, hates it. If your work partners hate a tool, then it’s serious problem. I’ll put up with annoyances to me, but I’ll learn new code for the team.

    Think About What’s Easier

    Before I get into the code I used to solve the issue, I want to take a moment to talk about theory and understanding usage.

    One of the critiques about Gutenberg is that it’s changing too much too quickly, and it’s not listening to users. The problem with that complaint is it lacks context. I’m big on context because I believe that only with understanding the usage and context can we as a whole make the correct decisions going forward. What changes, when, and why depends entirely on what’s being used, for what, and why.

    It’s much more direct to understand this when I look at my little listicles block. You see, we use it for one thing: to make lists with a specific format. And we have few requirements.

    • Add and remove items
    • Add content of myriad types to each item
    • Be able to reverse the item count (1 to 10 or 10 to 1)

    That’s really it. Except now I’m adding one more thing:

    • An easier, inline, way to add/remove/toggle items.

    So I sat and I thought about what would be the easiest to use, and I came up with a simple solution. Three buttons, one to add an item, one to remove, and one to toggle the order. Have those show perpetually at the bottom of the list, and you could easily add and remove as needed.

    It’s Actually Easy To Add Buttons

    Once I knew what I wanted, I took a page from some work Yoast is doing and sketched my idea to look like this:

    Three buttons: Add item, remove item, and toggle order

    In order to do this, I needed to add some code to the bottom of my <dl> code:

    <div className='listicles-buttons'>
    	<IconButton
    		icon='insert'
    		onClick={ () => setAttributes( { items: parseInt(`${ items }`)+1 } ) }
    		className='editor-inserter__toggle'
    	>Add Item</IconButton>
    
    	<IconButton
    		icon='dismiss'
    		onClick={ () => setAttributes( { items: parseInt(`${ items }`)-1 } ) }
    		className='editor-inserter__toggle'
    	>Remove Item</IconButton>
    
    	<IconButton
    		icon='controls-repeat'
    		onClick={ () => setAttributes( { reversed: ! reversed } ) }
    		className='editor-inserter__toggle'
    	>Toggle Order</IconButton>
    </div>

    This sits inside the <dl> and just below my <InnerBlocks...> insert. It generates the buttons, which change when you hover by default.

    Now it’s not perfect. If you deleted all the items and pressed delete again, it would sure try to delete. I didn’t put in checks to make sure we didn’t go below 0 or above 18 (which is my current limits). But this is the start to make sure we can keep improving and iterating.

  • Shortcodes Aren’t Leaving Us

    Shortcodes Aren’t Leaving Us

    In a recent post, someone asked:

    We have this scenario a lot:

    Lorem ipsum some text [shortcode]affected text[/shortcode] more text, all within one parapgraph.

    How can this be solved with Gutenberg?

    Quick answer? It won’t.

    Just like [shortcode] that outputs a single content, or [shortcode text="affected text"], there will remain a need for singe, inline, in context, shortcode for a while.

    I know it’s confusing. This is because there are different kinds of shortcodes for different purposes. Some, yes, will no longer be needed as much.

    Shortcodes for Inline Content

    This is the easiest one to understand. Think about videos. Or my example of inline editable blocks. Those are a very clear one to one. If you’re inserting a shortcode (or an embed) on it’s own line today, that will be ‘replaced’ in time with a block.

    Embeds are already supported, by the way. If you create your own embeds, they too are automatically blocked.

    Shortcodes for Layout

    This is harder to understand, since it’s not fully ready yet. But if you’ve used a plugin or a theme that told you to use a lot of shortcodes to make a two column layout, guess what you’re not going to be doing anymore in the future? That’s right, using shortcodes. Instead, we’re creating listicles, tables, how-to directions and more.

    Example of Yoast SEO’s How To Block — coming soon. (Credit: Yoast.com)

    Their looks a lot better than my basic blocks, but it shows you what we can do and how great we can do it. Those simple and complex layouts will become blocks, where we will be able to actually see what it’s supposed to look like. This includes the Gallery shortcode we all know and love.

    Shortcodes for Text Insertion

    And finally we have the ‘classic’ block. This is the sort the poster was asking me about. Guess what? Nothing’s going to change with those any time soon. Oh, I’m sure eventually someone will take my inline-editable-block concept and make it so when we add the shortcode, it’ll adjust the text accordingly right before our eyes.

    And really, if we look at the concept of [shortcode]affected text[/shortcode] we realize how much more we can do already with Gutenberg. What are we affecting the text with? We can already make it bold. But can we make it blue? Not yet. But I can see this coming soon.

    But right now? Those inline shortcodes, in the middle of your block, are staying put.

  • Selective DeGutenberging

    Selective DeGutenberging

    Okay let’s be honest, friends. Not everything is ready for Gutenberg. In fact, I myself have certain sites that aren’t ready for it today. That’s why there exists an excellent plugin known as the Classic Editor. This allows you to decide it you want to block Gutenberg (which is the default option) or if you want to allow users to chose between Gutenberg and Classic editors.

    The settings page for the Classic editor.

    But what if you want to go a step further?

    What if you want to have Gutenberg on by default for posts and pages, but not for a custom post type?

    Don’t worry. We can do that.

    The Code

    The basic idea is that if WP is 5.0 or higher, make sure that the Classic Editor is installed. If it’s less than 5.0, make sure Gutenberg is installed. Then block the code as needed:

    class HELF_Gutenberg {
    
    	public $gutenfree = array();
    
    	public function __construct() {
    		$this->gutenfree = array( 'post_type_ONE', 'post_type_TWO', 'post_type_THREE' );
    		add_action( 'current_screen', array( $this, 'gutenberg_removal' ) );
    	}
    
    	public function gutenberg_removal() {
    
    		// WP 5.0+ requires Classic Editor
    		// WP 4.9- requires Gutenberg
    		if ( ( version_compare( get_bloginfo( 'version' ), 5.0, '<=' ) && ! is_plugin_active( 'gutenberg/gutenberg.php' ) ) || ( version_compare( get_bloginfo( 'version' ), 5.0, '=>' ) && ! is_plugin_active( 'classic-editor/classic-editor.php' ) ) ) {
    			return;
    		}
    
    		// Intercept Post Type
    		$current_screen    = get_current_screen();
    		$current_post_type = $current_screen->post_type;
    
    		// If this is one of our custom post types, we don't gutenize
    		if ( in_array( $current_post_type, $this->gutenfree, true ) ) {
    			remove_filter( 'replace_editor', 'gutenberg_init' );
    			remove_action( 'load-post.php', 'gutenberg_intercept_edit_post' );
    			remove_action( 'load-post-new.php', 'gutenberg_intercept_post_new' );
    			remove_action( 'admin_init', 'gutenberg_add_edit_link_filters' );
    			remove_filter( 'admin_url', 'gutenberg_modify_add_new_button_url' );
    			remove_action( 'admin_print_scripts-edit.php', 'gutenberg_replace_default_add_new_button' );
    			remove_action( 'admin_enqueue_scripts', 'gutenberg_editor_scripts_and_styles' );
    			remove_filter( 'screen_options_show_screen', '__return_false' );
    		}
    	}
    
    }
    
    new HELF_Gutenberg();
  • Shortcodes vs Blocks

    Shortcodes vs Blocks

    One of the things that Gutenberg changes for everyone is the concept of shortcodes not being absolutely necessary anymore. 

    Shortcodes Today

    One of the nice things about WordPress is that we can use shortcodes to insert dynamic content into our posts. For example, I could use [ copyright ] and have it echo © 2025. And when you have inline dynamism, that works great.

    One of the other cool things you can do is to have an embed like [ embed height="400" width="600"]...[/embed ] to embed a video:

    Yes, you could just paste in the video URL, but this is a demonstration of a shortcode as a sort of block. And speaking of blocks…

    Blocks Tomorrow

    In Gutenberg (which this post was written with), you can keep using in-line shortcodes and even add a shortcode as a block:

    An example of the Shortcode block.

    To a degree, that works well. But there are other options with Gutenberg.

    First, you can see this in a normal paragraph block. Adding options to the sidebar:

    An example of the sidebar editor, with options and the empty additional css class.

    This allows you to edit the various options on the right sidebar and, thus, change the customizable aspects of the block. For many shortcodes, you can keep that interface and have everything be perfectly functional. Have a block that shows user information? Put a dropdown for the users on the sidebar, and so on.

    Inline Edits

    The other options is an integrated block. For example, here’s my example of a review block, with text fields and dropdowns to allow you to edit in situ.

    An example of a block with dropdowns in the block itself.

    In my opinion, this is a more fluid interface, as you can see example what you’re doing and what it’s going to look like.

    How Will You Use The Future?

    There are still hurdles to overcome with Gutenberg, but as we step forward, we’re getting a better look at an integrated editor that gives you a lot of that desired ‘What you see is what you get’ experience.