OriginChain docs
dashboard · schema

Create a schema in the dashboard.

A table has to exist before anything can be written to it. This page registers one entirely from the console - no SDK, no cURL. You'll use the Schema designer, its visual Builder, the raw TOML manifest behind it, and the Index config dialog for full-text and vector settings. The table built here - shop.orders - is the same one the quickstart uses, so the two pages tell one story.

You need a running instance. If you don't have one yet, start at Create a free account.

1

Where schemas live.

In the console sidebar, open Data → Schema. Every table registered on the instance selected in the top bar is drawn as a node on one canvas; the lines between nodes are relations, labelled with the column that points across.

The console Schema designer for a demo account. Two table nodes - orders with 12,480 rows and customers with 3,120 rows - sit on a dark canvas joined by an edge labelled customer. The right rail shows the selected orders table: namespace shop, its six columns id, customer, amount_cents, status, notes and placed_ms with their types, and one outgoing edge to shop.customers.id.
Data → Schema. Click any node to inspect its columns, indexes and edges in the right rail.
namespace vs table

Every table is identified by namespace.table - here, shop.orders. A namespace is a grouping label, not an object you create: there is no "new namespace" button, and no separate step to make one. You type a namespace when you register a table, and it appears in the all namespaces dropdown as soon as a table uses it. SQL refers to tables by the full two-part name.

the toolbar
  • + New table - opens the register dialog. This is the main event; sections 2 and 3 cover it.
  • Import CSV - creates a table from a CSV header and loads the rows in one go. Covered on Insert data from the dashboard.
  • Index config - full-text and vector settings, which are not table columns. Section 5.
  • Recount - recomputes exact row counts. The sidebar figure is a live counter that can read high after a lot of updates; this corrects it, and it runs off the write path.
  • Export · SVG / DOT - downloads the canvas as an SVG image or a Graphviz DOT file.
  • Reset layout - you can drag nodes around, and the arrangement is saved to your account for that instance. Reset puts them back on the automatic layout.
about the kind chips

Each column carries a small chip. REL marks a column that sources a relation to another table; GPH marks a plain field. The VEC and TXT filter chips in the toolbar are placeholders - vector and full-text structures aren't tagged onto columns yet, so those filters currently match nothing.

2

Variant A - the visual builder.

Click + New table. The dialog opens in Builder mode with a single starter column. Fill it in top to bottom:

  1. Namespace and table. Type shop and orders.
  2. Columns. Use + column for each one. Every row is a name, a type dropdown, and two toggles - REQ (required) and PK (primary key). The × at the end removes a row.
  3. Secondary indexes (optional). A name plus a comma-separated column list. These make SQL lookups on those columns cheaper.
  4. Graph relations (optional). A name, the local column, then the target namespace, table and primary key. The target table must already be registered - the dialog says so, and registering against a table that doesn't exist yet fails validation.
  5. Check constraints (optional). A name and an expression such as amount_cents >= 0, enforced on every write.
  6. Click Register.
The New table dialog in Builder mode. Namespace shop and table orders are filled in. Six column rows are listed - id as str with REQ and PK both lit, customer as str with REQ, amount_cents as i64 with REQ, status as str with REQ, notes as str, and placed_ms as u64. Below them a secondary index named by_status on the status column, an empty graph-relations section, and a check constraint named amount_non_negative with the expression amount_cents is greater than or equal to zero.
The Builder for shop.orders. REQ makes a column mandatory; PK marks it part of the primary key.
the types the dropdown offers

Eleven, in this order:

str Text. Use it for IDs, ULIDs, UUIDs travelling as text, and anything with leading zeros.
i64 · u64 Signed and unsigned 64-bit integers. Money in minor units, counters, epoch milliseconds.
f64 64-bit float.
bool True / false.
bytes Raw binary.
timestamp · uuid Dedicated time and UUID types.
decimal · enum The Builder can pick the type, but not its parameters - decimal precision and enum variants need the TOML pane.
json A nested document in one column.

Vector embeddings and full-text search are not column types here - they're configured separately, in Index config.

what the dialog checks before it submits
  • A table name is present - otherwise enter a table name.
  • No column is left unnamed - column 3 has no name - name it or remove the row. An unnamed column would silently vanish from the manifest, so it's caught here instead.
  • At least one column has PK ticked - tick at least one PK column.

Anything past that is the engine's call. If it rejects the manifest, the reason appears in red at the bottom-left of the dialog and nothing is created.

3

Variant B - the TOML manifest.

The TOML toggle in the top right of the same dialog swaps the form for an editable manifest - the exact text the engine stores. Switching to TOML regenerates it from whatever the Builder currently holds, so you can lay a table out visually and then hand-finish it.

You can also paste a manifest straight in - handy for copying a table between instances, or for the things the form can't express: enum variants, decimal precision, and foreign-key actions.

The New table dialog switched to TOML mode. The editable manifest shows namespace shop, table orders, primary_key of id, then the id and customer column blocks with ty str and required true. The status line at the bottom left reads registered with a tick, next to the Cancel and Register buttons.
TOML mode, just after Register: the engine accepted the manifest and the canvas reloads with the new node.
the complete manifest for shop.orders

The textarea shows about a dozen lines at a time (drag its bottom-right corner to grow it). Here it is in full - paste this and it registers as-is:

namespace   = "shop"
table       = "orders"
primary_key = ["id"]

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

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

[[columns]]
name = "amount_cents"
ty = "i64"
required = true

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

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

[[columns]]
name = "placed_ms"
ty = "u64"

[[indexes]]
name = "by_status"
columns = ["status"]

[[check_constraints]]
name = "amount_non_negative"
expression = "amount_cents >= 0"

Relations get their own block. Add one only once the target table exists:

[[relations]]
name = "placed_by"
from_col = "customer"
target = { namespace = "shop", table = "customers", pk = "id" }
bidirectional = true
one-way trip

The two modes are not two views of the same state. Builder → TOML regenerates the manifest from the form. TOML → Builder does not parse your text back into the form - and because the Builder becomes the submit source again, hand-edits made in the TOML pane are dropped. If you edit the manifest, stay in TOML and press Register from there.

The same manifest format works from code - see Schemas for the field-by-field reference and the SDK calls.

4

Load example.

If you'd rather start from something that already works, hit Load example. It fills the Builder with a complete shop.products table - seven columns spanning str, f64, i64, timestamp and json, two secondary indexes, and one check constraint. Rename anything you like, or press Register and it lands as-is.

The New table dialog after clicking Load example. Namespace shop and table products are filled in, with columns sku as str marked REQ and PK, name as str marked REQ, price as f64 marked REQ, category as str, stock as i64 and created_at as timestamp. Two secondary indexes follow, by_category on category and by_created on created_at.
Load example fills every section except relations - a relation's target table has to exist first, so a prefilled one would fail.
5

Index config: full-text and vector.

Index config in the designer toolbar opens a dialog with two tabs - Full-text and Vector. Those are the only two; there is no third tab.

Full-text

Configuration is per field: you name a table and a field, then set two things.

  • Synonyms - a term on the left, its comma-separated alternatives on the right. + add synonym adds another pair.
  • Stopwords - comma- or space-separated. Leaving the field empty disables the built-in stopword list for that field rather than doing nothing.

Save FTS config writes it. Each save replaces the whole list for that field - it isn't a merge, so send the full set every time.

The Index config dialog on its Full-text tab. Table is orders and field is notes. Two synonym rows are filled in: refund maps to refunded, chargeback, reversal, and express maps to expedited, rush, priority. A stopwords field below holds the, a, an, of, and a Save FTS config button sits at the bottom right.
Full-text config is per field. Each save replaces that field's whole synonym and stopword list.

Vector

The Vector tab is upfront about how vector tables work today: they are created and configured on the first insert. Dimension, metric, index family and quantization ride along with that first write - there is no separate "create vector table" step, and no Save button on this tab.

So the tab is a builder for that first call. Pick the settings and the exact request updates live underneath, with a copy button:

  • table and dim - the vector table's name and the embedding dimension.
  • metric - cosine, dot, l2 or manhattan.
  • index - hnsw, ivf or ivf_pq.
  • quantization - none, scalar or binary.
The Index config dialog on its Vector tab. Table is order_notes_vec, dim is 768, metric is cosine, index is ivf_pq and quantization is none. Below, an insert call panel shows a POST to the tenant vector put path with a JSON body carrying id, embedding, dim, metric and index. An amber note warns that IVF and IVF-PQ need trained centroids installed before the first insert, while HNSW needs no pre-step.
Choose an index family and the exact first-insert call rewrites itself. Picking ivf or ivf_pq surfaces the centroid warning.
two real actions further down the tab

Both of these run against the engine when you click them - unlike the snippet above, which is only a template to copy.

  • Train IVF centroids - set partitions (K) and click Train + install. It runs mini-batch k-means over the vectors already in the table named above. Run it once the table holds at least 4 × K vectors.
  • Build IVF-PQ index - pick a preset and click Build index. It trains and installs a compressed index over the table's existing vectors, and refuses if the corpus is under the k-means floor.
the two IVF-PQ presets
preset what it does pick it when
high_recall Finer product quantization, and it keeps the raw vector so results can be re-ranked exactly. Roughly 0.94 recall. Costs more disk. Answer quality matters more than footprint - search over documents users read, recommendations you'd notice getting worse.
compressed Codes only - no raw vector kept. About 190 bytes per vector. Lower recall as a result. The corpus is large and storage is the constraint, or approximate neighbours are good enough for the next stage of your pipeline.

When the build finishes, the console prints what the engine actually installed next to the button - the preset, the PQ sub-quantizer count, the number of cells, whether the raw vector was kept, the dimension, and how many vectors it trained on.

The lower half of the Vector tab. A Train IVF centroids section sets partitions to 256 with a Train and install button. Below it the Build IVF-PQ index section has the preset dropdown set to high_recall, and a green confirmation line reading high_recall, pq_m 24, 256 cells, keep_raw true, dim 768, 12480 vecs. A paragraph underneath explains the difference between the high_recall and compressed presets.
Build index with high_recall. The confirmation echoes the shape of the index the engine installed.

For the query side - top-k search, filters, and the metric trade-offs - see Vector search. For full-text querying, see Full-text search.

6

Inspect the table.

Click a node on the canvas, then Open table detail → at the bottom of the right rail (the same link appears in the corner of the canvas). The detail page leads with five tiles - rows, files · segments, on disk, columns, edges in / out - and six tabs: Overview, Columns, Indexes, Edges, Sample, Manifest.

The table detail page for shop.orders. Tiles across the top read 12,480 rows, 2 files and segments, 3.4 MB on disk, 6 columns, and 0 slash 1 edges in and out. The Overview tab lists all six columns with their types and kind chips, and a panel on the right lists two btree indexes, by_status on status and by_placed on placed_ms. A red Delete this table section sits at the bottom.
Table detail. Open in workbench and Explain jump to the query pages with this table pre-loaded.

Sample runs a live SELECT * … LIMIT 10 against the table - the quickest way to confirm a write landed. Manifest shows the canonical TOML the engine holds, which is the authoritative answer to "what is this table, really".

The Manifest tab of the shop.orders table detail page, showing the stored TOML: namespace shop, table orders, primary_key of id, version 1, then column blocks for id, customer and amount_cents with their ty and required values.
Manifest - what the engine stores, including the schema version that migrations increment.
7

Edit columns.

Edit columns on the table detail page opens a migration builder. You stage changes, review the exact request, then start it - reads stay correct while rows backfill in the background, and you cut over when it's ready.

Be clear about the boundaries, because the dialog is:

  • You can add a column, rename one, or drop one.
  • You cannot change a column's type, or edit indexes, relations or check constraints. Those aren't editable from the dashboard at all.
  • Primary-key columns are locked. The PK row shows pk · locked and has no rename or drop control.
  • An added column needs a default, and its type comes from a shorter list than the register dialog offers: str, i64, u64, f64, bool, bytes.
  • Renaming a column that a relation is built on raises a warning in the dialog before you submit.
The Edit columns dialog for shop.orders. The id row is marked pk and locked with no controls; the other rows offer rename and drop. The notes row is marked DROPPING with an undo link. Two staged changes are listed - drop notes, and add currency as str with default INR - and a migration request panel below shows the JSON that will be sent, beginning with schema shop.orders and a diff array whose first entry is a DropColumn of notes.
Stage the changes, read the request the console will send, then Start migration.

Once a migration is running, a panel appears at the top of the table page with its state and version change - v1 → v2. The states are Pending, Validating, Backfilling, CutoverPending and Completed, and the buttons follow the state: Boost backfill while backfilling, Abort while it's still in flight, and Cut over when it reaches CutoverPending.

cutover is manual

A migration does not finish on its own. It stops at CutoverPending and waits for you to press Cut over. Until you do, the table stays on the old version - and Edit columns is disabled, so you can't start a second migration on the same table.

8

Delete a table.

At the bottom of the table detail page there's a red Delete this table panel. It removes the table, every row in it, and every index, vector, full-text and graph structure built on it. It's owner/admin only, there's no undo, and backups honour the deletion.

The confirmation is type-to-confirm: the dialog names the table, shows how many rows will go, and keeps Delete forever disabled until you type the full table id - namespace included. A near miss like shop.order leaves the button greyed out.

The delete confirmation dialog, outlined in red. It reads Delete shop.orders question mark, warns that this permanently deletes 12,480 rows plus all indexes, vectors, full-text and graph data with no undo, and asks for the full table id. The text field contains shop.orders and the Delete forever button is now enabled next to Cancel.
Type the full namespace.table id to arm the button. Nothing about this is reversible.

Good to know.

register order matters

A relation's target table has to be registered before the table that points at it. Build shop.customers first, then shop.orders with its placed_by relation - not the other way round.

what the dashboard doesn't do

There's no console control for changing a column's type, adding or removing a secondary index, editing a relation, or editing a check constraint after registration. If you need one of those, the honest answer today is: register a new table with the shape you want and move the data across. The Schemas and HTTP API references cover what the API itself accepts.

row counts drift, then get corrected

The row figure on the canvas and the rows tile come from a live counter that can read high after a lot of updates, because an update counts like a write. Recount in the designer toolbar recomputes the exact numbers and runs off the write path, so it's safe on a busy instance.