{"record":{"id":"ec5cdfa513b51658","repo":"gatsbyjs/gatsby","slug":"pages-can-only-be-created-by-plugins-there-wasn-t","errorCode":null,"errorMessage":"Pages can only be created by plugins. There wasn't a plugin set when creating this page.","messagePattern":"Pages can only be created by plugins\\. There wasn't a plugin set when creating this page\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/gatsby/src/redux/reducers/pages.ts","lineNumber":29,"sourceCode":"  state: IGatsbyState[\"pages\"] = new Map<string, IGatsbyPage>(),\n  action:\n    | IDeleteCacheAction\n    | ICreatePageAction\n    | IDeletePageAction\n    | IMaterializePageMode\n): IGatsbyState[\"pages\"] => {\n  switch (action.type) {\n    case `DELETE_CACHE`:\n      return new Map()\n\n    case `CREATE_PAGE`: {\n      // throws an error if the page is not created by a plugin\n      if (!action.plugin?.name) {\n        console.log(``)\n        console.error(JSON.stringify(action, null, 4))\n        console.log(``)\n\n        throw new Error(\n          `Pages can only be created by plugins. There wasn't a plugin set when creating this page.`\n        )\n      }\n\n      // Add page to the state with the path as key\n      state.set(action.payload.path, action.payload)\n\n      return state\n    }\n\n    case `DELETE_PAGE`: {\n      state.delete(action.payload.path)\n\n      return state\n    }\n\n    case `MATERIALIZE_PAGE_MODE`: {\n      const page = state.get(action.payload.path)","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/gatsbyjs/gatsby/blob/8b06340921ffdf23125a365b9c9923690cb62ce6/packages/gatsby/src/redux/reducers/pages.ts#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Always use the actions.createPage(...) helper passed into Gatsby Node APIs rather than dispatching CREATE_PAGE yourself.","If dispatching manually in tests, include `plugin: { name: 'your-plugin' }` on the action.","Make sure you are calling createPage from within a Gatsby-recognized hook (createPages, createPagesStatefully) so plugin context is bound."],"exampleFix":"// before (test/internal)\nstore.dispatch({ type: `CREATE_PAGE`, payload: { path: `/`, component } })\n// after\nstore.dispatch({ type: `CREATE_PAGE`, plugin: { name: `test` }, payload: { path: `/`, component } })\n// or better - use the wrapped API in gatsby-node:\nexports.createPages = ({ actions }) => actions.createPage({ path: `/`, component })","handlingStrategy":"validation","validationCode":"// Always go through actions.createPage; if you must dispatch manually, set plugin.\nfunction safeCreatePage(store, plugin, payload) {\n  if (!plugin?.name) throw new Error('createPage requires plugin.name')\n  store.dispatch({ type: `CREATE_PAGE`, plugin, payload })\n}","typeGuard":"function hasPluginContext(action) {\n  return !!action?.plugin?.name\n}","tryCatchPattern":null,"preventionTips":["Use the actions.createPage helper passed to Node APIs; never dispatch raw CREATE_PAGE.","In tests, supply a plugin: { name: 'test' } on every dispatched page action.","Bind plugin context once at the top of each Gatsby Node hook."],"tags":["pages","reducer","plugin-context","validation"],"backgroundTag":null,"analyzedSha":"8b06340921ffdf23125a365b9c9923690cb62ce6","analyzedAt":"2026-08-13T02:36:21.405Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}