CyclingHero Admin
The admin panel where every form draws itself from a metadata endpoint
Internal admin for the CyclingHero ops team — a generic, metadata-driven form engine that turns any new content type into a working CRUD UI without writing a form.
- statuslive
- modelretainer
- since2022
- > built and maintained alongside the main CyclingHero site
- > no public links
CyclingHero Admin is the internal panel the ops team uses to plan tours, manage bookings, build day plans, edit climbs and routes, and curate the content the customer-facing site reads. Sister project to the public CyclingHero site — same domain, different audience.
The headline: forms that build themselves
The interesting design decision is that no form is hard-coded. Every content type — tour, booking, route, climb, day plan, special event — exposes a /{contenttype}/__metadata endpoint that returns a list of field descriptors:
{
key: "title",
type: "richtext",
required: true,
show_in_list: true,
// ...
}
A single <GenericEditForm /> component fetches the metadata + entity in parallel, groups fields into Basic / Media / Relationships, and a <FormField /> dispatcher renders the right component per type:
richtext→ Tiptap editor with the toolbar plumbed inassetdata/asseturl→ asset picker with inline preview and drag-reorderarray→ tag input or asset list (depending onitemtype)child_relation→ searchable foreign-key picker (modal, multi-select)boolean→ checkbox;datetime→ date input; default → text
metadata.fields[]
│
▼
┌─────────────────────┐
│ <GenericEditForm/> │ ── fetches metadata + entity
└──────────┬──────────┘
│ for each field:
▼
┌─────────────────────┐
│ <FormField │
│ type={…} /> │
└──────────┬──────────┘
│
┌───────┼───────────────────────────┐
▼ ▼ ▼ ▼
richtext assetdata array child_relation
│ │ │ │
Tiptap AssetPicker TagInput RelationPicker
(modal)
$ tplocic stats --admin
- field types in the dispatcher 8
- loc in the generic form engine 655
- hard-coded forms 0

Adding a new content type means adding a row in the metadata endpoint, not building a form. Dirty-state tracking, validation, save flow, delete flow, and field grouping all come for free.
Other bits
- Multi-environment — every route is prefixed with
/[env](prod,test,app,local), so the same UI talks to dev/test/prod cleanly. Query keys are env-prefixed too, so caches don’t bleed across environments. - Iron-session auth at the middleware layer with public-path allowlists.
- TanStack Table for the list views with sorting and filtering.
- Mapbox GL for the route and climb editing surfaces.
- Asset bundle loader — fetches assets in batch by key, keyed off a singleton
AssetMapso the same asset isn’t requested 12 times on a busy page.
Role
Long-running engagement alongside the public CyclingHero site. The form engine and the metadata convention were built once and now power every CRUD screen the team uses day-to-day.