• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How to build custom Carbon Fields custom wrappers extensions utilizing modern Block Patterns API schemas

How to build custom Carbon Fields custom wrappers extensions utilizing modern Block Patterns API schemas

Leveraging Carbon Fields for Advanced E-commerce UI with Block Patterns

For e-commerce platforms built on WordPress, delivering a seamless and intuitive content management experience is paramount. Carbon Fields, a robust framework for custom fields, offers extensive capabilities. When combined with WordPress’s modern Block Patterns API, we can construct highly sophisticated and reusable UI components. This post details how to build custom Carbon Fields wrapper extensions that integrate directly with Block Patterns, enabling dynamic content generation and management for complex e-commerce layouts.

Understanding the Synergy: Carbon Fields and Block Patterns

Block Patterns are pre-designed arrangements of blocks that users can insert into their posts or pages. They are defined using a JSON schema that describes the structure and content of the blocks. Carbon Fields, on the other hand, provides a PHP-based API for creating custom meta boxes, options pages, and taxonomies. The challenge and opportunity lie in bridging these two systems: using Carbon Fields to define the *data structure* and *input controls* for dynamic content, and then exposing this data through Block Patterns that can be easily inserted and managed by content creators.

Our goal is to create a system where a content editor can select a pre-defined “product showcase” pattern, which then dynamically renders product information managed via a custom Carbon Fields meta box attached to a custom post type (e.g., ‘Products’).

Defining the Custom Post Type and Carbon Fields Meta Box

First, we need a custom post type to hold our product data. We’ll then attach a Carbon Fields meta box to this CPT. This meta box will house the fields for product name, price, description, and an image.

Registering the ‘Products’ Custom Post Type

This code snippet should be placed in your theme’s `functions.php` file or within a custom plugin.

add_action( 'init', function() {
    $labels = array(
        'name'                  => _x( 'Products', 'Post type general name', 'your-text-domain' ),
        'singular_name'         => _x( 'Product', 'Post type singular name', 'your-text-domain' ),
        'menu_name'             => _x( 'Products', 'Admin Menu text', 'your-text-domain' ),
        'name_admin_bar'        => _x( 'Product', 'Add New on Toolbar', 'your-text-domain' ),
        'add_new'               => __( 'Add New', 'your-text-domain' ),
        'add_new_item'          => __( 'Add New Product', 'your-text-domain' ),
        'edit_item'             => __( 'Edit Product', 'your-text-domain' ),
        'new_item'              => __( 'New Product', 'your-text-domain' ),
        'view_item'             => __( 'View Product', 'your-text-domain' ),
        'all_items'             => __( 'All Products', 'your-text-domain' ),
        'search_items'          => __( 'Search Products', 'your-text-domain' ),
        'parent_item_colon'     => __( 'Parent Products:', 'your-text-domain' ),
        'not_found'             => __( 'No products found.', 'your-text-domain' ),
        'not_found_in_trash'    => __( 'No products found in Trash.', 'your-text-domain' ),
        'featured_image'        => _x( 'Product Cover Image', 'Overrides the "Featured Image" phrase for this post type. Added in 4.3', 'your-text-domain' ),
        'set_featured_image'    => _x( 'Set cover image', 'Overrides the "Set featured image" phrase for this post type. Added in 4.3', 'your-text-domain' ),
        'remove_featured_image' => _x( 'Remove cover image', 'Overrides the "Remove featured image" phrase for this post type. Added in 4.3', 'your-text-domain' ),
        'use_featured_image'    => _x( 'Use as cover image', 'Overrides the "Use as featured image" phrase for this post type. Added in 4.3', 'your-text-domain' ),
        'archives'              => _x( 'Product archives', 'The post type archive label used in nav menus. Default is the post type name.', 'your-text-domain' ),
        'insert_into_item'      => _x( 'Insert into product', 'Used when inserting a post into a post. Similar to "Insert into item", but more specific to the post type.', 'your-text-domain' ),
        'uploaded_to_this_item' => _x( 'Uploaded to this product', 'Used when attaching a media file to this post type. e.g. "Uploaded to this document".', 'your-text-domain' ),
        'filter_items_list'     => _x( 'Filter products list', 'Screen reader text for the filter links heading on the post type listing screen.', 'your-text-domain' ),
        'items_list_navigation' => _x( 'Products list navigation', 'Screen reader text for the pagination of the post type listing screen.', 'your-text-domain' ),
        'items_list'            => _x( 'Products list', 'Screen reader text for the items list of the post type.', 'your-text-domain' ),
    );

    $args = array(
        'labels'             => $labels,
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'product' ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'menu_icon'          => 'dashicons-cart',
        'supports'           => array( 'title', 'editor', 'thumbnail' ),
        'show_in_rest'       => true, // Important for Gutenberg integration
    );

    register_post_type( 'product', $args );
} );

Setting up the Carbon Fields Meta Box

This code defines the meta box and its fields. Ensure Carbon Fields is installed and activated.

use Carbon_Fields\Container;
use Carbon_Fields\Field;

add_action( 'carbon_fields_register_fields', function() {
    Container::make( 'post_meta', __( 'Product Details', 'your-text-domain' ) )
        ->where( 'post_type', '=', 'product' )
        ->add_fields( array(
            Field::make( 'text', 'crb_product_price', __( 'Price', 'your-text-domain' ) )
                ->set_attribute( 'type', 'number' )
                ->set_attribute( 'step', '0.01' )
                ->set_attribute( 'min', '0' ),
            Field::make( 'textarea', 'crb_product_short_description', __( 'Short Description', 'your-text-domain' ) )
                ->set_rows( 3 ),
            Field::make( 'image', 'crb_product_image', __( 'Product Image', 'your-text-domain' ) )
                ->set_storage_key( 'crb_product_image_id' ) // Store attachment ID
                ->set_width( 50 ),
        ) );
} );

Creating the Block Pattern Schema

Now, we define the Block Pattern. This JSON describes the structure of the pattern, including the blocks it contains and their attributes. Crucially, we’ll use a custom block (or a core block with specific attributes) that can dynamically pull data from our Carbon Fields meta box. For simplicity, we’ll assume a custom block `your-namespace/product-display` exists or we’ll simulate it using core blocks and custom attributes.

The Block Pattern API allows us to define patterns that can include dynamic blocks. A dynamic block is rendered server-side, allowing it to fetch and display data that might not be directly embedded in the pattern itself. However, for patterns that *initiate* the display of content managed by meta fields, we often define the pattern with placeholder content or specific attributes that a custom block or theme template can interpret.

A more direct approach for integrating Carbon Fields with Block Patterns involves creating a custom block that *references* the post ID and then fetches the Carbon Fields data within its `render_callback`. The pattern then simply includes this dynamic block.

Example Block Pattern JSON

This JSON would typically be registered via `register_block_pattern` or placed in a `block-patterns.php` file within your theme or plugin.

{
    "title": "Featured Product Showcase",
    "description": "Displays a featured product with its details.",
    "categories": ["ecommerce", "products"],
    "content": "<!-- wp:your-namespace/product-display {"postId": 0} /-->"
}

In this schema:

  • title and description are for UI display in the editor.
  • categories help organize patterns.
  • content is the core. We’re using a placeholder for postId (which will be dynamically set when the pattern is inserted into a post, or we can hardcode it if the pattern is meant for a specific product page template). The your-namespace/product-display is our custom dynamic block.

Developing the Custom Dynamic Block

This custom block will be responsible for fetching the product data from the Carbon Fields meta box associated with a given post ID and rendering it.

Registering the Dynamic Block

This PHP code registers the dynamic block. It should be part of your plugin or theme’s `functions.php`.

add_action( 'init', function() {
    register_block_type( 'your-namespace/product-display', array(
        'render_callback' => 'render_product_display_block',
        'attributes'      => array(
            'postId' => array(
                'type'    => 'number',
                'default' => 0,
            ),
        ),
    ) );
} );

function render_product_display_block( $attributes ) {
    $post_id = isset( $attributes['postId'] ) ? (int) $attributes['postId'] : get_the_ID();

    if ( $post_id === 0 || get_post_type( $post_id ) !== 'product' ) {
        return '<p>' . __( 'No product selected or invalid post ID.', 'your-text-domain' ) . '</p>';
    }

    // Ensure Carbon Fields data is available
    if ( ! function_exists( 'carbon_get_post_meta' ) ) {
        return '<p>' . __( 'Carbon Fields plugin is not active.', 'your-text-domain' ) . '</p>';
    }

    $product_price = carbon_get_post_meta( $post_id, 'crb_product_price' );
    $short_desc    = carbon_get_post_meta( $post_id, 'crb_product_short_description' );
    $image_id      = carbon_get_post_meta( $post_id, 'crb_product_image_id' ); // This is the attachment ID

    $output = '<div class="product-display-wrapper">';

    // Product Image
    if ( $image_id ) {
        $image_url = wp_get_attachment_image_url( $image_id, 'medium' ); // Or 'large', 'full', or a custom size
        if ( $image_url ) {
            $output .= '<img src="' . esc_url( $image_url ) . '" alt="' . esc_attr( get_the_title( $post_id ) ) . '" class="product-image" />';
        }
    }

    // Product Title
    $output .= '<h3 class="product-title">' . esc_html( get_the_title( $post_id ) ) . '</h3>';

    // Product Price
    if ( $product_price ) {
        $output .= '<p class="product-price">' . wc_price( $product_price ) . '</p>'; // Using WooCommerce price formatting if available
    }

    // Short Description
    if ( $short_desc ) {
        $output .= '<div class="product-short-description">' . wp_kses_post( $short_desc ) . '</div>';
    }

    $output .= '</div>';

    return $output;
}

Explanation:

  • register_block_type registers our dynamic block.
  • render_callback points to the PHP function that will generate the HTML.
  • attributes define the block’s configurable properties. We include postId to specify which product to display.
  • The render_product_display_block function:
    • Retrieves the postId from attributes or defaults to the current post ID.
    • Checks if the post type is ‘product’.
    • Uses carbon_get_post_meta to fetch data from our Carbon Fields.
    • Constructs the HTML output, including image, title, price, and description.
    • Uses wc_price() for formatted currency display (requires WooCommerce active, otherwise fallback needed).
    • Uses wp_get_attachment_image_url to get the image URL from the attachment ID stored by Carbon Fields.

Integrating with the Block Editor

Once the block pattern and dynamic block are registered, content creators can:

  1. Navigate to the WordPress editor (for a post, page, or even a product page template).
  2. Click the ‘+’ icon to add a block or pattern.
  3. Search for “Featured Product Showcase” (or whatever title you gave your pattern).
  4. Insert the pattern.
  5. The your-namespace/product-display block will be added. If the pattern was inserted into a context where postId isn’t automatically inferred (e.g., a generic page), you might need a way to select the product. This could involve:
    • A custom inspector control for the block to select a product via a dropdown or search.
    • If the pattern is used within a specific template (e.g., a product archive template), the postId could be dynamically set to the current product being viewed.

For the postId attribute in the pattern’s content, if the pattern is intended to be used on a page *about* a specific product, you would manually edit the block’s attributes in the editor to point to that product’s ID. If the pattern is part of a template that *loops* through products, the postId would be the ID of the product in the current loop iteration.

Advanced Considerations and Enhancements

Dynamic Post ID Selection in the Editor

To make the block more user-friendly within the editor, you can add an inspector control to allow users to select a product directly. This requires registering the block’s JavaScript components.

In your block’s block.json (or registered via PHP):

{
    "name": "your-namespace/product-display",
    "title": "Product Display",
    "category": "widgets",
    "icon": "cart",
    "attributes": {
        "postId": {
            "type": "number",
            "default": 0
        }
    },
    "editor_script": "file:./build/index.js",
    "editor_style": "file:./build/index.css",
    "render": "file:./render.php" // Points to the PHP render callback
}

And in your build/index.js (using React and the Block Editor API):

import { registerBlockType } from '@wordpress/blocks';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl } from '@wordpress/components';
import apiFetch from '@wordpress/api-fetch';

registerBlockType( 'your-namespace/product-display', {
    edit: ( { attributes, setAttributes } ) => {
        const { postId } = attributes;

        // Fetch products for the dropdown
        const [ products, setProducts ] = React.useState( [] );
        React.useEffect( () => {
            apiFetch( { path: '/wp/v2/product?per_page=100' } ).then( ( fetchedProducts ) => {
                const productOptions = fetchedProducts.map( ( product ) => ( {
                    label: product.title.rendered,
                    value: product.id,
                } ) );
                setProducts( [ { label: 'Select a Product', value: 0 }, ...productOptions ] );
            } );
        }, [] );

        return (
            <>
                <InspectorControls>
                    <PanelBody title="Product Selection">
                        { products.length > 0 ? (
                            <SelectControl
                                label="Choose Product"
                                value={ postId.toString() }
                                options={ products }
                                onChange={ ( newPostId ) => setAttributes( { postId: parseInt( newPostId, 10 ) } ) }
                            />
                        ) : (
                            <p>Loading products...</p>
                        ) }
                    </PanelBody>
                </InspectorControls>
                <div className="editor-product-preview">
                    { postId > 0 ? `Preview for Product ID: ${postId}` : 'Select a product to see a preview.' }
                    {/* Optionally render a server-side preview using a placeholder */}
                </div>
            </>
        );
    },
    save: () => null, // Dynamic blocks return null in save()
} );

Styling the Output

The rendered HTML includes classes like .product-display-wrapper, .product-image, .product-title, etc. You can style these using CSS in your theme’s stylesheet or a dedicated block stylesheet.

.product-display-wrapper {
    border: 1px solid #eee;
    padding: 20px;
    margin-bottom: 20px;
    text-align: center;
    background-color: #f9f9f9;
}
.product-image {
    max-width: 100%;
    height: auto;
    margin-bottom: 15px;
}
.product-title {
    font-size: 1.8em;
    margin-bottom: 10px;
    color: #333;
}
.product-price {
    font-size: 1.4em;
    font-weight: bold;
    color: #0073aa;
    margin-bottom: 15px;
}
.product-short-description {
    font-size: 1.1em;
    color: #555;
}

Handling WooCommerce Integration

If you are using WooCommerce, you can leverage its functions for price formatting (like wc_price()) and potentially integrate with WooCommerce product data more deeply. Ensure your Carbon Fields setup aligns with WooCommerce’s product structure if you intend to replace or augment its functionality.

Conclusion

By combining Carbon Fields for robust data management and the Block Patterns API with custom dynamic blocks for flexible rendering, you can create powerful, reusable e-commerce UI components within WordPress. This approach empowers content editors to easily manage product details and integrate them seamlessly into their site’s design, leading to a more efficient and visually appealing online store.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store
  • How to refactor legacy event ticket registers queries using modern WP_Query and custom Transient caching
  • Step-by-Step Guide: Offloading high-frequency member profile directories metadata writes to a Redis KV store

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (662)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (5)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (647)
  • SEO & Growth (492)
  • Server (118)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (726)
  • WordPress Theme Development (357)

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (726)
  • Debugging & Troubleshooting (662)
  • Security & Compliance (647)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala