> ## 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.

# runQuery

> Read rows from the workspace's Postgres database via a typed filter DSL.

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

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 ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

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="CAPABILITY" />

`runQuery` fetches rows from the workspace's managed Postgres database using a typed filter DSL. It is the in-page data-fetching primitive: blocks like `Table` and `KanbanBoard` use it to populate themselves from live data. You declare the filter in the page document and the runtime translates it to a parameterized Postgres query server-side — raw SQL is never accepted and never exposed. By the end of this page you can wire a Table block to a schema table with filters, sort order, and cursor pagination.

## Invoke from a Table block

The canonical pattern is binding `Table` to a `schema_table`. The block calls `runQuery` automatically and surfaces sort, filter, and pagination through its UI.

```json leads-table.json theme={null}
{
  "block": "Table",
  "props": {
    "schema_table": "leads",
    "sort": [{ "field": "created_at", "dir": "desc" }],
    "limit": 50,
    "filter": {
      "status": { "eq": "active" }
    }
  }
}
```

A `KanbanBoard` uses `runQuery` the same way, grouping rows by a status field:

```json deals-kanban.json theme={null}
{
  "block": "KanbanBoard",
  "props": {
    "schema_table": "deals",
    "status_field": "stage",
    "sort": [{ "field": "updated_at", "dir": "desc" }]
  }
}
```

The runtime resolves the filter against the table's column types, parameterizes the query, executes against Postgres, and returns the rows in a validated envelope.

<Note>
  You do not write SQL. The `filter` argument is a typed DSL validated against the page's declared schema. The runtime translates it to a parameterized Postgres query server-side — raw SQL is not accepted as input.
</Note>

<Warning>
  `runQuery` runs in the security context of the calling page. The user's session — read via [`authUser`](/capabilities/auth-user) — gates which rows return. If the calling user does not have access to a row, that row is excluded. Multi-tenant isolation is enforced at this layer; changing the `table` name does not grant access to another workspace's data.
</Warning>

<Tip>
  For server-side scripts and integrations, prefer the public REST API (see the [API reference](/api-reference/introduction)). `runQuery` is designed for in-page data fetching where the user's session should govern row-level access. Using it from a background job would require a user session, which is the wrong tool for the job.
</Tip>

## Inputs

<DefList>
  <Def term="table" type="string" required>
    Table name as declared in the page's schema. Must exist in the workspace schema.
  </Def>

  <Def term="filter" type="object">
    Typed DSL filter expression. Validated server-side against the table's column types. Not raw SQL.
  </Def>

  <Def term="sort" type="Array<{ field: string; dir: &#x22;asc&#x22; | &#x22;desc&#x22; }>">
    Ordered list of sort fields and directions, applied before pagination.
  </Def>

  <Def term="limit" type="number">Maximum rows to return. Hard-capped at `1000`.</Def>

  <Def term="cursor" type="string">
    Pagination cursor from a previous response's `next_cursor`. Omit for the first page.
  </Def>
</DefList>

## Outputs

<DefList>
  <Def term="rows" type="Array<Record<string, unknown>>">
    The returned rows, typed against the table's declared schema columns.
  </Def>

  <Def term="next_cursor" type="string">
    Pass as `cursor` on the next call to fetch the following page. Absent when there are no more rows.
  </Def>

  <Def term="total" type="number">
    Total row count matching the filter, when requested. Not always present — check for the field before reading.
  </Def>
</DefList>

## Provisioning

The workspace's managed Postgres database is auto-provisioned. Tables must be declared in the page document's `schema` section before they can be queried. Schemas are managed in the Builder's schema editor or via the pages API.

## Error codes

<ErrorTable
  errors={[
{
  code: "unknown_table",
  cause: "The `table` value does not exist in the workspace's declared schema.",
  fix: "Declare the table in the page document's `schema` section, or correct the table name.",
},
{
  code: "unauthorized",
  cause: "The calling user's session does not grant access to the requested rows or table.",
  fix: "Confirm the user is signed in and has a role that permits access — see `authUser`.",
},
{
  code: "invalid_input",
  cause: "One or more fields failed schema validation.",
  fix: "Inspect `error.details` for field-level messages and correct the args.",
},
]}
/>

Every error returns the standard envelope:

<ExpectedOutput caption="Error envelope">
  ```json error-envelope.json theme={null}
  {
    "ok": false,
    "error": {
      "code": "unknown_table",
      "message": "Table 'leadz' is not declared in the workspace schema.",
      "details": { "table": "leadz" }
    }
  }
  ```
</ExpectedOutput>

## Related

<Columns cols={2}>
  <Card title="authUser" icon="user" href="/capabilities/auth-user">
    The session whose roles gate row-level access on every `runQuery` call.
  </Card>

  <Card title="Data queries guide" icon="database" href="/guides/data-queries">
    End-to-end: declare a schema, wire a Table, paginate with `next_cursor`.
  </Card>
</Columns>
