WordPress 6.4 New Features and Block Editor Improvements for Developers

WordPress 6.4 (released November 7, 2023, codenamed “Shirley” after jazz vocalist Shirley Horn) focused on three developer-facing areas: block editor enhancements, performance improvements to the query loop block and data views, and new PHP APIs for block theme development. The most impactful addition for block theme developers is the Block Bindings API — a new system that allows connecting block attributes to registered data sources without writing JavaScript. A block binding maps a block attribute (such as a Paragraph block’s content or an Image block’s url) to a PHP callback registered with register_block_bindings_source(), so the value is populated dynamically at render time. Built-in bindings include core/post-meta (bind a block attribute to a post meta field) and core/pattern-overrides (allow individual pattern instances to override specific block attribute values). The DataViews component — a new React component in @wordpress/dataviews — provides a reusable table/list/grid view for WordPress admin screens, designed to eventually replace the legacy WP_List_Table class. The wp_enqueue_block_style() function (stable in 6.4) enqueues a stylesheet that is automatically loaded only when the specified block is rendered on a page — solving the problem of block-specific styles being loaded on every page even when the block is not present. The is_block_editor() function was added as a cleaner alternative to checking get_current_screen()->is_block_editor(). The Query Loop block gained a new “Inherit query from URL” toggle that exposes category, tag, and search query parameters to the loop, enabling fully templated archive pages. The theme.json post covered the static configuration layer; WordPress 6.4’s Block Bindings API adds the dynamic data layer to block themes.

Problem: A block theme for a real estate site needs property listing cards that display custom field values (price, bedrooms, area) inside block editor-authored layouts — previously this required either a custom server-side-rendered block (JavaScript required) or shortcodes that break the block editor visual preview.

Solution: Use the WordPress 6.4 Block Bindings API to register a custom data source that maps Paragraph and Heading block content attributes to post meta fields — enabling visual editing with live preview and no custom block JavaScript required.

// Register a custom block bindings source for property meta fields (WordPress 6.4+)
add_action( 'init', 'myplugin_register_property_bindings' );

function myplugin_register_property_bindings(): void {
    register_block_bindings_source( 'myplugin/property-meta', [
        'label'              => __( 'Property Meta', 'myplugin' ),
        'get_value_callback' => 'myplugin_get_property_meta_value',
        'uses_context'       => [ 'postId', 'postType' ],
    ] );
}

/**
 * Called by the block renderer when a block has a binding to 'myplugin/property-meta'.
 *
 * @param array  $source_args  The 'args' from the block binding definition in block markup.
 * @param object $block_instance The block instance being rendered.
 * @param string $attribute_name The block attribute that is bound (e.g., 'content', 'url').
 * @return string|null The value to inject into the block attribute.
 */
function myplugin_get_property_meta_value( array $source_args, $block_instance, string $attribute_name ): ?string {
    $post_id = $block_instance->context['postId'] ?? get_the_ID();
    $key     = $source_args['key'] ?? '';

    if ( ! $post_id || ! $key ) return null;

    // Allowlist of bindable meta keys
    $allowed_keys = [ '_property_price', '_property_bedrooms', '_property_area_sqm', '_property_address' ];
    if ( ! in_array( $key, $allowed_keys, true ) ) return null;

    $value = get_post_meta( $post_id, $key, true );
    if ( ! $value ) return null;

    // Format values for display
    return match ( $key ) {
        '_property_price'    => '$' . number_format( (float) $value, 0, '.', ',' ),
        '_property_area_sqm' => number_format( (float) $value ) . ' m²',
        default              => esc_html( $value ),
    };
}

// ── wp_enqueue_block_style: load stylesheet only when block is used ────────
add_action( 'init', 'myplugin_enqueue_block_styles' );

function myplugin_enqueue_block_styles(): void {
    // This CSS is only loaded on pages that contain a Query Loop block
    wp_enqueue_block_style( 'core/query', [
        'handle' => 'myplugin-property-query-style',
        'src'    => get_theme_file_uri( 'css/property-listing.css' ),
        'path'   => get_theme_file_path( 'css/property-listing.css' ),
        'ver'    => wp_get_theme()->get( 'Version' ),
    ] );
}

{
    "name": "core/paragraph",
    "attributes": {
        "content": "Price placeholder",
        "metadata": {
            "bindings": {
                "content": {
                    "source": "myplugin/property-meta",
                    "args": { "key": "_property_price" }
                }
            }
        }
    }
}

NOTE: The Block Bindings API in WordPress 6.4 only supports binding attributes in a subset of core blocks: Paragraph (content), Heading (content), Image (url, alt, title), and Button (url, text). Third-party blocks must explicitly opt in to support bindings by declaring "usesContext": ["postId"] in their block.json and handling the bound attribute in their render callback. The binding syntax in block markup (the metadata.bindings JSON structure in the block comment delimiter) is stored as-is in the database — ensure that custom binding sources always sanitize and escape their output since the value is injected directly into block HTML on render.