> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Schema

> How gavAI workspaces declare tables and field shapes as JSON on the app document — and how the runtime validates every write against that declaration.

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const Explanation = ({children}) => <div className="gv-code-explainer__explanation">{children}</div>;

export const Code = ({children}) => <div className="gv-code-explainer__code">{children}</div>;

export const CodeExplainer = ({eyebrow, title, children}) => <div className="gv-code-explainer">
    {(eyebrow || title) && <header className="gv-code-explainer__header">
        {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
        {title && <h3 className="gv-code-explainer__title">{title}</h3>}
      </header>}
    <div className="gv-code-explainer__grid">{children}</div>
  </div>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="CONCEPT" />

<PageMeta audience="Developers" readingTime="6 min" prereqs={["PageBlocks"]} level="Concepts" />

A gavAI app needs to store data — customers, orders, leads, whatever the app is about. The **schema** is how the app declares the shape of that data. It lives as a JSON object on the workspace's app document, names the tables, and declares each field's type and constraints. The runtime validates every write against this schema. By the end of this page you will know what the schema is, where it lives, what goes inside it, and how blocks and capabilities bind to it.

## A schema, in 15 lines

Here is a schema that declares two tables — `customers` and `orders` — with a foreign key from orders back to customers:

```json schema.json theme={null}
{
  "schema": {
    "customers": {
      "fields": {
        "id":    { "type": "uuid",   "primary": true },
        "name":  { "type": "string", "required": true },
        "email": { "type": "string", "required": true, "unique": true }
      }
    },
    "orders": {
      "fields": {
        "id":          { "type": "uuid",   "primary": true },
        "customer_id": { "type": "uuid",   "references": "customers.id" },
        "amount":      { "type": "number", "required": true },
        "status":      { "type": "string", "required": true }
      }
    }
  }
}
```

That is the whole declaration. No `CREATE TABLE`, no migration file, no SQL dialect to learn. When a block or an API call writes a record to `customers`, the runtime checks the value against the declared field shapes — wrong types are rejected, missing required fields are rejected, duplicate `email` values are rejected — before anything reaches the database.

## Why JSON, not SQL

The schema is a JSON object, not a SQL DDL script, for two reasons.

**It lives on the same document the page does.** Pages and schema travel together. When you `GET` a page document, you get the schema with it. When you `PATCH` the document to add a `phone` field on `customers`, the page document and the schema update in one transaction.

**The Builder can edit it.** A non-technical owner uses the drag-drop schema designer to add a field; the designer writes JSON. A developer integrating from the API edits the same JSON. Both reach the same artifact through different surfaces.

The trade-off is that the schema is narrower than full SQL. Composite indexes, partial indexes, triggers, and stored procedures aren't expressible. For the apps gavAI is built for — internal tools, customer portals, signup flows — the JSON shape covers what you need.

## What goes in a schema

A schema is an object keyed by table name. Each table has `fields`, and each field has a `type` plus optional constraints.

<CodeExplainer>
  <Code>
    ```json schema.json theme={null}
    {
      "customers": {
        "fields": {
          "id":    { "type": "uuid",   "primary": true },
          "email": { "type": "string", "required": true, "unique": true },
          "phone": { "type": "string" }
        }
      }
    }
    ```
  </Code>

  <Explanation>
    **Table name.** The top-level key (`customers`) names the table. Blocks and capabilities reference this name to bind reads and writes.

    **Field type.** Every field declares a `type` — `uuid`, `string`, `number`, `boolean`, and the other primitives the runtime knows. The type is checked on every write.

    **Constraints.** `primary` marks the primary key. `required` rejects writes with a missing value. `unique` rejects writes that duplicate an existing value.

    **Relationships.** `references` declares a foreign key — `"references": "customers.id"` says this field's value must match a row in `customers`. The runtime enforces the link at write time.
  </Explanation>
</CodeExplainer>

The full constraint vocabulary on a field, for quick reference:

<DefList>
  <Def term="type" type="string" required>The field's primitive type — `uuid`, `string`, `number`, `boolean`, and the rest the runtime knows.</Def>
  <Def term="primary" type="boolean">Marks the primary key for the table.</Def>
  <Def term="required" type="boolean">Rejects writes with a missing value.</Def>
  <Def term="unique" type="boolean">Rejects writes that duplicate an existing value.</Def>
  <Def term="references" type="string">Foreign key declaration — `"customers.id"` requires the value to match a row in `customers`. Enforced at write time.</Def>
</DefList>

You define tables and fields in the Builder's schema designer (point-and-click), or by editing the JSON document directly through the API. Both surfaces produce the same artifact.

## How blocks bind to schema

Blocks that read or write data bind to schema tables by name. The runtime resolves the binding when the page renders.

```mermaid theme={null}
flowchart LR
    F[Form block<br/>schema_table: leads] -->|writes| T[(leads<br/>table)]
    TB[Table block<br/>schema_table: orders] -->|reads| O[(orders<br/>table)]
    DP[DetailPanel block<br/>schema_table: customers] -->|read+write| C[(customers<br/>table)]
```

* A **Form** block specifies a `schema_table` — when a visitor submits, the runtime writes a record to that table.
* A **Table** block fetches all records from the named table, with pagination, filtering, and sorting built in.
* A **DetailPanel** reads a single record from the named table and renders it in read mode or edit mode, gated by user permissions.

Bindings are checked at page-publish time. A `Form` block pointing at a non-existent table is rejected before the page deploys — you don't ship broken pages.

See the [block reference](/pageblocks/overview) for the binding options each block exposes.

## How capabilities bind to schema

The `runQuery` capability executes typed queries against schema-defined tables. You pass a table name and filter criteria; the runtime returns records validated against the declared field shapes.

This is the primary way blocks and agent workflows read data at runtime. Where a Table block is enough for a flat list, `runQuery` is the answer when you need a custom filter, a join, or a programmatic read from inside a workflow.

## Related

<Columns cols={2}>
  <BrandCard eyebrow="DOCUMENT" title="The page document" href="/pageblocks/page-document" icon="file-code">
    See how the schema sits inside the page document alongside the block tree.
  </BrandCard>

  <BrandCard eyebrow="ACTIONS" title="Capabilities" href="/concepts/capabilities" icon="zap">
    How `runQuery` and other capabilities call into the data the schema describes.
  </BrandCard>
</Columns>
