# ACF Mode — content schema via ACF Local JSON

**Since:** 2026-06-12
**Status:** Active. The `brentwood-migration` plugin is deactivated and fully
redundant — everything it provided is defined here.

## What this is

The Brentwood content schema (custom post types, taxonomies, and custom
fields) is defined by the JSON files in this folder, loaded by **ACF Pro's
Local JSON** feature. This replaced the code-defined schema in the
`brentwood-migration` plugin (`wp-content/plugins/brentwood-migration/
brentwood-migration.php`), which originally registered the same things in PHP
"so the schema is reproducible / version-controlled."

Local JSON keeps that property — these files ARE the version-controlled
schema — while also being editable in the WordPress admin:

- Every item here is **synced into the database** (done 2026-06-12), so it
  appears under **ACF → Field Groups / Post Types / Taxonomies** and can be
  edited in the UI like any hand-made item.
- When an item is edited and saved in the UI, ACF **writes the change back to
  the JSON file in this folder** automatically. The files and the UI never
  diverge; if a JSON file is newer than the database copy (e.g. restored from
  a backup), ACF shows a "Sync available" notice on the listing screen.
- At runtime ACF loads the definitions **from these files**, not the
  database, so the schema works even on a fresh database import as long as
  the theme is present.

## What's defined here

### Post types (4)

| File | Post type | URL | Notes |
|---|---|---|---|
| `post_type_bwm_staff.json` | `staff` | `/staff/{slug}` | No archive; excluded from on-site search; supports page-attributes |
| `post_type_bwm_livestream.json` | `livestream` | `/livestreams/{id}` | Has archive; numeric slugs preserved from Laravel |
| `post_type_bwm_landing.json` | `landing` | `/l/{slug}` | Campaign/ad landing pages; no archive; excluded from search |
| `post_type_bwm_course.json` | `course` | `/courses/{slug}` | Has archive |

### Taxonomies (5, all hierarchical, admin column on)

| File | Taxonomy | Attached to | URL base |
|---|---|---|---|
| `taxonomy_bwm_department.json` | `department` | staff | `staff-department` |
| `taxonomy_bwm_livestream_category.json` | `livestream_category` | livestream | `livestream-category` |
| `taxonomy_bwm_course_category.json` | `course_category` | course | `course-category` |
| `taxonomy_bwm_course_subject.json` | `course_subject` | course | `course-subject` |
| `taxonomy_bwm_course_grade.json` | `course_grade` | course | `course-grade` |

### Field groups (5)

| File | Group | Location | Fields (name : type) |
|---|---|---|---|
| `group_bwm_page_hero.json` | **Hero** | page, post, staff, course, livestream, landing | `hero_type` : select (none/home_video/image/gallery/carousel/video/youtube), `hero_youtube_id`, `hero_video` + `hero_video_poster`, `hero_image_focus_x/y` + `hero_image_zoom` (range), `hero_gallery_rows` (repeater: image + focus_x/y + zoom), `hero_carousel` (repeater) + `hero_carousel_height(_custom)`, `hero_gallery_columns` |
| `group_bwm_staff.json` | Staff Details | staff | `staff_position` : text, `staff_credentials` : text |
| `group_bwm_livestream.json` | Livestream Details | livestream | `ls_youtube_id` : text, `ls_start` : date_time_picker (`Y-m-d H:i:s`), `ls_length` : number, `ls_unlisted` : true/false |
| `group_bwm_blog.json` | Blog Details | post | `blog_author` : text |
| `group_bwm_course.json` | Course Details | course | `course_ap` : true/false, `course_elective` : true/false, `course_credit` : text |

**The hero is one group, one renderer.** As of 2026-06-19 the hero is a single
"Hero" field group (`group_bwm_page_hero`) attached to every post type that can
have one — not a per-post-type copy. To give another post type a hero, add a
`post_type` location rule; never duplicate the fields under a new key. Default
`hero_type` is `image` for page/post (historic "auto featured-image hero")
and forced to `none` on staff/course/livestream/landing by an
`acf/load_value/key=field_bwm_page_hero_type` filter in `inc/brentwood-hero.php`,
so those types show no hero unless an editor opts in. The white text card is a
separate block (`brentwood/hero-card`); the renderer only draws the media.

## Why the data "just worked" (field keys matter)

ACF stores each value as two rows of post meta: the value under the field
*name* (e.g. `staff_position`) and a reference under `_<name>` pointing to
the field *key* (e.g. `_staff_position` → `field_bwm_staff_position`).

These JSON files deliberately **reuse the original `field_bwm_*` keys** from
the migration plugin. That is why all existing data (≈6,000 posts of
`blog_author`/`hero_type`, 170 staff, 289 livestreams, 136 courses, 123
galleries) stayed perfectly linked with zero database changes.

**Do not change a field's key or name** when editing in the UI — the key
links every existing value. Adding new fields is always safe.

**Hero consolidation (2026-06-19).** The post hero fields formerly lived in
`group_bwm_blog` under `field_bwm_hero_*` keys. They were removed there and the
single `group_bwm_page_hero` group (`field_bwm_page_hero_*` keys) now drives the
hero on posts too. ACF reads values by *name* (`hero_type`, `hero_video`, …),
which is identical across both, so all ≈6,000 posts kept their values with no
data change; the stale `_hero_type` reference simply re-points to the new key on
each post's next save. The 123 `gallery` posts had their flat `hero_gallery`
field copied into the new `hero_gallery_rows` repeater (centered focus) in the
same pass — the legacy `hero_gallery` meta is left in place as a fallback.

## Related code in the child theme

- **Hero renderer** — `inc/brentwood-hero.php`: one renderer (`bw_render_hero`)
  reads the Hero fields by name and draws the media for page/post (and, after
  the 2026-06-19 work, landing + the CPTs when a hero is explicitly set). It
  supersedes the old interim `post_thumbnail_html` gallery renderer.

## Things that are NOT part of ACF mode

- `_bw_src_*`, `_bw_done`, `_bw_admin_note` post meta — bookkeeping left by
  the one-off Laravel import script. Not registered as fields anywhere;
  harmless; can be purged before launch.
- `_bw_dev_title_override` — long Laravel page titles, owned by the **bw-dev**
  plugin's Title Override module (separate plugin, still active).
- `footer_image_background` — a UI-created field in the separate
  "Page Settings" group (made directly in ACF, never part of the plugin).

## Gotchas

- The hero gallery/carousel/focus fields appear on **every post type the Hero
  group is attached to**, each only when its matching "Hero type" is selected
  (conditional logic hides the rest). The old flat "Featured Gallery" field
  (`hero_gallery`) is retired in favour of the `hero_gallery_rows` repeater;
  it survives only as a render fallback for any unmigrated row.
- If a post type or taxonomy ever appears missing after a restore: check
  ACF Pro is active, then re-save permalinks (Settings → Permalinks).
- The files in this folder must stay group-readable (664) so WordPress can
  load them, and the folder writable (775) so UI edits can save back.
