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.
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)
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.
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
- 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.
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
- You can't set plugin status without a plugin
- An ID must be provided when creating or setting job
- An ID must be provided when ending a job
- You must pass an object into setPluginStatus. What was passe
- 11323
AI-assisted analysis of gatsbyjs/gatsby@8b06340921 (2026-08-13).
Data as JSON: /api/errors/ec5cdfa513b51658.
Report an issue: GitHub.