• 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 Timber Twig templating engines extensions utilizing modern Block Patterns API schemas

How to build custom Timber Twig templating engines extensions utilizing modern Block Patterns API schemas

Leveraging Timber’s Extensibility with Custom Twig Functions for Block Patterns

Timber, a popular WordPress framework that leverages Twig for templating, offers robust extensibility. This post details how to create custom Twig functions that dynamically generate or manipulate HTML for WordPress Block Patterns, integrating seamlessly with the Block Editor’s schema. We’ll focus on practical implementation, demonstrating how to pass complex data structures from PHP to Twig and render them as structured HTML suitable for Block Pattern registration.

Registering Custom Twig Functions in Timber

Timber allows you to extend its Twig environment by registering custom functions. These functions can be called directly within your Twig templates. The standard approach involves hooking into Timber’s timber/twig filter.

Consider a scenario where you need to generate a complex “feature card” block pattern. This pattern might require an image, a title, a description, and a call-to-action button. Instead of hardcoding this in Twig, we can create a PHP function that generates the necessary HTML structure and pass it as a string or an array of attributes to Twig.

PHP Function for Block Pattern HTML Generation

Let’s define a PHP function that will be responsible for generating the HTML for our feature card. This function will accept an array of arguments representing the content of the card.

/**
 * Generates HTML for a feature card block pattern.
 *
 * @param array $args Associative array of arguments.
 *                    Expected keys: 'title', 'description', 'image_url', 'button_text', 'button_url'.
 * @return string HTML output for the feature card.
 */
function my_timber_generate_feature_card_html( $args ) {
    $defaults = array(
        'title'       => '',
        'description' => '',
        'image_url'   => '',
        'button_text' => 'Learn More',
        'button_url'  => '#',
    );
    $args = wp_parse_args( $args, $defaults );

    // Basic sanitization for output
    $title       = esc_html( $args['title'] );
    $description = wp_kses_post( $args['description'] );
    $image_url   = esc_url( $args['image_url'] );
    $button_text = esc_html( $args['button_text'] );
    $button_url  = esc_url( $args['button_url'] );

    ob_start();
    ?>
    <div class="wp-block-my-plugin-feature-card">
        <?php if ( $image_url ) : ?>
            <img src="<?php echo $image_url; ?>" alt="<?php echo esc_attr( $title ); ?>" class="feature-card__image" />
        <?php endif; ?>
        <div class="feature-card__content">
            <h3 class="feature-card__title"><?php echo $title; ?></h3>
            <p class="feature-card__description"><?php echo $description; ?></p>
            <a href="<?php echo $button_url; ?>" class="feature-card__button"><?php echo $button_text; ?></a>
        </div>
    </div>
    <?php
    return ob_get_clean();
}

Registering the Function with Timber

Now, we register this PHP function as a Twig extension. This is typically done within your theme’s functions.php file or a custom plugin.

add_filter( 'timber/twig', function ( Twig\Environment $twig ) {
    // Register the function. The first argument is the name used in Twig,
    // the second is the callable PHP function.
    $twig->addFunction( new Twig\TwigFunction( 'render_feature_card', 'my_timber_generate_feature_card_html' ) );
    return $twig;
} );

Utilizing the Custom Twig Function in Block Patterns

With the function registered, we can now use it within our Twig templates. For Block Patterns, we often need to define the pattern’s HTML structure. This structure can be generated dynamically using our custom Twig function.

Defining the Block Pattern Schema

WordPress’s Block Patterns API allows for programmatic registration. The render_callback for a pattern can be a string containing HTML, or a callable that returns HTML. We’ll leverage our Twig function to generate this HTML.

/**
 * Registers the custom feature card block pattern.
 */
function my_plugin_register_feature_card_pattern() {
    // Data to be passed to the Twig function.
    // In a real-world scenario, this data might come from post meta,
    // options, or be dynamically generated based on context.
    $pattern_data = array(
        'title'       => 'Innovative Solutions',
        'description' => 'We provide cutting-edge solutions to drive your business forward. Our team is dedicated to delivering excellence.',
        'image_url'   => get_template_directory_uri() . '/assets/images/placeholder-feature.jpg', // Example image
        'button_text' => 'Discover Our Services',
        'button_url'  => '/services/',
    );

    // We need to pass this data to our Twig function.
    // The 'render_callback' can accept arguments.
    // We'll use a closure to pass our specific data.
    $render_callback = function( $attributes, $content, $block ) use ( $pattern_data ) {
        // Timber needs to be initialized to use Twig functions.
        // This is usually handled by Timber itself when rendering templates.
        // For standalone pattern rendering, we might need to ensure Timber's
        // environment is available or manually instantiate it if necessary.
        // A common approach is to ensure Timber is loaded and then call the function.

        // If Timber is active and its Twig environment is accessible:
        if ( class_exists( 'Timber' ) ) {
            // Attempt to get the Timber environment. This might require
            // more advanced setup if Timber isn't fully bootstrapped.
            // A simpler approach for patterns is often to directly call
            // the registered Twig function if Timber's global environment is available.

            // For simplicity, let's assume Timber's Twig environment is accessible
            // via a global or a method that can be called.
            // A more robust solution might involve passing the data directly
            // and letting Timber handle the Twig rendering context.

            // However, the most straightforward way for a render_callback
            // is to return the HTML string directly.
            // We can call our PHP function directly here, as it's already registered.
            return my_timber_generate_feature_card_html( $pattern_data );
        }
        return '<p>Timber not available for pattern rendering.</p>';
    };

    register_block_pattern(
        'my-plugin/feature-card', // Unique pattern name
        array(
            'title'       => __( 'Feature Card', 'my-plugin' ),
            'description' => __( 'A card showcasing a feature with image, title, and description.', 'my-plugin' ),
            'content'     => '', // Content can be empty if fully rendered by callback
            'render_callback' => $render_callback,
            'categories'  => array( 'my-custom-category' ), // Optional: custom category
            'keywords'    => array( 'feature', 'card', 'marketing' ),
            'viewportWidth' => 800, // Optional: for preview
        )
    );
}
add_action( 'init', 'my_plugin_register_feature_card_pattern' );

In the above example, the $render_callback is a closure that captures the $pattern_data. Inside the closure, we directly call our PHP function my_timber_generate_feature_card_html. This bypasses the need to directly interact with the Twig environment within the register_block_pattern call, as the PHP function already generates the required HTML. The key is that the PHP function itself is designed to produce output that *could* be generated by Twig, making it a bridge.

Alternative: Rendering via a Timber Template

For more complex scenarios, or if you prefer to keep all rendering logic within Twig files, you can register a Twig template as the pattern’s content and pass data to it. This requires Timber to be fully initialized and its Twig environment to be accessible.

/**
 * Registers the custom feature card block pattern using a Twig template.
 */
function my_plugin_register_feature_card_pattern_twig() {
    // Data to be passed to the Twig template.
    $pattern_data = array(
        'title'       => 'Advanced Features',
        'description' => 'Explore the advanced capabilities of our platform. Designed for power users.',
        'image_url'   => get_template_directory_uri() . '/assets/images/advanced-feature.jpg',
        'button_text' => 'See Features',
        'button_url'  => '/features/',
    );

    // We need a way to render a Twig template within the render_callback.
    // This often involves instantiating Timber\Site or Timber\Timber.
    // Ensure Timber is loaded before this action.
    if ( ! class_exists( 'Timber' ) ) {
        return;
    }

    $render_callback = function( $attributes, $content, $block ) use ( $pattern_data ) {
        // Create a Timber context.
        $context = Timber::context();
        // Merge our pattern data into the context.
        $context['feature_card_data'] = $pattern_data;

        // Specify the Twig template file. This path is relative to your theme's
        // template directory or a directory configured in Timber.
        $template_path = 'patterns/feature-card.twig'; // e.g., themes/your-theme/templates/patterns/feature-card.twig

        // Render the Twig template.
        return Timber::compile( $template_path, $context );
    };

    register_block_pattern(
        'my-plugin/feature-card-twig',
        array(
            'title'       => __( 'Feature Card (Twig)', 'my-plugin' ),
            'description' => __( 'A card showcasing a feature, rendered via Twig.', 'my-plugin' ),
            'content'     => '',
            'render_callback' => $render_callback,
            'categories'  => array( 'my-custom-category' ),
            'keywords'    => array( 'feature', 'card', 'twig' ),
            'viewportWidth' => 800,
        )
    );
}
add_action( 'init', 'my_plugin_register_feature_card_pattern_twig' );

And here’s the corresponding Twig template (e.g., templates/patterns/feature-card.twig):

{# templates/patterns/feature-card.twig #}
{% import 'macros.twig' as macros %} {# Assuming you have a macros file for common elements #}

{% set card_data = feature_card_data | default({}) %}

{% set title = card_data.title | default('') %}
{% set description = card_data.description | default('') %}
{% set image_url = card_data.image_url | default('') %}
{% set button_text = card_data.button_text | default('Learn More') %}
{% set button_url = card_data.button_url | default('#') %}

{% if image_url %} {{ title | e('html_attr') }} {% endif %}

{{ title }}

{{ description | raw }}

{# Use raw if description can contain HTML #} {{ button_text }}

This approach keeps the presentation logic within Twig, which is often preferred for maintainability. The render_callback acts as an orchestrator, preparing the data and invoking Timber’s rendering engine.

Passing Complex Data Structures

The real power comes when you need to pass more than just simple strings. For instance, a “team member” block pattern might require an array of social media links, each with a name, URL, and icon class.

PHP Function for Team Member Card

/**
 * Generates HTML for a team member card block pattern.
 *
 * @param array $args Associative array of arguments.
 *                    Expected keys: 'name', 'position', 'bio', 'photo_url', 'social_links' (array of arrays).
 * @return string HTML output for the team member card.
 */
function my_timber_generate_team_member_html( $args ) {
    $defaults = array(
        'name'         => '',
        'position'     => '',
        'bio'          => '',
        'photo_url'    => '',
        'social_links' => array(),
    );
    $args = wp_parse_args( $args, $defaults );

    $name         = esc_html( $args['name'] );
    $position     = esc_html( $args['position'] );
    $bio          = wp_kses_post( $args['bio'] );
    $photo_url    = esc_url( $args['photo_url'] );
    $social_links = $args['social_links']; // Assume sanitization happens on individual link items

    ob_start();
    ?>
    <div class="wp-block-my-plugin-team-member">
        <div class="team-member__profile">
            <?php if ( $photo_url ) : ?>
                <img src="<?php echo $photo_url; ?>" alt="<?php echo esc_attr( $name ); ?>" class="team-member__photo" />
            <?php endif; ?>
            <div class="team-member__info">
                <h4 class="team-member__name"><?php echo $name; ?></h4>
                <p class="team-member__position"><?php echo $position; ?></p>
            </div>
        </div>
        <div class="team-member__bio">
            <p><?php echo $bio; ?></p>
        </div>
        <?php if ( ! empty( $social_links ) ) : ?>
            <ul class="team-member__social-links">
                <?php foreach ( $social_links as $link ) : ?>
                    <?php
                        $link_url = esc_url( $link['url'] ?? '#' );
                        $link_text = esc_html( $link['text'] ?? '' );
                        $icon_class = esc_attr( $link['icon_class'] ?? '' );
                    ?>
                    <li>
                        <a href="<?php echo $link_url; ?>" target="_blank" rel="noopener noreferrer" class="<?php echo $icon_class; ?>" aria-label="<?php echo $link_text; ?>">
                            <span class="screen-reader-text"><?php echo $link_text; ?></span> {# For accessibility #}
                        </a>
                    </li>
                <?php endforeach; ?>
            </ul>
        <?php endif; ?>
    </div>
    <?php
    return ob_get_clean();
}

Registering the Team Member Function

add_filter( 'timber/twig', function ( Twig\Environment $twig ) {
    // Register the function.
    $twig->addFunction( new Twig\TwigFunction( 'render_team_member', 'my_timber_generate_team_member_html' ) );
    return $twig;
} );

Registering the Block Pattern

/**
 * Registers the custom team member block pattern.
 */
function my_plugin_register_team_member_pattern() {
    // Complex data structure for social links.
    $team_member_data = array(
        'name'         => 'Dr. Evelyn Reed',
        'position'     => 'Lead Researcher',
        'bio'          => 'Dr. Reed leads our research initiatives, focusing on sustainable technologies and their global impact. She holds a PhD from MIT.',
        'photo_url'    => get_template_directory_uri() . '/assets/images/dr-evelyn-reed.jpg',
        'social_links' => array(
            array(
                'text'       => 'LinkedIn',
                'url'        => 'https://linkedin.com/in/evelynreed',
                'icon_class' => 'fab fa-linkedin', // Font Awesome example
            ),
            array(
                'text'       => 'Twitter',
                'url'        => 'https://twitter.com/evelynreed',
                'icon_class' => 'fab fa-twitter',
            ),
        ),
    );

    $render_callback = function( $attributes, $content, $block ) use ( $team_member_data ) {
        // Directly call the PHP function to generate HTML.
        return my_timber_generate_team_member_html( $team_member_data );
    };

    register_block_pattern(
        'my-plugin/team-member',
        array(
            'title'       => __( 'Team Member Profile', 'my-plugin' ),
            'description' => __( 'A profile card for a team member with bio and social links.', 'my-plugin' ),
            'content'     => '',
            'render_callback' => $render_callback,
            'categories'  => array( 'team-profiles' ),
            'keywords'    => array( 'team', 'member', 'profile', 'staff' ),
            'viewportWidth' => 400,
        )
    );
}
add_action( 'init', 'my_plugin_register_team_member_pattern' );

This demonstrates how to pass nested arrays and structured data from PHP to your rendering logic. The PHP function handles the iteration and rendering of the social links, ensuring correct HTML output and sanitization.

Considerations for Production

  • Security: Always sanitize and escape all data before outputting it. Use functions like esc_html(), esc_url(), wp_kses_post(), and esc_attr() appropriately. For complex HTML structures, wp_kses() with a defined allowed HTML element list is crucial.
  • Performance: For very complex data or numerous patterns, consider caching mechanisms. If data is fetched from external APIs, ensure it’s cached effectively.
  • Maintainability: Organize your custom Twig functions and related PHP logic into a dedicated plugin or a well-structured theme. Use clear naming conventions and add comprehensive docblocks.
  • Timber Initialization: Ensure Timber is loaded and its Twig environment is available when your pattern registration code runs, especially if using the Twig template rendering approach. Hooks like after_setup_theme or init are generally safe, but context matters.
  • Block Editor Integration: For patterns that should be editable within the Block Editor, consider registering them as actual blocks using the Block API, rather than just static patterns. This allows for dynamic content population via block attributes and supports more complex user interactions.

By creating custom Twig functions and integrating them with WordPress’s Block Patterns API, you can build highly dynamic, reusable, and maintainable templating solutions within Timber-powered WordPress sites. This approach bridges the gap between PHP data handling and Twig’s presentation layer, offering a powerful way to extend WordPress’s capabilities.

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

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

Categories

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

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

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