Skip to content

Creating a Custom Block

Build a new Styble block end to end — the JavaScript editor side and the PHP registration/render side.

A Styble block has two halves that share the same slug: a JavaScript folder under src/blocks/ (the editor experience) and a PHP folder under blocks/Types/ (registration and optional server-side rendering). This guide walks through both. If you just want to scaffold the files, jump to the skill.

See the Architecture overview first for the big picture.

The /add-new-block skill

The fastest path is the project's custom skill, which scaffolds both sides following project conventions:

text
/add-new-block blockName="my-custom-block" blockTitle="My Custom Block" blockDescription="A description" isDynamic=false
ParameterRequiredNotes
blockNameyeskebab-case slug, e.g. my-custom-block
blockTitleyesHuman-readable title
blockDescriptionnoBlock description
isDynamicnotrue for PHP-rendered blocks, false for static
parentBlocknoParent block slug for nested/child blocks
categorynoDefaults to styble

It creates the JS files (index.js, edit.jsx, save.jsx, inspect.js, dynamicCss.js, editor.scss, style.scss), the PHP files (<Block_Name>.php, attributes.php), and registers the block in src/blocks/index.js. The rest of this page explains what those files do so you can edit them confidently.

JavaScript side (src/blocks/<block-name>/)

index.js — register the block

Styble blocks register through a thin wrapper, registerStybleBlock (aliased as @styble-pro/registerStybleBlock), which calls registerBlockType() under the hood and applies shared defaults.

js
import { registerStybleBlock } from '@styble-pro/registerStybleBlock';
import { __ } from '@wordpress/i18n';
import Edit from './edit.jsx';
import save from './save.jsx';
import { MyBlockIcon } from '@styble-pro/blocksIcons';

const options = {
	apiVersion: 3,
	category: 'styble',
	name: 'styble/my-custom-block',
	title: __( 'My Custom Block', 'styble-pro' ),
	description: __( 'A description.', 'styble-pro' ),
	icon: <MyBlockIcon />,
	supports: {
		align: [ 'wide', 'full' ],
	},
	edit: ( props ) => <Edit { ...props } />,
	save,
};

registerStybleBlock( options.name, options );

edit.jsx — the editor UI

edit.jsx renders the block inside the editor and wires up inspector controls. Use useBlockProps() for the wrapper and pull in shared controls from @styble-pro/controls.

jsx
import { useBlockProps } from '@wordpress/block-editor';

export default function Edit( { attributes, setAttributes } ) {
	const blockProps = useBlockProps();
	return <div { ...blockProps }>{ /* editor markup + inspector */ }</div>;
}

For dynamic blocks, the editor typically previews data fetched through the useApiData hook or @wordpress/server-side-render.

save.jsx — the saved markup

  • Static block: return the markup to persist in post content.
  • Dynamic block: return null so WordPress calls the PHP render_block() instead.
jsx
import { useBlockProps } from '@wordpress/block-editor';

// Static block:
export default function save( { attributes } ) {
	const blockProps = useBlockProps.save();
	return <div { ...blockProps }>{ /* saved markup */ }</div>;
}

// Dynamic block:
// export default function save() { return null; }

Register in src/blocks/index.js

Every block folder is imported in src/blocks/index.js so it loads in the editor bundle. Add a line alongside the others:

js
import './my-custom-block';

Inspector controls

Add settings panels with the project's control components and the Inspector Control skill (add-inspector-control). Controls and constants live under src/controls/ (@styble-pro/controls, @styble-pro/constants).

PHP side (blocks/Types/<Block_Name>/)

The folder name is StudlyCase with underscores — My_Custom_Block for the slug my-custom-block. Blocks_Register scans this directory and instantiates the matching class.

The block class

Create blocks/Types/My_Custom_Block/My_Custom_Block.php extending AbstractBlock:

php
<?php
namespace ShapedPlugin\StyblePro\Types\My_Custom_Block;

use ShapedPlugin\StyblePro\Includes\AbstractBlock;

defined( 'ABSPATH' ) || exit;

class My_Custom_Block extends AbstractBlock {

	/**
	 * Set block-specific properties.
	 */
	protected function set_block_properties() {
		$this->block_name = 'my-custom-block';
		$this->is_dynamic = true; // false for static blocks
		// Optionally: $this->scripts, $this->styles, $this->keywords,
		//             $this->skip_common_attributes (true for child blocks).
	}

	/**
	 * Render the block (dynamic blocks only).
	 *
	 * @param array          $attributes Block attributes.
	 * @param string         $content    Inner content.
	 * @param \WP_Block|null $block      Block instance.
	 * @return string
	 */
	public function render_block( $attributes, $content = '', $block = null ) {
		return '<div>' . esc_html( $attributes['someText'] ?? '' ) . '</div>';
	}
}

How it works:

  1. AbstractBlock::__construct() calls set_block_properties(), then load_attributes(), then register_block().
  2. register_block() builds the args via get_args() and calls register_block_type( 'styble/my-custom-block', $args ). api_version is 3 and the category is styble.
  3. For dynamic blocks (is_dynamic = true), get_args() sets render_callback to render_block_callback(), which fires styble_pro_before_render / styble_pro_after_render actions around your render_block(). See Hooks.

Block name matters

block_name (kebab-case) must match the src/blocks/<block-name>/ folder and the styble/<block-name> name you registered in JS. The PHP folder Types/My_Custom_Block/ is its StudlyCase form.

attributes.php

Block-specific attributes live in blocks/Types/My_Custom_Block/attributes.php, returning an array. AbstractBlock::load_attributes() merges these on top of CommonAttributes::get() (unless skip_common_attributes is set). Full details in the Attributes guide.

php
<?php
defined( 'ABSPATH' ) || exit;

return array(
	'someText' => array(
		'type'    => 'string',
		'default' => '',
	),
);

Style class (blocks/Includes/Styles/)

For server-rendered or dynamically styled blocks, add a style class such as blocks/Includes/Styles/MyCustomBlock.php and wire it into DynamicCssGenerator. This produces the per-block CSS from attributes. See Dynamic CSS Generation.

Checklist

  • [ ] src/blocks/<block-name>/ with index.js, edit.jsx, save.jsx
  • [ ] Import added to src/blocks/index.js
  • [ ] blocks/Types/<Block_Name>/<Block_Name>.php extending AbstractBlock
  • [ ] set_block_properties() sets block_name (and is_dynamic)
  • [ ] render_block() implemented for dynamic blocks (and save.jsx returns null)
  • [ ] blocks/Types/<Block_Name>/attributes.php
  • [ ] Style class in blocks/Includes/Styles/ if the block needs dynamic CSS
  • [ ] npm start / npm run build and npx gulp run cleanly — see Build & Assets

Released under the GPL-2.0-or-later License.