In Part 1 of the Building a custom block series, we look at how you can quickly set up a new custom block from scratch using the create-block package provided by WordPress and the anatomy of a block.
This first part is going to be focussed on getting something loading in the block editor and explaining some key principles to set you up for success as you keep working with the block. There are a few things you’ll want to have in place for your project if you’re going to follow along, which I’ve outlined below.
Project Requirements
If you’re building custom blocks you’ll need to have a build process in place to bundle the scripts and stylesheets for your custom blocks. The best way to do this is to use either the @wordpress/scripts or 10up-toolkit package since they both leverage extraction of WordPress dependencies. Generally speaking, @wordpress/scripts is probably fine if you don’t have any specific opinions and just want something that works.
10up-toolkit might be a better fit if you prefer their style of configuration and their project defaults. It all comes down to personal preference and either one will handle this use case. If you aren’t sure, just use @wordpress/scripts.
This tutorial assumes that your build process has a one-time “build” task and a “watch” task like npm run start or similar.
Don’t have a build process set up yet? Check out this article first before you continue: How to set up @wordpress/scripts.
You should also have a local environment set up while you’re making changes to see things update in real time. Your project will be either a plugin or a theme that is loaded and active in this local environment.
Don’t know how to set one up? I made a free course that’ll show you exactly how to get up and running easily.
Scaffolding a block
In a programming context, the term scaffolding refers to the act of generating code or project files when you’re getting started. While you certainly could create the files for a block by hand, scaffolding your blocks is faster and can lead to less errors like forgetting to add a necessary file, misspellings, etc.
For this tutorial, we’re going to make a new block called Notice. This will allow us to display a dismissible notice-style element. We’ll start simple and evolve the features of the block with additional functionality including displaying global notices and things like that.
Thankfully WordPress makes it really easy to whip up a new block from scratch when you use the @wordpress/create-block package. You can open a terminal at the blocks folder for your project (ex: src/blocks) and then use the npx @wordpress/create-block command with a few simple options to spin up your block:
--no-plugin tells create-block to not make an entire project and just make a block folder. If you don’t specify this, it sets up an entire plugin just for the one block. While some people may like to do this, I do not advise it. If nothing else because you can easily include all of your blocks in a single plugin or the theme. This is especially convenient if they are all designed to be used together anyway.
--namespace Sets the namespace of the block. Every block has to have a namespace in addition to a block name. If you’re building a site for someone else, this namespace might be the name of the company, the theme name, or something else entirely. I’m using LWPD (Learn WP Dev) here, but you can swap in any kebab-case string you want to use instead. Just make sure you’re using the same namespace for any blocks you’re registering in the same project unless you have a specific reason not to.
The last argument is the name of the block, which should also be kebab-case like the namespace. Putting these values together gives you the full block name, such as core/button or lwpd/notice.
npx @wordpress/create-block --no-plugin --namespace=lwpd noticeBashOnce you run this command, a new notice folder will be created in your blocks folder that contains all of the block’s files. By default, the generated block will be a static block, which is what we want in our case.
Static vs Dynamic Blocks
By default, the create-block package creates static blocks. This means that the resulting HTML markup of the block is saved into the database and not generated with PHP on each request. All blocks on a given page will be saved to the post_content column in the wp_posts table. If you’re making a block that doesn’t need to dynamically render with PHP, this is absolutely what you want.
If a block is dynamic, that means it’s block markup is output using PHP and the outside data is needed to render the block. For instance, imagine you’re making a block that displayed a list of posts based on the post it’s on (ex: related posts). If changes are made to those related posts (a new title, URL update, new featured image, etc) those updates should be reflected in your block. If you just saved the title, URL, image, etc for each related post, you wouldn’t have the correct data if the post updates or if the posts themselves are added or removed.
Dynamic blocks are typically necessary when you need to get linked data. We want to get the current title, image, excerpt, etc. of a certain post. Which means we need to ask the post for that information and not just save it ourselves. So a dynamic block could run WP_Query or any other commands it needs to get the correct data for us, whatever that is.
If we wanted to create the block as dynamic, we could update the above command to include --variant=dynamic which would then create a dynamic block instead.
# Example dynamic block command
npx @wordpress/create-block --no-plugin --namespace=lwpd --variant=dynamic noticeBashIn our case, we’re building a block that displays a notice to users. We’re not fetching another post or other dynamic data. We just need to display the content we enter with the styles we choose. So a static block is just the ticket here.
Main Block Files
Now that we have our static block generated, let’s briefly go over the files generated by this command:
- block.json
The configuration file for our block. Attributes, block supports, and other options are set here. - edit.js
Exports the Edit component for our block. This determines how the block renders in the editor. - editor.scss
Block styles for the editor only. - index.js
The main “entry point” for the block. Contains the block registration code. - save.js
Exports the Save component for our block. The output of the Save component is what is saved to the database. - style.scss
Styles for the block. Loads on the frontend and backend. - view.js
JavaScript code to load on the frontend only.
The *.scss files and the view.js file aren’t always necessary for each block. For instance, if your block doesn’t have its own styles and is fully styled in the editor, you may not have anything to put in style.scss. Similarly, if you don’t have any editor specific styles needed, you don’t need to have an editor.scss file.
If you remove any files, just be sure to remove any references to those files, like in block.json, edit.js, index.js, etc. Similarly if you changed the names of any of these files you’d need to update those references accordingly.
However, I would advise you keep the names consistent. If you were to inspect the block.json file, you’ll see that there are a few script keys defined that reference the built versions of some of the above files:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "lwpd/notice",
"version": "0.1.0",
"title": "Notice",
"category": "widgets",
"icon": "smiley",
"description": "Example block scaffolded with Create Block tool.",
"example": {},
"supports": {
"html": false
},
"textdomain": "notice",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js"
}JSONThe keys follow a naming convention to help clarify what they do:
| Key Name | Output in Editor | Output in Frontend |
|---|---|---|
| editor* | ✅ | ❌ |
| view* | ❌ | ✅ |
| style | ✅ | ✅ |
This naming convention is why the generated files are named what they’re named. It keeps things simple and obvious so even someone else can look at the files and largely know what they’re for based on their name alone.
Dynamic Block Files
Dynamic blocks also have another file:
- render.php
Handles dynamic rendering of the block. Can be used with or instead of save.js.
The block.json file will also have a render key that points to this file. This key defines the template file used to render your block in a way that is much simpler than it used to be1.
If you’re building a dynamic block, most of the time you won’t have a save component and will just have a render.php file. However a block can actually use both PHP rendering and static rendering2. In practice though, you probably won’t need to use both very much and most of the time you’ll just have save.js or render.php.
Registering the block with WordPress
Having these files is great, but you need to tell WordPress about your block and it’s files so it will work in the editor. In our case, the generated code has already accounted for the client-side registration (by calling registerBlockType) and now we just need to do the same with PHP.
If you’re working within an existing project that has other blocks, this may already be taken care of. But if this is your first block in the project, you’ll need to add some PHP code to register your block(s) on the server.
I wrote an explanation of how you can easily register all the blocks in your theme or plugin at once. You can read it here if you’re not familiar with how that works already. I’ve included the relevant snippet below:
<?php
function register_plugin_blocks() {
// Get the directory containing all blocks in the build folder.
$blocks_dir = __DIR__ . '/build/blocks/';
// Check if the directory is valid.
if ( is_dir( $blocks_dir ) ) {
$block_json_files = glob( $blocks_dir . '*/block.json' );
foreach ( $block_json_files as $filename ) {
register_block_type( $filename );
}
}
}
add_action( 'init', 'register_plugin_blocks' );PHPThe $blocks_dir variable should point to wherever your built blocks are in your project. If you’re using the defaults from @wordpress/scripts and have your blocks in a src/blocks folder, you shouldn’t need to change anything.
This hook callback will automatically find all of our blocks and register them on the server. If we add a new one, it would also be registered automatically. Similarly, its files would be built and processed by webpack just like our notice block now. This all happens automatically because both webpack and the above PHP are just looking for block.json files.
Go back to your terminal, navigate to the top of your project folder and start watching for file changes:
npm run startBashFrom here, go to create a new page and type /notice. You should see the new block in the inserter prompt. Once selected, the default output should appear:

Next Steps
In Part 2, we will review how block attributes and supports work and define these for our new Notice block.
Further Reading
- If you’ve ever done block development in the earlier days, you may recall there’s “render_callback” function you can specify for dynamic blocks. This is effectively that, but just in a template file instead of a function and no hook required. Way less code, way faster result. ↩︎
- Check out this article from the Block Editor Handbook for more details. ↩︎