# @suaveplan/api-graphql-ui-graphiql — GraphiQL UI mount for the Suaveplan API plugin system. Exposes createGraphiqlUi() — a factory returning a RouterPlugin that registers GET /graphql/ui (or any configured endpoint) and serves a self-contained HTML shell + bundled assets with a strict CSP. Default GraphQL explorer when operators want GraphiQL; sibling api-graphql-ui-altair covers the Altair alternative. GraphiQL UI mount for the Suaveplan API plugin system. Stage 7 of the umbrella plan. `createGraphiqlUi(opts)` returns a `RouterPlugin` that registers `GET /graphql/ui` (or any configured endpoint) on a host `@suaveplan/api-router` `Router`. The endpoint serves a self-contained HTML shell plus bundled JS/CSS assets — no CDN fetches. ## Documentation tree - [assets](./src/assets/assets.md) — Bundled static-asset registry for the GraphiQL UI plugin. The HTML shell references two assets (`graphiql.js`, `graphiq… - [assets](./src/assets/assets.md) — Bundled static-asset registry for the GraphiQL UI plugin. The HTML shell references two assets (`graphiql.js`, `graphiq… - [error-codes](./src/error-codes/error-codes.md) — Owns the stable `GRAPHQL_UI_GRAPHIQL.*` error-code registry consumed by the GraphiQL UI plugin. Every code in the regis… - [error-codes](./src/error-codes/error-codes.md) — Owns the stable `GRAPHQL_UI_GRAPHIQL.*` error-code registry consumed by the GraphiQL UI plugin. Every code in the regis… - [factory](./src/factory/create-graphiql-ui.md) — Public entry point of the package. `createGraphiqlUi(opts)` returns a `RouterPlugin` — a plain value with `name`, `vers… - [create-graphiql-ui](./src/factory/create-graphiql-ui.md) — Public entry point of the package. `createGraphiqlUi(opts)` returns a `RouterPlugin` — a plain value with `name`, `vers… - [headers](./src/headers/csp.md) — Owns the `Content-Security-Policy` header values emitted on the GraphiQL HTML response. The policy is hard-coded to a p… - [csp](./src/headers/csp.md) — Owns the `Content-Security-Policy` header values emitted on the GraphiQL HTML response. The policy is hard-coded to a p… - [html](./src/html/render-shell.md) — Renders the GraphiQL HTML shell. The output embeds the configured GraphQL endpoint, subscription endpoint, theme, and e… - [render-shell](./src/html/render-shell.md) — Renders the GraphiQL HTML shell. The output embeds the configured GraphQL endpoint, subscription endpoint, theme, and e… - [instruments](./src/instruments/instruments.md) — Owns the OpenTelemetry instrument bundle emitted by the GraphiQL UI plugin. A single `Counter` (`graphql.ui.requests_to… - [instruments](./src/instruments/instruments.md) — Owns the OpenTelemetry instrument bundle emitted by the GraphiQL UI plugin. A single `Counter` (`graphql.ui.requests_to… - [options](./src/options/options.md) — Zod-validated configuration surface for `createGraphiqlUi`. Owns `GraphiqlUiOptions` (resolved shape with every default… - [options](./src/options/options.md) — Zod-validated configuration surface for `createGraphiqlUi`. Owns `GraphiqlUiOptions` (resolved shape with every default… ## Why opt-in Per the design doc (`docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md` §10.6) the API foundation reserves `GET /graphql/ui` for an interactive explorer but does not bundle one by default — UI choice is operator- chosen so the runtime image stays minimal for production deployments that don't need it. Operators wire ONE of: - `@suaveplan/api-graphql-ui-graphiql` — canonical GraphiQL (this package). - `@suaveplan/api-graphql-ui-altair` — Altair alternative. The GraphQL engine adapters (`api-graphql-yoga`, `api-graphql-apollo`) return 404 at `/graphql/ui` when no UI plugin is wired. ## Quick start ```ts import { Router } from "@suaveplan/api-router"; import { createGraphiqlUi } from "@suaveplan/api-graphql-ui-graphiql"; const router = new Router({ plugin: "host" }); createGraphiqlUi({}).register(router); // GET /graphql/ui -> the explorer // GET /graphql/ui/assets/graphiql..js -> bundled JS // GET /graphql/ui/assets/graphiql..css -> bundled CSS ``` ## Configuration | Option | Default | Notes | |---|---|---| | `uiEndpoint` | `/graphql/ui` | Must start with `/`. | | `graphqlEndpoint` | `/graphql` | Embedded into the explorer config. | | `subscriptionEndpoint` | `/graphql/ws` | Embedded into the explorer config. | | `theme` | `"system"` | `"light"` / `"dark"` / `"system"`. | | `defaultQuery` | `""` | HTML-escaped before embedding. | | `defaultVariableEditorOpen` | `false` | Opens the variables pane on load. | | `editorOptions` | `{}` | CodeMirror passthroughs. | | `meterProvider` | OTel global | OTel `MeterProvider` for the counter. | ## Security posture - **Bundled assets only.** Every `src` / `href` in the rendered HTML points inside the package's own `//assets/` namespace. Cross-checked by `src/html/render-shell.test.ts`. - **Strict CSP.** `default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data:; connect-src 'self'`. Inline scripts are NOT permitted. Cross-checked by `src/headers/csp.test.ts`. - **Cache-immutable assets.** Bundled JS/CSS carries `Cache-Control: public, max-age=31536000, immutable`. - **Stale-hash tolerance.** Hashed filenames resolve any matching stem+ext to the current asset bytes — rolling deploys don't 404 in-flight sessions. ## OTel - `graphql.ui.requests_total { ui: "graphiql" }` — counter incremented on each successful HTML response. Asset hits are NOT counted. ## Error codes - `GRAPHQL_UI_GRAPHIQL.ASSET_MISSING` — asset lookup failure. - `GRAPHQL_UI_GRAPHIQL.ENDPOINT_COLLISION` — `` already registered by another plugin. ## Tests ```bash bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-graphql-ui-graphiql ``` 100% statements / branches / functions / lines required on touched modules (`provider: istanbul`).