# How to Create a Custom Source Plugin in Gatsby to Pull Data from a CMS or API

> Learn to create a custom Gatsby source plugin to pull data from any CMS or API. Fetch data, create nodes, and integrate seamlessly with your Gatsby site for powerful data management.

- Repository: [Gatsby/gatsby](https://github.com/gatsbyjs/gatsby)
- Tags: how-to-guide
- Published: 2026-03-06

---

**To create a custom source plugin in Gatsby, export a `sourceNodes` function from [`gatsby-node.ts`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/package.json) to point to your Node API entry:

```json
{
  "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`](https://github.com/gatsbyjs/gatsby/blob/main/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:

```typescript
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`](https://github.com/gatsbyjs/gatsby/blob/main/src/source-nodes.ts), define a `nodeBuilder` function that constructs the node object with required fields and passes it to `actions.createNode`:

```typescript
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`](https://github.com/gatsbyjs/gatsby/blob/main/src/types.ts) and [`src/constants.ts`](https://github.com/gatsbyjs/gatsby/blob/main/src/constants.ts) to maintain type safety:

```typescript
// src/constants.ts
export const NODE_TYPES = {
  Post: `Post`,
  Author: `Author`,
} as const;

```

```typescript
// 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`](https://github.com/gatsbyjs/gatsby/blob/main/src/gatsby-node.ts) as shown in the tutorial files at `docs/tutorial/creating-a-source-plugin/part-2/index.mdx#L81-L87`:

```typescript
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`](https://github.com/gatsbyjs/gatsby/blob/main/src/on-preinit.ts) and call `actions.addRemoteFileAllowedUrl` inside the `onPreInit` hook, as implemented in [`packages/gatsby-source-contentful/src/gatsby-node.js`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby-source-contentful/src/gatsby-node.js):

```typescript
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`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/redux/reducers/stateful-source-plugins.ts).

## Configuring and Using Your Plugin

Add your local or published plugin to [`gatsby-config.js`](https://github.com/gatsbyjs/gatsby/blob/main/gatsby-config.js) using a relative path or npm package name:

```javascript
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`:

```graphql
query {
  allPost {
    nodes {
      id
      title
      slug
      imageUrl
      authorId
    }
  }
}

```

## Summary

- **Export `sourceNodes`** from [`src/gatsby-node.ts`](https://github.com/gatsbyjs/gatsby/blob/main/src/gatsby-node.ts) to hook into Gatsby’s data sourcing phase.
- **Use `createNode`** inside `sourceNodes` to persist data, ensuring every node has a unique `id` (via `createNodeId`) and a `contentDigest` for caching.
- **Structure your plugin** with [`constants.ts`](https://github.com/gatsbyjs/gatsby/blob/main/constants.ts) for node type names and [`types.ts`](https://github.com/gatsbyjs/gatsby/blob/main/types.ts) for TypeScript interfaces to prevent field name errors.
- **Enable Image CDN** support by exporting `onPreInit` and calling `actions.addRemoteFileAllowedUrl` with your CMS domain pattern.
- **Reference core files** such as [`packages/gatsby/src/utils/source-nodes.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/source-nodes.ts) to 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`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/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`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/redux/reducers/stateful-source-plugins.ts).