* Functions related to registering and parsing blocks.
* Removes the block asset's path prefix if provided.
* @param string $asset_handle_or_path Asset handle or prefixed path.
* @return string Path without the prefix or the original value.
function remove_block_asset_path_prefix( $asset_handle_or_path ) {
if ( ! str_starts_with( $asset_handle_or_path, $path_prefix ) ) {
return $asset_handle_or_path;
if ( str_starts_with( $path, './' ) ) {
$path = substr( $path, 2 );
* Generates the name for an asset based on the name of the block
* and the field name provided.
* @since 6.1.0 Added `$index` parameter.
* @since 6.5.0 Added support for `viewScriptModule` field.
* @param string $block_name Name of the block.
* @param string $field_name Name of the metadata field.
* @param int $index Optional. Index of the asset when multiple items passed.
* @return string Generated asset name for the block's field.
function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) {
if ( str_starts_with( $block_name, 'core/' ) ) {
$asset_handle = str_replace( 'core/', 'wp-block-', $block_name );
if ( str_starts_with( $field_name, 'editor' ) ) {
$asset_handle .= '-editor';
if ( str_starts_with( $field_name, 'view' ) ) {
$asset_handle .= '-view';
if ( str_ends_with( strtolower( $field_name ), 'scriptmodule' ) ) {
$asset_handle .= '-script-module';
$asset_handle .= '-' . ( $index + 1 );
'editorScript' => 'editor-script',
'editorStyle' => 'editor-style',
'viewScript' => 'view-script',
'viewScriptModule' => 'view-script-module',
'viewStyle' => 'view-style',
$asset_handle = str_replace( '/', '-', $block_name ) .
'-' . $field_mappings[ $field_name ];
$asset_handle .= '-' . ( $index + 1 );
* Gets the URL to a block asset.
* @param string $path A normalized path to a block asset.
* @return string|false The URL to the block asset or false on failure.
function get_block_asset_url( $path ) {
// Path needs to be normalized to work in Windows env.
static $wpinc_path_norm = '';
if ( ! $wpinc_path_norm ) {
$wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) );
if ( str_starts_with( $path, $wpinc_path_norm ) ) {
return includes_url( str_replace( $wpinc_path_norm, '', $path ) );
static $template_paths_norm = array();
$template = get_template();
if ( ! isset( $template_paths_norm[ $template ] ) ) {
$template_paths_norm[ $template ] = wp_normalize_path( realpath( get_template_directory() ) );
if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $template ] ) ) ) {
return get_theme_file_uri( str_replace( $template_paths_norm[ $template ], '', $path ) );
if ( is_child_theme() ) {
$stylesheet = get_stylesheet();
if ( ! isset( $template_paths_norm[ $stylesheet ] ) ) {
$template_paths_norm[ $stylesheet ] = wp_normalize_path( realpath( get_stylesheet_directory() ) );
if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $stylesheet ] ) ) ) {
return get_theme_file_uri( str_replace( $template_paths_norm[ $stylesheet ], '', $path ) );
return plugins_url( basename( $path ), $path );
* Finds a script module ID for the selected block metadata field. It detects
* when a path to file was provided and optionally finds a corresponding asset
* file with details necessary to register the script module under with an
* automatically generated module ID. It returns unprocessed script module
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
* @param int $index Optional. Index of the script module ID to register when multiple
* items passed. Default 0.
* @return string|false Script module ID or false on failure.
function register_block_script_module_id( $metadata, $field_name, $index = 0 ) {
if ( empty( $metadata[ $field_name ] ) ) {
$module_id = $metadata[ $field_name ];
if ( is_array( $module_id ) ) {
if ( empty( $module_id[ $index ] ) ) {
$module_id = $module_id[ $index ];
$module_path = remove_block_asset_path_prefix( $module_id );
if ( $module_id === $module_path ) {
$path = dirname( $metadata['file'] );
$module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) );
$module_id = generate_block_asset_handle( $metadata['name'], $field_name, $index );
$module_asset_path = wp_normalize_path(
realpath( $module_asset_raw_path )
$module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) );
$module_uri = get_block_asset_url( $module_path_norm );
$module_asset = ! empty( $module_asset_path ) ? require $module_asset_path : array();
$module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array();
$block_version = isset( $metadata['version'] ) ? $metadata['version'] : false;
$module_version = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version;
$supports_interactivity_true = isset( $metadata['supports']['interactivity'] ) && true === $metadata['supports']['interactivity'];
$is_interactive = $supports_interactivity_true || ( isset( $metadata['supports']['interactivity']['interactive'] ) && true === $metadata['supports']['interactivity']['interactive'] );
$supports_client_navigation = $supports_interactivity_true || ( isset( $metadata['supports']['interactivity']['clientNavigation'] ) && true === $metadata['supports']['interactivity']['clientNavigation'] );
// Blocks using the Interactivity API are server-side rendered, so they are
// by design not in the critical rendering path and should be deprioritized.
$args['fetchpriority'] = 'low';
$args['in_footer'] = true;
// Blocks using the Interactivity API that support client-side navigation
// must be marked as such in their script modules.
if ( $is_interactive && $supports_client_navigation ) {
wp_interactivity()->add_client_navigation_support_to_script_module( $module_id );
wp_register_script_module(
* Finds a script handle for the selected block metadata field. It detects
* when a path to file was provided and optionally finds a corresponding asset
* file with details necessary to register the script under automatically
* generated handle name. It returns unprocessed script handle otherwise.
* @since 6.1.0 Added `$index` parameter.
* @since 6.5.0 The asset file is optional. Added script handle support in the asset file.
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
* @param int $index Optional. Index of the script to register when multiple items passed.
* @return string|false Script handle provided directly or created through
* script's registration, or false on failure.
function register_block_script_handle( $metadata, $field_name, $index = 0 ) {
if ( empty( $metadata[ $field_name ] ) ) {
$script_handle_or_path = $metadata[ $field_name ];
if ( is_array( $script_handle_or_path ) ) {
if ( empty( $script_handle_or_path[ $index ] ) ) {
$script_handle_or_path = $script_handle_or_path[ $index ];
$script_path = remove_block_asset_path_prefix( $script_handle_or_path );
if ( $script_handle_or_path === $script_path ) {
return $script_handle_or_path;
$path = dirname( $metadata['file'] );
$script_asset_raw_path = $path . '/' . substr_replace( $script_path, '.asset.php', - strlen( '.js' ) );
$script_asset_path = wp_normalize_path(
realpath( $script_asset_raw_path )
// Asset file for blocks is optional. See https://core.trac.wordpress.org/ticket/60460.
$script_asset = ! empty( $script_asset_path ) ? require $script_asset_path : array();
$script_handle = isset( $script_asset['handle'] ) ?
$script_asset['handle'] :
generate_block_asset_handle( $metadata['name'], $field_name, $index );
if ( wp_script_is( $script_handle, 'registered' ) ) {
$script_path_norm = wp_normalize_path( realpath( $path . '/' . $script_path ) );
$script_uri = get_block_asset_url( $script_path_norm );
$script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array();
$block_version = isset( $metadata['version'] ) ? $metadata['version'] : false;
$script_version = isset( $script_asset['version'] ) ? $script_asset['version'] : $block_version;
if ( 'viewScript' === $field_name && $script_uri ) {
$script_args['strategy'] = 'defer';
$result = wp_register_script(
if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $script_dependencies, true ) ) {
wp_set_script_translations( $script_handle, $metadata['textdomain'] );
* Finds a style handle for the block metadata field. It detects when a path
* to file was provided and registers the style under automatically
* generated handle name. It returns unprocessed style handle otherwise.
* @since 6.1.0 Added `$index` parameter.
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
* @param int $index Optional. Index of the style to register when multiple items passed.
* @return string|false Style handle provided directly or created through
* style's registration, or false on failure.
function register_block_style_handle( $metadata, $field_name, $index = 0 ) {
if ( empty( $metadata[ $field_name ] ) ) {
$style_handle = $metadata[ $field_name ];
if ( is_array( $style_handle ) ) {
if ( empty( $style_handle[ $index ] ) ) {
$style_handle = $style_handle[ $index ];
$style_handle_name = generate_block_asset_handle( $metadata['name'], $field_name, $index );
// If the style handle is already registered, skip re-registering.
if ( wp_style_is( $style_handle_name, 'registered' ) ) {
return $style_handle_name;
static $wpinc_path_norm = '';
if ( ! $wpinc_path_norm ) {
$wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) );
$is_core_block = isset( $metadata['file'] ) && str_starts_with( $metadata['file'], $wpinc_path_norm );
// Skip registering individual styles for each core block when a bundled version provided.
if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) {
$style_path = remove_block_asset_path_prefix( $style_handle );
$is_style_handle = $style_handle === $style_path;
// Allow only passing style handles for core blocks.
if ( $is_core_block && ! $is_style_handle ) {
// Return the style handle unless it's the first item for every core block that requires special treatment.
if ( $is_style_handle && ! ( $is_core_block && 0 === $index ) ) {
// Check whether styles should have a ".min" suffix or not.
$suffix = SCRIPT_DEBUG ? '' : '.min';
$style_path = ( 'editorStyle' === $field_name ) ? "editor{$suffix}.css" : "style{$suffix}.css";
$style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) );
$style_uri = get_block_asset_url( $style_path_norm );
$block_version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
$version = $style_path_norm && defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? filemtime( $style_path_norm ) : $block_version;
$result = wp_register_style(
wp_style_add_data( $style_handle_name, 'path', $style_path_norm );
$rtl_file = str_replace( "{$suffix}.css", "-rtl{$suffix}.css", $style_path_norm );
$rtl_file = str_replace( '.css', '-rtl.css', $style_path_norm );
if ( is_rtl() && file_exists( $rtl_file ) ) {
wp_style_add_data( $style_handle_name, 'rtl', 'replace' );
wp_style_add_data( $style_handle_name, 'suffix', $suffix );
wp_style_add_data( $style_handle_name, 'path', $rtl_file );
return $style_handle_name;
* Gets i18n schema for block's metadata read from `block.json` file.
* @return object The schema for block's metadata.
function get_block_metadata_i18n_schema() {
static $i18n_block_schema;
if ( ! isset( $i18n_block_schema ) ) {
$i18n_block_schema = wp_json_file_decode( __DIR__ . '/block-i18n.json' );
return $i18n_block_schema;
* Registers all block types from a block metadata collection.
* This can either reference a previously registered metadata collection or, if the `$manifest` parameter is provided,
* register the metadata collection directly within the same function call.
* @see wp_register_block_metadata_collection()
* @see register_block_type_from_metadata()
* @param string $path The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ).
* @param string $manifest Optional. The absolute path to the manifest file containing the metadata collection, in
* order to register the collection. If this parameter is not provided, the `$path` parameter
* must reference a previously registered block metadata collection.
function wp_register_block_types_from_metadata_collection( $path, $manifest = '' ) {
wp_register_block_metadata_collection( $path, $manifest );
$block_metadata_files = WP_Block_Metadata_Registry::get_collection_block_metadata_files( $path );
foreach ( $block_metadata_files as $block_metadata_file ) {
register_block_type_from_metadata( $block_metadata_file );
* Registers a block metadata collection.
* This function allows core and third-party plugins to register their block metadata
* collections in a centralized location. Registering collections can improve performance
* by avoiding multiple reads from the filesystem and parsing JSON.
* @param string $path The base path in which block files for the collection reside.
* @param string $manifest The path to the manifest file for the collection.
function wp_register_block_metadata_collection( $path, $manifest ) {
WP_Block_Metadata_Registry::register_collection( $path, $manifest );
* Registers a block type from the metadata stored in the `block.json` file.
* @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.
* @since 6.7.0 Allow PHP filename as `variations` argument.
* @param string $file_or_folder 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`.
* @param array $args Optional. 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.
* @return WP_Block_Type|false The registered block type on success, or false on failure.
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.
$file_or_folder = wp_normalize_path( $file_or_folder );
$metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ?
trailingslashit( $file_or_folder ) . 'block.json' :
$is_core_block = str_starts_with( $file_or_folder, wp_normalize_path( ABSPATH . WPINC ) );
$metadata_file_exists = $is_core_block || file_exists( $metadata_file );
$registry_metadata = WP_Block_Metadata_Registry::get_metadata( $file_or_folder );
if ( $registry_metadata ) {
$metadata = $registry_metadata;
} elseif ( $metadata_file_exists ) {
$metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
$metadata['file'] = $metadata_file_exists ? wp_normalize_path( realpath( $metadata_file ) ) : null;
* Filters the metadata provided for registering a block type.
* @param array $metadata Metadata for registering a block type.
$metadata = apply_filters( 'block_type_metadata', $metadata );