GraphQL
Query your Plexivo Postgres tables with GraphQL, including live queries that update as data changes.
Every Plexivo Postgres with the Data API on is also a GraphQL API. The schema is built from the tables and functions your Data API exposes, so it follows your database as it changes, and row-level security applies to every field.
The tables on this page#
create table customers (
id bigint generated always as identity primary key,
owner uuid not null default auth.uid(),
name text not null
);
create table orders (
id bigint generated always as identity primary key,
owner uuid not null default auth.uid(),
customer_id bigint references customers (id),
total numeric(10, 2) not null,
status text not null default 'open',
created_at timestamptz not null default now()
);
alter table customers enable row level security;
alter table orders enable row level security;
create policy own on customers for all to authenticated using (owner = auth.uid()) with check (owner = auth.uid());
create policy own on orders for all to authenticated using (owner = auth.uid()) with check (owner = auth.uid());
create trigger plexivo_realtime after insert or update or delete on orders
for each row execute function auth.realtime_notify('id');import { createClient } from '@plexivo/client';
const db = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY);
await db.auth.signUp({ email, password });
const asha = await db.from('customers').create({ name: 'Asha' });
await db.from('orders').create([{ customer_id: asha.id, total: 1299 }, { customer_id: asha.id, total: 480 }]);The GraphQL endpoint#
Send queries to your Project URL followed by /graphql/v1, with the same keys and user tokens as the Data API. POST takes one query or a batch of up to 20; GET runs queries only.
curl "$PLEXIVO_DATA_URL/graphql/v1" \
-H "apikey: $PLEXIVO_PUBLISHABLE_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ orders(limit: 5) { id total } }"}'Try queries first in the GraphQL tab’s Playground, As a visitor or As the service role, and download the schema as a .graphql file from Schema.
The schema#
For a table orders, you get:
| Field | What it does |
|---|---|
orders(where, orderBy, limit, offset) | Reads rows |
ordersByPk(id) | Reads one row by its primary key |
ordersAggregate(where) | Counts and aggregates |
insertOrders, insertOrdersOne | Inserts, with onConflict for an upsert |
updateOrders, updateOrdersByPk | Updates, with set and inc |
deleteOrders, deleteOrdersByPk | Deletes |
- Each column is a field of the same name. Each foreign key is a field both ways:
customeron an order forcustomer_id, andorderson a customer, with its ownwhere,orderByandlimit. - Filters:
eq,neq,gt,gte,lt,lte,in,nin,isNull,like,ilike,regex,iregex;containsandcontainedInfor arrays and JSON, andoverlapsfor arrays;searchon full-text (tsvector) columns; andand,orandnot. - Your exposed SQL functions appear as queries when they are
stableorimmutable, and as mutations otherwise. PostgreSQL enums become GraphQL enums.
Queries and mutations#
query OpenOrders {
orders(where: { status: { eq: "open" } }, orderBy: [{ created_at: DESC }], limit: 20) {
id
total
customer { name }
}
ordersAggregate(where: { status: { eq: "open" } }) { count }
}
mutation Pay($id: BigInt!) {
updateOrdersByPk(id: $id, set: { status: "paid" }) { id status }
}All the mutations in one request commit together or not at all. A bigint column is a BigInt, sent as a string so no digit is lost; numeric is a BigFloat, and timestamps are Datetime.
const { data, errors } = await db.graphql(
'query($s: String!) { orders(where: { status: { eq: $s } }) { id total } }',
{ s: 'open' }
);import 'package:plexivo_client/plexivo_client.dart';
final db = PlexivoClient(dataUrl, publishableKey);
await db.signUp(email: email, password: password);
final result = await db.graphql(
r'query($s: String!) { orders(where: { status: { eq: $s } }) { id total } }',
variables: {'s': 'open'},
);Send { persisted: true } in JavaScript, or persisted: true in Dart, to use automatic persisted queries: after the first time, only the query’s hash travels.
Live queries#
A live query sends its answer now, and again whenever a row it reads changes. Every table it reads must have Realtime switched on.
const live = db.graphql.subscribe(
'subscription { orders(where: { status: { eq: "open" } }) { id total } }',
{},
({ data }) => console.log(`${data.orders.length} open orders`)
);
// A new order sends the answer again
setTimeout(() => db.from('orders').create({ customer_id: asha.id, total: 99 }), 1000);
setTimeout(() => live.unsubscribe(), 8000);final stop = await db.graphqlSubscribe(
'subscription { orders(where: { status: { eq: "open" } }) { id total } }',
(result) => print(result['data']),
);
await stop();Changes that arrive together are sent as one update. One live-query channel holds up to 20 live queries; the SDKs open a channel for each live query.
Security#
GraphQL runs every field as the caller, exactly like the Data API: tables and functions not exposed there are not in the schema, and row-level security decides which rows each user sees and writes.
Limits#
- Queries nest up to 12 levels and select up to 500 fields.
- A query document is up to 100,000 bytes.
- A list returns up to 1,000 rows.
Next steps#
Updated 28 October 2026