payloadcms/payload · error · APIError

Collection ${collectionSlug} not found

Error message

Collection ${collectionSlug} not found

What it means

Thrown by the R2 storage adapter's default client-upload access callback (defaultR2ClientUploadsAccess). It looks up the requested collection slug in req.payload.collections (the Payload-registered collections) and fires a bare APIError (HTTP 500 by default) when the slug is absent or has no config. This guards the multipart-upload endpoint before any create-access policy is evaluated.

Source

Thrown at packages/storage-r2/src/handleMultiPartUpload.ts:22

import { APIError, Forbidden } from 'payload'

import type { R2StorageOptions } from './index.js'
import type { R2Bucket, R2StorageMultipartUploadHandlerParams } from './types.js'

type Args = {
  access?: UploadInstructionsAccess
  bucket: R2Bucket
  collections: R2StorageOptions['collections']
  useCompositePrefixes?: boolean
}

export const defaultR2ClientUploadsAccess: UploadInstructionsAccess = async ({
  collectionSlug,
  req,
}) => {
  const collection = req.payload.collections[collectionSlug]
  if (!collection?.config) {
    throw new APIError(`Collection ${collectionSlug} not found`)
  }

  const createAccess = collection.config.access?.create
  return createAccess
    ? Boolean(await createAccess({ slug: collectionSlug, req }))
    : Boolean(req.user)
}

// Adapted from https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/
export const getHandleMultiPartUpload =
  ({
    access = defaultR2ClientUploadsAccess,
    bucket,
    collections,
    useCompositePrefixes = false,
  }: Args): PayloadHandler =>
  async (req) => {
    const params = Object.fromEntries(req.searchParams) as R2StorageMultipartUploadHandlerParams

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Verify the ?collection query parameter on the failing request exactly matches a slug in payload.config.collections.
  2. Check that the collection is actually registered (not commented out / conditionally excluded) in the Payload config loaded by the worker/server.
  3. If you supply a custom access function via the R2 adapter's `access` option, validate collectionSlug against req.payload.collections yourself before throwing, or return a clearer error.
  4. Confirm the collection's config object is defined (collection?.config) — a partial registration can leave the entry without a config.

Example fix

// before
const collection = req.payload.collections[collectionSlug]
if (!collection?.config) {
  throw new APIError(`Collection ${collectionSlug} not found`)
}

// after — explicit 404-style response with the registered slugs for debugging
const collection = req.payload.collections[collectionSlug]
if (!collection?.config) {
  throw new APIError(
    `Collection '${collectionSlug}' not found. Registered: ${Object.keys(req.payload.collections).join(', ')}`,
    404,
  )
}
Defensive patterns

Strategy: validation

Validate before calling

import type { CollectionSlug } from 'payload'

function isValidCollectionSlug(slug: string, config: { collections: Record<string, unknown> }): slug is CollectionSlug {
  return Boolean(config.collections[slug])
}

// before issuing the multipart request
if (!isValidCollectionSlug(collectionSlug, payloadConfig)) {
  throw new Error(`Refusing upload: '${collectionSlug}' is not a registered collection`)
}

Type guard

function isRegisteredCollection(
  slug: string,
  collections: Record<string, { config?: unknown }>,
): slug is string {
  return Boolean(collections[slug]?.config)
}

Try / catch

try {
  await initiateMultipartUpload({ collection: slug })
} catch (err) {
  if (err instanceof APIError && /not found/i.test(err.message)) {
    // surface a user-friendly 'collection unavailable' message
  }
  throw err
}

Prevention

When it happens

Trigger: A multipart upload POST hits the R2 handler with ?collection=<slug> where <slug> is not a key in req.payload.collections (typo, unregistered collection, or a slug only configured on the storage adapter but not registered as a Payload collection).

Common situations: Client sends a collection slug that differs from the server's registered slug (casing, pluralization); the collection was removed/disabled in config but the client still references it; a custom upload UI hardcodes a stale slug; multi-tenant setups where the slug is dynamically constructed and sometimes empty.

Related errors


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