Advanced Custom Fields (ACF) – fluent-wp-client
Overview
Section titled “Overview”fluent-wp-client derives its discovery catalog from the REST schema returned by WordPress OPTIONS requests. When ACF schema metadata is available in that catalog, read it with getSchemaValue() instead of hard-coding individual field paths in UI components.
ACF does not expose choice labels in its REST schema by default. Add the keys you want to discover with acf/rest/get_field_schema:
<?php/** * Adds ACF field descriptions and choice labels to the REST schema. */add_filter( 'acf/rest/get_field_schema', static function ( array $schema, array $field ): array { if ( ! empty( $field['description'] ) ) { $schema['description'] = $field['description']; }
$choice_field_types = [ 'select', 'radio', 'checkbox', 'button_group' ];
if ( isset( $field['type'], $field['choices'] ) && in_array( $field['type'], $choice_field_types, true ) && is_array( $field['choices'] ) ) { $schema['choices'] = array_map( static fn ( $value, $label ): array => [ 'value' => (string) $value, 'label' => (string) $label, ], array_keys( $field['choices'] ), array_values( $field['choices'] ) ); }
return $schema; }, 10, 2);For UI code that does not know each field type ahead of time, fetch the full ACF schema field map once and build a reusable choice-label lookup.
type AcfChoice = { label: string; value: string;};
type AcfSchemaField = { choices?: AcfChoice[];};
const acfFields = await wp.content("posts").getSchemaValue< Record<string, AcfSchemaField>>( "properties.acf.properties",);
const choiceLabels = new Map<string, Map<string, string>>();
for (const [fieldName, fieldSchema] of Object.entries(acfFields ?? {})) { if (!Array.isArray(fieldSchema.choices)) continue;
choiceLabels.set( fieldName, new Map( fieldSchema.choices.map((choice) => [choice.value, choice.label]), ), );}Use that lookup while rendering ACF values. Fields without choices automatically fall back to their raw value.
Handle empty choice values
Section titled “Handle empty choice values”Do not assume that a choice field always contains one of its configured choice values. ACF’s empty REST value depends on the field type, configuration, plugin version, and how the value was cleared. Consumers should be prepared for null, false, "", or [] at this boundary.
In the project’s ACF 6.7.1 integration environment, a nullable single select that is cleared with null through REST reads back as "". A stylized select (ui: 1) cleared with ACF’s cross icon also reads back as "" after the post is saved. A cleared multi-select reads back as []. A true/false field reads back as boolean false, but that value means off, not empty.
Only interpret false as empty when the discovered field has choices. Do not apply a general falsy check to every ACF value: it would erase the valid off state of true/false fields and can also mishandle numeric or string choices such as 0 and "0".
If a stored choice is later removed from the field configuration, ACF preserves the stored value. The REST response still contains that raw value even though the current choices metadata no longer advertises it. Treat an unknown non-empty choice as stale data rather than as an empty field. The rendering helper below already does this by falling back to the raw value when no label is available.
function getAcfDisplayValue(fieldName: string, value: unknown): string { const labels = choiceLabels.get(fieldName);
if (labels) { const values = Array.isArray(value) ? value : [value];
return values .filter( (item) => item !== null && item !== undefined && item !== false && item !== "", ) .map((item) => labels.get(String(item)) ?? String(item)) .join(", "); }
if (value === null || value === undefined) return "";
return String(value);}
const post = await wp.content("posts").item("test-post-001");const acf = post.acf && typeof post.acf === "object" ? post.acf : {};
const renderedFields = Object.entries(acf).map(([fieldName, value]) => ({ fieldName, value, label: getAcfDisplayValue(fieldName, value),}));AI SDK integrations can still use describeResourceTool to expose the same descriptions and choice labels to a model, while read tools use the discovered field paths for validation.