# GraphQL on Zuplo

Zuplo fronts GraphQL APIs the same way it fronts REST: requests pass through a
gateway route — with whatever authentication, rate limiting, and
GraphQL-specific policies you attach — and your Dev Portal documents the schema.
This guide covers both halves: adding a GraphQL endpoint to your gateway routes,
then rendering a schema reference and playground in the Dev Portal with
`@zudoku/plugin-graphql`.

## Prerequisites

- A Zuplo project
- An upstream GraphQL API to proxy
- For the Dev Portal section: your project's
  [Dev Portal](../dev-portal/introduction.mdx) lives in its `docs/` folder. The
  plugin requires the `zudoku` package version `0.80.1` or newer — check the
  version in `docs/package.json` and
  [update the Dev Portal](../dev-portal/updating.mdx) if yours is older.

## Add a GraphQL endpoint to your gateway

A GraphQL API serves every query and mutation from a single endpoint, so the
gateway route uses the [URL Rewrite handler](../handlers/url-rewrite.mdx) —
every request goes to exactly the upstream URL — rather than URL Forward, which
appends the incoming path.

### Add a new GraphQL route

<Stepper>

1. **Open the Route Designer**

   In the Zuplo Portal, open the **Code** tab and click **routes.oas.json**.
   This opens the Route Designer, which lists your project's existing routes and
   an **Add** menu.

2. **Add the endpoint**

   Click **Add** and select **GraphQL Endpoint**. This creates a `POST /graphql`
   route preconfigured with the URL Rewrite handler and a demo upstream. If
   `/graphql` is already taken, the Portal appends a short suffix (for example
   `/graphql-b891`) — edit the path to whatever you prefer. GraphQL routes show
   a **GraphQL** badge in the route list instead of an HTTP method.

3. **Point it at your upstream**

   Expand the new route. Its handler is preset to **URL Rewrite** in the
   **Request Handler** drop-down; in the URL text box below it, replace the demo
   URL with your GraphQL API's endpoint, for example
   `https://api.example.com/graphql`. To use a different backend per
   environment, reference an [environment variable](./environment-variables.mdx)
   such as `${env.GRAPHQL_API_URL}`.

4. **Save**

   Save your changes. Your gateway now proxies GraphQL requests at `/graphql`.

</Stepper>

### Mark an existing route as GraphQL

Already proxying a GraphQL API through a regular route? Open the route in the
Route Designer, click the **⋯** menu at the end of the route's options row (next
to the CORS and docs toggles), and check **Mark as GraphQL**. This tags the
route as a GraphQL endpoint without changing its handler or policies.

### The resulting route configuration

Both paths produce a route in `routes.oas.json` with the `x-graphql` extension
set:

```json title="config/routes.oas.json"
{
  "paths": {
    "/graphql": {
      "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": []
          }
        },
        "operationId": "graphql-1a2bc3d4"
      }
    }
  }
}
```

:::tip{title="Secure the endpoint"}

GraphQL has its own attack surface — deeply nested queries, expensive
operations, and schema introspection. Zuplo ships policies for all three. See
[Secure your GraphQL API](./graphql-security.mdx) to add complexity limits and
disable introspection in production.

:::

## Document the API in your Dev Portal

The `@zudoku/plugin-graphql` package renders GraphQL APIs in your Dev Portal
from a schema. Each plugin instance produces a browsable type reference
(queries, mutations, objects, enums, and so on) plus a playground, generated
straight from your schema.

:::caution{title="Beta"}

The GraphQL plugin is in beta. During the beta it isn't available in your
project's working copy — it only works in projects
[connected to source control](./source-control.mdx).

:::

<Stepper>

1. **Install the plugin**

   In your project's `docs/` folder (where `zudoku.config.tsx` lives), install
   the plugin:

   ```bash
   npm install @zudoku/plugin-graphql
   ```

   The plugin requires `zudoku` `0.80.1` or newer.

2. **Add a schema**

   Drop a GraphQL schema definition language (SDL) file next to your config:

   ```graphql title="schema.graphql"
   type Query {
     product(id: ID!): Product
   }

   type Product {
     id: ID!
     name: String!
     price: Float!
   }
   ```

   Don't have an SDL file handy? Skip this step and introspect a live endpoint
   at build time with `type: "url"` in the next step — the schema is fetched for
   you when the portal builds.

3. **Register the plugin**

   Import `graphqlPlugin` and add an instance per API. The `path` is where the
   docs mount, and `input` points at your schema:

   ```tsx title="zudoku.config.tsx"
   import { graphqlPlugin } from "@zudoku/plugin-graphql";

   const config = {
     plugins: [
       graphqlPlugin({
         type: "file",
         input: "./schema.graphql",
         path: "/graphql/ecommerce",
         options: {
           title: "E-Commerce GraphQL API",
           description: "Products, orders, and customers.",
           playground: {
             endpoint: "https://my-gateway.example.com/graphql",
           },
         },
       }),
     ],
   };

   export default config;
   ```

   Set `playground.endpoint` to the gateway route from the first half of this
   guide so readers run real queries through your gateway. With a file-based
   schema the plugin doesn't know where your API lives — leave the endpoint
   unset and the reference pages still render, but the playground asks you to
   configure `options.playground.endpoint` before it can run operations.

   With `type: "url"`, set `input` to your GraphQL endpoint's URL instead: the
   schema is fetched via introspection when the portal builds, and the
   playground defaults to that same URL. You can register the plugin more than
   once to document several schemas, each with its own `path`.

4. **Link it in the navigation**

   Point a navigation link at the instance's `path`. Set `stack: true` so the
   API's own pages render as a stacked sub-navigation instead of expanding
   inline:

   ```tsx title="zudoku.config.tsx"
   navigation: [
     {
       type: "category",
       label: "Documentation",
       items: [
         {
           type: "link",
           label: "E-Commerce API",
           to: "/graphql/ecommerce",
           stack: true,
         },
       ],
     },
   ];
   ```

   Prefer the API as its own top-level section instead? Drop the link at the top
   level of `navigation` and leave off `stack`. See the
   [navigation reference](../dev-portal/zudoku/configuration/navigation.mdx) for
   all link, category, and stacking options.

</Stepper>

### Plugin options

All options live under the instance's `options` key:

| Option                | Type      | Description                                                                                                                                            |
| --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `title`               | `string`  | Display title for the API's overview page.                                                                                                             |
| `description`         | `string`  | Short description shown on the overview page.                                                                                                          |
| `showDeprecated`      | `boolean` | Include deprecated fields and operations in the reference.                                                                                             |
| `playground.enabled`  | `boolean` | Show the playground. Defaults to `true`.                                                                                                               |
| `playground.endpoint` | `string`  | URL the playground sends operations to. Defaults to `input` for `type: "url"`. File schemas have no default; the playground prompts for one until set. |
| `playground.headers`  | `object`  | Default headers the playground sends with each operation.                                                                                              |

## Verify it worked

Open the
[**Environments**](https://portal.zuplo.com/+/account/project/environments) tab
in the Zuplo Portal — your build is listed there along with the URLs for both
your gateway and your Dev Portal. Send a query through the gateway URL:

```bash
curl https://my-gateway.example.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ __typename }"}'
```

A working route returns a response from your upstream, such as
`{"data":{"__typename":"Query"}}`.

For the Dev Portal, open its URL from the **Environments** tab (or run
`npm run dev` in your `docs/` folder) and go to the plugin's `path` (for example
`/graphql/ecommerce`). The overview page lists the schema's types, and the
playground runs operations against your configured endpoint.

## Next steps

- [Secure your GraphQL API](./graphql-security.mdx) — complexity limits and
  introspection policies
- [Testing GraphQL queries](./testing-graphql.mdx) — test the endpoint from the
  Zuplo Portal or external tools
- [URL Rewrite handler](../handlers/url-rewrite.mdx) — full reference for the
  handler GraphQL routes use
- [MCP Server GraphQL endpoints](../mcp-server/graphql.mdx) — expose GraphQL
  queries as MCP tools for AI agents
