Skip to content

[plugin-ecommerce] validateOptions crashes when a sibling variant has no options #16368

@jhb-dev

Description

@jhb-dev

Describe the Bug

The validateOptions hook in @payloadcms/plugin-ecommerce performs a duplicate-check against every existing variant of the same product. It does this by calling combo.length / combo.every(...) on each sibling variant's options array (source: packages/plugin-ecommerce/src/collections/variants/createVariantsCollection/hooks/validateOptions.ts):

const existingOptions: (number | string)[][] = []

variants.forEach((variant: any) => {
  existingOptions.push(variant.options)
})

const exists = existingOptions.some(
  (combo) => combo.length === values.length && combo.every((val) => values.includes(val)),
)

If any sibling variant has options that is not an array — e.g. undefined or null — then combo.length throws:

TypeError: Cannot read properties of undefined (reading 'length')
    at validateOptions.js:53:64
    at Array.some (<anonymous>)

This is easy to hit with:

  • Legacy rows from before the options field existed
  • Partially migrated / imported variants
  • Variants created via a direct DB write
  • Any raw document where options was dropped by a broken migration

Once that state exists in the DB, it becomes impossible to ever create or update a variant for that product through Payload — every create attempt crashes validation.

Suggested fix — skip sibling variants whose options is not an array instead of unconditionally dereferencing .length:

 variants.forEach((variant: any) => {
-  existingOptions.push(variant.options)
+  if (Array.isArray(variant.options)) {
+    existingOptions.push(variant.options)
+  }
 })

Or, equivalently, guard inside the some predicate:

 const exists = existingOptions.some(
-  (combo) => combo.length === values.length && combo.every((val) => values.includes(val)),
+  (combo) =>
+    Array.isArray(combo) &&
+    combo.length === values.length &&
+    combo.every((val) => values.includes(val)),
 )

Link to the code that reproduces this issue

https://github.com/jhb-dev/payload-ecommerce-variant-options-undefined

Reproduction Steps

  1. Clone the reproduction repository and run the development server.
  2. Hit any API route (e.g. curl http://localhost:3000/api/users) — this triggers onInit which:
    • Creates a variantType (Color) and two variantOptions (Red, Blue).
    • Creates a product with variants enabled.
    • Creates one valid variant via the Local API (options: [red]).
    • Inserts a legacy variant directly into MongoDB (bypassing Payload) with no options field.
    • Then calls payload.create({ collection: 'variants', ... options: [blue] }) to trigger validateOptions.
  3. Expected: The create succeeds — the plugin should skip sibling variants whose options is not an array and treat them as non-duplicates.
  4. Actual: The create throws before the variant is ever written:
[ERROR]: 🔴 variant creation threw while validating options:
[ERROR]: TypeError: Cannot read properties of undefined (reading 'length')
    at eval (@payloadcms/plugin-ecommerce/dist/collections/variants/createVariantsCollection/hooks/validateOptions.js:53:64)
    at Array.some (<anonymous>)
    at eval (@payloadcms/plugin-ecommerce/dist/.../validateOptions.js:53:44)
    at async promise (payload/dist/fields/hooks/beforeChange/promise.js:97:38)
    at async Promise.all (index 2)
    at async traverseFields (payload/dist/fields/hooks/beforeChange/traverseFields.js:40:5)
    at async beforeChange (payload/dist/fields/hooks/beforeChange/index.js:15:5)
    at async createOperation (payload/dist/collections/operations/create.js:137:35)

Which area(s) are affected?

plugin: ecommerce

Environment Info

```
Binaries:
Node: 24.3.0
npm: 11.4.2
pnpm: 10.33.0
Relevant Packages:
payload: 3.84.0
@payloadcms/plugin-ecommerce: 3.84.0
next: 15.4.8
@payloadcms/db-mongodb: 3.84.0
@payloadcms/graphql: 3.84.0
@payloadcms/next/utilities: 3.84.0
@payloadcms/richtext-lexical: 3.84.0
@payloadcms/translations: 3.84.0
@payloadcms/ui/shared: 3.84.0
react: 19.2.1
react-dom: 19.2.1
Operating System:
Platform: darwin
Arch: arm64
```

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions