Generate from GraphQL
Generate a CLI, MCP server, or native SDK from GraphQL SDL, introspection JSON, or an introspectable endpoint.
Generate CLI, MCP, and SDK Targets from a GraphQL schema. Supply SDL, introspection JSON, or an endpoint that permits introspection, then configure the API URL and authentication that SDL cannot describe.
Use Connect a spec URL to generate a package or the Quickstart to save a Project. This page explains GraphQL inputs and generated behavior.
Inputs
Three shapes work as a project's spec URL or as an ad hoc input:
- SDL: a
.graphqlschema file. - Introspection JSON: the
__schemapayload. - A GraphQL endpoint URL: Typeship runs introspection server-side and uses the endpoint as the client's default
baseUrl. Add write-only source headers for protected endpoints. Typeship sends them during the initial GET and introspection POST but never returns them or writes them into generated artifacts. Use SDL when introspection is disabled.
What the schema cannot say
A GraphQL schema has no equivalent of OpenAPI's servers and securitySchemes. When the URL is an introspectable GraphQL endpoint, Typeship uses it as the generated client's default baseUrl and defaults to bearer authentication. A URL or pasted SDL document still generates without an endpoint; you pass baseUrl when constructing that client.
For a Project, open Authentication to choose bearer tokens, an API-key header, an API-key header that also accepts OAuth bearer tokens, basic authentication, an API key sent as the Basic username, or anonymous access. This updates the shared Spec for every Target. Keep the endpoint, environments, and custom scalar mappings in the Spec settings. One-shot CLI and API calls accept the same values under config.graphql. See Configuration.
- Endpoint: every request is one
POSTto the configured URL. A schema fetched from its endpoint already has a default. Named environments become client environments. - Auth:
bearersendsAuthorization: Bearerand is the default.basicsuits key-pair APIs.basic_api_keysuits APIs whose key is the Basic-auth username with an empty password: customers set one variable, such asPARCEL_API_KEY, and the client sendsAuthorization: Basic <base64 of key:>.api_keysends a configured header.api_key_or_bearersends a key in the configured header and also accepts an OAuth access token asAuthorization: Beareron the same client. Withapi_key_header: Authorization, personal keys go out as the rawAuthorization: <key>value; customers setPARCEL_API_KEYfor a key orPARCEL_TOKENfor an access token, and the key wins when both are set.nonegenerates no credential option. Generated documentation, CLI, and MCP surfaces describe the selected scheme. - Name: the package and client names (
parcel,ParcelClient) come fromtitle, or from the endpoint's host (or the first environment's) when it is not set. With neither, the names use the placeholderGraphQL APIand generation warns; settitleto replace it.
Headers an API needs on every call, such as a version header, go in defaultHeaders on the client.
The mapping
- Each root field becomes an operation. Queries land on
client.query, mutations onclient.mutation. Field arguments become a typed body object, required when any argument is non-null. - Object types carry an optional
__typenameliteral. Unions, and interfaces with implementers, become a union of their concrete types, so results narrow on__typename. - Custom scalars use
Spec.graphql.scalarsfor Projects orconfig.graphql.scalarsfor one-shot generation. Supported JSON representations arestring,integer,number,boolean, andjson. Scalars with well-known names need no mapping:Date,DateTime,DateTimeISO,Time,TimelessDate,LocalDate,LocalTime,Duration,ISO8601Duration,UUID,GUID,URL,URI,Email, andEmailAddressare strings, andJSONandJSONObjectare any JSON value.DateTimeandDateTimeISOare also date-times: the Go SDK types them as timestamps, and the CLI and MCP server accept relative forms such as-7dfor them. A configured mapping overrides these. Any other unmapped scalar stays untyped and produces a warning. - Subscriptions are skipped, with a warning.
- Operation documents (files of queries you already wrote) are not an input. Generation works from the schema alone.
Selections
A schema does not say which fields to fetch. Generated methods select scalar and enum fields to depth 2 by default. A union or interface with several concrete types selects only __typename, id, and a few fields the interface declares; pass select for member fields. A union's only member is selected inside ... on. Deprecated fields, and fields documented as internal or as needing admin rights or a special scope, are left out of defaults. Every default is checked against the schema when the package is generated.
Methods returning objects accept an optional select argument, and so do connections, where it selects each item. Scalar-returning methods do not.
In TypeScript, select is a typed object checked against the schema. A typo is a compile error, and the result type narrows to exactly what was picked:
const client = new ParcelClient({
baseUrl: "https://sandbox.api.parcel.example/graphql",
basicAuth: { username: publicKey, password: privateKey },
});
const whole = await client.query.transaction({ id: "txn_1" });
const slim = await client.query.transaction(
{ id: "txn_1" },
{ id: true, amount: true, customer: { email: true } },
);
// slim.data: { id: string; amount: number; customer: { email: string | null } | null } | nullUnions and interfaces take on, keyed by concrete type name, and come back discriminated by __typename:
const node = await client.query.node(
{ id },
{
id: true,
__typename: true,
on: {
Transaction: { amount: true },
Customer: { email: true },
},
},
);
if (node.ok && node.data?.__typename === "Transaction") {
node.data.amount;
}Each method exports its default selection. Add a field by spreading it: { ...queryTransactionSelection, status: true }.
A raw selection-set string returns the full type.
Python result types are TypedDicts. A field is required only when every default selection that returns its type selects it; other fields, such as nested connections, are NotRequired, so a type checker flags issue["attachments"] instead of the call raising KeyError. Read those fields with .get(). Selection<T> and Selected<T, S> are exported for custom helpers.
client.query.transaction(id="txn_1", select="{ id amount }")Connections
Relay-style connections auto-paginate. Typeship detects a list operation when the field accepts first and after and returns edges plus pageInfo.
Iteration yields the items themselves. When the connection offers nodes, pages select and read nodes; otherwise each edge's node is yielded. Each page uses the previous endCursor as its next after value:
for await (const transaction of client.query.transactions({ first: 50 })) {
transaction.amount;
}select on a connection picks the fields of each item. The connection and the pageInfo fields pagination reads are added to the query, and in TypeScript each item's type narrows to what was picked:
for await (const transaction of client.query.transactions(
{ first: 50 },
{ id: true, amount: true },
)) {
transaction.amount;
}A raw selection that names nodes, edges, or pageInfo selects the whole connection instead. The page info is still added.
- A call that passes neither
firstnorlastsendsfirst: 100, because Relay servers such as GitHub reject a connection query without one. Setgraphql.page_size(1 to 1000) to change the default. A connection whosefirstargument has a schema default keeps the server's default. - Passing
lastpages backward: each next request sendsbeforewith the previous page'sstartCursorand stops whenhasPreviousPageis false. This needs a connection that acceptslastandbefore. - A page whose response has no item list fails with a pagination error instead of ending the iteration as if the list were empty.
Errors
GraphQL may report failures in a 200 response containing an errors array. The runtime throws GraphQLRequestError with the full array. It is an ApiError, so one ApiError check covers GraphQL and HTTP failures.
A field whose union or interface result includes error types reports failures as data instead. When the result resolves to an error type, the runtime throws PayloadError with that result as its body and the type name in typename. By default, error types are the members whose names end in Error, such as UserError or AccessDeniedError, when the result can also be something else. If your schema names them differently, list them in graphql.error_types (for example ["Problem", "ValidationFailure"]); the list replaces the default, and an empty list turns the check off. A name that is not an object type in the schema produces a generation warning. The generated CLI exits 1 and the MCP tool returns an error for the same result. The runtime always selects __typename on these fields. See Failures inside successful responses.
HTTP errors, malformed successful JSON, and transport failures use the same status family classes, ResponseParseError, and TransportError as OpenAPI. Successful calls resolve to the selected field value rather than the GraphQL envelope.
Partial data
A server can return data for the fields it resolved and errors for the rest, such as a permission error on one field. By default the call fails, and the error keeps the data that did arrive:
- MCP: the tool call succeeds with
{ "data": ..., "errors": [...], "partial": true }, so an agent can use what arrived and see what is missing. A response with errors and no data is still a tool error. - TypeScript:
GraphQLRequestError.dataholds the operation's data, orundefinedwhen none came back. - Python:
GraphQLRequestError.dataholds the operation's data, orNone. - Go:
GraphQLError.Dataholds the raw data.DecodeDatadecodes it into the method's result type and reports whether there was any.
To receive partial data as a successful result, create the TypeScript client with graphqlPartial: "allow" or the Python client with graphql_partial="allow". The call then returns the data. In TypeScript, read the errors from ResponseMeta.graphqlErrors in the per-call onResponse option:
const client = new ParcelClient({
baseUrl: "https://sandbox.api.parcel.example/graphql",
graphqlPartial: "allow",
});
const transaction = await client.query.transaction({ id: "txn_1" }, undefined, {
onResponse: (meta) => {
if (meta.graphqlErrors) console.warn(meta.graphqlErrors);
},
});When the operation's field comes back null, there is no partial data and the call fails with the errors either way.
Limits
- Runtime validation does not cover GraphQL operations.
- Spec patches address OpenAPI documents. GraphQL schemas generate unpatched, with a warning when patches are configured.
- The free plan's 25-operation allowance counts root fields, in schema order (queries first, then mutations). The generated README says when a Target was capped.
Identify the signed-in account
Generated CLIs can use a GraphQL query such as viewer or me for whoami. The query must run without required arguments. Optional arguments and arguments with schema defaults are allowed; mutations are never identity reads.
Set Identity operation in Authentication when you want to choose a specific query, such as query.viewer. The generated CLI's whoami and auth check call it with the current credential, and MCP uses it as the identity tool. Your API must validate that credential and enforce the caller's permissions. A query that allows anonymous access cannot prove authentication just by returning a successful response.