ZuploZuplo
LoginSign Up
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop using the Portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingMCP - Quick start
    Develop Locally
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth
Concepts
Development
Policies
    Policy Catalog
    Authentication
    Authorization
    Security & Validation
    Metrics, Billing & Quotas
    Testing
    Request Modification
    Response Modification
    Upstream Authentication
    Archival
    GraphQL
    Other
    Guides
      Multiple Auth PoliciesSecure your GraphQL APIPer-User Rate LimitsComposite Policy PatternsClient mTLS Authentication
Handlers
API Keys
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Guides

Composite Policy: Limitations and Patterns

Composite policies let you group multiple policies into a single reusable unit that you can apply across routes. While they simplify configuration and keep your policies.json organized, there are important limitations to understand — especially around nesting composite policies inside other composite policies.

This guide covers those limitations, explains the error messages you might encounter, and provides recommended patterns for scaling policy management.

How composite policies work

A composite policy references other policies by their name as defined in your policies.json file. When a route uses a composite policy, Zuplo executes each referenced policy in order, just as if you had listed them individually on the route.

Code
{ "name": "security-group", "policyType": "composite-inbound", "handler": { "export": "CompositeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "policies": ["api-key-auth", "rate-limit", "request-validation"] } } }

In this example, any route that references security-group runs the api-key-auth, rate-limit, and request-validation policies in sequence.

For full configuration details, see the Composite Inbound Policy and Composite Outbound Policy reference pages.

Limitations

Nested composite policies are not supported

You cannot place a composite policy inside another composite policy. While Zuplo's configuration does not prevent you from referencing one composite policy in another composite policy's policies array, doing so leads to unexpected behavior at runtime.

Nested composite policies are not supported. Always list all required policies directly in a single, flat composite policy. Nesting composites inside other composites can cause policies to malfunction or produce confusing errors.

For example, the following configuration does not work as expected:

❌ Unsupported: nested composites
[ { "name": "shared-template", "policyType": "composite-inbound", "handler": { "export": "CompositeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "policies": ["api-key-auth", "rate-limit"] } } }, { "name": "project-template", "policyType": "composite-inbound", "handler": { "export": "CompositeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "policies": ["shared-template", "request-validation"] } } } ]

In this example, project-template references shared-template, which is itself a composite policy. This nesting causes the inner policies to not execute correctly.

Request validation inside nested composites

One specific failure mode involves the Request Validation policy. When used inside a nested composite policy, the Request Validation policy may not be able to resolve the OpenAPI schema for the current route. You may see an error similar to:

Code
No schema defined for method ...

This error does not mean your schema is missing from your OpenAPI specification. It indicates that the validation policy lost access to the route's schema context because of the unsupported nesting.

Circular references

Composite policies that reference each other (directly or indirectly) create circular references that can cause your gateway to fail. Always verify that your composite policy chains do not form loops.

Recommended patterns for policy reuse

Use flat composite policies

Instead of nesting composite policies, list every policy directly in a single composite:

✅ Flat composite with all policies listed
{ "name": "project-template", "policyType": "composite-inbound", "handler": { "export": "CompositeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "policies": [ "api-key-auth", "rate-limit", "request-validation", "custom-logging" ] } } }

This approach is explicit and avoids any nesting issues. The trade-off is that when a "shared" set of policies changes, you need to update every composite policy that includes them.

Create purpose-specific composite policies

Group policies by function or security level. Rather than one universal composite, define composites that map to specific route requirements:

policies.json
[ { "name": "public-api-policies", "policyType": "composite-inbound", "handler": { "export": "CompositeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "policies": ["rate-limit", "request-validation"] } } }, { "name": "authenticated-api-policies", "policyType": "composite-inbound", "handler": { "export": "CompositeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "policies": [ "api-key-auth", "rate-limit", "request-validation", "audit-log" ] } } } ]

Routes can then reference the appropriate composite by name without needing to repeat individual policy lists.

Use custom policies for advanced composition

When you need conditional logic or dynamic policy invocation, write a custom code inbound policy that calls context.invokeInboundPolicy() programmatically:

modules/conditional-policies.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export default async function (request: ZuploRequest, context: ZuploContext) { // Always run authentication const authResult = await context.invokeInboundPolicy("api-key-auth", request); if (authResult instanceof Response) { return authResult; } // Always run rate limiting const rateLimitResult = await context.invokeInboundPolicy( "rate-limit", authResult, ); if (rateLimitResult instanceof Response) { return rateLimitResult; } // Conditionally run validation based on method if (request.method === "POST" || request.method === "PUT") { const validationResult = await context.invokeInboundPolicy( "request-validation", rateLimitResult, ); if (validationResult instanceof Response) { return validationResult; } return validationResult; } // Skip validation for GET, DELETE, etc. return rateLimitResult; }

This approach gives you full control over execution order and conditional logic. See Conditional Policy Execution for more examples.

Use context.invokeInboundPolicy() when you need to programmatically decide which policies to run. Use composite policies when you have a fixed set of policies that always run together.

Error messages and troubleshooting

"No schema defined for method..."

Cause: The Request Validation policy cannot find the OpenAPI schema for the current route. This commonly occurs when the validation policy runs inside a nested composite policy.

Fix: Move the Request Validation policy out of any nested composite structure. List it directly in a flat composite policy or directly on the route.

Gateway may fail due to circular references

Cause: Circular references in composite policy configuration. For example, policy A references policy B, and policy B references policy A.

Fix: Review your policies.json and verify that no composite policy references another composite that eventually references it back. Trace the full chain of policy references to identify the loop.

Policies execute in unexpected order

Cause: Policies within a composite run in the order listed in the policies array. If you have multiple composites on a route, each composite runs its policies sequentially, and the composites themselves run in the order they appear on the route.

Fix: Verify the order of policies in your composite's policies array and the order of policies assigned to your route.

Summary

  • Composite policies group multiple policies into a single reusable reference.
  • Nested composite policies are not supported. Always use flat composites that list all required policies directly.
  • The Request Validation policy produces a "No schema defined" error when used inside nested composites.
  • For advanced composition logic, use context.invokeInboundPolicy() in a custom code policy.
  • Avoid circular references between composite policies.
Edit this page
Last modified on March 27, 2026
Per-User Rate LimitsClient mTLS Authentication
On this page
  • How composite policies work
  • Limitations
    • Nested composite policies are not supported
    • Request validation inside nested composites
    • Circular references
  • Recommended patterns for policy reuse
    • Use flat composite policies
    • Create purpose-specific composite policies
    • Use custom policies for advanced composition
  • Error messages and troubleshooting
    • "No schema defined for method..."
    • Gateway may fail due to circular references
    • Policies execute in unexpected order
  • Summary
JSON
JSON
JSON
JSON
TypeScript