Data
Read and write rows of the app's own tables. Every call is a request to the app's data API under the signed-in user's identity, so the database's per-role row-level security decides what each user sees and changes.
On this page
Use query and get (or the hooks) for anything that lists, counts or presents records: they run server-side and stay correct however large a table grows. rows() is a bounded working set (the newest 200 rows the user may read) for small lookups.
Owner columns (user_id, org_id, created_by) are stamped by the server. Pass only the row's own fields.
Working set
app.rowsmethod
The table's working set: the newest rows (at most 200) the user may read, loaded on first use. Synchronous; the component re-renders as rows arrive or change.
app.rows(entity: string): Row[]Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name, as in the contract's entities[].name. |
- Returns
- Row[] (empty while loading).
- Errors
- Does not throw.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const categories = app.rows("categories");
return <select>{categories.map((c) => <option key={String(c.id)}>{String(c.name)}</option>)}</select>;Source: packages/appkit/src/kits/primitives/app-handle.ts:167
app.loadingmethod
True while the table's working set is still loading.
app.loading(entity: string): booleanParameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
- Returns
- boolean.
- Errors
- Does not throw.
- Permissions
- Local state.
if (app.loading("categories")) return <Skeleton />;Source: packages/appkit/src/kits/primitives/app-handle.ts:168
app.revisionmethod
A number that moves whenever the table changes (a write here or a realtime event). Put it in a useEffect dependency list to refetch a query.
app.revision(entity: string): numberParameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
- Returns
- number (0 when the runtime doesn't track revisions).
- Errors
- Does not throw.
- Permissions
- Local state.
useEffect(() => {
void app.query("tickets", { where: { status: "open" } }).then((r) => setOpen(r.total));
}, [app, app.revision("tickets")]);Source: packages/appkit/src/kits/primitives/app-handle.ts:171
Queries
app.querymethod
A server-side filtered, sorted page of rows plus the total match count.
app.query(entity: string, q?: AppQuery): Promise<{ rows: Row[]; total: number }>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
qoptional | AppQuery | { where?, sort?, limit?, offset? }. where is equality filters (ANDed); sort is { field, dir? } with dir "asc" by default; limit defaults to 50 (the server caps it at 500). |
- Returns
- { rows: Row[]; total: number }. total counts every match, not just this page.
- Errors
- The server's message: TABLE_FORBIDDEN when the role may not read the table, PARAMS_INVALID for bad filters, RATE_LIMITED past the project's request budget.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const { rows, total } = await app.query("tickets", {
where: { status: "open", assignee: app.me("agent") ?? "" },
sort: { field: "created_at", dir: "desc" },
limit: 20,
});Source: packages/appkit/src/kits/primitives/app-handle.ts:173
app.getmethod
One row by id.
app.get(entity: string, id: string): Promise<Row | null>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row's id. |
- Returns
- The row, or null when it doesn't exist or row-level security hides it.
- Errors
- The server's message for a refused read (TABLE_FORBIDDEN) or a failed request.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const ticket = await app.get("tickets", app.params.id);
if (!ticket) return <p>This ticket no longer exists.</p>;Source: packages/appkit/src/kits/primitives/app-handle.ts:174
Writes
app.createmethod
Insert a row. Owner columns are stamped server-side, before-write rules run, and a row-changed event fires (workflows on created run).
app.create(entity: string, values: Row): Promise<Row>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
valuesrequired | Row | The row's own fields. Leave out id, owner columns and timestamps. |
- Returns
- The created row, with its id.
- Errors
- A unique-constraint conflict, verbatim ("That booking already exists — resource + slot must be unique."), checked locally first when the runtime knows the field is unique.
- RULE_REFUSED with the rule's message when a before-write rule says no; TABLE_FORBIDDEN when the role may not create.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const row = await app.create("tickets", { title: "Printer jam", priority: "high" });
app.navigate(`/tickets/${row.id}`);Source: packages/appkit/src/kits/primitives/app-handle.ts:176
app.updatemethod
Patch a row. Only the fields you pass change.
app.update(entity: string, id: string, patch: Row): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row's id. |
patchrequired | Row | Fields to change. |
- Returns
- Resolves when saved.
- Errors
- E_LIFECYCLE_DIRECT_WRITE when the patch touches a lifecycle field: use transition.
- RULE_REFUSED, TABLE_FORBIDDEN, or a unique-constraint conflict message.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
await app.update("tickets", id, { priority: "low" });Source: packages/appkit/src/kits/primitives/app-handle.ts:177
app.removemethod
Delete a row.
app.remove(entity: string, id: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row's id. |
- Returns
- Resolves when deleted.
- Errors
- TABLE_FORBIDDEN when the role may not delete, or the server's message.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
if (confirm("Delete this ticket?")) await app.remove("tickets", id);Source: packages/appkit/src/kits/primitives/app-handle.ts:178
app.transitionmethod
Move a lifecycle enum field (status) along the contract's transition graph. A plain write to a lifecycle field is refused.
app.transition(entity: string, id: string, field: string, to: string): Promise<void>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | The row's id. |
fieldrequired | string | The lifecycle field, e.g. "status". |
torequired | string | The target state, e.g. "resolved". |
- Returns
- Resolves when the move is saved.
- Errors
- E_TRANSITION_FORBIDDEN when the graph has no edge from the current state; E_TRANSITION_ROLE when the role may not make this move; E_FIELD_NOT_LIFECYCLE for an ordinary field.
- Permissions
- The transition plane checks the graph and the role restrictions; row-level security still applies to the row.
await app.transition("tickets", id, "status", "resolved");Source: packages/appkit/src/kits/primitives/app-handle.ts:180
React hooks
useEntityQueryfunction
A page of rows plus the total match count, as a resource that refetches when the table's revision moves. The previous page stays on screen while the next loads.
useEntityQuery(entity: string, q: EntityQuery, opts?: { … }): Resource<QueryResult>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name ("" disables the hook). |
qrequired | EntityQuery | EntityQuery: { filter?, sort?, limit?, offset?, search? }. filter clauses take every op in QueryFilterOp (eq, in, lt, contains, range…). |
optsoptional | { enabled?: boolean; keepPrevious?: boolean } | { enabled?: boolean; keepPrevious?: boolean } (keepPrevious defaults to true). |
- Returns
- Resource<QueryResult>: { data, loading, refreshing, error, retry }.
- Errors
- Never throws; a failed load sets error and retry() tries again.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const tickets = useEntityQuery("tickets", {
filter: [{ field: "status", op: "in", value: ["open", "pending"] }],
sort: { field: "created_at", dir: "desc" },
limit: 25,
});
if (tickets.loading) return <Skeleton />;
return <List rows={tickets.data?.rows ?? []} total={tickets.data?.total ?? 0} />;Source: packages/appkit/src/kits/runtime.tsx:251
useRecordfunction
One record by id, as a resource. data is null when the record doesn't exist (or row-level security hides it), undefined while loading.
useRecord(entity: string, id: string | undefined): Resource<Row | null>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
idrequired | string | undefined | The row id; undefined disables the hook. |
- Returns
- Resource<Row | null>.
- Errors
- Never throws; failures set error.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const ticket = useRecord("tickets", params.id);
if (ticket.data === null) return <p>Not found</p>;Source: packages/appkit/src/kits/runtime.tsx:266
useAggregatefunction
A server-side count, sum, avg, min or max over the matching rows, as a resource.
useAggregate(entity: string, a: AggregateSpec): Resource<number>Parameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | The table name. |
arequired | AggregateSpec | AggregateSpec: { metric, field?, filter? }. field is required unless metric is "count". |
- Returns
- Resource<number>.
- Errors
- Never throws; failures set error.
- Permissions
- Runs through the app's data API as the signed-in user: the table's per-role access rules and row-level security decide which rows it can touch. A signed-out visitor acts as a guest.
const revenue = useAggregate("orders", {
metric: "sum", field: "total",
filter: [{ field: "created_at", op: "range", value: "this_month" }],
});Source: packages/appkit/src/kits/runtime.tsx:284
Types
type AppQuery = {
where?: Record<string, string | number | boolean | null>;
sort?: {
field: string;
dir?: "asc" | "desc";
};
limit?: number;
offset?: number;
}Source: packages/appkit/src/kits/primitives/app-handle.ts:38
type Row = Record<string, unknown>Source: packages/appkit/src/kits/query.ts:16
type EntityQuery = {
filter?: QueryFilter[];
sort?: QuerySort;
limit?: number;
offset?: number;
search?: {
field: string;
q: string;
};
}Source: packages/appkit/src/kits/query.ts:30
type QueryFilter = {
field: string;
op: QueryFilterOp;
value?: unknown;
}Source: packages/appkit/src/kits/query.ts:27
type QueryFilterOp = "eq" | "neq" | "in" | "not_in" | "lt" | "lte" | "gt" | "gte" | "contains" | "is_null" | "is_not_null" | "range"Source: packages/appkit/src/kits/query.ts:18
type QuerySort = {
field: string;
dir: "asc" | "desc";
}Source: packages/appkit/src/kits/query.ts:28
type QueryResult = {
rows: Row[];
total: number;
}Source: packages/appkit/src/kits/query.ts:41
type AggregateSpec = {
metric: AggregateMetric;
field?: string;
filter?: QueryFilter[];
}Source: packages/appkit/src/kits/query.ts:44
type AggregateMetric = "count" | "sum" | "avg" | "min" | "max"Source: packages/appkit/src/kits/query.ts:43
type Resource<T> = {
data: T | undefined;
loading: boolean;
refreshing: boolean;
error: Error | undefined;
retry: () => void;
}Source: packages/appkit/src/kits/runtime.tsx:183