{"record":{"id":"2858c3e1abc4d3c6","repo":"payloadcms/payload","slug":"variant-with-id-variant-id-is-out-of-stock-or-d","errorCode":null,"errorMessage":"Variant with ID ${variant.id} is out of stock or does not have enough inventory.","messagePattern":"Variant with ID (.+?) is out of stock or does not have enough inventory\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/plugin-ecommerce/src/utilities/defaultProductsValidation.ts","lineNumber":24,"sourceCode":"  currenciesConfig,\n  currency,\n  product,\n  quantity = 1,\n  variant,\n}) => {\n  if (!currency) {\n    throw new Error('Currency must be provided for product validation.')\n  }\n\n  const priceField = `priceIn${currency.toUpperCase()}`\n\n  if (variant) {\n    if (!variant[priceField]) {\n      throw new Error(`Variant with ID ${variant.id} does not have a price in ${currency}.`)\n    }\n\n    if (variant.inventory === 0 || (variant.inventory && variant.inventory < quantity)) {\n      throw new Error(\n        `Variant with ID ${variant.id} is out of stock or does not have enough inventory.`,\n      )\n    }\n  } else if (product) {\n    // Validate the product's details only if the variant is not provided as it can have its own inventory and price\n    if (!product[priceField]) {\n      throw new Error(`Product does not have a price in.`, {\n        cause: { code: MissingPrice, codes: [product.id, currency] },\n      })\n    }\n\n    if (product.inventory === 0 || (product.inventory && product.inventory < quantity)) {\n      throw new Error(`Product is out of stock or does not have enough inventory.`, {\n        cause: { code: OutOfStock, codes: [product.id] },\n      })\n    }\n  }\n}","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-ecommerce/src/utilities/defaultProductsValidation.ts#L6-L42","documentation":"Thrown by defaultProductsValidation when a variant's inventory is exactly 0, or is a positive number less than the requested quantity. The check `variant.inventory === 0 || (variant.inventory && variant.inventory < quantity)` covers both out-of-stock and insufficient-stock for the variant branch. Inventory tracking is per variant when a variant is supplied.","triggerScenarios":"Cart validation/checkout where a variant's inventory is 0 or below the requested quantity. Triggered by defaultProductsValidation when variant is set and the price check passed but inventory is insufficient.","commonSituations":"Concurrent purchases depleting variant stock between add-to-cart and checkout; inventory not decremented after sales showing stale positive numbers; high-quantity orders exceeding available stock; admin set inventory to 0 to disable a variant; sync from an inventory system that hasn't propagated yet.","solutions":["Re-fetch variant inventory at checkout time and reject/reduce quantity if insufficient.","Use optimistic inventory locking or atomic decrement on add-to-cart for high-demand items.","Surface remaining stock to the customer and cap the selectable quantity.","Decrement inventory atomically on order completion and reconcile with the source-of-truth inventory system."],"exampleFix":"// before: stale inventory shown at checkout\nconst variant = await findByID(id) // inventory 0 by now\n// after: re-read and clamp quantity at confirm time\nconst variant = await findByID(id)\nif (variant.inventory < qty) qty = variant.inventory\nif (qty === 0) throw new Error('Out of stock')","handlingStrategy":"validation","validationCode":"function variantHasStock(variant: { inventory?: number }, quantity: number): boolean {\n  if (variant.inventory === undefined) return true // untracked inventory\n  return variant.inventory === 0 ? false : variant.inventory >= quantity\n}\n\nif (!variantHasStock(variant, quantity)) {\n  throw new Error(`Only ${variant.inventory} units of variant ${variant.id} available`)\n}","typeGuard":"export function variantHasEnoughStock(variant: { inventory?: number }, quantity: number): boolean {\n  return typeof variant.inventory !== 'number' || variant.inventory >= quantity\n}","tryCatchPattern":"try {\n  defaultProductsValidation({ currency, variant, quantity })\n} catch (err) {\n  if (err instanceof Error && /out of stock or does not have enough inventory/.test(err.message)) {\n    // clamp quantity to available stock and retry, or surface remaining quantity\n    const available = variant.inventory ?? 0\n    if (available > 0) return defaultProductsValidation({ currency, variant, quantity: available })\n    return { ok: false, reason: 'out-of-stock', variantId: variant.id }\n  }\n  throw err\n}","preventionTips":["Re-fetch variant inventory at checkout time; do not trust long-lived client state.","Decrement inventory atomically on order completion and reconcile with the source system.","Cap the customer-selectable quantity to current stock on the product page.","For high-demand items, use optimistic locking on add-to-cart to prevent oversell."],"tags":["validation","variant","inventory","ecommerce","stock"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}