Configuration layers, lowest precedence first:
- Built-in defaults
gqlgate.yaml(or--config <path>;./gqlgate.yamland./gqlgate.ymlare picked up automatically)GQLGATE_*environment variables- Command-line flags
Flag names are the config paths, so precedence needs no mapping table:
--routes.strict_params=false. Only flags you actually pass take effect, so an
unset flag never clobbers a file or environment value.
Environment keys lowercase the name and treat a single underscore as a level separator; a double underscore stands for a literal underscore:
| Key | Environment variable |
|---|---|
upstream.url |
GQLGATE_UPSTREAM_URL |
server.max_body_bytes |
GQLGATE_SERVER_MAX__BODY__BYTES |
routes.strict_params |
GQLGATE_ROUTES_STRICT__PARAMS |
routes.pagination_headers.total_count |
GQLGATE_ROUTES_PAGINATION__HEADERS_TOTAL__COUNT |
gqlgate config example prints the full annotated reference, generated from
the config structs so it cannot drift from the code. gqlgate config validate
checks the effective configuration and exits non-zero with a bulleted list of
problems.
| Key | Default | Meaning |
|---|---|---|
url |
(required) | Base URL of the target GraphQL API |
path |
/graphql |
Endpoint path relative to url |
timeout |
25s |
Per-request timeout for the upstream call |
max_idle_conns |
100 |
Idle connections kept alive |
strip_headers |
[] |
Extra inbound headers never forwarded |
forward_response_headers |
[] |
Upstream response headers copied to the client |
claim_prefix |
X-Pdbq-Claim- |
Claim header prefix; inbound headers matching it are always stripped |
inject_claims |
{} |
Claims gqlgate asserts itself — see security.md |
allow_insecure_claims |
false |
Permit injection toward a public upstream |
request_id_header |
X-Request-Id |
Correlation id; generated when absent |
| Key | Default | Meaning |
|---|---|---|
addr |
:8080 |
Listen address |
request_timeout |
30s |
Whole-request timeout, upstream call included |
max_body_bytes |
1048576 |
Maximum request body size |
cors_origins |
[] |
Allowed origins (exact, or *). Empty disables CORS entirely |
compression |
false |
Gzip responses for clients that accept it |
shutdown_grace |
10s |
Drain time for in-flight requests after SIGTERM |
When CORS is enabled, the pagination headers are added to
Access-Control-Expose-Headers automatically — browsers cannot read them
otherwise, and collection responses carry their pagination there.
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
Serve the generated OpenAPI document |
path |
/openapi.json |
Where the document is served |
title |
gqlgate |
Title shown in API documentation |
version |
1.0.0 |
Version reported in the info block |
description |
"" |
Description shown in API documentation |
server_url |
"" |
Base URL written into the servers block. Empty uses /, so Scalar calls the origin it loaded the document from |
include_aliases |
false |
Also document the flat /q/ and /m/ routes |
allow_any_origin |
true |
Serve the document with Access-Control-Allow-Origin: * |
The document is rendered from the plan whenever routes are built, so it follows schema reloads automatically and can never describe an endpoint that is not served.
Set server_url when the browser reaches gqlgate at an address other than the
one it fetched the document from — behind a proxy, or when Scalar runs on
its own port. The bundled compose stack does this from .env.
allow_any_origin affects the document only; the data endpoints still obey
server.cors_origins. It is on by default because the document describes
exactly what gqlgate routes already prints. Turn it off to keep the API's
shape private, and remember that a docs UI on another origin then cannot
load it.
gqlgate openapi prints the same document without starting a server, for
client generation and CI spec-diffing.
| Key | Default | Meaning |
|---|---|---|
path |
"" |
SDL or introspection-JSON file. Empty introspects the upstream at boot |
poll_interval |
0s |
Re-introspect on this interval. 0 disables; must be >= 1s otherwise |
reload_on_hup |
true |
Rebuild the route table on SIGHUP |
boot_timeout |
30s |
How long to keep retrying introspection at boot |
boot_retry |
2s |
Delay between boot attempts |
schema.path accepts either format; the file is sniffed, so no flag says
which. Generate one with pdbq schema print --json or pdbq schema print.
A file source never retries at boot — a missing file will not fix itself — and
never polls, but SIGHUP still reloads it.
| Key | Default | Meaning |
|---|---|---|
resources |
{} |
Per-type path segment overrides, e.g. {SpatialRefSy: spatial-ref-sys} |
fields |
{} |
Per-field route overrides, e.g. {searchPosts: "GET /posts/search"} |
ignore |
[] |
Fields never exposed |
param_aliases |
{limit: first, skip: offset, sort: orderBy} |
Query-parameter aliases |
upsert_prefixes |
[upsert] |
Field-name prefixes identifying upserts |
strict_params |
true |
Reject query parameters matching no argument |
allow_unfiltered_bulk |
false |
Permit bulk update/delete with an empty filter |
delete_returns_row |
false |
Return 200 and the row rather than 204 |
default_page_size |
0 |
first applied when the client sends no pagination |
max_select_depth |
5 |
Maximum ?select= nesting |
max_select_fields |
200 |
Maximum ?select= field count |
select_cache_size |
256 |
Compiled ?select= documents cached |
flat_aliases |
true |
Also expose every field at /q/ or /m/ |
pagination_headers names the response headers carrying connection metadata:
total_count (X-Total-Count), has_next_page, has_previous_page,
start_cursor, end_cursor, affected_count.
See routing.md for what these do to the route table.
| Key | Default | Meaning |
|---|---|---|
status_map |
see below | extensions.code → HTTP status |
include_extensions |
true |
Include upstream extensions in error bodies |
include_path |
true |
Include upstream path in error bodies |
The default map covers the PostgreSQL SQLSTATEs pdbq surfaces:
errors:
status_map:
"23505": 409 # unique violation
"23503": 422 # foreign key violation
"23514": 422 # check violation
"23502": 422 # not-null violation
"40001": 503 # serialization failure (with Retry-After)
"40P01": 503 # deadlock detected
"42501": 403 # insufficient privilegeCodes not in the map fall back to their SQLSTATE class: 23* → 409,
22* → 400. Failing that, gqlgate reads the message for known phrases, and
finally splits on whether any data came back — null data means the operation
never ran (400), partial data means execution failed server-side (500).
| Key | Default | Meaning |
|---|---|---|
level |
info |
debug, info, warn, error |
format |
text |
text or json |
requests |
true |
One line per request |