• 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 Shortcode API schemas

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

Leveraging Timber’s Twig with WordPress Shortcodes: A Deep Dive

WordPress’s shortcode API, while powerful, often leads to tangled HTML within theme files or a proliferation of PHP functions. Timber, with its Twig templating engine, offers a cleaner, more maintainable approach. This post explores how to bridge these two paradigms by building custom Timber Twig extensions that abstract shortcode logic, enabling developers to embed dynamic, shortcode-driven content directly within their Twig templates in a structured and reusable manner.

The Problem: Shortcodes in Twig

Directly calling do_shortcode() within Twig templates is an anti-pattern. It tightly couples presentation logic (Twig) with WordPress’s core shortcode execution, making templates harder to read, test, and refactor. The ideal scenario is to have Twig focus on rendering data, while shortcodes are managed at a higher level, perhaps within a dedicated plugin or the theme’s PHP layer.

The Solution: Custom Twig Extensions for Shortcodes

We can create custom Twig extensions that act as wrappers for shortcode functionality. These extensions will expose Twig functions or filters that, when called, will internally execute specific shortcodes with their associated attributes and content. This keeps the shortcode execution logic out of the Twig files themselves.

Building a Basic Shortcode Twig Extension

Let’s start by creating a simple Twig extension that can render a hypothetical `[my_custom_shortcode]` shortcode. This extension will be registered with Timber.

1. The Shortcode Definition

First, ensure your shortcode is registered. For demonstration, we’ll create a simple one that echoes a message with attributes.

Example Shortcode (`functions.php` or plugin file)

<?php
/**
 * Registers a simple custom shortcode.
 */
function my_custom_shortcode_handler( $atts, $content = null ) {
    $atts = shortcode_atts(
        array(
            'message' => 'Hello',
            'name'    => 'World',
        ),
        $atts,
        'my_custom_shortcode'
    );

    $output = '<p>' . esc_html( $atts['message'] ) . ', ' . esc_html( $atts['name'] ) . '!</p>';
    if ( ! is_null( $content ) ) {
        $output .= '<div class="shortcode-content">' . do_shortcode( $content ) . '</div>'; // Allow nested shortcodes
    }
    return $output;
}
add_shortcode( 'my_custom_shortcode', 'my_custom_shortcode_handler' );
?>

2. The Twig Extension Class

Next, we create the Twig extension class. This class will extend \Twig\Extension\AbstractExtension and define a new Twig function, say render_shortcode.

Twig Extension (`inc/twig/ShortcodeExtension.php`)

<?php
namespace MyTheme\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
 * Timber Twig Extension to render WordPress shortcodes.
 */
class ShortcodeExtension extends AbstractExtension {

    /**
     * Register new Twig functions.
     *
     * @return TwigFunction[]
     */
    public function getFunctions() {
        return [
            new TwigFunction( 'render_shortcode', [ $this, 'renderShortcode' ], [ 'is_safe' => [ 'html' ] ] ),
        ];
    }

    /**
     * Renders a given WordPress shortcode.
     *
     * @param string $tag The shortcode tag (e.g., 'my_custom_shortcode').
     * @param array  $attributes Associative array of shortcode attributes.
     * @param string|null $content The content enclosed by the shortcode tags.
     * @return string The rendered HTML output of the shortcode.
     */
    public function renderShortcode( string $tag, array $attributes = [], ?string $content = null ): string {
        // Ensure shortcode exists before attempting to render.
        if ( ! shortcode_exists( $tag ) ) {
            // Optionally log an error or return an empty string/placeholder.
            error_log( "Shortcode '{$tag}' does not exist." );
            return '';
        }

        // Prepare attributes for do_shortcode.
        // do_shortcode expects attributes as a string, not an array.
        $attribute_string = '';
        foreach ( $attributes as $key => $value ) {
            // Basic sanitization for attribute values.
            $attribute_string .= sprintf( '%s="%s" ', esc_attr( $key ), esc_attr( $value ) );
        }
        $attribute_string = trim( $attribute_string );

        // Construct the shortcode string.
        $shortcode_string = '[' . $tag . ' ' . $attribute_string . ']';
        if ( $content !== null ) {
            // Ensure content is processed by do_shortcode recursively if it contains other shortcodes.
            $shortcode_string .= do_shortcode( $content );
            $shortcode_string .= '[/' . $tag . ']';
        }

        // Execute the shortcode.
        return do_shortcode( $shortcode_string );
    }
}
?>

3. Registering the Extension with Timber

Finally, we need to tell Timber to use our new extension. This is typically done in your theme’s `functions.php` file or a dedicated plugin’s bootstrap file.

Registering the Extension (`functions.php`)

<?php
use Timber\Timber;
use MyTheme\Twig\ShortcodeExtension; // Assuming your namespace and class name

// Ensure Timber is loaded.
if ( ! class_exists( 'Timber' ) ) {
    add_action( 'admin_notices', function() {
        echo '<div class="error"><p>' . esc_html__( 'Timber not activated. Make sure the Timber plugin is installed and activated.', 'my-theme-text-domain' ) . '</p></div>';
    } );
    return;
}

// Add the custom extension to Timber.
add_filter( 'timber_context', function( array $context ) {
    $context['extensions'] = $context['extensions'] ?? [];
    $context['extensions'][] = new ShortcodeExtension();
    return $context;
} );

// If you are using Timber's automatic Twig environment setup,
// you might need to add the extension directly to the environment.
// This is less common if using the Timber\Timber::init() approach.
// Example for manual setup:
/*
add_action( 'timber_init', function() {
    $twig = Timber::get_context()['_environment']; // Get the Twig Environment instance
    if ( $twig ) {
        $twig->addExtension( new ShortcodeExtension() );
    }
} );
*/
?>

4. Using the Shortcode in Twig

Now, you can use the render_shortcode function in your Twig templates.

Example Twig Template (`page.twig`)

{# page.twig #}
{% extends "base.twig" %}

{% block content %}
    <article>
        <h1>{{ post.title }}</h1>

        <div class="entry-content">
            {{ post.content }}
        </div>

        {# Using the render_shortcode Twig function #}
        <div class="dynamic-content">
            <h2>Dynamic Section</h2>

            {# Simple usage with attributes #}
            {{ render_shortcode('my_custom_shortcode', {'message': 'Greetings', 'name': 'Twig User'}) }}

            {# Usage with content and nested shortcodes #}
            {{ render_shortcode('my_custom_shortcode', {'name': 'Nested Example'}, 'This is the content. <b>Bold text</b>.') }}

            {# Example of a shortcode that might not exist #}
            {{ render_shortcode('non_existent_shortcode', {'message': 'This will fail silently or log an error'}) }}
        </div>
    </article>
{% endblock %}

Advanced Scenarios and Considerations

1. Shortcodes with Complex Attributes (Arrays, Booleans)

The current renderShortcode method assumes string attributes. Shortcodes expecting arrays or booleans will require more sophisticated attribute parsing within the Twig extension or a pre-processing step in PHP before passing to Twig. For instance, a shortcode expecting 'items': ['apple', 'banana'] cannot be directly passed as a string. A common approach is to serialize complex data (e.g., JSON) and unserialize it within the shortcode handler, or to pass data as separate arguments if the Twig function is designed to accept them.

2. Security: Escaping and Sanitization

The renderShortcode function includes basic escaping for attribute keys and values using esc_attr(). However, the content passed to the shortcode is processed by do_shortcode(), which itself relies on the shortcode handler’s internal sanitization. It’s crucial that your shortcode handlers are robust and properly sanitize all output and attribute usage. The 'is_safe' => [ 'html' ] flag in the TwigFunction is essential to prevent Twig from double-escaping the output of do_shortcode(), but it places the onus of security on the shortcode handler itself.

3. Performance Implications

Calling do_shortcode() repeatedly within a template can impact performance, especially if shortcodes are complex or involve database queries. Consider caching strategies for shortcode output if performance becomes an issue. Alternatively, refactor complex shortcode logic into dedicated PHP classes or functions that prepare data for Twig, rather than rendering directly.

4. Handling Shortcode Registration and Dependencies

Ensure that shortcodes are registered *before* the Twig environment is initialized and extensions are added. This typically means placing shortcode registration in `functions.php` or an early-loaded plugin. If your shortcodes depend on specific plugins or theme features, ensure those are active and available.

5. Alternative: Data Preparation in PHP

For more complex scenarios, a cleaner architectural pattern involves preparing data in PHP and passing it to Twig, rather than directly rendering shortcodes. You could create a PHP function that mimics the shortcode’s functionality, accepts parameters, and returns structured data (e.g., an array or object) that Twig can then render. This adheres more strictly to the separation of concerns.

Example: PHP Data Preparation Function

<?php
/**
 * Prepares data for the 'my_custom_shortcode' functionality.
 *
 * @param array $args Arguments, similar to shortcode attributes.
 * @param string|null $content Enclosed content.
 * @return array Structured data for Twig.
 */
function prepare_my_custom_shortcode_data( array $args = [], ?string $content = null ): array {
    $defaults = [
        'message' => 'Hello',
        'name'    => 'World',
    ];
    $atts = shortcode_atts( $defaults, $args, 'my_custom_shortcode' );

    $data = [
        'message' => esc_html( $atts['message'] ),
        'name'    => esc_html( $atts['name'] ),
        'has_content' => ! is_null( $content ),
        'processed_content' => null,
    ];

    if ( $data['has_content'] ) {
        // If content needs further processing (e.g., other shortcodes), do it here.
        // For simplicity, we'll just pass it through do_shortcode.
        $data['processed_content'] = do_shortcode( $content );
    }

    return $data;
}

// Register this function as a Twig function that returns data.
// In your ShortcodeExtension.php or a new one:
// public function getFunctions() {
//     return [
//         new TwigFunction( 'get_my_custom_shortcode_data', 'prepare_my_custom_shortcode_data' ),
//         // ... other functions
//     ];
// }

// In Twig:
// {% set shortcode_data = get_my_custom_shortcode_data({'message': 'Data Driven', 'name': 'Twig'}, 'Some content here.') %}
// <p>{{ shortcode_data.message }}, {{ shortcode_data.name }}!</p>
// {% if shortcode_data.has_content %}
//     <div class="content">{{ shortcode_data.processed_content|raw }}</div>
// {% endif %}
?>

Conclusion

By creating custom Twig extensions, we can elegantly integrate WordPress shortcodes into Timber-powered themes. This approach enhances code organization, improves maintainability, and allows developers to leverage the strengths of both the Timber/Twig ecosystem and the WordPress shortcode API without compromising architectural integrity. Remember to prioritize security and performance, and consider data preparation in PHP for highly complex scenarios.

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

  • How to build custom Genesis child themes extensions utilizing modern Block Patterns API schemas
  • How to securely integrate HubSpot Contacts endpoints into WordPress custom plugins using Transients API
  • Implementing automated compliance reporting for custom online course lessons ledgers using custom PHP-Spreadsheet exports
  • Step-by-Step Guide to building a custom automated coupon generator block for Gutenberg using PHP block-render callbacks
  • How to build custom Carbon Fields custom wrappers extensions utilizing modern REST API Controllers schemas

Categories

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

Recent Posts

  • How to build custom Genesis child themes extensions utilizing modern Block Patterns API schemas
  • How to securely integrate HubSpot Contacts endpoints into WordPress custom plugins using Transients API
  • Implementing automated compliance reporting for custom online course lessons ledgers using custom PHP-Spreadsheet exports

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (841)
  • Debugging & Troubleshooting (636)
  • Security & Compliance (615)
  • 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