Schema
Your Schema is the canonical catalog of the events and attributes your product sends to Apex: their names, types, descriptions, surface coverage, and whether Apex has actually seen them in your live data. It is the single source of truth that the communication composer, segments, and goals all read from, so a variable looks and resolves the same way everywhere you reach for it.
Find it at Settings → Workspace → Schema
(/dashboard/settings/workspace/schema). It populates automatically
from your apex.identify() and apex.track() calls. You review and curate it
here; you do not have to build it by hand.
Canonical attributes
Every workspace ships with a canonical attribute registry in snake_case. These are the standard identity traits Apex recognizes, grouped by entity (User, Account, Order, Product):
| Canonical | Type | Common aliases Apex maps |
|---|---|---|
first_name | string | firstName, fname, given_name |
last_name | string | lastName, family_name, surname |
email | emailAddress, email_address | |
avatar_url | url | avatarUrl, photoUrl, image, profile_image |
postal_code | string | zip, zipcode, postalCode |
country / country_code | string | countryName / countryCode |
plan | string | planName, tier, subscription_plan |
lifetime_value | number | ltv, lifetimeValue |
lifecycle_stage | string | lifecycle, funnel_stage, stage, customer_stage |
Custom attributes stay first-class. The registry only defines what Apex recognizes as canonical; anything else you send shows up as a custom field you can keep, describe, or map.
Aliasing: send what you have, reference one clean name
You do not have to rename your traits to use Apex. If your app sends firstName
or zip, Apex maps them onto the canonical first_name and postal_code. The
composer shows one clean canonical variable, and {{first_name}} resolves at
send time no matter which alias the payload used.
// Your app can send either of these:
apex.identify("u_123", { firstName: "Alex", zip: "94105" });
apex.identify("u_123", { first_name: "Alex", postal_code: "94105" });
// In a template, you always reference the canonical name:
// "Hi {{first_name}}" → "Hi Alex"
Info
Prefer canonical snake_case names at the source. They skip the mapping step
entirely, so your Schema stays clean and every variable is green from day one.
The SDK exports a CanonicalUserTraits type for autocomplete.
Wired vs not wired
Apex marks each variable wired (green) once it has observed it in a real payload, and not wired (red) when it is a canonical field you have not sent yet.
This is observation-based, never code analysis. A variable turns green only
after a real identify or track flows through POST /api/events. Green proves
your instrumentation works end to end, not just that the code was written. There
is no way to mark something wired without data flowing, by design.
In the composer's Variables panel and the {{ autocomplete, an unwired variable
shows a red dot and a Set up link that deep-links into the right install
step. Fire the event (or a test event) and the next read shows green.
Surfaces
Surface is a dimension of a variable, not a separate copy. A single attribute
observed on both web and iOS carries web and ios coverage chips, so you can
see at a glance where each event and attribute is actually flowing.
Mapping a non-canonical field
If your workspace already sends a non-canonical name and you want it to resolve under its canonical name, map it:
- In the dashboard: Settings → Workspace → Schema, then click Map to
<canonical>on the field. - Programmatically:
POST /api/schema/mapwith{ entityKey, fieldName, canonicalName }(passcanonicalName: nullto clear). - From an AI agent (MCP): the
map_attributetool, andget_schemato inspect the current Schema and coverage.
Next steps
- Instrumenting your app — client vs server, and canonical names
- Custom events —
apex.trackpayloads - The Apex Spec — the canonical event taxonomy
- Segments — slice customers by these attributes