gatsbyjs/gatsby · error · Error

Pages can only be created by plugins. There wasn't a plugin…

Error message

Pages can only be created by plugins. There wasn't a plugin set when creating this page.

What it means

The pages reducer's CREATE_PAGE case requires action.plugin?.name to be set, because every page must be attributable to the plugin (or the user's site) that created it - this drives ownership, telemetry, and later page-to-plugin reverse lookups. If the action reaches the reducer without a plugin (e.g. dispatched directly against the store bypassing the wrapped action creator), it logs the action JSON and throws.

Solutions

  1. Always use the actions.createPage(...) helper passed into Gatsby Node APIs rather than dispatching CREATE_PAGE yourself.
  2. If dispatching manually in tests, include `plugin: { name: 'your-plugin' }` on the action.
  3. Make sure you are calling createPage from within a Gatsby-recognized hook (createPages, createPagesStatefully) so plugin context is bound.

Example fix

// before (test/internal)
store.dispatch({ type: `CREATE_PAGE`, payload: { path: `/`, component } })
// after
store.dispatch({ type: `CREATE_PAGE`, plugin: { name: `test` }, payload: { path: `/`, component } })
// or better - use the wrapped API in gatsby-node:
exports.createPages = ({ actions }) => actions.createPage({ path: `/`, component })
Defensive patterns

Strategy: validation

Validate before calling

// Always go through actions.createPage; if you must dispatch manually, set plugin.
function safeCreatePage(store, plugin, payload) {
  if (!plugin?.name) throw new Error('createPage requires plugin.name')
  store.dispatch({ type: `CREATE_PAGE`, plugin, payload })
}

Type guard

function hasPluginContext(action) {
  return !!action?.plugin?.name
}

Prevention

When it happens

Trigger: Dispatching a raw CREATE_PAGE action to the redux store without an action.plugin object (or with plugin.name falsy). The public actions.createPage wrapper injects the plugin automatically, so this typically means a plugin or test is bypassing the wrapper and dispatching manually.

Common situations: A plugin/test calling store.dispatch({ type: 'CREATE_PAGE', payload: {...} }) directly instead of using the provided `actions.createPage`; an internal code path that lost the plugin context; mocking/stubbing the redux store in tests without setting plugin.

Related errors


AI-assisted analysis of gatsbyjs/gatsby@8b06340921 (2026-08-13). Data as JSON: /api/errors/ec5cdfa513b51658. Report an issue: GitHub.

Appendix: source

Thrown at packages/gatsby/src/redux/reducers/pages.ts:29

  state: IGatsbyState["pages"] = new Map<string, IGatsbyPage>(),
  action:
    | IDeleteCacheAction
    | ICreatePageAction
    | IDeletePageAction
    | IMaterializePageMode
): IGatsbyState["pages"] => {
  switch (action.type) {
    case `DELETE_CACHE`:
      return new Map()

    case `CREATE_PAGE`: {
      // throws an error if the page is not created by a plugin
      if (!action.plugin?.name) {
        console.log(``)
        console.error(JSON.stringify(action, null, 4))
        console.log(``)

        throw new Error(
          `Pages can only be created by plugins. There wasn't a plugin set when creating this page.`
        )
      }

      // Add page to the state with the path as key
      state.set(action.payload.path, action.payload)

      return state
    }

    case `DELETE_PAGE`: {
      state.delete(action.payload.path)

      return state
    }

    case `MATERIALIZE_PAGE_MODE`: {
      const page = state.get(action.payload.path)

View on GitHub (pinned to 8b06340921)