---
type: plan
project: leaguelaw
title: staff → lawyers post type migration
status: DONE — executed and verified 2026-08-10
created: 2026-08-10
---

> **Executed 2026-08-10.** 13 lawyers migrated, all URLs and featured images
> intact, 12 clean emails. Kept as the record of *why* the registration looks the
> way it does — the two traps below are still live constraints on the code in
> `themes/kadence-child/includes/post-types.php`.

# `staff` → `lawyers` migration spec

Decided 2026-08-10. Replace the Total-theme-owned `staff` CPT with our own
`lawyers` CPT carrying **position, phone, email**. Migrate content only — the
layout is rebuilt in Kadence and nothing about the old presentation is preserved.

**Prerequisite:** the Kadence child theme must exist first, because it is what
registers the new CPT. Registration goes in ACF Local JSON at
`themes/kadence-child/acf-json/`, matching how other WordPress projects here
define post types.

## The URLs do not change — and that is the point

Current lawyer URLs are `https://www.leaguelaw.com/lawyers/<name>/`. That comes
from the Total theme's `staff_slug` Customizer setting, which is set to
**`lawyers`**. So:

- Register the new CPT as **`lawyers`** with `rewrite => [ 'slug' => 'lawyers' ]`
- Migrate by changing `post_type` on the existing rows — **do not create new
  posts**. IDs, slugs, meta, featured images and Yoast data all stay attached.
- Result: **identical URLs, no redirects needed.** 13 lawyer pages keep their
  rankings.

### ⚠️ Two traps

1. **`has_archive` must stay `false`.** The page "Our Lawyers" (ID 14661) has the
   slug **`lawyers`** — the same string as the CPT rewrite slug. This only works
   today because Total registers the CPT with `has_archive => false`. Register the
   new CPT with an archive and it collides with that page, and one of them stops
   resolving.
2. **The slug lives in a Total theme mod.** Theme mods are stored per-theme
   (`theme_mods_Total`), so the moment Total is deactivated that setting is gone.
   The new CPT must **hardcode** `'slug' => 'lawyers'` rather than inherit it.

## Fields — just `email`

**Revised 2026-08-10.** Position, phone, and the `staff_category` taxonomy are all
**dropped**, not migrated. The stale data is not worth carrying:

| Dropped | Why |
|---|---|
| `position` | Only ever "Principal" (Darren) / "Associate" (all others) — stale |
| `phone` | Every record holds the same firm main line; no direct lines exist |
| `staff_category` | Lawyer ×11, Principal Lawyer ×1, Articling Student ×0 — duplicated `position` |

That leaves the `lawyers` CPT as **title + content + featured image + `email`**.

> **Flag for the schema phase:** Person schema uses `jobTitle`, so a position
> field will most likely need to come back once the client confirms the roster.
> Dropping the stale values now is right; expect to re-add the field with fresh
> data rather than treating it as gone forever.

### `email` is an ACF Link field, not text

It stores a serialized array (`title` / `url` / `target`), so migration has to
extract a value rather than copy the meta across:

```
a:3:{s:5:"title";s:23:"dwilliams@leaguelaw.com";
     s:3:"url";s:30:"mailto:dwilliams@leaguelaw.com";s:6:"target";s:0:"";}
```

**Extraction rule: take `url`, strip `mailto:`, then trim.** Three records
(Lisa Guidi, Reid Warren, Rob Graham) have `mailto: ` with a **stray space**, so
trim after stripping. Do *not* use `title` — for Reid Warren and Rob Graham it
holds a label ("Email Rob Graham") rather than an address.

**Joji Kawaguchi — resolved.** Use **`jkawaguchi@leaguelaw.com`** (the ACF value).
The older `jjordan@leaguelaw.com` in the Total field is a pre-name-change address;
confirmed by Rian 2026-08-10.

**"IS IT YOU?"** has no email and is a careers call-to-action dressed as a staff
member. **Migrate it with the rest for now** — it keeps its ID and URL, costs
nothing, and Rian has deferred the decision. It becomes a proper CTA during the
design phase, not now.

## Featured images — already done

**All 13 records already have `_thumbnail_id` set.** There is no per-lawyer
header-image field to convert; the only header-image setting is the *global* ACF
options-page field `lawyer_header_image`, which is a single image for the lawyers
page header, not per-person. So "header image becomes featured image" needs no
migration — the featured images are already in place and carry over untouched
with the `post_type` change.

## Metronet Profile Picture cross-check — safe to remove

Cross-checked all 8 `mt_pp` records against the 13 lawyer records. **Only 4 of
the 8 have an image at all.**

| `mt_pp` for | Its image | Lawyer record's featured image | Verdict |
|---|---|---|---|
| Darren Williams | 17175 | **17175** | **Identical — already preserved** |
| Andrew Broadley | 15825 | 21502 | Lawyer record has a different, newer photo |
| Georgia Lockhart | *(none)* | 21665 | Nothing to lose |
| Keenan VerBrugge | *(none)* | — | Not a lawyer record |
| Janice Williams | 16894 | — | **No lawyer record exists** (see below) |
| "Rob Anderson" | 17245 | — | Not a current lawyer |
| "Austin Darling" | *(none)* | — | Not a current lawyer |
| Site Admin | *(none)* | — | — |

**Conclusion: no lawyer loses a photo.** Every one of the 13 already has its own
featured image, and Darren's is literally the same attachment. Removing Metronet
is safe. Note the images themselves stay in the media library either way — only
the user-to-image association disappears.

**The real thing Metronet was doing is author avatars on blog posts.** Dropping it
removes those bylineimages. The durable replacement is binding blog author *users*
to *lawyer records*, which is exactly what the AI schema work needs anyway — so do
that rather than trying to preserve `mt_pp`.

**Janice Williams — resolved: not a lawyer, no record.** Confirmed by Rian
2026-08-10; she runs the blog rather than practising. This has a consequence for
the schema work — see `.memory/author-attribution.md`.

## Taxonomy — dropped

`staff_category` (Lawyer ×11, Principal Lawyer ×1, Articling Student ×0) is
**not carried over**. Decided 2026-08-10. Check before deleting the terms that
nothing in the current templates orders or filters by it — the new team page
should not need it.

## Execution order

1. Snapshot the database (fresh one, not the pre-theme-swap snapshot).
2. Kadence + child theme installed; `acf-json/` created in the child.
3. Register the `lawyers` CPT — `slug => 'lawyers'`, `has_archive => false`,
   `supports` including `thumbnail`, `show_in_rest => true`.
4. Define one field on it: `email`, plain text.
5. **Migrate rows**: change `post_type` from `staff` to `lawyers` on all 13.
   Never re-create posts, never touch slugs.
6. **Transform the meta**: write `email` per the extraction rule above.
7. **Verify before removing anything:**
   - all 13 list in wp-admin under Lawyers
   - `/lawyers/darren-williams/` still resolves — spot-check three
   - all 13 still have their featured image
   - 12 of 13 have a clean email ("IS IT YOU?" legitimately has none), with no
     `mailto:` fragment or leading space left in any value
8. Clean up the dropped meta (`position`, `phone`, and their Total-theme
   equivalents) and the `staff_category` terms.
9. Only then remove Total, Metronet, Molongui, and the old ACF groups.
