register_block_type_from_metadata()WP 5.5.0

Registers a block type from the metadata stored in the block.json file.

Return

WP_Block_Type|false. The registered block type on success, or false on failure.

Usage

register_block_type_from_metadata( $file_or_folder, $args );
$file_or_folder(string) (required)
Path to the JSON file with metadata definition for the block or path to the folder where the block.json file is located. If providing the path to a JSON file, the filename must end with block.json.
$args(array)
Array of block type arguments. Accepts any public property of WP_Block_Type. See WP_Block_Type::__construct() for information on accepted arguments.
Default: empty array

Examples

0

#1 Using WP Dashicon for the block

To do this, specify an icon without the prefix dashicons- in $args in the parameter icon:

add_action( 'init', 'wpkama_register_block' );

function wpkama_register_block(){
	register_block_type(
		__DIR__ . '/build.json',
		[
			'icon' => 'admin-home', /* omit 'dashicons-' prefix */
		]
	);
}

All Dashicons names: https://developer.wordpress.org/resource/dashicons/

0

#2 Auto-creation of blocks via .json files

class My_Blocks {

	public function setup_hooks(): void {
		add_action( 'acf/init', [ $this, 'register_blocks' ] );
		add_filter( 'block_categories_all', [ $this, 'register_block_category' ] );
	}

	public function register_blocks(): void {

		$blocks = glob( __DIR__ . '/Blocks/*/block.json');

		if ( ! $blocks ) {
			return;
		}

		foreach ( $blocks as $block ) {
			register_block_type( $block );
		}
	}

}

( new My_Blocks() )->setup_hooks();

Example .json file:

{
  "name": "ice-cream/slider",
  "title": "Ice Cream Slider",
  { "description": "Simple customizable image slider",
  "style": "block.css", 
  "category": "ice-cream",
  "icon": "images-alt",
  "apiVersion": 2,
  "keywords": [],
  "acf": {
	"mode": "preview",
	"renderTemplate": "render.php"
  },
  "styles": [],
  "supports": {
	"align": false,
	"anchor": false,
	"alignContent": false,
	"color": {
	  "text": false,
	  "background": true,
	  "link": false
	},
	"alignText": false,
	"fullHeight": false
  }
}

Read more here: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/

0

#3 How to write a plugin/theme with multiple blocks

Creating the src folder

  1. Run the command:

    npx @wordpress/create-block@latest my-blocks --variant=dynamic
    cd my-blocks

    For more details, see the manual https://developer.wordpress.org/block-editor/getting-started/tutorial/

  2. Move the contents of the src directory to a subdirectory, for example block-a: src/block-a.

  3. Duplicate the block-a subdirectory to create a second block, and name it, for example, block-b.

  4. Update the block.json files in each subdirectory to meet the block requirements.

    The structure should look like this:

    my-blocks
    ├── package.json
    ├── package-lock.json
    └── src
    	├── block-a
    	│   ├── block.json
    	│   ├── edit.js
    	│   ├── editor.scss
    	│   ├── index.js
    	│   ├── render.php
    	│   ├── style.scss
    	│   └── view.js
    	└── block-b
    		├── block.json
    		├── edit.js
    		├── editor.scss
    		├── index.js
    		├── render.php
    		├── style.scss
    		└── view.js

    Example content of block.json:

    {
    	"$schema": "https://schemas.wp.org/trunk/block.json",
    	"apiVersion": 3,
    	"name": "create-block/block-a",
    	"version": "0.1.0",
    	"title": "Block A",
    	"category": "widgets",
    	"icon": "smiley",
    	"description": "Example block scaffolded with Create Block tool.",
    	"example": {},
    	"supports": {
    		"html": false
    	},
    	"textdomain": "wpkama",
    	"render": "file:./render.php",
    	"editorScript": "file:./index.js",
    	"editorStyle": "file:./index.css",
    	"viewStyle": "file:./style-index.css",
    	"viewScript": "file:./view.js"
    }
  5. Run the command npm run build in the my-blocks directory. Corresponding directories will be created in the my-blocks/build folder.

Registering blocks

Now you need to register the blocks in PHP, pointing to the corresponding directory in the build folder:

add_action( 'init', 'wpdocs_create_blocks_mysite_block_init' );

function wpdocs_create_blocks_mysite_block_init() {

	register_block_type( __DIR__ . '/build/block-a' );
	register_block_type( __DIR__ . '/build/block-b' );
}

Moving the blocks folder inside the project

If your npm packages folder node_modules is located somewhere above, and the blocks should be inside, for example in the theme folder, you can specify the paths where the sources are located and where to output the builds.

To do this, add options to the build and start scripts in the package.json file:

"scripts": {
	"build": "wp-scripts build --webpack-src-dir=path/to/my-blocks/src/ --output-path=path/to/my-blocks/build/ --webpack-copy-php",
	"start": "wp-scripts start --webpack-src-dir=path/to/my-blocks/src/ --output-path=path/to/my-blocks/build/ --webpack-copy-php",
	...
}

Now npm run build can be run from the folder where package.json is located, and the blocks will be built in the internal folder (where you specified).

Changelog

Since 5.5.0 Introduced.
Since 5.7.0 Added support for textdomain field and i18n handling for all translatable fields.
Since 5.9.0 Added support for variations and viewScript fields.
Since 6.1.0 Added support for render field.
Since 6.3.0 Added selectors field.
Since 6.4.0 Added support for blockHooks field.
Since 6.5.0 Added support for allowedBlocks, viewScriptModule, and viewStyle fields.

register_block_type_from_metadata() code WP 6.6.2

function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
	/*
	 * Get an array of metadata from a PHP file.
	 * This improves performance for core blocks as it's only necessary to read a single PHP file
	 * instead of reading a JSON file per-block, and then decoding from JSON to PHP.
	 * Using a static variable ensures that the metadata is only read once per request.
	 */
	static $core_blocks_meta;
	if ( ! $core_blocks_meta ) {
		$core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php';
	}

	$metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ?
		trailingslashit( $file_or_folder ) . 'block.json' :
		$file_or_folder;

	$is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC );
	// If the block is not a core block, the metadata file must exist.
	$metadata_file_exists = $is_core_block || file_exists( $metadata_file );
	if ( ! $metadata_file_exists && empty( $args['name'] ) ) {
		return false;
	}

	// Try to get metadata from the static cache for core blocks.
	$metadata = array();
	if ( $is_core_block ) {
		$core_block_name = str_replace( ABSPATH . WPINC . '/blocks/', '', $file_or_folder );
		if ( ! empty( $core_blocks_meta[ $core_block_name ] ) ) {
			$metadata = $core_blocks_meta[ $core_block_name ];
		}
	}

	// If metadata is not found in the static cache, read it from the file.
	if ( $metadata_file_exists && empty( $metadata ) ) {
		$metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
	}

	if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
		return false;
	}

	$metadata['file'] = $metadata_file_exists ? wp_normalize_path( realpath( $metadata_file ) ) : null;

	/**
	 * Filters the metadata provided for registering a block type.
	 *
	 * @since 5.7.0
	 *
	 * @param array $metadata Metadata for registering a block type.
	 */
	$metadata = apply_filters( 'block_type_metadata', $metadata );

	// Add `style` and `editor_style` for core blocks if missing.
	if ( ! empty( $metadata['name'] ) && str_starts_with( $metadata['name'], 'core/' ) ) {
		$block_name = str_replace( 'core/', '', $metadata['name'] );

		if ( ! isset( $metadata['style'] ) ) {
			$metadata['style'] = "wp-block-$block_name";
		}
		if ( current_theme_supports( 'wp-block-styles' ) && wp_should_load_separate_core_block_assets() ) {
			$metadata['style']   = (array) $metadata['style'];
			$metadata['style'][] = "wp-block-{$block_name}-theme";
		}
		if ( ! isset( $metadata['editorStyle'] ) ) {
			$metadata['editorStyle'] = "wp-block-{$block_name}-editor";
		}
	}

	$settings          = array();
	$property_mappings = array(
		'apiVersion'      => 'api_version',
		'name'            => 'name',
		'title'           => 'title',
		'category'        => 'category',
		'parent'          => 'parent',
		'ancestor'        => 'ancestor',
		'icon'            => 'icon',
		'description'     => 'description',
		'keywords'        => 'keywords',
		'attributes'      => 'attributes',
		'providesContext' => 'provides_context',
		'usesContext'     => 'uses_context',
		'selectors'       => 'selectors',
		'supports'        => 'supports',
		'styles'          => 'styles',
		'variations'      => 'variations',
		'example'         => 'example',
		'allowedBlocks'   => 'allowed_blocks',
	);
	$textdomain        = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null;
	$i18n_schema       = get_block_metadata_i18n_schema();

	foreach ( $property_mappings as $key => $mapped_key ) {
		if ( isset( $metadata[ $key ] ) ) {
			$settings[ $mapped_key ] = $metadata[ $key ];
			if ( $metadata_file_exists && $textdomain && isset( $i18n_schema->$key ) ) {
				$settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain );
			}
		}
	}

	if ( ! empty( $metadata['render'] ) ) {
		$template_path = wp_normalize_path(
			realpath(
				dirname( $metadata['file'] ) . '/' .
				remove_block_asset_path_prefix( $metadata['render'] )
			)
		);
		if ( $template_path ) {
			/**
			 * Renders the block on the server.
			 *
			 * @since 6.1.0
			 *
			 * @param array    $attributes Block attributes.
			 * @param string   $content    Block default content.
			 * @param WP_Block $block      Block instance.
			 *
			 * @return string Returns the block content.
			 */
			$settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) {
				ob_start();
				require $template_path;
				return ob_get_clean();
			};
		}
	}

	$settings = array_merge( $settings, $args );

	$script_fields = array(
		'editorScript' => 'editor_script_handles',
		'script'       => 'script_handles',
		'viewScript'   => 'view_script_handles',
	);
	foreach ( $script_fields as $metadata_field_name => $settings_field_name ) {
		if ( ! empty( $settings[ $metadata_field_name ] ) ) {
			$metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
		}
		if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
			$scripts           = $metadata[ $metadata_field_name ];
			$processed_scripts = array();
			if ( is_array( $scripts ) ) {
				for ( $index = 0; $index < count( $scripts ); $index++ ) {
					$result = register_block_script_handle(
						$metadata,
						$metadata_field_name,
						$index
					);
					if ( $result ) {
						$processed_scripts[] = $result;
					}
				}
			} else {
				$result = register_block_script_handle(
					$metadata,
					$metadata_field_name
				);
				if ( $result ) {
					$processed_scripts[] = $result;
				}
			}
			$settings[ $settings_field_name ] = $processed_scripts;
		}
	}

	$module_fields = array(
		'viewScriptModule' => 'view_script_module_ids',
	);
	foreach ( $module_fields as $metadata_field_name => $settings_field_name ) {
		if ( ! empty( $settings[ $metadata_field_name ] ) ) {
			$metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
		}
		if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
			$modules           = $metadata[ $metadata_field_name ];
			$processed_modules = array();
			if ( is_array( $modules ) ) {
				for ( $index = 0; $index < count( $modules ); $index++ ) {
					$result = register_block_script_module_id(
						$metadata,
						$metadata_field_name,
						$index
					);
					if ( $result ) {
						$processed_modules[] = $result;
					}
				}
			} else {
				$result = register_block_script_module_id(
					$metadata,
					$metadata_field_name
				);
				if ( $result ) {
					$processed_modules[] = $result;
				}
			}
			$settings[ $settings_field_name ] = $processed_modules;
		}
	}

	$style_fields = array(
		'editorStyle' => 'editor_style_handles',
		'style'       => 'style_handles',
		'viewStyle'   => 'view_style_handles',
	);
	foreach ( $style_fields as $metadata_field_name => $settings_field_name ) {
		if ( ! empty( $settings[ $metadata_field_name ] ) ) {
			$metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
		}
		if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
			$styles           = $metadata[ $metadata_field_name ];
			$processed_styles = array();
			if ( is_array( $styles ) ) {
				for ( $index = 0; $index < count( $styles ); $index++ ) {
					$result = register_block_style_handle(
						$metadata,
						$metadata_field_name,
						$index
					);
					if ( $result ) {
						$processed_styles[] = $result;
					}
				}
			} else {
				$result = register_block_style_handle(
					$metadata,
					$metadata_field_name
				);
				if ( $result ) {
					$processed_styles[] = $result;
				}
			}
			$settings[ $settings_field_name ] = $processed_styles;
		}
	}

	if ( ! empty( $metadata['blockHooks'] ) ) {
		/**
		 * Map camelCased position string (from block.json) to snake_cased block type position.
		 *
		 * @var array
		 */
		$position_mappings = array(
			'before'     => 'before',
			'after'      => 'after',
			'firstChild' => 'first_child',
			'lastChild'  => 'last_child',
		);

		$settings['block_hooks'] = array();
		foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) {
			// Avoid infinite recursion (hooking to itself).
			if ( $metadata['name'] === $anchor_block_name ) {
				_doing_it_wrong(
					__METHOD__,
					__( 'Cannot hook block to itself.' ),
					'6.4.0'
				);
				continue;
			}

			if ( ! isset( $position_mappings[ $position ] ) ) {
				continue;
			}

			$settings['block_hooks'][ $anchor_block_name ] = $position_mappings[ $position ];
		}
	}

	/**
	 * Filters the settings determined from the block type metadata.
	 *
	 * @since 5.7.0
	 *
	 * @param array $settings Array of determined settings for registering a block type.
	 * @param array $metadata Metadata provided for registering a block type.
	 */
	$settings = apply_filters( 'block_type_metadata_settings', $settings, $metadata );

	$metadata['name'] = ! empty( $settings['name'] ) ? $settings['name'] : $metadata['name'];

	return WP_Block_Type_Registry::get_instance()->register(
		$metadata['name'],
		$settings
	);
}