Persisted Queries
Persisted queries (also known as trusted documents or query allowlists) are a security and performance technique where GraphQL operations are pre-registered with the server. Instead of sending the full query string, clients send a short hash identifier. The server looks up the operation from its allowlist.
Gazania includes a CLI command to extract all your query definitions and export them as a manifest file that can be uploaded to your server's allowlist.
The gazania extract command
The extract command scans your source files, finds all Gazania builder calls using type-aware detection, evaluates them at analysis time, and writes a JSON manifest containing each operation's body and hash.
npx gazania extractGazania defaults to tsconfig.json in the config file's directory (or current directory) for type-aware detection to identify builder identifiers by type (including re-exported, aliased, and factory-created builders).
By default this scans src/ and outputs the manifest to stdout. Use --output <path> to write to a file.
Manifest format
{
"operations": {
"FetchAnime": {
"body": "query FetchAnime($id: Int = 127549) {\n Media(id: $id, type: ANIME) {\n id\n title {\n romaji\n english\n native\n }\n }\n}",
"hash": "sha256:a1b2c3d4...",
"loc": {
"start": { "line": 10, "column": 1, "offset": 245 },
"end": { "line": 15, "column": 2, "offset": 412 }
}
},
"CreateUser": {
"body": "mutation CreateUser($input: CreateUserInput!) { ... }",
"hash": "sha256:e5f6a7b8...",
"loc": {
"start": { "line": 20, "column": 1, "offset": 600 },
"end": { "line": 25, "column": 2, "offset": 820 }
}
}
},
"fragments": {
"UserFields": {
"body": "fragment UserFields on User {\n id\n name\n email\n}",
"hash": "sha256:c9d0e1f2...",
"loc": {
"start": { "line": 3, "column": 14, "offset": 88 },
"end": { "line": 3, "column": 52, "offset": 126 }
}
}
}
}Each entry includes a loc field with start and end source positions. Each position contains line (1-based), column (1-based), and offset (0-based character offset from file start).
Operations (queries, mutations, subscriptions) go into operations. Named fragments go into fragments.
Partials and sections
The extractor understands gazania.partial() and gazania.section() builders and includes their generated fragments in the manifest.
Same-file partials and sections are resolved automatically.
Cross-file partials and sections — where a partial defined in one file is imported and spread into a query in another file — are resolved via TypeScript module resolution. Files are processed in dependency order so that evaluated partials and sections are available when the importer files are evaluated.
tsconfig
Gazania defaults to tsconfig.json in the config file's directory (or current directory). Your tsconfig must include all source files that contain partials, sections, or operations you want to extract. A minimal example:
{
"compilerOptions": {
"moduleResolution": "bundler"
},
"include": ["src"]
}If your project already has a tsconfig.json, you can point directly to it.
Options
gazania extract [options]
Options:
-d, --dir <path> Directory to scan (default: src)
-o, --output <path> Output manifest file path, use - for stdout (default: stdout)
--include <glob> File glob pattern to include (default: **/*.{ts,tsx,js,jsx,vue,svelte})
--algorithm <alg> Hash algorithm (default: sha256)
--tsconfig <path> Path to tsconfig.json (default: tsconfig.json)
-c, --config <path> Path to config file
--silent Suppress progress output (errors still shown)
--ignore-unresolved Skip unresolved reference errors
--ignore-analysis Skip analysis failure errors
--ignore-circular Skip circular reference errors
--ignore-all Skip all extraction errors
--no-emit Suppress manifest output (useful for validation)
-h, --help Show helpExamples
Basic usage (outputs to stdout):
npx gazania extractWrite to a file:
npx gazania extract --output dist/persisted-queries.jsonScan the selected directory:
npx gazania extract --dir appUse SHA-512 hashes:
npx gazania extract --algorithm sha512Ignore all extraction errors:
npx gazania extract --ignore-allValidation only (no output):
npx gazania extract --no-emitTypical workflow
1. Add extract to your build
Run gazania extract as part of your CI or build pipeline, after TypeScript compilation:
{
"scripts": {
"build": "tsc && gazania extract --output dist/manifest.json",
"generate": "gazania generate"
}
}2. Upload the manifest to your server
How you register the manifest depends on your GraphQL server. Most frameworks have a concept of a "trusted document store" or "persisted query allowlist". Provide the manifest as-is or transform it to your server's expected format.
For example, with Apollo Server's automatic persisted queries (APQ) or Envelop's usePersistedOperations plugin, you load the operations as a map of { hash: body }.
3. Configure your client to send hashes
Instead of sending the full query string, your GraphQL client sends the hash. The exact configuration depends on your client library.
Choco, URQL, Apollo Client
Each client has a different mechanism for persisted queries. Consult your client's documentation for how to enable hash-based operation sending.
Behavior notes
- Type-aware detection: The extractor uses type-aware detection to identify Gazania builders by their type (via the
~isGazaniamarker). This means re-exported, aliased, and factory-created builders (import { g } from './utils',const g = createGazania()) are all detected correctly. - Static analysis only: The extractor evaluates builders with static analysis. When a Gazania call cannot be statically evaluated (e.g., unresolved references, runtime-dependent values, circular partials), extraction fails by default. Use
--ignore-*flags to suppress specific failure categories and allow extraction to continue. - Vue and Svelte:
.vueand.sveltefiles are supported. The extractor parses each<script>block (including<script setup>and<script context="module">) separately and treats them as independent JS/TS modules. - Anonymous operations: Unnamed operations receive an auto-generated key based on the first 8 hex characters of their hash (e.g.
Anonymous_a1b2c3d4). - Duplicate names: If the same operation or fragment name is defined in multiple files with different bodies, extraction fails with an error. If the bodies are identical, the duplicate is silently skipped.