Types: how to create and manage them
In Relata, a type is what other databases call a table — think of it as a tab in a spreadsheet. Unlike a traditional database you never run CREATE TABLE: you register types at runtime through the API or an SDK, change them online while the server is running, and the same types are instantly usable from every door (HTTP, psql, S3, MongoDB, your AI agent…).
This page walks through the whole lifecycle with copy-paste examples. No database administration background needed.
"I want to create my first type"
With the Python SDK (see Python SDK):
client.register_type(
"Customer",
description="People who buy from us",
owner="growth-team",
properties={
"id": {"type": "text"},
"name": {"type": "text"},
"email": {"type": "text"},
},
)
# {"created": True, "name": "Customer"}The same call over plain HTTP (what the SDK does under the hood):
curl -X POST http://localhost:9090/types \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RELATA_ADMIN_TOKEN" \
-d '{
"name": "Customer",
"description": "People who buy from us",
"properties": {
"id": {"type": "text"},
"name": {"type": "text"},
"email": {"type": "text"}
}
}'_, continue with letters, digits, or _, up to 128 characters. Customer, sanctions_hit, and Order2026 are valid; 2026Orders and my-type are not (you'll get a clear 400 error).RELATA_ADMIN_TOKEN on server/cluster profiles), not the regular bearer token. Reading and writing rows uses the normal token."How do I see what I've registered?"
client.list_types()
# {"types": [{"name": "Customer", "rows": 4}, {"name": "Order", "rows": 12}, ...]}
client.type_detail("Customer") # full schema + row stats for one typeOr: GET /types and GET /types/Customer over HTTP. The detail view includes the properties, owner, and row count — handy sanity check after ingest.
"I need another column — do I have to take the server down?"
No. Schema changes are online; existing rows keep working:
# Add a column (existing rows get it as empty)
client.schema_alter("Customer", "add_column", "tier", col_type="text")
# Rename a column (values move with it)
client.schema_alter("Customer", "rename_column", "tier", new_column="plan_tier")
# Retype a column
client.schema_alter("Customer", "change_type", "plan_tier", col_type="float")
# Drop a column
client.schema_alter("Customer", "remove_column", "plan_tier")add_column, remove_column, rename_column, change_type. (Some older examples elsewhere show shortened names like "add" — those are rejected with a 400; use the full names.)"How do I connect two types?" (relationships)
Register a typed edge — a named, directional link between types that the graph operators (MATCH, path queries, PageRank…) understand:
client.register_edge_type("Customer", "Order", "PLACED")
# {"from_type": "Customer", "to_type": "Order", "label": "PLACED"}
client.list_edge_types() # inspect what exists"My new type returns 403 when I query it"
Custom types aren't readable until you grant them. One line in your environment fixes it:
RELATA_ACL_GRANT="Customer:read+write" relata serve # restart requiredFull walkthrough (including read-only sharing and column hiding): Access Control & Permissions.
"I want to remove a type"
client.deregister_type("ExperimentalType")
# {"deleted": True, "name": "ExperimentalType"}client.export_data("ExperimentalType", format="json").client.type_detail(name) shows its row count first — anything non-zero deserves an export.The long tail (optional extras)
Computed columns, state machines, search tuning, auto-detection
POST /types accepts more than a name and properties:
- Computed columns — values derived from other columns (
concatof fields, or astatic), maintained automatically. Cyclic definitions (A needs B, B needs A) are rejected up front with a 400. - State machines — constrain a status column to legal transitions (e.g.
new → reviewed → closed), so an illegal update fails at write time. - BM25 tuning — per-type
bm25_params(k1,b) to shape keyword-search ranking for that type's text. - Redefinitions — changing a computed-column formula or state machine on a type that already has rows returns a 409 unless you pass
"force": true(existing rows are never silently recomputed — you opt in).
Two companion endpoints hang off the same surface:
GET/POST /types/{name}/detect-config— per-type SmartIngest detector packs, soCustomerdocuments auto-detect your internal account-number format on ingest.GET/POST /types/{name}/embed-config— per-type embedding configuration for vector search.
And when you want to land a whole ontology (types + links + constraints) in one governed, SHACL-validated call:
client.ontology_migrate({
"types": [
{"name": "Customer", "properties": {"email": {"type": "text"}}},
{"name": "Order", "properties": {"total": {"type": "float"}}},
],
"links": [
{"from": "Customer", "to": "Order", "label": "PLACED"},
],
})Next: Ingest data — put rows into your new type, or Schema cookbook for the condensed SDK cheat-sheet.