payloadcms/payload · critical · Error

Error: missing secret key. A secret key is needed to secure

Error message

Error: missing secret key. A secret key is needed to secure Payload.

What it means

Thrown right after the config resolves, when `this.config.secret` is falsy. Payload derives its encryption keyring (and the active legacy session key) from the secret, so an empty/undefined secret makes sessions, cookies, and field encryption insecure or non-functional. This is a deliberate security guard.

Source

Thrown at packages/payload/src/index.ts:923

      process.env.NODE_ENV !== 'production' &&
      process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true' &&
      !checkedDependencies
    ) {
      checkedDependencies = true
      void checkPayloadDependencies()
    }

    this.importMap = options.importMap!

    if (!options?.config) {
      throw new Error('Error: the payload config is required to initialize payload.')
    }

    this.config = await options.config
    this.logger = getLogger('payload', this.config.logger)

    if (!this.config.secret) {
      throw new Error('Error: missing secret key. A secret key is needed to secure Payload.')
    }

    this.encryptionKeyring = buildEncryptionKeyring([
      this.config.secret,
      ...(this.config.previousSecrets ?? []),
    ])
    this.secret = this.encryptionKeyring.active.legacyKey

    this.globals = {
      config: this.config.globals,
    }

    for (const collection of this.config.collections) {
      let customIDType: string | undefined = undefined
      const findCustomID: TraverseFieldsCallback = ({ field }) => {
        if (
          ['array', 'blocks', 'group'].includes(field.type) ||
          (field.type === 'tab' && 'name' in field)

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Set a `secret` directly in buildConfig: `secret: process.env.PAYLOAD_SECRET`.
  2. Ensure `PAYLOAD_SECRET` (32+ chars) is present in every environment (dev, CI, staging, prod).
  3. Add a startup assertion that the secret is non-empty before init, to fail with a clearer message.

Example fix

// before
export default buildConfig({ /* secret missing */ })

// after
export default buildConfig({
  secret: process.env.PAYLOAD_SECRET,
})
Defensive patterns

Strategy: validation

Validate before calling

const secret = process.env.PAYLOAD_SECRET
if (!secret || secret.length < 32) {
  throw new Error('PAYLOAD_SECRET must be set and at least 32 chars')
}

await payload.init({ config, secret })

Type guard

function isValidSecret(value: string | undefined): value is string {
  return typeof value === 'string' && value.length >= 32
}

if (!isValidSecret(process.env.PAYLOAD_SECRET)) {
  throw new Error('Missing or weak PAYLOAD_SECRET')
}

Try / catch

try {
  await payload.init({ config, secret })
} catch (err) {
  if (err instanceof Error && err.message.includes('missing secret key')) {
    // set PAYLOAD_SECRET in this environment and restart
  }
  throw err
}

Prevention

When it happens

Trigger: Initializing Payload with a config whose `secret` is undefined, empty string, or null — typically because the env var backing it was not set in the current environment.

Common situations: Deploying without `PAYLOAD_SECRET` (or your custom env var); a `.env` file not loaded in the deployment; secret referenced via `process.env.PAYLOAD_SECRET` but the variable name differs; CI/preview environment missing the secret.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/7511f9cc121926f3. Report an issue: GitHub.