API
Extend the GraphQL Schema
Learn how to add custom types, queries, and mutations to the Webiny GraphQL API using the GraphQLSchemaFactory.
- how to add custom GraphQL types, queries, and mutations
- how to register resolvers with dependency injection
- how to handle errors in GraphQL responses using the Result pattern
Overview
Webiny exposes a GraphQL API for all its core applications. You can extend it with your own types, queries, and mutations by implementing the GraphQLSchemaFactory interface. The factory receives a schema builder, you call addTypeDefs() and addResolver() on it, and Webiny merges your additions into the running schema.
The GraphQL layer stays thin: resolvers delegate to use cases and services that carry the business logic.
Adding a Custom Query
Create an extension file and implement GraphQLSchemaFactory.Interface:
import { GraphQLSchemaFactory } from "webiny/api/graphql";
import { GetModelUseCase } from "webiny/api/cms/model";
import { ListPublishedEntriesUseCase } from "webiny/api/cms/entry";
interface IListCmsEntriesArgs {
modelId: string;
limit?: number;
after?: string;
}
class ListCmsEntriesGraphQL implements GraphQLSchemaFactory.Interface {
public async execute(builder: GraphQLSchemaFactory.SchemaBuilder): GraphQLSchemaFactory.Return {
builder.addTypeDefs(/* GraphQL */ `
type CustomListCmsEntriesResponseItem {
id: ID!
title: String!
}
type CustomListCmsEntriesResponse {
data: [CustomListCmsEntriesResponseItem!]
meta: CmsListMeta
error: CmsError
}
extend type Query {
listCmsEntries(modelId: ID!, limit: Int, after: String): CustomListCmsEntriesResponse!
}
`);
builder.addResolver<IListCmsEntriesArgs>({
path: "Query.listCmsEntries",
dependencies: [GetModelUseCase, ListPublishedEntriesUseCase],
resolver(
getModel: GetModelUseCase.Interface,
listEntries: ListPublishedEntriesUseCase.Interface
) {
return async ({ args }) => {
const { modelId, limit, after } = args;
const modelResult = await getModel.execute(modelId);
if (modelResult.isFail()) {
return { error: modelResult.error, data: null, meta: null };
}
const model = modelResult.value;
const entriesResult = await listEntries.execute(model, {
limit: limit ?? 10,
after: after ?? null
});
if (entriesResult.isFail()) {
return { error: entriesResult.error, data: null, meta: null };
}
const { entries, meta } = entriesResult.value;
return {
data: entries.map(item => ({
id: item.id,
title: item.values[model.titleFieldId] || "No title"
})),
meta,
error: null
};
};
}
});
return builder;
}
}
export default GraphQLSchemaFactory.createImplementation({
implementation: ListCmsEntriesGraphQL,
dependencies: []
});Then register it in webiny.config.tsx:
import React from "react";
import { Api } from "webiny/extensions";
export const Extensions = () => {
return (
<>
{/* ... other extensions */}
<Api.Extension src={"/extensions/listCmsEntriesGraphQL.ts"} />
</>
);
};How It Works
addTypeDefs()— accepts a GraphQL SDL string (use the/* GraphQL */tag for editor syntax highlighting). Extend built-in types withextend type Queryorextend type Mutation.addResolver()— registers a resolver for a path in the schema (e.g."Query.listCmsEntries"). Thedependenciesarray lists the DI tokens that Webiny injects into theresolverfactory in the same order.- Result pattern — all use cases return a
Resultobject. Checkresult.isFail()before accessingresult.value, and return the error from the GraphQL response so clients receive structured error information.
Adding a Custom Mutation
The same pattern applies for mutations — extend Mutation instead of Query:
import { GraphQLSchemaFactory } from "webiny/api/graphql";
import { GetModelUseCase } from "webiny/api/cms/model";
import { CreateEntryUseCase } from "webiny/api/cms/entry";
interface ILogMyClickArgs {
id: string;
ip: string;
}
class LogMyClickGraphQL implements GraphQLSchemaFactory.Interface {
public async execute(builder: GraphQLSchemaFactory.SchemaBuilder): GraphQLSchemaFactory.Return {
builder.addTypeDefs(/* GraphQL */ `
type LogMyClickResponseItem {
id: ID!
ip: String!
createdOn: String!
}
type LogMyClickResponse {
data: LogMyClickResponseItem
error: CmsError
}
extend type Mutation {
logMyClick(id: ID!, ip: String!): LogMyClickResponse!
}
`);
builder.addResolver<ILogMyClickArgs>({
path: "Mutation.logMyClick",
dependencies: [GetModelUseCase, CreateEntryUseCase],
resolver(getModel: GetModelUseCase.Interface, createEntry: CreateEntryUseCase.Interface) {
return async ({ args }) => {
const modelResult = await getModel.execute("logMyClickModel");
if (modelResult.isFail()) {
return { error: modelResult.error, data: null };
}
const model = modelResult.value;
const result = await createEntry.execute<ILogMyClickArgs>(model, {
values: { id: args.id, ip: args.ip }
});
if (result.isFail()) {
return { error: result.error, data: null };
}
return { data: result.value, error: null };
};
}
});
return builder;
}
}
export default GraphQLSchemaFactory.createImplementation({
implementation: LogMyClickGraphQL,
dependencies: []
});Deploying Changes
After creating or modifying a GraphQL extension, deploy the API:
yarn webiny deploy apiDuring development, use watch mode for automatic redeployment:
yarn webiny watch api