GraphQL Schema Specification Template
GraphQL API spec: types, queries, mutations, subscriptions, connections, nullability, errors, auth, cost limits, federation
使い方: Use it for any GraphQL API alongside the SDL file in the repository. Fill the overview, nullability policy and naming conventions first, since they are hard to change after clients ship. The main pitfall is unbounded queries: set depth and cost limits before going public.
プレビュー
GraphQL Schema Specification: [API or graph name]
Specification of one GraphQL API: schema overview, types, queries, mutations, subscriptions, pagination, errors, nullability, naming, deprecation, authorisation, query cost limits, persisted queries, federation, performance and evolution. Written against the GraphQL specification, September 2025 edition (spec.graphql.org), with the widely used cursor connections convention for pagination. Replace every
[bracketed]value and every row marked Example. Worked examples use a bookshop catalogue and ordering graph.
Document control
| Field | Value |
|---|---|
| Document ID | [API-GQL-001] |
| Graph / API name | [bookshop-graph] |
| Schema version or tag | [2026-03-14 / commit abc1234] |
| GraphQL spec edition | September 2025 |
| Endpoint | [https://api.example.com/graphql] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Superseded] |
| Owner | [@graph-team] |
| Classification | [Internal / Partner / Public] |
| Last updated | YYYY-MM-DD |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Graph owner / API lead | [@name] | [Approved / Approved with comments / Rejected] | YYYY-MM-DD |
| Security reviewer | [@name] | [Decision] | YYYY-MM-DD |
| Client representative | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| [0.1] | YYYY-MM-DD | [@author] | Initial draft |
| [0.2] | YYYY-MM-DD | [@author] | Added subscriptions and persisted queries |
Contents
- Introduction
- Schema overview
- Types
- Queries
- Mutations
- Subscriptions
- Nullability
- Pagination
- Naming conventions
- Deprecation and evolution
- Errors
- Authorisation
- Query cost and depth limits
- Persisted queries and transport
- Federation and composition
- Performance
- Observability
- Schema governance
- Glossary and references
1. Introduction
1.1 Purpose
[Example: The bookshop graph gives the web shop and mobile apps one API for browsing the catalogue, managing baskets and placing orders. It replaces four REST backends-for-frontends.]
1.2 Consumers
| Client | Type | Main operations | Contact |
|---|---|---|---|
| Example: Web shop | First-party web | Catalogue browse, basket, checkout | @web-team |
| Example: iOS and Android apps | First-party mobile | Same as web, plus order tracking subscription | @mobile-team |
| Example: Partner bookstores | Third-party | Read-only catalogue and stock | @partner-success |
| [Client] | [Type] | [Operations] | [@contact] |
1.3 Design principles
- Demand-oriented: the schema models what client screens need, expressed in domain language, not the shape of backing services.
- One graph: one schema per organisation area, so clients do not stitch data together themselves.
- Evolve, do not version: additive change plus deprecation; no
/v2endpoint. - Secure by default: every field is authorised server-side, regardless of which client asks.
1.4 Out of scope
[Example: Warehouse operations and finance reporting stay on their own APIs; the graph does not expose admin mutations.]
2. Schema overview
The diagram shows the main object types and their relationships.
| Area | Root fields | Owning team | Backing service |
|---|---|---|---|
| Example: Catalogue | book, books, author, search | Team Catalogue | catalogue-service |
| Example: Basket | viewer.basket, addToBasket | Team Checkout | basket-service |
| Example: Orders | viewer.orders, placeOrder, orderStatusChanged | Team Orders | order-service |
| [Area] | [Fields] | [Team] | [Service] |
3. Types
Full schema: [link to schema.graphql]. The type system uses the kinds
defined by the GraphQL specification: scalars, objects, interfaces, unions,
enums and input objects.
3.1 Custom scalars
Every custom scalar MUST declare @specifiedBy with a URL to its format.
"An instant in time, RFC 3339 format in UTC, e.g. 2026-03-14T09:30:00Z."
scalar DateTime @specifiedBy(url: "https://www.rfc-editor.org/rfc/rfc3339")
"ISO 4217 currency code, e.g. EUR."
scalar CurrencyCode @specifiedBy(url: "https://www.iso.org/iso-4217-currency-codes.html")
3.2 Core object types and interfaces
Objects that clients may need to refetch implement the Node interface
with a globally unique, opaque id. Money is always an object with a
decimal string and a currency, never a float. Enums are used for closed
sets of values that clients branch on; clients MUST handle unknown enum
values gracefully because new values are added without a new version.
Every type and field carries a description string, which becomes the
reference documentation.
"An object with a globally unique ID that can be refetched with Query.node."
interface Node {
id: ID!
}
type Money {
"Decimal amount as a string to avoid floating-point errors, e.g. \"12.90\"."
amount: String!
currency: CurrencyCode!
}
type Book implements Node {
id: ID!
isbn13: String!
title: String!
authors: [Author!]!
price: Money!
publishedOn: String
stock: StockLevel
}
enum StockLevel {
IN_STOCK
LOW_STOCK
OUT_OF_STOCK
}
3.3 Type register
| Type | Kind | Description | Owner |
|---|---|---|---|
Example: Book | Object (implements Node) | A sellable edition identified by ISBN-13 | Team Catalogue |
Example: SearchResult | Union (Book, Author) | One hit in a catalogue search | Team Catalogue |
Example: PaymentMethodInput | Input object (@oneOf) | Exactly one of card token or gift card code | Team Checkout |
| [Type] | [Kind] | [Description] | [Owner] |
4. Queries
type Query {
"Refetch any object by its global ID."
node(id: ID!): Node
book(id: ID!): Book
books(first: Int, after: String, filter: BookFilter): BookConnection!
search(text: String!, first: Int = 20, after: String): SearchResultConnection!
"The authenticated customer, or null for anonymous callers."
viewer: Viewer
}
| Field | Arguments | Auth | Cost | Notes |
|---|---|---|---|---|
Example: books | first (max 50), after, filter | Public | 1 + first | Filter by genre, author, price range |
Example: viewer | none | Customer token | 1 | Entry point for all personal data |
| [Field] | [Arguments] | [Auth] | [Cost] | [Notes] |
5. Mutations
Conventions:
- Name mutations verb first:
addToBasket,placeOrder,cancelOrder. - Each mutation takes a single argument
input: <MutationName>Input!and returns<MutationName>Payload!. - Payloads return the changed object and a list of typed
userErrorsfor expected failures. - Mutations with side effects accept a
clientMutationIdor an idempotency key in the input.
input PlaceOrderInput {
basketId: ID!
paymentMethod: PaymentMethodInput!
idempotencyKey: String!
}
input PaymentMethodInput @oneOf {
cardToken: String
giftCardCode: String
}
type PlaceOrderPayload {
order: Order
userErrors: [PlaceOrderError!]!
}
type PlaceOrderError {
code: PlaceOrderErrorCode!
message: String!
"Path to the input field that caused the error, e.g. [\"paymentMethod\", \"cardToken\"]."
field: [String!]
}
enum PlaceOrderErrorCode {
BASKET_EMPTY
ITEM_OUT_OF_STOCK
PAYMENT_DECLINED
}
type Mutation {
placeOrder(input: PlaceOrderInput!): PlaceOrderPayload!
}
6. Subscriptions
| Subscription | Event source | Auth | Delivery | Limits |
|---|---|---|---|---|
Example: orderStatusChanged(orderId: ID!) | OrderStatusChanged broker event | Customer token, must own order | At most once; client refetches on reconnect | 5 active per connection |
| [Subscription] | [Source] | [Auth] | [Guarantee] | [Limits] |
type Subscription {
orderStatusChanged(orderId: ID!): Order!
}
Transport: [Example: GraphQL over WebSocket using the graphql-transport-ws
subprotocol; connection authenticated in the connection_init payload].
7. Nullability
| Rule | Policy |
|---|---|
| Object fields backed by another service | Nullable, so one failing service does not null the parent |
| Identifiers and fields stored with the object | Non-null |
| Lists | [Item!]!: the list and its items are non-null; an empty list means none |
| Arguments | Non-null only when required; optional arguments have defaults where sensible |
| Changing nullability | Output: non-null to nullable is breaking for clients; input: nullable to non-null is breaking |
8. Pagination
type BookConnection {
edges: [BookEdge!]!
pageInfo: PageInfo!
"Approximate; may be omitted for expensive filters."
totalCount: Int
}
type BookEdge {
cursor: String!
node: Book!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
firstdefault [20], maximum [50]; larger values return a validation error.- Cursors are opaque, base64-encoded and valid for at least [24 hours].
- Backward pagination (
last,before) is supported only where [listed].
9. Naming conventions
| Element | Convention | Example |
|---|---|---|
| Types, interfaces, unions, enums, inputs | PascalCase | Book, SearchResult, PlaceOrderInput |
| Fields and arguments | camelCase | publishedOn, isbn13 |
| Enum values | SCREAMING_SNAKE_CASE | OUT_OF_STOCK |
| Mutations | verb + noun | cancelOrder |
| Input types | <Mutation>Input | CancelOrderInput |
| Payload types | <Mutation>Payload | CancelOrderPayload |
| Boolean fields | is / has / can prefix | isGift, canCancel |
| Subscriptions | noun + past-tense verb | orderStatusChanged |
Names come from the domain's ubiquitous language, never from backing service or table names.
10. Deprecation and evolution
type Book implements Node {
id: ID!
"Use price.amount and price.currency."
priceInCents: Int @deprecated(reason: "Use `price`. Removal after 2026-12-31.")
price: Money!
}
| Schema coordinate | Deprecated on | Replacement | Usage last 30 days | Removal date | Status |
|---|---|---|---|---|---|
Example: Book.priceInCents | YYYY-MM-DD | Book.price | 1.2% of operations (iOS 5.x) | YYYY-MM-DD | Waiting for app adoption |
| [Type.field] | YYYY-MM-DD | [Replacement] | [Usage] | YYYY-MM-DD | [Status] |
Policy: [Example: minimum 90 days after deprecation and two app release cycles; removal requires zero usage from first-party clients for 14 days].
11. Errors
| Category | Where it appears | Example |
|---|---|---|
| Validation of the document | Top-level errors, no data | Unknown field pricee |
| Business outcome expected by the client | Payload userErrors or union result type | ITEM_OUT_OF_STOCK |
| Authorisation failure on a field | Top-level errors with extensions.code = "FORBIDDEN", field null | Reading another customer's order |
| Downstream failure | Top-level errors with extensions.code = "UNAVAILABLE", partial data | Stock service timeout |
{
"data": {"book": {"title": "Dune", "stock": null}},
"errors": [{
"message": "Stock information is temporarily unavailable.",
"path": ["book", "stock"],
"extensions": {"code": "UNAVAILABLE", "traceId": "4bf92f35"}
}]
}
Error messages MUST NOT include stack traces or internal hostnames. The
list of extensions.code values is fixed: [UNAUTHENTICATED, FORBIDDEN,
NOT_FOUND, BAD_USER_INPUT, RATE_LIMITED, UNAVAILABLE, INTERNAL].
12. Authorisation
| Rule | Implementation |
|---|---|
| Authentication | OAuth 2.0 bearer token validated at the gateway; anonymous allowed for catalogue only |
| Object-level checks | Resolvers call the owning service, which checks ownership (order.customerId == token.sub) |
| Field-level checks | Sensitive fields (Customer.email) require scope customer:read:pii |
| Introspection | Disabled for public traffic in production; enabled for authenticated developers |
node(id:) | Applies the same checks as the typed field; never a bypass |
13. Query cost and depth limits
Cost is calculated statically before execution. Each field costs 1; a
connection multiplies its children by its first argument:
where is the page size for connections and 1 otherwise.
| Limit | Value | Response when exceeded |
|---|---|---|
| Maximum depth | [10] | Validation error, extensions.code = "QUERY_TOO_DEEP" |
| Maximum cost per operation | [1,000] | Validation error, QUERY_TOO_COMPLEX |
| Cost budget per client per minute | [50,000] | RATE_LIMITED, with Retry-After header |
| Maximum aliases per operation | [30] | Validation error |
| Request body size | [100 KB] | HTTP 413 |
14. Persisted queries and transport
| Topic | Decision |
|---|---|
| HTTP transport | POST application/json for all operations; GET allowed for persisted queries to enable caching. Follows the GraphQL over HTTP working draft. |
| Persisted queries | First-party clients register operations at build time; server accepts only known hashes from first-party client IDs |
| Third-party clients | Arbitrary operations allowed, subject to section 13 limits |
| Caching | Cache-Control set per operation from the lowest field max-age; personalised data is private |
| Batching | [Not supported / Up to 5 operations] |
15. Federation and composition
| Subgraph | Owner | Entities owned | Entities extended | Router |
|---|---|---|---|---|
| Example: catalogue | Team Catalogue | Book (key id), Author | none | [Router product and version] |
| Example: inventory | Team Stock | none | Book.stock | Same |
| Example: orders | Team Orders | Order | Customer.orders | Same |
| [Subgraph] | [Team] | [Entities] | [Extensions] | [Router] |
Composition checks run in CI; a subgraph change that breaks composition or an active client operation is blocked.
16. Performance
- Resolvers that load by key use a per-request batching loader (the DataLoader pattern) so
books { authors }makes one author call per request, not one per book. - Loaders cache only within one request; cross-request caching uses the services' own caches.
- Target latency: p95 [250 ms] for persisted first-party queries, p99 [800 ms].
- Resolver timeouts: [2 s] per downstream call; the field returns null with an
UNAVAILABLEerror when exceeded.
The sequence below shows batched loading for a list query.
17. Observability
| Signal | What is captured |
|---|---|
| Metrics | Requests, errors and latency per operation name and client name; resolver latency for top fields |
| Traces | One span per operation, child spans per downstream call; trace ID in error extensions |
| Field usage | Field-level usage per client, retained [90 days], used for deprecation decisions |
| Logs | Operation name, hash, client, duration, error codes; never variables containing personal data |
18. Schema governance
| Check | Tool | Blocking |
|---|---|---|
| SDL lint (naming, descriptions on all types and fields) | [Tool] | Yes |
| Breaking change detection against production schema | [Tool] | Yes, unless approved |
| Validation of registered client operations against new schema | [Tool] | Yes |
| Schema review by graph owners | Pull request | Yes for new root fields |
Change process: a schema change starts as a pull request against the SDL file, owned by the team that owns the affected types. The graph owners review new root fields, new cross-team relationships and anything that changes nullability or authorisation. Approved changes are published to the schema registry, which notifies client teams of new deprecations.
Clients must send an operation name and a client name header
([x-client-name]) so usage can be attributed.
19. Glossary and references
| Term | Meaning |
|---|---|
| SDL | Schema definition language, the textual form of a GraphQL schema |
| Schema coordinate | A string identifying a schema element, such as Book.price |
| Connection | Cursor-paginated list wrapper with edges and page info |
| Persisted query | An operation registered in advance and called by hash or ID |
| Subgraph | A separately owned schema composed into the supergraph |
References: GraphQL specification September 2025 (https://spec.graphql.org/September2025/); Relay cursor connections specification; GraphQL over HTTP working draft; [federation documentation used].
Standard and licence note
Structure aligned with the GraphQL specification, September 2025 edition
(https://spec.graphql.org/), for the type system, directives (@deprecated,
@specifiedBy, @oneOf), schema coordinates, error and response format.
Pagination follows the Relay cursor connections convention; federation and
the GraphQL over HTTP transport are referenced as separate documents. This
template is original text written for this collection and reproduces no
text from those specifications; consult them for normative requirements.
Template licence: MIT.
関連テンプレート
Microservice Specification Template (Service Canvas)
Service canvas: bounded context, owner, APIs and events, data owned, dependencies, SLOs, scaling, config, deploy, observability
アーキテクチャ・システム設計Software Architecture Document Template (4+1 Views)
SAD with Kruchten 4+1 views – goals and constraints, scenarios, logical, process, development, physical views, sizing, view mapping
アーキテクチャ・システム設計Cloud Architecture Review Template (Six Pillars)
Well-architected style review across six pillars: questions, current state, high/medium/none risk, improvement plan, sign-off