payloadcms/payload · error · Error
storage contains an invalid entry: expected an object with a
Error message
storage contains an invalid entry: expected an object with an `init` function. Ensure you are passing the result of a storage adapter factory (e.g. s3Storage({…})) to `storage`, not to `plugins`. What it means
Thrown at config build time when an entry in `config.storage` does not expose an `init` function. Storage adapters must be the result of a factory call (e.g. `s3Storage({…})`) that returns `{ init }`. The check exists because a common mistake is placing the adapter in `plugins` or passing the raw options object instead of the constructed adapter.
Source
Thrown at packages/payload/src/config/build.ts:22
/**
* @description Builds and validates Payload configuration
* @param config Payload Config
* @returns Built and sanitized Payload Config
*/
export async function buildConfig(config: Config): Promise<SanitizedConfig> {
if (Array.isArray(config.plugins)) {
const sorted = [...config.plugins].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
for (const plugin of sorted) {
config = await plugin(config)
}
}
if (Array.isArray(config.storage)) {
for (const adapter of config.storage) {
if (typeof adapter?.init !== 'function') {
throw new Error(
`storage contains an invalid entry: expected an object with an \`init\` function. ` +
`Ensure you are passing the result of a storage adapter factory (e.g. s3Storage({…})) ` +
`to \`storage\`, not to \`plugins\`.`,
)
}
config = await adapter.init(config)
}
}
return sanitizeConfig(config)
}
View on GitHub (pinned to 00c58b35c0)
Solutions
- Move the adapter from `plugins` into the `storage` array.
- Ensure each storage entry is a factory invocation, e.g. `storage: [s3Storage({ bucket: 'x' })]`, not the bare options or the function reference.
- If writing a custom adapter, return an object whose `init` is an async `(config) => config`.
Example fix
// before
buildConfig({ plugins: [s3Storage({ bucket: 'x' })] })
// after
import { s3Storage } from '@payloadcms/storage-s3'
buildConfig({ storage: [s3Storage({ bucket: 'x' })] }) Defensive patterns
Strategy: type-guard
Validate before calling
import { buildConfig } from 'payload'
const storage = [s3Storage({ bucket: 'x' })]
if (!Array.isArray(storage) || storage.some((a) => typeof a?.init !== 'function')) {
throw new Error('Every storage entry must be a factory result with an init() function')
}
await buildConfig({ storage }) Type guard
import type { StorageAdapter } from 'payload'
function isStorageAdapter(a: unknown): a is StorageAdapter {
return typeof a === 'object' && a !== null && typeof (a as any).init === 'function'
}
// assert: if (!storage.every(isStorageAdapter)) throw Prevention
- Always call the factory: `s3Storage({...})`, never pass the options object or the function itself.
- Put adapters in `storage`, never in `plugins`.
- When writing a custom adapter, return `{ init: async (config) => config }`.
When it happens
Trigger: Passing `s3Storage` (the factory) instead of `s3Storage({...})` (its result) to `storage`; pushing a storage adapter into the `plugins` array by mistake; passing `{ bucket: 'x' }` options object directly to `storage`.
Common situations: Migrating from the old upload-adapter API to the v3 `storage` config; copy-paste from a plugin example into the storage slot; forgetting to call the factory.
Related errors
- Error: missing MongoDB connection URL.
- ${sanitizedConfig.admin!.user} is not a valid admin user col
- `jobs.processingLease.safetyBuffer` must be non-negative and
- Collection slug already in use: "${config.collections![i]!.s
- Field ${field.label} has reserved name '${fieldName}'.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/38841d7ce5b9845f.
Report an issue: GitHub.