How to build custom ACF Pro dynamic fields extensions utilizing modern REST API Controllers schemas
Leveraging WordPress REST API Controllers for Advanced ACF PRO Dynamic Field Extensions
Advanced Custom Fields (ACF) PRO offers a powerful mechanism for creating dynamic field population through its “Dynamic Content” feature. While this is excellent for many use cases, extending its capabilities beyond simple database lookups or static options often requires a more robust solution. This is where the WordPress REST API and custom controller schemas come into play. By building custom REST API endpoints and exposing structured data, we can create highly sophisticated dynamic field behaviors that are both scalable and maintainable.
This guide will walk you through building a custom ACF PRO dynamic field extension that fetches data from a custom REST API endpoint. We’ll focus on creating a well-defined schema for our API response, enabling ACF to reliably parse and utilize this data for field options, choices, or even complex nested structures.
Designing the Custom REST API Endpoint
The core of our extension will be a custom REST API endpoint. For this example, let’s imagine we need to populate a select field with a list of active projects from an external project management system. We’ll simulate this by creating a custom endpoint within our WordPress site that returns project data.
We’ll use the `register_rest_route` function to define our endpoint. It’s crucial to define a clear namespace and route. For better organization, we’ll use a custom plugin or theme’s `functions.php` file.
Registering the REST Route
Here’s the PHP code to register our custom endpoint:
<?php
/**
* Plugin Name: ACF Dynamic Project Fields
* Description: Extends ACF PRO with dynamic fields for project data.
* Version: 1.0
* Author: Antigravity
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'acfdpf/v1', '/projects', array(
'methods' => 'GET',
'callback' => 'acfdpf_get_projects_callback',
'permission_callback' => '__return_true', // For simplicity, allow public access. In production, implement proper authentication.
) );
} );
function acfdpf_get_projects_callback( WP_REST_Request $request ) {
// In a real-world scenario, this would fetch data from an external API,
// a custom database table, or another complex data source.
// For this example, we'll return a static array.
$projects = array(
array(
'id' => 101,
'name' => 'Project Alpha',
'status' => 'active',
'client' => 'Acme Corp',
'deadline' => '2024-12-31',
),
array(
'id' => 102,
'name' => 'Project Beta',
'status' => 'active',
'client' => 'Globex Inc',
'deadline' => '2025-03-15',
),
array(
'id' => 103,
'name' => 'Project Gamma',
'status' => 'completed',
'client' => 'Initech',
'deadline' => '2023-11-30',
),
array(
'id' => 104,
'name' => 'Project Delta',
'status' => 'active',
'client' => 'Cyberdyne Systems',
'deadline' => '2024-09-01',
),
);
// Filter for active projects as per our requirement
$active_projects = array_filter( $projects, function( $project ) {
return $project['status'] === 'active';
} );
// Re-index array keys after filtering
$active_projects = array_values( $active_projects );
// Prepare the response data according to our desired schema
$response_data = array_map( function( $project ) {
return array(
'value' => $project['id'], // The value to be stored in the ACF field
'label' => sprintf( '%s (%s)', $project['name'], $project['client'] ), // The display label in the ACF field
'details' => array( // Additional data that can be leveraged by ACF
'status' => $project['status'],
'client' => $project['client'],
'deadline' => $project['deadline'],
),
);
}, $active_projects );
return new WP_REST_Response( $response_data, 200 );
}
?>
In this callback function:
- We define a static array of projects, simulating data fetched from an external source.
- We filter this array to include only ‘active’ projects.
- We then transform the filtered data into a specific structure that ACF can easily consume: an array of objects, each with a
valueand alabel. We also include a nesteddetailsarray for richer data. - The response is wrapped in a
WP_REST_Responseobject with a 200 OK status.
To test this endpoint, you can navigate to YOUR_SITE_URL/wp-json/acfdpf/v1/projects. You should see a JSON response similar to this:
[
{
"value": 101,
"label": "Project Alpha (Acme Corp)",
"details": {
"status": "active",
"client": "Acme Corp",
"deadline": "2024-12-31"
}
},
{
"value": 102,
"label": "Project Beta (Globex Inc)",
"details": {
"status": "active",
"client": "Globex Inc",
"deadline": "2025-03-15"
}
},
{
"value": 104,
"label": "Project Delta (Cyberdyne Systems)",
"details": {
"status": "active",
"client": "Cyberdyne Systems",
"deadline": "2024-09-01"
}
}
]
Configuring ACF PRO Dynamic Fields
Now that we have our REST API endpoint ready, we can configure an ACF PRO field to use this data dynamically. We’ll assume you have ACF PRO installed and activated.
Setting up the Select Field
In your ACF Field Group settings, add a new field. Choose the “Select” field type. Then, scroll down to the “Conditional Logic” section and enable “Dynamic Content”.
Under the “Dynamic Content” settings, configure the following:
- Source: Select “REST API”.
- REST API URL: Enter the full URL to your custom endpoint:
YOUR_SITE_URL/wp-json/acfdpf/v1/projects. - Value Format: This tells ACF which key in your JSON response should be used for the field’s stored value. Based on our schema, this should be
value. - Label Format: This specifies the key for the display text in the dropdown. For us, this is
label. - Return Format: Choose “Value” if you want to store only the selected project ID.
For more advanced scenarios, you can use the “Details Format” to map additional data. For instance, if you wanted to store the client name alongside the project ID (though typically you’d just store the ID), you could configure it. However, for a standard select field, value and label are sufficient.
Advanced Data Handling and Schema Design
The power of this approach lies in the flexibility of the REST API response schema. You are not limited to simple key-value pairs. ACF PRO can interpret nested structures.
Leveraging Nested Data
Consider a scenario where you want to display the project name and client in the select dropdown, but store the project ID. Our current schema already supports this with value and label. However, if you needed to access the details array for further processing (e.g., in JavaScript or within PHP after saving), your API response should be structured accordingly.
Let’s say you wanted to populate a “Client” field dynamically based on the selected project. This would typically involve JavaScript on the front-end or a more complex ACF setup. However, if your API could provide a lookup for client details, you could structure it like this:
[
{
"value": 101,
"label": "Project Alpha",
"client_name": "Acme Corp", // Directly available
"client_details": { // Nested object
"id": "acme-corp-id",
"contact_person": "Jane Doe"
}
},
// ... other projects
]
ACF PRO’s dynamic fields primarily use the top-level value and label keys. For accessing nested data or performing actions based on selections, you would typically need to:
- Client-side JavaScript: Use ACF’s JavaScript API to listen for changes on the select field and then make further AJAX calls or manipulate other fields based on the selected value and its associated data (which you might embed as data attributes or fetch via another API call).
- Server-side Processing: When the post is saved, you can access the saved value and use it to perform additional actions or lookups within your plugin’s save hooks (e.g.,
save_post).
Customizing the REST API Response Schema
The key is to ensure your callback function returns data in a predictable and parseable format. ACF PRO expects an array of objects, where each object has at least a value and a label key. You can add any other keys you need for your specific application logic.
For instance, if you were fetching data from an external service that returned data in a different format, you would map it within your callback:
function acfdpf_get_external_data_callback( WP_REST_Request $request ) {
$external_api_data = fetch_data_from_external_service(); // Assume this function exists
$acf_compatible_data = array_map( function( $item ) {
return array(
'value' => $item['external_id'], // Map external ID to ACF 'value'
'label' => $item['display_name'], // Map external name to ACF 'label'
'external_status' => $item['status'], // Keep original status for potential later use
'nested_info' => array( // Nest other relevant data
'category' => $item['category'],
'created_at' => $item['timestamp'],
),
);
}, $external_api_data );
return new WP_REST_Response( $acf_compatible_data, 200 );
}
Error Handling and Permissions
In a production environment, robust error handling and secure permissions are paramount. The current example uses 'permission_callback' => '__return_true' for simplicity, which is insecure.
Implementing Permissions
You should implement proper checks to ensure only authorized users or applications can access your data. This could involve:
- Checking user capabilities:
current_user_can( 'edit_posts' ). - Using nonce verification for authenticated requests.
- Implementing OAuth or API keys if the endpoint is meant for external applications.
Example with capability check:
add_action( 'rest_api_init', function () {
register_rest_route( 'acfdpf/v1', '/projects', array(
'methods' => 'GET',
'callback' => 'acfdpf_get_projects_callback',
'permission_callback' => function ( WP_REST_Request $request ) {
// Only allow users who can edit posts to access this data
return current_user_can( 'edit_posts' );
},
) );
} );
Handling API Errors
If your data source is external, it might fail. Your callback should gracefully handle these failures and return appropriate error responses.
function acfdpf_get_projects_callback( WP_REST_Request $request ) {
// Attempt to fetch data from an external API
$response = wp_remote_get( 'https://api.external-project-manager.com/projects' );
if ( is_wp_error( $response ) ) {
return new WP_Error( 'external_api_error', 'Failed to connect to the external project API.', array( 'status' => 500 ) );
}
$body = wp_remote_retrieve_body( $response );
$external_data = json_decode( $body, true );
if ( json_last_error() !== JSON_ERROR_NONE || empty( $external_data ) ) {
return new WP_Error( 'external_api_parse_error', 'Failed to parse response from external project API.', array( 'status' => 500 ) );
}
// ... (rest of the data transformation logic as before) ...
return new WP_REST_Response( $acf_compatible_data, 200 );
}
By implementing these practices, you can build highly dynamic and robust ACF PRO field extensions that integrate seamlessly with your WordPress site and external data sources.