How to Create a Custom Source Plugin in Gatsby to Pull Data from a CMS or API
To create a custom source plugin in Gatsby, export a sourceNodes function from gatsby-node.ts that fetches data from your API, transforms each record into a node via createNode with a unique id and contentDigest, and optionally enable remote file support by calling actions.addRemoteFileAllowedUrl in onPreInit.
Creating a custom source plugin in Gatsby bridges external systems—such as headless CMSs, REST APIs, or databases—into Gatsby’s GraphQL data layer. By leveraging the Node APIs in the gatsbyjs/gatsby repository, you can transform any data source into queryable nodes that persist across builds. This implementation guide references the official source plugin tutorial and core runtime files to show you the exact code structure required.
Understanding the Source Plugin Architecture
A source plugin acts as a data pipeline. Gatsby’s build process looks for named exports in your src/gatsby-node.ts file, specifically the sourceNodes lifecycle hook, which is invoked during the node sourcing phase. According to the core implementation in packages/gatsby/src/utils/source-nodes.ts, Gatsby executes this function for every registered plugin, then handles stale node cleanup unless you explicitly mark the plugin as stateful via actions.enableStatefulSourceNodes (tracked in packages/gatsby/src/redux/reducers/stateful-source-plugins.ts).
Setting Up the Plugin Skeleton
Every Gatsby source plugin requires a minimal file structure and a package.json that declares gatsby as a peer dependency.
Create the following directory layout:
my-gatsby-source-plugin/
├─ package.json
└─ src/
├─ gatsby-node.ts
├─ source-nodes.ts
├─ constants.ts
└─ types.ts
Configure package.json to point to your Node API entry:
{
"name": "my-gatsby-source-plugin",
"version": "0.1.0",
"main": "src/gatsby-node.ts",
"peerDependencies": {
"gatsby": "^5"
}
}
Implementing the Core Data Flow
The heart of your plugin resides in src/source-nodes.ts, where you fetch data and create nodes. You must generate a unique ID using createNodeId and a content digest using createContentDigest to enable Gatsby’s data caching.
Fetching Data from Your API
Use any HTTP client (such as node-fetch or got) inside sourceNodes to retrieve data. The following example queries a GraphQL endpoint, but you can adapt the fetchGraphQL helper for REST or SDK-based fetching:
import type { GatsbyNode, SourceNodesArgs, NodeInput } from "gatsby";
import fetch from "node-fetch";
import { NODE_TYPES } from "./constants";
async function fetchGraphQL<T>(url: string, query: string): Promise<T> {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
return (await res.json()) as T;
}
export const sourceNodes: GatsbyNode["sourceNodes"] = async (gatsbyApi) => {
interface IApiResponse {
data: {
posts: Array<{ id: string; slug: string; title: string; authorId: string; imageUrl?: string }>;
authors: Array<{ id: string; name: string }>;
};
}
const { data } = await fetchGraphQL<IApiResponse>(
`https://api.example.com/graphql`,
`
query {
posts { id slug title authorId imageUrl }
authors { id name }
}
`
);
// Node creation logic follows...
};
Creating Gatsby Nodes with sourceNodes
Transform each API record into a Gatsby node using a builder helper. In src/source-nodes.ts, define a nodeBuilder function that constructs the node object with required fields and passes it to actions.createNode:
import type { NodeBuilderInput } from "./types";
export function nodeBuilder({
gatsbyApi,
input,
}: {
gatsbyApi: SourceNodesArgs;
input: NodeBuilderInput;
}) {
const id = gatsbyApi.createNodeId(`${input.type}-${input.data.id}`);
const node = {
...input.data,
id,
parent: null,
children: [],
internal: {
type: input.type,
contentDigest: gatsbyApi.createContentDigest(input.data),
},
} satisfies NodeInput;
gatsbyApi.actions.createNode(node);
}
Define your types and constants in src/types.ts and src/constants.ts to maintain type safety:
// src/constants.ts
export const NODE_TYPES = {
Post: `Post`,
Author: `Author`,
} as const;
// src/types.ts
import { NODE_TYPES } from "./constants";
export interface IPostInput {
id: string;
slug: string;
title: string;
authorId: string;
imageUrl?: string;
}
export interface IAuthorInput {
id: string;
name: string;
}
export type NodeBuilderInput =
| { type: typeof NODE_TYPES.Author; data: IAuthorInput }
| { type: typeof NODE_TYPES.Post; data: IPostInput };
Finally, export the API hooks from src/gatsby-node.ts as shown in the tutorial files at docs/tutorial/creating-a-source-plugin/part-2/index.mdx#L81-L87:
export type { IPluginOptions } from "./types";
export { sourceNodes } from "./source-nodes";
export { onPreInit } from "./on-preinit";
Enabling Advanced Features
Supporting Remote Images via the Image CDN
If your CMS provides image URLs that you want to process through Gatsby’s Image CDN, you must explicitly allowlist the domain. Create src/on-preinit.ts and call actions.addRemoteFileAllowedUrl inside the onPreInit hook, as implemented in packages/gatsby-source-contentful/src/gatsby-node.js:
import type { GatsbyNode } from "gatsby";
export const onPreInit: GatsbyNode["onPreInit"] = ({ actions }, pluginOptions) => {
if (typeof actions.addRemoteFileAllowedUrl === "function") {
actions.addRemoteFileAllowedUrl(`https://${pluginOptions.host}/*`);
}
};
This registration must happen early in the lifecycle, before Gatsby attempts to download and transform remote files.
Handling Stateful Source Plugins
By default, Gatsby deletes nodes that were not recreated in the latest sourceNodes run. If your plugin manages its own node lifecycle (for example, handling deletions via webhooks or delta sync), call actions.enableStatefulSourceNodes to prevent Gatsby from automatically purging your nodes. This behavior is governed by the reducer logic in packages/gatsby/src/redux/reducers/stateful-source-plugins.ts.
Configuring and Using Your Plugin
Add your local or published plugin to gatsby-config.js using a relative path or npm package name:
module.exports = {
plugins: [
{
resolve: require.resolve("../my-gatsby-source-plugin"),
options: {
host: "api.example.com",
},
},
],
};
Run gatsby develop to execute the plugin. Gatsby will invoke your sourceNodes implementation, create nodes for each data record, and expose them in the GraphQL schema. Query your data in the GraphiQL explorer at http://localhost:8000/___graphql:
query {
allPost {
nodes {
id
title
slug
imageUrl
authorId
}
}
}
Summary
- Export
sourceNodesfromsrc/gatsby-node.tsto hook into Gatsby’s data sourcing phase. - Use
createNodeinsidesourceNodesto persist data, ensuring every node has a uniqueid(viacreateNodeId) and acontentDigestfor caching. - Structure your plugin with
constants.tsfor node type names andtypes.tsfor TypeScript interfaces to prevent field name errors. - Enable Image CDN support by exporting
onPreInitand callingactions.addRemoteFileAllowedUrlwith your CMS domain pattern. - Reference core files such as
packages/gatsby/src/utils/source-nodes.tsto understand how Gatsby orchestrates the sourcing lifecycle.
Frequently Asked Questions
Do I need to use TypeScript to create a Gatsby source plugin?
No, you can write the plugin in plain JavaScript. However, the gatsbyjs/gatsby repository provides TypeScript types (GatsbyNode, SourceNodesArgs, NodeInput) that enforce correct API usage and autocompletion in editors, which reduces runtime errors when handling node creation.
How do I handle authentication when fetching from a private API?
Pass authentication headers inside your fetch implementation within sourceNodes. Access plugin options (such as API tokens) via the second argument of sourceNodes, then include them in the request headers. Store sensitive tokens in environment variables and reference them in gatsby-config.js to avoid committing secrets.
What is the difference between sourceNodes and createSchemaCustomization?
sourceNodes is responsible for fetching data and creating nodes in the Gatsby store, while createSchemaCustomization allows you to explicitly define GraphQL types, add relationships between nodes, and hide internal fields before Gatsby infers the schema. Export createSchemaCustomization from gatsby-node.ts if you need strict type control or resolver customization.
How do I prevent Gatsby from deleting my nodes on every build?
Call actions.enableStatefulSourceNodes inside your plugin initialization if you manage node deletions manually (for example, via delta sync). By default, Gatsby treats source plugins as stateless and purges any nodes not recreated in the current sourceNodes execution, as managed by the logic in packages/gatsby/src/redux/reducers/stateful-source-plugins.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →