DocsConfiguration

Configuration

All environment variables and configuration options for Postrust.

Boolean settings

A true/false variable accepts true, 1, yes and on, or false, 0, no and off, in any case. Anything else fails startup, naming the variable — a typo read as false could silently reverse a default or disable a feature you asked for. Before 2.0.0, anything other than a lowercase true read as false.

Database

VariableRequiredDefaultDescription
DATABASE_URLYes-PostgreSQL connection string
PGRST_DB_SCHEMASNopublicSchemas to expose (comma-separated)
PGRST_DB_ANON_ROLENo-Role for unauthenticated requests
PGRST_DB_POOL_SIZENo10Connection pool size
PGRST_DB_PRE_REQUESTNo-A function (schema-qualified if you like, e.g. auth.check) called before every REST request's query, in the same transaction and after the role and claims are set. Raising an error from it refuses the request. Not called for GraphQL.

Schema Reloading

VariableRequiredDefaultDescription
PGRST_DB_CHANNEL_ENABLEDNofalseListen for schema reloads, so a migration that ends with NOTIFY pgrst, 'reload schema' is picked up without a restart. Reloads the REST schema cache and rebuilds the GraphQL schema. Not available on AWS Lambda, which cannot hold a listening connection.
PGRST_DB_CHANNELNopgrstThe channel to LISTEN on for schema reloads

Authentication

VariableRequiredDefaultDescription
PGRST_JWT_SECRETNo-An HMAC secret (HS256), or a JWK or JWKS of public keys (RS, PS, ES, EdDSA)
PGRST_JWT_SECRET_IS_BASE64NofalseIf secret is base64 encoded
PGRST_JWT_AUDNo-Required JWT audience claim
PGRST_JWT_ISSNo-Required issuer: a token's iss must equal it, and must be present
PGRST_JWT_JWKS_URLNo-Fetch the verification keys from an identity provider's JWKS URL (https) and keep them current
PGRST_JWT_JWKS_REFRESHNo300Seconds between JWKS fetches when the response has no Cache-Control max-age
PGRST_JWT_ROLE_CLAIM_KEYNoroleClaim key for role

Server

VariableRequiredDefaultDescription
PGRST_SERVER_HOSTNo127.0.0.1Bind address
PGRST_SERVER_PORTNo3000Port to listen on
PGRST_SERVER_CORS_ORIGINSNo*CORS allowed origins

Limits

VariableRequiredDefaultDescription
PGRST_MAX_ROWSNounlimitedMaximum rows returned by one request; caps requests with no limit
PGRST_MAX_BODY_SIZENo10485760Max request body in bytes

Compatibility

VariableRequiredDefaultDescription
PGRST_COMPAT_MODENofalsePostgREST compatibility mode: serves the REST API at the root (/rpc/fn, /table) in addition to /api, un-wraps RPC responses to PostgREST's shape, and answers database errors in PostgreSQL's own words. Object key order is a build-time choice, not covered by this setting - see below. Alias: POSTRUST_COMPAT_MODE
PGRST_DB_ERRORS_VERBATIMNofalseAnswer database errors with PostgreSQL's own code, message, details and hint instead of "Database error", without the rest of compatibility mode. true for every one, or a comma-separated list of SQLSTATEs and prefixes ending in *, such as P0001,PT* - so a function's RAISE EXCEPTION reaches the client while faults like 42P01 or 23505 stay sanitised

Hasura Authentication

VariableRequiredDefaultDescription
PGRST_HASURA_ADMIN_SECRETNo-Shared secret authenticating an administrator. A caller holding it may ask to be treated as any role. Alias: HASURA_GRAPHQL_ADMIN_SECRET
PGRST_HASURA_UNAUTHORIZED_ROLENo-Role for a request nothing authenticated. Unset means such a request is refused, which is the default. Alias: HASURA_GRAPHQL_UNAUTHORIZED_ROLE

GraphQL Names, Permissions and Federation

VariableRequiredDefaultDescription
PGRST_GRAPHQL_METADATANo-Names for tables, columns, root fields, relationships and computed fields that the schema cannot supply; which root a function is exposed on; what each role may do with each table; column_types, which exposes a text, integer or UUID column as GraphQL ID without changing its database type; and Apollo Federation table metadata, such as "federation": {"shared": true}. A JSON document, or a path to a file holding one. Unset means every name is derived and there is no permission layer. Also read as PGRST_GRAPHQL_NAMES, which is what it was called when names were all it carried.
PGRST_GRAPHQL_FEDERATIONNofalseExpose the GraphQL API as an Apollo Federation v2 subgraph, adding _service, _entities and the Federation directives, and naming the root types Query, Mutation and Subscription. Off, Federation metadata is inert and the SDL is unchanged. See the GraphQL docs.
PGRST_GRAPHQL_TYPE_PREFIXNo-Prefix generated table types and root fields, so several Postrust subgraphs can be composed without colliding. A shared Federation entity keeps its unprefixed object type; its root fields and helper types still take the prefix.
PGRST_SUBSCRIPTION_REFRESHNo30Seconds between re-reads of a live subscription when nothing has notified it: the floor under changes a trigger cannot see, such as through a view. A message is sent only when the answer changed. 0 turns the refresh off.

Logging

VariableRequiredDefaultDescription
PGRST_LOG_LEVELNoinfoLog level (error, warn, info, debug)
RUST_LOGNo-Detailed tracing configuration
PGRST_LOG_FORMATNotexttext, or json for one JSON object per line, with request_id and trace_id

Observability

VariableRequiredDefaultDescription
OTEL_EXPORTER_OTLP_ENDPOINTNo-OpenTelemetry collector (OTLP over HTTP, usually port 4318); turns on traces and metrics
OTEL_SERVICE_NAMENopostrustThe service.name traces and metrics are reported under
PGRST_ADMIN_SERVER_PORTNo-Admin port: health checks, and Prometheus metrics at /metrics
PGRST_OTEL_DB_STATEMENTNofalseRecord each request's parameterized SQL on its spans as db.query.text

Key ordering is a build-time choice

Postrust returns the keys within each object alphabetically. PostgREST returns them in the order of the select list. That difference is decided when the binary is compiled rather than at run time, because it depends on the map type holding a JSON object, so PGRST_COMPAT_MODE cannot switch it on. It is a Cargo feature:

Terminal
cargo build --release -p postrust-server --features compat-key-order

# Default build
curl 'localhost:3000/api/users?select=status,name,id&limit=1'
# -> [{"id":1,"name":"Alice","status":"active"}]

# With compat-key-order
curl 'localhost:3000/api/users?select=status,name,id&limit=1'
# -> [{"status":"active","name":"Alice","id":1}]

It is off by default because it is not free. Measured by running both builds as containers against the same database and alternating between them, a three-column response cost 1% and an eight-column response 15%: for objects this small a few short string comparisons beat hashing every key, so the sorted map is genuinely the faster one. Turn it on when byte-level compatibility matters more.

Running with PGRST_COMPAT_MODE=true on a binary built without the feature logs a warning at startup, so the difference is not left to be found by diffing responses.

Hasura authentication

The GraphQL surface reads Hasura’s headers, so a deployment migrating from Hasura keeps sending what it already sends. Both spellings of each variable are read, so an existing HASURA_GRAPHQL_* environment needs no renaming.

Set the secret, and a caller that holds it is an administrator — and an administrator may ask to be treated as someone else:

bash
curl localhost:3000/v1/graphql \
  -H 'X-Hasura-Admin-Secret: shh' \
  -H 'X-Hasura-Role: user' \
  -H 'X-Hasura-User-Id: 1' \
  -d '{"query":"{ article { id title } }"}'

That request is answered as user, and x-hasura-user-id becomes a session variable a row-level policy reads as current_setting('hasura.user_id') — or that a function taking hasura_session json receives whole. A Hasura role is not a database role: Artist and anonymous need not exist in any catalogue.

Choosing a role with a token

A token that allows more than one identity carries x-hasura-default-role — who the caller is when it asks for nothing — beside x-hasura-allowed-roles, the list it may ask for instead. The asking is done with an X-Hasura-Role header, and no admin secret is needed for it. That list sits inside the signature, so a caller may choose among the identities it was issued and cannot add one. Asking for a role the token does not list is refused. A token carrying no list allows only the role it already names.

One place this deliberately differs from Hasura

Hasura with no admin secret configured treats every caller as an administrator — which also means an unsecured deployment lets any caller name its own role and its own identity. Postrust does not: with no secret configured, x-hasura-* headers carry no weight and session variables come only from a verified token. A policy reading a value the caller chose is not a policy, and the failure is silent — the query succeeds, against the wrong rows.

See Hasura conformance for how much of the dialect this covers, measured.

Example Configuration

.env
# Required
DATABASE_URL=postgres://user:password@localhost:5432/mydb

# Authentication
PGRST_DB_ANON_ROLE=web_anon
PGRST_JWT_SECRET=your-secret-key-at-least-32-characters

# Hasura-dialect GraphQL at /v1/graphql
PGRST_HASURA_ADMIN_SECRET=shh
PGRST_HASURA_UNAUTHORIZED_ROLE=anonymous

# Apollo Federation subgraph (optional)
# PGRST_GRAPHQL_FEDERATION=true
# PGRST_GRAPHQL_TYPE_PREFIX=billing

# Server
PGRST_SERVER_HOST=0.0.0.0
PGRST_SERVER_PORT=3000
PGRST_SERVER_CORS_ORIGINS=https://myapp.com

# Limits
PGRST_MAX_ROWS=100
PGRST_LOG_LEVEL=info

# Keys from an identity provider instead of a shared secret (optional)
# PGRST_JWT_JWKS_URL=https://<tenant>.auth0.com/.well-known/jwks.json
# PGRST_JWT_ISS=https://<tenant>.auth0.com/

# OpenTelemetry traces and metrics (optional; in the published images)
# OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
# PGRST_LOG_FORMAT=json