Skip to Content
StreamsSchemas

Schemas

A schema declares what an event payload looks like: which fields it carries, and what type each one is. Schemas are workspace-scoped and reusable — one schema can describe the traffic from many event sources.

A schema on its own describes; it does not reject anything. Deciding what happens when an event disagrees with the declaration is a contract’s job. The two are separate objects and it is worth keeping them apart in your head:

SchemaContract
ScopeWorkspace-wide, attached to sourcesOne event source, one event name
Says”These fields exist, with these types""Reject / warn / allow when they do not”
Effect on trafficNone by itselfCan block an event at ingest
VersionedYes — saving bumps the versionNo

Everything downstream reads the schema’s flattened field list: the forwarder’s column resolution, the audience filter builder’s property picker, and the warehouse table shape all come from it.

The two ways to write one

Both tabs of the schema editor describe the same definition, and switching between them converts. Save whichever you were working in.

Field list

A flat list of dot-paths, each with a type and a Required checkbox:

PathTypeRequired
order_idString✓
cart.totalNumber✓
cart.itemsArray of Objects
placed_atTimestamp

A dot-path addresses a nested field, so cart.total declares total inside the cart object. This is usually the faster way in, and it is the representation the rest of the platform reads.

JSON Schema

A constrained subset of JSON Schema, which you can paste or upload as a file:

{ "type": "object", "properties": { "order_id": { "type": "string" }, "cart": { "type": "object", "properties": { "total": { "type": "number" }, "items": { "type": "array", "items": { "type": "object", "properties": { "sku": { "type": "string" } } } } } }, "placed_at": { "type": "string", "format": "date-time" } }, "required": ["order_id"] }

Supported keywords are type, properties, items, required, and format: date-time. Composition and reference keywords — $ref, allOf, anyOf, oneOf, not, patternProperties, if/then/else, $dynamicRef — are rejected with an error rather than ignored, because the derived field list would not represent what the schema meant.

Two bounds apply to both representations: at most 200 fields, and at most 6 levels of nesting.

Field types

TypeJSON it describesNotes
String"abc"
Number42, 99.99JSON Schema integer folds into this
Booleantrue, false
Timestamp"2026-03-15T14:30:00Z"A string with format: date-time
JSON / Object{ … }An object whose inner fields you are not declaring
Array[1, 2, 3]An array of scalars
Array of Objects[{ … }, { … }]Declare the element’s fields with dot-paths

Leaving Required unchecked marks the field nullable: it may be absent, or present as null.

Attaching a schema to a source

An event source points at one schema through its Attached schema setting.

  • Standard SDK sources always use the built-in standard schema and cannot take a custom one. It is read-only, and the editor renders it as a field catalogue rather than a form.
  • Custom sources either attach a schema you wrote, or let auto-discovery infer one.
  • A Braze Currents source can attach the prebuilt Braze Currents schema.

Pending schema review

A custom source with no schema attached sits in pending_schema. This state is worth understanding precisely, because it is neither healthy nor idle:

Events are accepted and observed, and not delivered downstream — no forwarding rule sees them and nothing is written to the warehouse.

You leave it by promoting the discovered schema, attaching one you wrote, or activating the source without a schema. Traffic starts flowing within about five minutes of activation.

Auto-discovery

While a custom source is sending, ingest infers a schema from live traffic and merges what it sees per source. Arrays are sampled by their first element, so an array of objects is discovered as Array of Objects.

From the source’s page you can:

  • Review the discovered fields and their observed types;
  • Promote the discovery into a real schema, attached to that source;
  • Reset it, discarding what has been observed so far and starting the inference again — useful after fixing an instrumentation bug that taught it the wrong shape.

Drift

Once a schema is attached, incoming traffic is compared against it and the source reports one of three states:

StateMeaning
NoneTraffic matches the declaration
New fieldsEvents carry fields the schema does not declare
Type conflictA declared field is arriving as a different type

Both drift states are informational — a schema does not block. The schema’s detail page renders the difference as a diff: the declared type beside the observed one, per path, with conflicts above merely-undeclared fields. A path that only ever arrives as its declared type is not drift, and integer satisfies a declared Number.

To make disagreement actually reject an event, add a contract with enforcement set to Block.

Versions and status

Saving a schema bumps its version, and downstream consumers re-sync against the new one — so a change to a widely-attached schema is not a local edit. A schema can be Active or Archived; archiving keeps it readable and out of the attach dropdown.

Last updated on