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
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
GraphQL
    OverviewSet up an endpointSecure your GraphQL APICache responses
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

Secure your GraphQL API with Zuplo

GraphQL gives clients enormous flexibility over what they ask for — which is also what makes it easy to abuse. A single request can nest arbitrarily deep, fan out into thousands of resolver calls, or map your entire schema through introspection. Zuplo ships three policies that close these gaps at the edge, before a malicious request ever reaches your origin:

RiskPolicy
Deep / costly querygraphql-complexity-limit-inbound
Schema discoverygraphql-disable-introspection-inbound
Schema over-sharinggraphql-introspection-filter-outbound

This guide explains each risk and shows the policy configuration that addresses it. It assumes you've already added a GraphQL endpoint to your gateway.

Understand the risks

Deeply nested or expensive queries

Without limits, a client can send a deeply nested query that forces your server to resolve a huge graph of data, or a flat-but-expensive query that triggers thousands of resolver calls. Either can exhaust resources and cause a denial-of-service — whether the client is a deliberate attacker or simply unaware of the cost of their query.

Introspection

GraphQL lets clients introspect your schema with __schema and __type queries. This is invaluable during development, but in production it hands potential attackers a complete map of your types, fields, and relationships.

Limit query depth and complexity

The graphql-complexity-limit-inbound policy guards against expensive queries in two complementary ways. You can use either or both.

Depth limit

A depth limit caps how many levels a query can nest. It needs no schema and is the simplest first line of defense. Add it under useDepthLimit:

config/policies.json
{ "name": "graphql-complexity-limit-policy", "policyType": "graphql-complexity-limit-inbound", "handler": { "export": "GraphQLComplexityLimitInboundPolicy", "module": "$import(@zuplo/graphql)", "options": { "useDepthLimit": { "depthLimit": 20 } } } }

Complexity limit

A complexity limit scores each query against your schema and rejects queries above a threshold, catching expensive queries that aren't necessarily deep. Because it scores against the schema, it needs your GraphQL endpoint URL for introspection. Combine it with the depth limit:

config/policies.json
{ "name": "graphql-complexity-limit-policy", "policyType": "graphql-complexity-limit-inbound", "handler": { "export": "GraphQLComplexityLimitInboundPolicy", "module": "$import(@zuplo/graphql)", "options": { "useDepthLimit": { "depthLimit": 20 }, "useComplexityLimit": { "complexityLimit": 50, "endpointUrl": "https://api.example.com/graphql" } } } }

A query that exceeds either limit is rejected with a 400 and a GraphQL error; requests within the limits pass through. See the policy reference for the full option set.

Disable introspection in production

The graphql-disable-introspection-inbound policy blocks any query containing __schema or __type with a 403, hiding your schema from clients. It takes no options:

config/policies.json
{ "name": "graphql-disable-introspection-policy", "policyType": "graphql-disable-introspection-inbound", "handler": { "export": "GraphQLDisableIntrospectionInboundPolicy", "module": "$import(@zuplo/graphql)", "options": {} } }

Expose a partial schema instead

Disabling introspection entirely isn't always what you want. If you need clients (or MCP-connected AI agents) to introspect part of your schema while keeping sensitive types and fields hidden, use the outbound graphql-introspection-filter-outbound policy. It strips chosen types and fields from introspection responses:

config/policies.json
{ "name": "graphql-introspection-filter-policy", "policyType": "graphql-introspection-filter-outbound", "handler": { "export": "GraphQLIntrospectionFilterOutboundPolicy", "module": "$import(@zuplo/graphql)", "options": { "excludeTypes": ["AdminUser"], "excludeTypeFields": { "User": ["password", "ssn"], "Query": ["adminUsers"] } } } }

Attach the policies to your route

Add the inbound policies to your GraphQL route in routes.oas.json. The outbound filter policy goes in the outbound array:

config/routes.oas.json
{ "post": { "summary": "GraphQL Endpoint", "x-graphql": true, "x-zuplo-route": { "corsPolicy": "none", "handler": { "export": "urlRewriteHandler", "module": "$import(@zuplo/runtime)", "options": { "rewritePattern": "https://api.example.com/graphql" } }, "policies": { "inbound": [ "graphql-complexity-limit-policy", "graphql-disable-introspection-policy" ] } }, "operationId": "graphql-secure" } }

Example repository

For a complete, runnable setup, see the GraphQL API with Zuplo example repository.

Next steps

  • Cache GraphQL responses — cut latency with edge caching
  • Test GraphQL queries — verify your secured endpoint
  • GraphQL analytics — surface errors and monitor traffic
Edit this page
Last modified on June 29, 2026
Set up an endpointCache responses
On this page
  • Understand the risks
    • Deeply nested or expensive queries
    • Introspection
  • Limit query depth and complexity
    • Depth limit
    • Complexity limit
  • Disable introspection in production
  • Expose a partial schema instead
  • Attach the policies to your route
  • Example repository
  • Next steps
JSON
JSON
JSON
JSON
JSON