ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop on the web portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
Development
Policies
    Policy Catalog
    Authentication
    Authorization
    MCP Authorization
    Security & Validation
    Metrics, Billing & Quotas
    Testing
    Request Modification
    Response Modification
    Upstream Authentication
    Archival
    GraphQL
      GraphQL CacheGraphQL Complexity LimitGraphQL Disable IntrospectionGraphQL Introspection FilterGraphQL Analytics
    Other
    Guides
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
GraphQL
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
GraphQL

GraphQL Analytics Policy

The GraphQL Analytics policy makes failed GraphQL operations visible on Zuplo's GraphQL analytics dashboard. GraphQL servers following the standard Apollo / graphql-yoga pattern return 200 OK with an errors[] array in the response body when an operation fails — invisible to HTTP-level analytics, which would report every such operation as a success.

Add this policy to a GraphQL route (marked x-graphql: true) and the gateway reads the response body, counts the GraphQL errors, and classifies each one by its extensions.code (syntax, validation, auth, timeout, or resolver) — no changes to your GraphQL server or client code required. The classification map is configurable for servers that emit custom error codes, and errors can optionally be written to the request log.

Beta

This policy is in beta. You can use it today, but it may change in non-backward compatible ways before the final release.

Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

config/policies.json
{ "name": "my-graphql-analytics-outbound-policy", "policyType": "graphql-analytics-outbound", "handler": { "export": "GraphqlAnalyticsOutboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "errorCodeClassification": { "RATE_LIMITED": "resolver", "NOT_LOGGED_IN": "auth" }, "defaultErrorClass": "resolver", "logErrors": false } } }

Policy Configuration

  • name <string> - The name of your policy instance. This is used as a reference in your routes.
  • policyType <string> - The identifier of the policy. This is used by the Zuplo UI. Value should be graphql-analytics-outbound.
  • handler.export <string> - The name of the exported type. Value should be GraphqlAnalyticsOutboundPolicy.
  • handler.module <string> - The module containing the policy. Value should be $import(@zuplo/runtime).
  • handler.options <object> - The options for this policy. See Policy Options below.

Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

  • errorCodeClassification <object> - Additional extensions.code → error-class mappings for codes your GraphQL server emits. Entries are merged over (and win against) the built-in Apollo-convention map (GRAPHQL_PARSE_FAILED → syntax, GRAPHQL_VALIDATION_FAILED / BAD_USER_INPUT → validation, UNAUTHENTICATED / FORBIDDEN → auth, timeout codes → timeout, INTERNAL_SERVER_ERROR → resolver). Keys are matched case-sensitively; built-in codes are matched case-insensitively.
  • defaultErrorClass <string> - The error class reported for a GraphQL error whose extensions.code is missing or not in the classification map. Allowed values are syntax, validation, auth, timeout, resolver. Defaults to "resolver".
  • logErrors <boolean> - When true, also write a structured warning to the request log (message, extensions.code, and path of each error — capped at the first 10) whenever a response contains GraphQL errors. Defaults to false.
  • maxScanBytes <integer> - How many bytes of the response body the policy reads to look for GraphQL errors. The body is read and scanned for the errors token up to this limit; a body larger than the limit — by Content-Length, or measured while reading when the header is absent — is treated as error-free, so any errors it carries go unreported. The default of 128 KiB suits the common case, where servers emit errors near the front of the body. Raise it (up to the 5 MiB maximum) to detect errors in larger responses, at the cost of reading more of every response. When the token is found and the body fits within the limit, the body is parsed and its errors are reported. Defaults to 131072.

Using the Policy

This policy reads GraphQL errors[] from response bodies and reports them to Zuplo's GraphQL analytics, so operations that fail with the standard "200 OK with errors" pattern (Apollo Server, graphql-yoga, and most other GraphQL servers) show up as failures on the GraphQL dashboard instead of successes.

The response always passes through to the client unchanged — the body is read from a clone, and any internal failure inside the policy is swallowed so error reporting can never break a request.

Requirements

The route must be marked with "x-graphql": true in routes.oas.json. That marker is what enables GraphQL analytics for the route (one graphql_operation event per request); this policy enriches that event with the errors it finds in the response body. On a route without the marker the policy logs a warning and does nothing.

Error classification

Each entry in the response's errors[] array counts as one error toward the operation's errorCount, and is classified into one of five classes from its extensions.code, following the Apollo Server conventions:

extensions.codeClass
GRAPHQL_PARSE_FAILEDsyntax
GRAPHQL_VALIDATION_FAILED, BAD_USER_INPUT, PERSISTED_QUERY_NOT_FOUND, PERSISTED_QUERY_NOT_SUPPORTED, OPERATION_RESOLUTION_FAILUREvalidation
UNAUTHENTICATED, FORBIDDENauth
REQUEST_TIMEOUT, TIMEOUT, GATEWAY_TIMEOUTtimeout
INTERNAL_SERVER_ERROR, DOWNSTREAM_SERVICE_ERRORresolver

An error whose code is missing or unrecognized is classified as defaultErrorClass (resolver unless configured otherwise). Servers that emit their own codes can extend or override the table with errorCodeClassification; those entries are matched case-sensitively and win against the built-ins, which are matched case-insensitively.

Batched (array) responses are supported — errors are collected across every result in the batch.

What is inspected

Only responses with a JSON content type (application/json or any +json type such as application/graphql-response+json) are read. The policy reads up to maxScanBytes of the body (128 KiB by default, 5 MiB maximum) and scans it for the errors token; a response larger than that — by Content-Length when the header is present, or measured while reading when it is absent — is treated as error-free, so any errors it carries go unreported. Raise maxScanBytes if your GraphQL responses are larger. Everything else passes through without the body being touched.

Logging

Set logErrors to true to also write a structured warning to the request log whenever a response contains GraphQL errors. The entry carries the message, extensions.code, and path of each error (capped at the first 10) under a consistent "GraphQL response contained errors" message, so you can search or alert on it.

Configuration

  • errorCodeClassification: Additional extensions.code → class mappings, merged over the built-in table. Default: none
  • defaultErrorClass: Class for errors with a missing or unrecognized code — one of syntax, validation, auth, timeout, resolver. Default: resolver
  • logErrors: Also log a structured warning per errored response. Default: false
  • maxScanBytes: How many bytes of the response body to read and scan for the errors token. A larger response is treated as error-free. Capped at 5 MiB. Default: 131072 (128 KiB)

Usage

Apply this policy to outbound responses on your GraphQL route:

Code
{ "policies": [ { "name": "graphql-analytics", "policyType": "graphql-analytics-outbound", "handler": { "export": "GraphqlAnalyticsOutboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "errorCodeClassification": { "RATE_LIMITED": "resolver", "NOT_LOGGED_IN": "auth" }, "logErrors": true } } } ] }

Read more about how policies work

Edit this page
Last modified on July 3, 2026
GraphQL Introspection FilterComposite Inbound (Group Policies)
On this page
  • Configuration
    • Policy Configuration
    • Policy Options
  • Using the Policy
  • Requirements
  • Error classification
  • What is inspected
  • Logging
  • Configuration
  • Usage
JSON
JSON