29
loading...
This website collects cookies to deliver better user experience
wp-content
directory, create a new directory called mu-plugins. If mu-plugins
already exists, then skip this step.composer.json
file inside the mu-plugins
directory. Most of this is the default settings for a composer.json
file, the key difference is that extra.installer-paths
is tweaked to force wordpress-muplugins
to simply be installed directly in the vendor
directory. This is necessary because Underpin is considered a mu-plugin
by Composer, and will install in an improper directory, otherwise.{
"name": "wpda/muplugin",
"type": "plugin",
"require": {},
"extra":{
"installer-paths": {
"vendor/{$vendor}/{$name}": ["type:wordpress-muplugin"]
}
}
}
mu-plugins
directory. It can named be whatever you want it to be. WordPress will automatically include this file, and run it on every page load. This happens really early in WordPress’s runtime so there are some limitations to this, but for our needs it’s perfect.<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Load Underpin, and its dependencies.
$autoload = plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';
require_once( $autoload );
mu-plugins
directory, and then run this command:composer require underpin/underpin
wp-content/plugins
and clone the boilerplate. Then you’ll need to-do a few find/replaces in the boilerplate.plugin-name-replace-me
with custom-blocks
(it can be whatever you want, just make sure spaces use dashes, and it’s all lowercase)Plugin Name Replace Me
with Custom Blocks
(Again, whatever you want just has to use spaces and Title Case)plugin_name_replace_me
with custom_blocks
(Same thing applies here, but you should use snake_case)Plugin_Name_Replace_Me
with Custom_Blocks
(using Upper_Snake_Case)bootstrap.php
file should look something like this:<?php
/*
Plugin Name: Custom Blocks
Description: Plugin Description Replace Me
Version: 1.0.0
Author: An awesome developer
Text Domain: custom_blocks
Domain Path: /languages
Requires at least: 5.1
Requires PHP: 7.0
Author URI: https://www.designframesolutions.com
*/
use Underpin\Abstracts\Underpin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Fetches the instance of the plugin.
* This function makes it possible to access everything else in this plugin.
* It will automatically initiate the plugin, if necessary.
* It also handles autoloading for any class in the plugin.
*
* @since 1.0.0
*
* @return \Underpin\Factories\Underpin_Instance The bootstrap for this plugin.
*/
function custom_blocks() {
return Underpin::make_class( [
'root_namespace' => 'Custom_Blocks',
'text_domain' => 'custom_blocks',
'minimum_php_version' => '7.0',
'minimum_wp_version' => '5.1',
'version' => '1.0.0',
] )->get( __FILE__ );
}
// Lock and load.
custom_blocks();
mu-plugins
directory and run this command:composer require underpin/block-loader
custom_blocks
like so:// Registers block
custom_blocks()->blocks()->add( 'my_custom_block', [
'name' => 'My Custom Block', // Names your block. Used for debugging.
'description' => 'A custom block specific to this site.', // Describes your block. Used for debugging
'type' => 'custom-blocks/hello-world', // See register_block_type
'args' => [], // See register_block_type
] );
custom_blocks()
actually retrieves this plugin’s instance of Underpinblocks()
Retrieves the loader registry for this instance of Underpinadd()
actually adds this block to the registryregister_block_type
using the provided args
and type
.bootstrap.php
will look like this:<?php
/*
Plugin Name: Custom Blocks
Description: Plugin Description Replace Me
Version: 1.0.0
Author: An awesome developer
Text Domain: custom_blocks
Domain Path: /languages
Requires at least: 5.1
Requires PHP: 7.0
Author URI: https://www.designframesolutions.com
*/
use Underpin\Abstracts\Underpin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Fetches the instance of the plugin.
* This function makes it possible to access everything else in this plugin.
* It will automatically initiate the plugin, if necessary.
* It also handles autoloading for any class in the plugin.
*
* @since 1.0.0
*
* @return \Underpin\Factories\Underpin_Instance The bootstrap for this plugin.
*/
function custom_blocks() {
return Underpin::make_class( [
'root_namespace' => 'Custom_Blocks',
'text_domain' => 'custom_blocks',
'minimum_php_version' => '7.0',
'minimum_wp_version' => '5.1',
'version' => '1.0.0',
] )->get( __FILE__ );
}
// Lock and load.
custom_blocks();
// Registers block
custom_blocks()->blocks()->add( 'my_custom_block', [
'name' => 'My Custom Block', // Names your block. Used for debugging.
'description' => 'A custom block specific to this site.', // Describes your block. Used for debugging
'type' => 'custom-blocks/hello-world', // See register_block_type
'args' => [], // See register_block_type
] );
mu-plugins
directory and run this command:composer require underpin/script-loader
custom_blocks()->scripts()->add( 'custom_blocks', [
'handle' => 'custom-blocks', // Script Handle used in wp_*_script
'src' => custom_blocks()->js_url() . 'custom-blocks.js', // Src used in wp_register_script
'name' => 'Custom Blocks Script', // Names your script. Used for debugging.
'description' => 'Script that loads in the custom blocks', // Describes your script. Used for debugging.
] );
custom_blocks()
actually retrieves this plugin’s instance of Underpinscripts()
Retrieves the loader registry for this instance of Underpinadd()
actually adds this script to the registrycustom_blocks()->js_url()
is a helper function that automatically gets the javascript url for this plugin. This is configured in the custom_blocks
function directly, and defaults to build
wp_register_script
using the arguments passed into the registry.custom_blocks()->scripts()->add( 'custom_blocks', [
'handle' => 'custom-blocks', // Script Handle used in wp_*_script
'src' => custom_blocks()->js_url() . 'custom-blocks.js', // Src used in wp_register_script
'name' => 'Custom Blocks Script', // Names your script. Used for debugging.
'description' => 'Script that loads in the custom blocks', // Describes your script. Used for debugging.
'middlewares' => [
'Underpin_Scripts\Factories\Enqueue_Admin_Script', // Enqueues the script in the admin area
],
] );
bootstrap.php
file should now look something like this:<?php
/*
Plugin Name: Custom Blocks
Description: Plugin Description Replace Me
Version: 1.0.0
Author: An awesome developer
Text Domain: custom_blocks
Domain Path: /languages
Requires at least: 5.1
Requires PHP: 7.0
Author URI: https://www.designframesolutions.com
*/
use Underpin\Abstracts\Underpin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Fetches the instance of the plugin.
* This function makes it possible to access everything else in this plugin.
* It will automatically initiate the plugin, if necessary.
* It also handles autoloading for any class in the plugin.
*
* @since 1.0.0
*
* @return \Underpin\Factories\Underpin_Instance The bootstrap for this plugin.
*/
function custom_blocks() {
return Underpin::make_class( [
'root_namespace' => 'Custom_Blocks',
'text_domain' => 'custom_blocks',
'minimum_php_version' => '7.0',
'minimum_wp_version' => '5.1',
'version' => '1.0.0',
] )->get( __FILE__ );
}
// Lock and load.
custom_blocks();
// Registers block
custom_blocks()->blocks()->add( 'my_custom_block', [
'name' => 'My Custom Block', // Names your block. Used for debugging.
'description' => 'A custom block specific to this site.', // Describes your block. Used for debugging
'type' => 'underpin/custom-block', // See register_block_type
'args' => [], // See register_block_type
] );
custom_blocks()->scripts()->add( 'custom_blocks', [
'handle' => 'custom-blocks', // Script Handle used in wp_*_script
'src' => custom_blocks()->js_url() . 'custom-blocks.js', // Src used in wp_register_script
'name' => 'Custom Blocks Script', // Names your script. Used for debugging.
'description' => 'Script that loads in the custom blocks', // Describes your script. Used for debugging.
'middlewares' => [
'Underpin_Scripts\Factories\Enqueue_Admin_Script', // Enqueues the script in the admin area
],
] );
webpack.config.js
to create a new entry file. It should look like this:/**
* WordPress Dependencies
*/
const defaultConfig = require( '@wordpress/scripts/config/webpack.config.js' );
/**
* External Dependencies
*/
const path = require( 'path' );
module.exports = {
...defaultConfig,
...{
entry: {
"custom-blocks": path.resolve( process.cwd(), 'src', 'custom-blocks.js' ) // Create "custom-blocks.js" file in "build" directory
}
}
}
src
directory, and compile it into build/custom-blocks.js
. From here, we need to create a new file in the src
directory called custom-blocks.js
.// Imports the function to register a block type.
import { registerBlockType } from '@wordpress/blocks';
// Imports the translation function
import { __ } from '@wordpress/i18n';
// Registers our block type to the Gutenberg editor.
registerBlockType( 'custom-blocks/hello-world', {
title: __( "Hello World!", 'beer' ),
description: __( "Display Hello World text on the site", 'beer' ),
edit(){
return (
<h1 className="hello-world">Hello World!</h1>
)
},
save() {
return (
<h1 className="hello-world">Hello World!</h1>
)
}
} );
registerBlockType
so we can use it in this file__
so we can make translate-able stringsregisterBlockType
to register our “Hello World” block to the editor.npm install
and npm run start
. This will create two files in your build
directory:@wordpress/blocks
or @wordpress/i18n
. That’s not a mistake. Since these are internal WordPress scripts, we need to tell WordPress to enqueue those scripts before our script. Fortunately, WordPress and Underpin make this pretty easy to-do.bootstrap.php
, update your script’s add
function to include a deps
argument. Since this argument is a path, it will automatically require the file, and use it to tell WordPress which scripts need enqueued. Since Webpack automatically generates this file for us, we no-longer need to worry about adding dependencies every time we want to use a WordPress library.custom_blocks()->scripts()->add( 'custom_blocks', [
'handle' => 'custom-blocks', // Script Handle used in wp_*_script
'src' => custom_blocks()->js_url() . 'custom-blocks.js', // Src used in wp_register_script
'name' => 'Custom Blocks Script', // Names your script. Used for debugging.
'description' => 'Script that loads in the custom blocks', // Describes your script. Used for debugging.
'deps' => custom_blocks()->dir() . 'build/custom-blocks.asset.php', // Load these scripts first.
'middlewares' => [
'Underpin_Scripts\Factories\Enqueue_Admin_Script', // Enqueues the script in the admin area
],
] );
registerBlockType
call, and registering the block though Underpin using custom_blocks()->blocks()->add
.mu-plugins
directory, run:composer require underpin/style-loader
bootstrap.php
file:custom_blocks()->styles()->add( 'custom_block_styles', [
'handle' => 'custom-blocks', // handle used in wp_register_style
'src' => custom_blocks()->css_url() . 'custom-block-styles.css', // src used in wp_register_style
'name' => 'Custom Blocks Style', // Names your style. Used for debugging
'description' => 'Styles for custom Gutenberg blocks', // Describes your style. Used for debugging
] );
webpack.config.js
to include custom-block-styles.css
, like so:/**
* WordPress Dependencies
*/
const defaultConfig = require( '@wordpress/scripts/config/webpack.config.js' );
/**
* External Dependencies
*/
const path = require( 'path' );
module.exports = {
...defaultConfig,
...{
entry: {
"custom-blocks": path.resolve( process.cwd(), 'src', 'custom-blocks.js' ), // Create "custom-blocks.js" file in "build" directory
"custom-block-styles": path.resolve( process.cwd(), 'src', 'custom-block-styles.css' )
}
}
}
// Registers block
custom_blocks()->blocks()->add( 'my_custom_block', [
'name' => 'My Custom Block', // Names your block. Used for debugging.
'description' => 'A custom block specific to this site.', // Describes your block. Used for debugging
'type' => 'custom-blocks/hello-world', // See register_block_type
'args' => [ // See register_block_type
'style' => 'custom-blocks', // Stylesheet handle to use in the block
],
] );
.hello-world {
background:rebeccapurple;
}
save
returns null
. This instructs the editor to simply not save HTML, and just put a placeholder there instead.// Registers our block type to the Gutenberg editor.
registerBlockType( 'custom-blocks/hello-world', {
title: __( "Hello World!", 'beer' ),
description: __( "Display Hello World text on the site", 'beer' ),
edit(){
return (
<h1 className="hello-world">Hello World!</h1>
)
},
save: () => null
} );
render_callback
in your registered block arguments, it will use the callback instead of what was originally in the save
callback.// Registers block
custom_blocks()->blocks()->add( 'my_custom_block', [
'name' => 'My Custom Block', // Names your block. Used for debugging.
'description' => 'A custom block specific to this site.', // Describes your block. Used for debugging
'type' => 'custom-blocks/hello-world', // See register_block_type
'args' => [ // See register_block_type
'style' => 'custom-blocks', // Stylesheet handle to use in the block
'render_callback' => function(){
return '<h1 class="hello-world">Hey, this is a custom callback!</h1>';
}
],
] );
edit
method returns, however, if you save and look at the actual post, you’ll find that the actual post will show “Hey, this is a custom callback” instead. This is because it’s using PHP to render the output on the fly. Now, if you change the content of the render_callback
, it will automatically render this output.add
method in WordPress, it automatically creates an instance of a class, and it uses the array of arguments to construct our class for us. However, now, we need to actually make the class ourselves so we can apply the Template trait to the class, and render our template. So, next up we’re going to take our registered block, and move it into it’s own PHP class, and then instruct Underpin to use that class directly instead of making it for us.lib
inside your plugin directory, and then inside lib
create another directory called blocks
. Inside that, create a new PHP file called Hello_World.php
. Underpin comes with an autoloader, so the naming convention matters here.├── lib
│ └── blocks
│ └── Hello_World.php
Hello_World
that extends Block
, then move all of your array arguments used in your add
method as parameters inside the class, like so:<?php
namespace Custom_Blocks\Blocks;
use Underpin_Blocks\Abstracts\Block;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Hello_World extends Block {
public $name = 'My Custom Block'; // Names your block. Used for debugging.
public $description = 'A custom block specific to this site.'; // Describes your block. Used for debugging
public $type = 'custom-blocks/hello-world'; // See register_block_type
public function __construct() {
$this->args = [ // See register_block_type
'style' => 'custom-blocks', // Stylesheet handle to use in the block
'render_callback' => function(){
return '<h1 class="hello-world">Hey, this is a custom callback!</h1>';
}
];
parent::__construct();
}
}
add
callback with a string that references the class you just created, like so:// Registers block
custom_blocks()->blocks()->add( 'my_custom_block', 'Custom_Blocks\Blocks\Hello_World' );
use \Underpin\Traits\Templates
to the top of your PHP class, and add the required methods to the trait as well, like so:<?php
namespace Custom_Blocks\Blocks;
use Underpin_Blocks\Abstracts\Block;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Hello_World extends Block {
use \Underpin\Traits\Templates;
public $name = 'My Custom Block'; // Names your block. Used for debugging.
public $description = 'A custom block specific to this site.'; // Describes your block. Used for debugging
public $type = 'custom-blocks/hello-world'; // See register_block_type
public function __construct() {
$this->args = [ // See register_block_type
'style' => 'custom-blocks', // Stylesheet handle to use in the block
'render_callback' => function(){
return '<h1 class="hello-world">Hey, this is a custom callback!</h1>';
}
];
parent::__construct();
}
public function get_templates() {
// TODO: Implement get_templates() method.
}
protected function get_template_group() {
// TODO: Implement get_template_group() method.
}
protected function get_template_root_path() {
// TODO: Implement get_template_root_path() method.
}
}
get_templates
should return an array of template file names with an array declaring if that template can be manipulated by a theme, or not, like so:public function get_templates() {
return ['wrapper' => [ 'override_visibility' => 'public' ]];
}
get_template_group
should return a string, that indicates what the template sub directory should be called. In our case, we’re going to make it hello-world
.protected function get_template_group() {
return 'hello-world';
}
get_template_root_path
should simply return custom_blocks()->template_dir()
, as we don’t need to use a custom template directory or anything.protected function get_template_root_path() {
return custom_blocks()->template_dir();
}
protected function get_override_dir() {
return 'custom-blocks/';
}
templates/hello-world
called wrapper.php
. Inside your theme, this template can be completely overridden by adding a file in custom-blocks/hello-world
called wrapper.php
. Let’s start by adding our template in the plugin file.<?php
if ( ! isset( $template ) || ! $template instanceof \Custom_Blocks\Blocks\Hello_World ) {
return;
}
?>
$template
and assigns it to the class that renders the actual template. So inside your template file $template
will always be the instance of your registered block. This allows you to create custom methods inside the block for rendering purposes if you want, but it also gives you access to rendering other sub-templates using $template->get_template()
, plus a lot of other handy things that come with the Template
trait. As you can see above, this also provides you with a handy way to validate that the required file is legitimate.<?php
if ( ! isset( $template ) || ! $template instanceof \Custom_Blocks\Blocks\Hello_World ) {
return;
}
?>
<h1 class="hello-world">Hey, this is a custom callback, and it is inside my template!</h1>
Hello_World
class, and update the render callback to use your template. This is done using get_template
, like so:public function __construct() {
$this->args = [ // See register_block_type
'style' => 'custom-blocks', // Stylesheet handle to use in the block
'render_callback' => function () {
return $this->get_template( 'wrapper' );
},
];
parent::__construct();
}
render_callback
to use get_template
, which will then retrieve the template file you created. If you look at your template’s output, you’ll notice that your h1 tag changed to read “Hey, this is a custom callback, and it is inside my template!”.custom-blocks/hello-world
called wrapper.php
. Copy the contents of your original wrapper.php
file, and paste them in. Finally, change the output a little bit. When you do this, the template will automatically be overridden by your theme.registerBlockType
. If necessary, you can create a block class for each block, and use the template system to render the content.