Ghost Schema & Proxies
Ghost Schema
Section titled “Ghost Schema”Traditional CMS platforms require you to define a schema up front — you create content types, add fields, specify validation rules, and only then can you use them in your code. ACMS flips this entirely.
With ACMS, the schema is generated automatically based on how you access content in your code. We call this a Ghost Schema because it materializes from your code’s behavior rather than from explicit definitions.
// You write this in your component:<h1>{acms.hero.title}</h1><p>{acms.hero.subtitle}</p><a href={acms.hero.ctaLink}>{acms.hero.ctaText}</a>The moment this code runs, ACMS detects three fields: hero.title, hero.subtitle, hero.ctaLink, and hero.ctaText. They are registered in acms.json with empty values, ready for editing.
No schema file. No content model definition. No configuration panel. The code is the schema.
The Proxy System
Section titled “The Proxy System”At the heart of ACMS is a JavaScript Proxy that intercepts property access on the acms object.
import { acms } from '@useacms/client';
// When you access acms.hero.title, the proxy:// 1. Intercepts the property access// 2. Checks if 'hero.title' has a value// 3. If not, POSTs to the dev server to register the field// 4. Returns the current value (or empty string)const title = acms.hero.title;The proxy is recursive — accessing acms.hero returns another proxy, so acms.hero.title triggers two interceptions. The system tracks the full path (hero.title) to register the correct nested field.
How It Works in Development
Section titled “How It Works in Development”Your Code Client Proxy Dev Server (port 3001)───────── ──────────── ──────────────────────acms.hero.title ──────────► Intercepts access │ ├─ Field exists? ──► Yes ──► Return value │ └─ Field missing? ──► POST /api/register ──► Creates field in acms.json Regenerates acms.d.ts ◄── Return empty valueHow It Works in Production
Section titled “How It Works in Production”In production, there is no dev server. The client fetches content from your configured storage adapter (GitHub, Vercel Edge Config, Cloudflare KV, etc.) and the proxy returns values from the fetched content object.
Your Code Client Proxy Storage Adapter───────── ──────────── ───────────────acms.hero.title ──────────► Intercepts access │ ├─ Content loaded? ──► Yes ──► Return value from cache │ └─ Not loaded? ──► Fetch from adapter ──► Cache and returnField Registration
Section titled “Field Registration”When the dev server receives a registration request, it performs several steps:
- Creates the field in
acms.jsonat the specified path with an empty value. - Adds metadata in the
_metasection with the field type and access timestamp. - Handles nesting — if
heroalready exists as a string and you accesshero.title, the system migratesheroto an object with adisplayNameproperty preserving the original value. - Regenerates types —
acms.d.tsis updated with the new field definition.
Sequential Registration
Section titled “Sequential Registration”Multiple fields may be registered simultaneously when a page renders. The dev server uses a promise queue to handle registrations sequentially, preventing race conditions when writing to acms.json.
Reserved Field Names
Section titled “Reserved Field Names”Two field names are reserved and cannot be used as content fields:
displayName— Used internally for the field migration system_meta— Stores field metadata (types, timestamps, labels)
Next Steps
Section titled “Next Steps”- Learn about Fields & Types and how ACMS infers them
- Set up your Configuration for production