Files
claude-websimple-devops/references/wp-acf.md

10 KiB

WP ACF

Use this skill for Websimple WordPress projects that use Advanced Custom Fields.

Use references/wp-theme-dev.md for general PHP/theme structure, references/wp-code-style.md for PHPCS/PHPCBF, references/wp-browser.md for visual checks, and references/wp-xdebug.md for runtime debugging.

Core principles

  • Keep ACF configuration predictable, portable, and version-controlled.
  • Prefer clear field names that map naturally to template usage.
  • Keep field registration/ACF hooks under includes/vendors/acf.php or the existing ACF vendor/module file.
  • Keep rendering logic in templates and reusable data helpers under includes/ when needed.
  • Avoid changing field keys, field names, return formats, or option names casually; these may affect stored content.

Local JSON

ACF field group JSON definitions should live under the active theme's acf-json/ directory.

  • Keep ACF JSON files version-controlled.
  • Preserve the project's existing JSON directory if one exists.
  • For greenfield projects, use acf-json/ at the theme root.
  • Ensure the acf-json/ directory exists and is writable by WordPress.
  • Do not delete or regenerate ACF JSON files without checking the diff.

When local JSON is enabled, saving a field group, post type, taxonomy, or options page in wp-admin automatically creates or updates its JSON file. Do not tell the user to manually re-export JSON after normal dashboard saves; instead, remind them to review and commit the changed JSON file.

There is no expected per-field-group json_sync flag in Websimple conventions. ACF local JSON is controlled by ACF's global JSON setting and save/load paths. ACF defaults local JSON on and uses the active theme's acf-json/ folder unless customized.

If PHP hooks customize ACF JSON paths, keep them in the ACF vendor/module file, e.g. includes/vendors/acf.php.

Field group keys and filenames

For greenfield field groups, use deterministic field group keys instead of ACF's auto-generated group keys.

The field group key should match the JSON filename and describe what the group targets:

acf-json/group_post_project.json
key: group_post_project

Examples:

group_post_project       # fields for the project custom post type
group_page_home          # fields for the home page/template
group_options_site       # site options fields
group_taxonomy_sector    # fields for the sector taxonomy

ACF field keys inside the group may remain auto-generated. The deterministic-key convention applies primarily to field groups so JSON files stay organized and easy to find.

For existing projects with auto-generated field group keys, preserve them unless the user explicitly approves a cleanup/migration. Do not rename existing field group keys casually because synced JSON, database references, and editor workflows may depend on them.

Synchronizing ACF JSON

For ACF 6.8+ with WP-CLI available, prefer the built-in ACF JSON CLI commands.

Safe inspection:

wp acf json status
wp acf json sync --dry-run

Apply pending local JSON changes to the database:

wp acf json sync

Useful scoped variants:

wp acf json sync --type=field-group
wp acf json sync --key=group_post_project

Notes:

  • wp acf json sync modifies the database; run --dry-run first, especially outside local development.
  • The command syncs pending local JSON changes for ACF item types such as field groups, post types, taxonomies, and options pages.
  • If no items are pending, expect an “already in sync” style result.
  • Do not use a guessed --all flag; current ACF CLI sync defaults to all supported item types unless scoped with --type or --key.

If the project uses an older ACF/ACF PRO version without these commands:

  1. Check the installed ACF version and whether an update is safe for the project.
  2. If updating is low-risk and explicitly approved, update ACF/ACF PRO first, then use the CLI workflow.
  3. Otherwise, use references/wp-browser.md to navigate wp-admin and sync from the ACF field group/admin screen.

For production or staging, ask before updating plugins or syncing database-backed ACF changes.

Field group organization

Use field groups that reflect editorial concepts and template needs.

Common grouping patterns:

  • page/template-specific fields;
  • reusable section/component fields;
  • site options;
  • post type-specific metadata;
  • taxonomy/user/menu-item metadata when needed.

Avoid dumping unrelated fields into a single large group. Prefer labels and instructions that make sense to editors.

Field naming

Use stable, descriptive, snake_case field names.

Good examples:

hero_title
hero_text
hero_image
cta_link
featured_posts
background_color

Avoid vague names like title_2, content_block, or misc unless the existing project convention requires it.

Do not rename existing field names unless the user accepts the content migration risk.

Return formats

Respect existing return formats. Changing them can break templates.

Common preferences:

  • image fields: use the format already expected by the theme; arrays are useful when templates need alt/title/sizes;
  • link fields: arrays are usually practical for URL/title/target;
  • post object / relationship fields: know whether the field returns IDs or objects before using it;
  • true/false fields: handle empty/false explicitly.

When uncertain, inspect the field group JSON or runtime value before changing template code.

Template usage

Prefer get_field() when the value needs escaping, conditionals, fallback handling, or reuse.

<?php $title = get_field( 'hero_title' ); ?>

<?php if ( $title ) : ?>
    <h1><?= esc_html( $title ); ?></h1>
<?php endif; ?>

Use compact output tags with escaping, following references/wp-theme-dev.md:

<?= esc_html( get_field( 'eyebrow' ) ); ?>
<?= wp_kses_post( get_field( 'intro' ) ); ?>

Avoid the_field() for dynamic frontend output unless the value is known safe for the exact context. Prefer explicit escaping with get_field().

For image arrays:

<?php $image = get_field( 'hero_image' ); ?>

<?php if ( $image ) : ?>
    <img
        src="<?= esc_url( $image['url'] ); ?>"
        alt="<?= esc_attr( $image['alt'] ?? '' ); ?>"
    >
<?php endif; ?>

For link arrays:

<?php $link = get_field( 'cta_link' ); ?>

<?php if ( $link ) : ?>
    <a href="<?= esc_url( $link['url'] ); ?>" target="<?= esc_attr( $link['target'] ?: '_self' ); ?>">
        <?= esc_html( $link['title'] ); ?>
    </a>
<?php endif; ?>

Options pages

Keep ACF options-page registration in the ACF vendor/module file.

Use options pages for true site-wide settings, not content that belongs to a page, post, taxonomy, or theme template.

When reading option fields, make the option scope explicit:

<?php $phone = get_field( 'phone_number', 'option' ); ?>

Flexible content and repeaters

Keep flexible content layouts readable and component-oriented.

  • Use layout names that map to template partials or sections.
  • Keep layout fields scoped to what the layout renders.
  • Avoid deeply nested repeaters/flexible fields unless there is a strong editorial reason.
  • For complex flexible content rendering, prefer dispatching to template parts instead of one giant template file.

The dispatch pattern depends on the theme. Check whether the theme is a Kaliroots child theme first (see references/wp-kaliroots.md) — Kaliroots has its own template lookup helpers and overriding them with generic WP dispatch fragments the resolution path.

For a generic (non-Kaliroots) WordPress theme:

<?php if ( have_rows( 'sections' ) ) : ?>
    <?php while ( have_rows( 'sections' ) ) : the_row(); ?>
        <?php get_template_part( 'templates/content/sections/' . get_row_layout() ); ?>
    <?php endwhile; ?>
<?php endif; ?>

For a Kaliroots child theme, dispatch through the Kaliroots section helper so child/parent override resolution works correctly:

<?php if ( have_rows( 'sections' ) ) : ?>
    <?php while ( have_rows( 'sections' ) ) : the_row(); ?>
        <?= kaliroots_section_template( get_row_layout() ); ?>
    <?php endwhile; ?>
<?php endif; ?>

In a Kaliroots project, the section partial then lives at templates/content/sections/{layout-name}.php and is located via Kaliroots' lookup pipeline — see references/wp-kaliroots.md for the helper set and naming rules.

Verify that the target partial exists before introducing either dispatch into an existing theme. If the project already uses a different established pattern, follow that — don't reorganize.

Inside the layout partial, use get_sub_field() (not get_field()) to read layout sub-fields, and escape at output:

<?php
$heading = get_sub_field( 'heading' );
$image   = get_sub_field( 'image' );
?>
<?php if ( $heading ) : ?>
    <h2><?= esc_html( $heading ); ?></h2>
<?php endif; ?>

Relationship and post object fields

Before using relationship/post object fields, confirm whether they return IDs or post objects.

  • If using objects, restore global post state after setup.
  • If using IDs, use explicit IDs with WordPress APIs.
  • Avoid expensive relationship queries inside repeated templates when values can be prepared once.

Refactoring workflow

  1. Inspect the active theme, ACF plugin availability, existing ACF JSON path, and ACF-related include files.
  2. Inspect existing field group JSON before changing field names, keys, return formats, or locations.
  3. Keep PHP hooks/registration in the existing ACF module, usually includes/vendors/acf.php.
  4. Keep template output escaped explicitly with get_field() where practical.
  5. Make small, reversible changes.
  6. After wp-admin field group edits, inspect the automatically updated acf-json/ diff.
  7. Run PHP lint/style checks via references/wp-code-style.md when PHP files changed.
  8. Smoke-test affected frontend/admin behavior with references/wp-browser.md when relevant.
  9. Inspect the diff, especially ACF JSON changes.

Safety

  • Do not rename existing field names or change return formats without calling out content/template impact.
  • Do not delete ACF JSON files without explicit approval.
  • Do not expose secrets from options pages or database values.
  • Do not assume the_field() output is safe; escape based on context.
  • Preserve the project's existing ACF structure when it is coherent.