{"record":{"id":"359d01fbcdcac6b8","repo":"payloadcms/payload","slug":"collection-collectionslug-not-found-359d01","errorCode":null,"errorMessage":"Collection ${collectionSlug} not found","messagePattern":"Collection (.+?) not found","errorType":"http","errorClass":"APIError","httpStatus":500,"severity":"error","filePath":"packages/storage-r2/src/handleMultiPartUpload.ts","lineNumber":22,"sourceCode":"import { APIError, Forbidden } from 'payload'\n\nimport type { R2StorageOptions } from './index.js'\nimport type { R2Bucket, R2StorageMultipartUploadHandlerParams } from './types.js'\n\ntype Args = {\n  access?: UploadInstructionsAccess\n  bucket: R2Bucket\n  collections: R2StorageOptions['collections']\n  useCompositePrefixes?: boolean\n}\n\nexport const defaultR2ClientUploadsAccess: UploadInstructionsAccess = async ({\n  collectionSlug,\n  req,\n}) => {\n  const collection = req.payload.collections[collectionSlug]\n  if (!collection?.config) {\n    throw new APIError(`Collection ${collectionSlug} not found`)\n  }\n\n  const createAccess = collection.config.access?.create\n  return createAccess\n    ? Boolean(await createAccess({ slug: collectionSlug, req }))\n    : Boolean(req.user)\n}\n\n// Adapted from https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/\nexport const getHandleMultiPartUpload =\n  ({\n    access = defaultR2ClientUploadsAccess,\n    bucket,\n    collections,\n    useCompositePrefixes = false,\n  }: Args): PayloadHandler =>\n  async (req) => {\n    const params = Object.fromEntries(req.searchParams) as R2StorageMultipartUploadHandlerParams","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/storage-r2/src/handleMultiPartUpload.ts#L4-L40","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify the ?collection query parameter on the failing request exactly matches a slug in payload.config.collections.","Check that the collection is actually registered (not commented out / conditionally excluded) in the Payload config loaded by the worker/server.","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.","Confirm the collection's config object is defined (collection?.config) — a partial registration can leave the entry without a config."],"exampleFix":"// before\nconst collection = req.payload.collections[collectionSlug]\nif (!collection?.config) {\n  throw new APIError(`Collection ${collectionSlug} not found`)\n}\n\n// after — explicit 404-style response with the registered slugs for debugging\nconst collection = req.payload.collections[collectionSlug]\nif (!collection?.config) {\n  throw new APIError(\n    `Collection '${collectionSlug}' not found. Registered: ${Object.keys(req.payload.collections).join(', ')}`,\n    404,\n  )\n}","handlingStrategy":"validation","validationCode":"import type { CollectionSlug } from 'payload'\n\nfunction isValidCollectionSlug(slug: string, config: { collections: Record<string, unknown> }): slug is CollectionSlug {\n  return Boolean(config.collections[slug])\n}\n\n// before issuing the multipart request\nif (!isValidCollectionSlug(collectionSlug, payloadConfig)) {\n  throw new Error(`Refusing upload: '${collectionSlug}' is not a registered collection`)\n}","typeGuard":"function isRegisteredCollection(\n  slug: string,\n  collections: Record<string, { config?: unknown }>,\n): slug is string {\n  return Boolean(collections[slug]?.config)\n}","tryCatchPattern":"try {\n  await initiateMultipartUpload({ collection: slug })\n} catch (err) {\n  if (err instanceof APIError && /not found/i.test(err.message)) {\n    // surface a user-friendly 'collection unavailable' message\n  }\n  throw err\n}","preventionTips":["Derive the collection slug from the registered Payload config on the client rather than hardcoding it.","Keep a single source of truth for collection slugs shared between client and server.","Add a startup assertion that every storage-adapter collection slug exists in payload.config.collections."],"tags":["r2-storage","upload","access-control","config"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}