Skip to content

ACF Builder (Island Pattern)

The ACF_Builder class is the core of how we define ACF fields in the Terra Framework. Instead of writing large ACF JSON exports or manually registering fields, we compose them from small, reusable “islands” — static methods that each return a single field configuration.

This pattern is inspired by the Sanity.io schema approach and is used across Flexible Content modules, Options Pages, Post Type Fields, and Custom Blocks.


  • Atomic fields: Each method returns one field config, ready to compose
  • Composable: Combine fields with array_merge() — no nesting complexity
  • Consistent defaults: Every field comes with sensible defaults you can override
  • Conditional logic: Built-in show_when shortcut for field visibility
  • Deterministic keys: Field keys are generated with md5(), not random — safe for migrations

Each static method returns a single-element array. You compose them together:

<?php
'fields' => array_merge(
ACF_Builder::spacing(),
ACF_Builder::bg_color(),
ACF_Builder::title(['name' => 'heading']),
ACF_Builder::wysiwyg(['name' => 'description', 'toolbar' => 'basic']),
ACF_Builder::image(['name' => 'photo']),
ACF_Builder::link(['name' => 'cta_button']),
)

Every method accepts an optional $args array to override defaults like name, label, width, etc.


These are used in almost every module to control spacing and background:

<?php
// Section spacing (top/bottom padding)
ACF_Builder::spacing()
// Background color selector (visual color swatches)
ACF_Builder::bg_color()
ACF_Builder::bg_color(['palette' => 'white-ebony'])
// Convenience: spacing + bg_color together
ACF_Builder::section_base()
<?php
ACF_Builder::title() // Single-line text (name: 'title')
ACF_Builder::title(['name' => 'heading', 'label' => 'Heading'])
ACF_Builder::subtitle() // Single-line text (name: 'subtitle')
ACF_Builder::pretitle() // Single-line text (name: 'pretitle')
ACF_Builder::text() // Textarea (name: 'text')
ACF_Builder::text(['name' => 'description', 'rows' => 3])
ACF_Builder::wysiwyg() // Rich text editor (name: 'content')
ACF_Builder::wysiwyg(['toolbar' => 'basic', 'media_upload' => 0])
<?php
ACF_Builder::image() // Image upload (name: 'image')
ACF_Builder::image(['name' => 'icon', 'label' => 'Icon'])
ACF_Builder::file() // File upload (name: 'file')
ACF_Builder::url(['name' => 'video_url']) // URL field
<?php
ACF_Builder::link() // Link/button (name: 'button')
ACF_Builder::boolean(['name' => 'show_cta']) // True/false toggle
ACF_Builder::select([ // Dropdown
'name' => 'layout',
'choices' => ['left' => 'Left', 'right' => 'Right'],
])
ACF_Builder::number(['name' => 'columns', 'min' => 1, 'max' => 6])
ACF_Builder::date(['name' => 'event_date']) // Date picker
ACF_Builder::taxonomy(['name' => 'topic', 'taxonomy' => 'resource-topic'])
ACF_Builder::geolocation() // Google Maps
<?php
// Repeater: list of items with sub-fields
ACF_Builder::repeater([
'name' => 'cards',
'label' => 'Cards',
'fields' => array_merge(
ACF_Builder::title(['name' => 'card_title']),
ACF_Builder::text(['name' => 'card_description']),
ACF_Builder::image(['name' => 'card_image']),
),
])
// Relationship: select existing posts
ACF_Builder::relationship([
'name' => 'related_resources',
'post_type' => ['resource'],
'max' => 3,
])
// Group: groups fields visually (no repeating)
ACF_Builder::group([
'name' => 'cta_settings',
'fields' => array_merge(
ACF_Builder::title(['name' => 'cta_text']),
ACF_Builder::url(['name' => 'cta_url']),
),
])
<?php
// Tab separator in the admin
ACF_Builder::tab(['label' => 'Content'])
ACF_Builder::tab(['label' => 'Settings'])
// Read-only message/note for editors
ACF_Builder::note(['message' => 'Keep the title under 60 characters for SEO.'])

Any island accepts a show_when parameter to conditionally show/hide the field based on another field’s value:

<?php
ACF_Builder::boolean(['name' => 'enable_button', 'label' => 'Show Button?']),
ACF_Builder::link([
'name' => 'button',
'show_when' => ['enable_button', '==', '1'],
]),

The framework automatically resolves show_when to ACF’s conditional_logic format at registration time — you never need to deal with field keys manually.


Here’s how a complete flexible content module uses the ACF Builder:

functions/project/config/flexible-modules/image_text.php
<?php
return array(
'label' => 'Image + Text',
'fields' => array_merge(
ACF_Builder::spacing(),
ACF_Builder::bg_color(['palette' => 'default']),
ACF_Builder::pretitle(),
ACF_Builder::title(['name' => 'heading']),
ACF_Builder::wysiwyg(['name' => 'content', 'toolbar' => 'basic']),
ACF_Builder::image(['name' => 'side_image']),
ACF_Builder::select([
'name' => 'image_position',
'label' => 'Image Position',
'choices' => ['left' => 'Left', 'right' => 'Right'],
]),
ACF_Builder::boolean(['name' => 'show_button', 'label' => 'Show Button?']),
ACF_Builder::link([
'name' => 'button',
'show_when' => ['show_button', '==', '1'],
]),
),
);

Every island accepts these common parameters via the $args array:

ParameterDescription
nameField name (used in get_field())
labelDisplay label in the admin
widthWrapper width percentage (e.g., '50' for half-width)
show_whenConditional visibility: ['field_name', '==', 'value']
requiredWhether the field is required (1 or 0)
instructionsHelp text shown below the label
wrapperCustom wrapper attributes (class, id)

Plus type-specific parameters like rows, toolbar, choices, min, max, return_format, etc.

Knowledge Check

Test your understanding of this section

Loading questions...