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

bash
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:

FieldWhat it does
orders(where, orderBy, limit, offset)Reads rows
ordersByPk(id)Reads one row by its primary key
ordersAggregate(where)Counts and aggregates
insertOrders, insertOrdersOneInserts, with onConflict for an upsert
updateOrders, updateOrdersByPkUpdates, with set and inc
deleteOrders, deleteOrdersByPkDeletes
  • Each column is a field of the same name. Each foreign key is a field both ways: customer on an order for customer_id, and orders on a customer, with its own where, orderBy and limit.
  • Filters: eq, neq, gt, gte, lt, lte, in, nin, isNull, like, ilike, regex, iregex; contains and containedIn for arrays and JSON, and overlaps for arrays; search on full-text (tsvector) columns; and and, or and not.
  • Your exposed SQL functions appear as queries when they are stable or immutable, and as mutations otherwise. PostgreSQL enums become GraphQL enums.

Queries and mutations#

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

js
const { data, errors } = await db.graphql(
  'query($s: String!) { orders(where: { status: { eq: $s } }) { id total } }',
  { s: 'open' }
);
dart
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.

js
const live = db.graphql.subscribe(
  'subscription { orders(where: { status: { eq: "open" } }) { id total } }',
  {},
  ({ data }) => render(data.orders)
);

live.unsubscribe();
dart
final stop = await db.graphqlSubscribe(
  'subscription { orders(where: { status: { eq: "open" } }) { id total } }',
  (result) => render(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 27 October 2026