OriginChain docs
reference · schemas

Schemas

A schema is the shape of your data - like the columns of a spreadsheet. You write it as a small TOML file, send it to OriginChain once, and then you can save and query rows that follow that shape.

Three pages, three reading paths. This page is the overview - read it first. Tutorial walks you through building one from scratch, step by step. Reference is the field-by-field manual you reach for once you know what you're looking for.

The smallest possible schema.

This is the minimum viable schema - one table with three columns. Save it as manifest.toml.

# manifest.toml - the smallest schema you can write.
namespace   = "shop"
table       = "customers"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "email"
ty   = "str"

[[columns]]
name = "signup_ms"
ty   = "u64"
what each piece means
Piece What it does
namespace A logical grouping for related tables. Like a Postgres schema. Use one per product area: shop, analytics, auth.
table The table's name. You query it as namespace.table - here, shop.customers.
primary_key A list of column names that uniquely identify a row. Most tables use a single ID column; put it in the array.
[[columns]] One block per column. name = the column name, ty = the type. Set required = true on columns that can't be null.

Six core types - and purpose-built ones on top.

Six base scalars cover most data. On top of them OriginChain ships purpose-built types - timestamp, date, uuid, decimal, enum, json, list, inet, and more - each format-validated on write. See the schema reference for the full list.

Type Use it for
str Any text. Names, emails, IDs (including ULIDs and UUIDs - they travel as their canonical text form), category labels, status strings, JSON blobs you don't need to query into.
i64 Signed 64-bit integers. Use for money stored in minor units (cents, paise, satoshis), quantities, refundable counters. Never use floats for money.
u64 Unsigned 64-bit integers. Monotonic counters, sequence numbers. For event time prefer the native timestamp type.
f64 Double-precision float. Use for rates, ratios, percentages, scientific measurements - things where rounding doesn't matter.
bool A single yes/no value.
bytes Opaque binary blob. Use for compressed payloads, encoded vectors stored alongside rows, anything that's already a byte sequence.

Purpose-built types

Declared the same way (ty = "timestamp") and validated on write - a wrong-shaped value is rejected with a 400. Each stores on the same substrate as its backing scalar, so filters and range scans stay fast.

Type For
timestampEvent time - epoch milliseconds. Range-scans like an integer.
dateCalendar date (epoch days), no time component.
intervalA duration in milliseconds.
uuidRFC-4122 UUID, format-validated.
decimalExact money - e.g. "19.99", sent as a string. No float rounding.
enumA string constrained to a declared set of variants.
textUnbounded UTF-8 prose.
jsonA structured JSON object or array, stored verbatim.
listA homogeneous array with an optional element type.
inetAn IPv4 or IPv6 address.
pointA geographic point - {lat, lng} or [lng, lat].

Register a schema.

Once you've written your manifest.toml, send it to OriginChain. After this succeeds, the table exists and you can start saving rows.

POST /v1/tenants/:t/schemas
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/schemas" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: text/plain" \
  --data-binary @manifest.toml
common mistakes
  • Wrong Content-Type. The body is TOML, not JSON. Use Content-Type: text/plain.
  • Forgetting primary_key. Every schema needs at least one column listed in the top-level primary_key = ["..."] array.
  • Wrong column type. Only the six types above work. ulid, decimal, timestamp, vector, string, and int all return 400.

What you can add beyond the basics.

The minimal schema above lets you save and read rows. Add any of these blocks when you need more:

What is not in the schema.

Two things people expect to declare on the schema but don't:

  • Vector embeddings. There is no [[extractions.vector]] block. Vectors are stored on a separate runtime endpoint (POST /vector/:table/put) and reference rows by primary key. See Vector tables.
  • Full-text indexes. There is no [[extractions.fts]] block. Full-text indexes live on their own runtime endpoint (POST /fts/:table/:field). Tokenizer and analyzer pipeline live on the index call, not the schema. See FTS tables.

The schema is purely the row contract. Vector and full-text are auxiliary indexes that link back to rows by primary key.

Where to go next.