How to build custom Timber Twig templating engines extensions utilizing modern REST API Controllers schemas
Leveraging REST API Controller Schemas for Timber Twig Extensions
This post details the construction of custom Timber Twig extensions in WordPress, specifically focusing on how to dynamically generate and utilize data structures derived from the WordPress REST API’s controller schemas. This approach allows for highly decoupled, data-driven templating logic, enabling frontend developers to consume complex WordPress data models with predictable, self-documenting interfaces.
Understanding REST API Controller Schemas
WordPress’s REST API, particularly since version 4.7, exposes metadata about its endpoints and the data they represent. This metadata, often referred to as the schema, provides a machine-readable description of the available fields, their types, validation rules, and capabilities for a given resource. For custom post types and taxonomies registered via code, these schemas are automatically generated. We can tap into this by querying the /wp/v2/ endpoint.
For instance, to inspect the schema for a custom post type named ‘project’, you would typically make a GET request to /wp-json/wp/v2/project/schema. The response is a JSON object detailing the structure of a ‘project’ post, including properties like ‘title’, ‘content’, ‘status’, and any custom fields registered with register_post_meta that are exposed to the REST API.
Registering a Custom Timber Function
Timber provides a robust mechanism for extending its Twig environment with custom PHP functions. These functions can then be called directly within your Twig templates. To create a function that fetches and processes a REST API schema, we’ll hook into Timber’s timber_context filter or register a function directly using Timber\Functions::add_function.
Method 1: Using timber_context Filter
This method injects data into the global Timber context, making it available to all templates. We’ll define a function that retrieves the schema for a given post type and returns it in a format suitable for Twig.
Example: Fetching Post Type Schema
Place the following PHP code in your theme’s functions.php file or a custom plugin:
<?php
/**
* Add custom function to Timber context to fetch REST API schema.
*
* @param array $context The Timber context array.
* @return array The modified Timber context array.
*/
add_filter( 'timber_context', function( $context ) {
$context['get_rest_schema'] = function( $post_type ) {
if ( ! post_type_exists( $post_type ) ) {
return null;
}
$request = new WP_REST_Request( 'GET', "/wp/v2/{$post_type}/schema" );
$response = rest_do_request( $request );
if ( is_wp_error( $response ) ) {
return null;
}
$schema = $response->get_data();
// Optionally, filter or transform the schema here.
// For example, to only expose 'properties' and 'required' fields.
$filtered_schema = [
'properties' => $schema['properties'] ?? [],
'required' => $schema['required'] ?? [],
];
return $filtered_schema;
};
return $context;
} );
?>
Method 2: Using Timber\Functions::add_function
This method registers a function directly with Timber’s Twig environment, offering more explicit control over its availability.
Example: Registering a Schema Fetcher Function
<?php
use Timber;
use WP_REST_Request;
// Ensure Timber is active before attempting to register functions.
if ( class_exists( 'Timber' ) ) {
Timber\Functions::add_function( 'get_rest_schema', function( $post_type ) {
if ( ! post_type_exists( $post_type ) ) {
return null;
}
$request = new WP_REST_Request( 'GET', "/wp/v2/{$post_type}/schema" );
$response = rest_do_request( $request );
if ( is_wp_error( $response ) ) {
return null;
}
$schema = $response->get_data();
// Example: Return only the 'properties' for simplicity in Twig.
return $schema['properties'] ?? [];
} );
}
?>
Utilizing Schemas in Twig Templates
Once the custom function is registered, you can call it directly within your Twig templates. This allows you to dynamically build forms, validate input, or simply display field information based on the API’s definition.
Example: Rendering a Dynamic Form Field
Suppose you have a custom post type ‘event’ and you want to render an input field for its ‘location’ property, which is defined in the REST API schema. You can do this in your Twig template:
{# Assuming 'get_rest_schema' is available in the context #}
{# Or if using Timber\Functions::add_function, it's globally available #}
{% set event_schema = get_rest_schema('event') %}
{% if event_schema and event_schema.location %}
<div class="form-group">
<label for="event_location">{{ event_schema.location.description|default('Location') }}</label>
<input
type="{{ event_schema.location.type }}"
id="event_location"
name="event_location"
value="{{ post.meta.location|default('') }}"
class="form-control"
aria-describedby="location-help"
{% if event_schema.location.arg and 'required' in event_schema.location.arg %}required{% endif %}
>
{% if event_schema.location.description %}
<small id="location-help" class="form-text text-muted">{{ event_schema.location.description }}</small>
{% endif %}
</div>
{% else %}
{# Fallback for when schema is not available or location field is missing #}
<div class="form-group">
<label for="event_location">Location</label>
<input type="text" id="event_location" name="event_location" value="{{ post.meta.location|default('') }}" class="form-control">
</div>
{% endif %}
Example: Displaying Field Metadata
You can also use the schema to display information about fields, such as their types or whether they are required.
<h3>Event Details</h3>
<ul>
{% for field_name, field_definition in get_rest_schema('event').properties %}
<li>
<strong>{{ field_name|capitalize }}</strong>:
Type: {{ field_definition.type }}
{% if field_definition.description %}
- {{ field_definition.description }}
{% endif %}
{% if field_definition.arg and 'required' in field_definition.arg %}
(Required)
{% endif %}
</li>
{% endfor %}
</ul>
Advanced Considerations and Best Practices
Caching Schema Data
Fetching the REST API schema on every request can be inefficient. For performance-critical applications, consider implementing a caching layer. WordPress Transients API is an excellent choice for this.
<?php
use Timber;
use WP_REST_Request;
if ( class_exists( 'Timber' ) ) {
Timber\Functions::add_function( 'get_rest_schema_cached', function( $post_type, $cache_duration = HOUR_IN_SECONDS ) {
$cache_key = 'rest_schema_' . sanitize_key( $post_type );
$cached_schema = get_transient( $cache_key );
if ( false !== $cached_schema ) {
return $cached_schema;
}
if ( ! post_type_exists( $post_type ) ) {
return null;
}
$request = new WP_REST_Request( 'GET', "/wp/v2/{$post_type}/schema" );
$response = rest_do_request( $request );
if ( is_wp_error( $response ) ) {
return null;
}
$schema = $response->get_data();
$filtered_schema = $schema['properties'] ?? [];
set_transient( $cache_key, $filtered_schema, $cache_duration );
return $filtered_schema;
} );
}
?>
Handling Custom Fields (register_post_meta)
Custom fields registered with register_post_meta are automatically included in the REST API schema if they are exposed. Ensure your register_post_meta calls include the show_in_rest argument set to true and provide a descriptive schema argument for proper API representation.
<?php
register_post_meta( 'event', 'event_location', [
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function() {
// Define authorization logic if needed
return current_user_can( 'edit_posts' );
},
'schema' => [
'description' => esc_html__( 'The location of the event.', 'your-text-domain' ),
'type' => 'string',
'arg' => [ // Defines arguments for the REST API
'required' => false,
'type' => 'string',
],
],
] );
?>
When show_in_rest is true and a schema is provided, this metadata will be reflected in the /wp/v2/event/schema response, making it accessible via your Timber extension.
Security and Authorization
The REST API schema endpoint itself is generally publicly accessible. However, the auth_callback for register_post_meta and the general REST API authentication mechanisms determine who can *modify* data. Your Timber extension primarily *reads* schema information, which is less of a security concern than data modification. Always be mindful of what data you expose and how it’s used in your templates.
Conclusion
By integrating WordPress REST API controller schemas with Timber Twig extensions, you can build more dynamic, maintainable, and data-aware templating systems. This approach promotes a clear separation of concerns, allowing your PHP backend to define data structures and your Twig templates to consume them intelligently, leading to more robust and scalable WordPress applications.