Skip to content

[Bug]: 500 on POST /admin/price-lists/:id/prices/batch when updating a plain currency price — admin UI sends empty rules {} (regression in 2.17.2, PR #15258) #15927

Description

@dudiqo

Package.json file

{
  "name": "medusa-backend",
  "version": "0.0.1",
  "dependencies": {
    "@medusajs/admin-sdk": "2.17.2",
    "@medusajs/cli": "2.17.2",
    "@medusajs/framework": "2.17.2",
    "@medusajs/medusa": "2.17.2",
    "@google/genai": "^1.34.0",
    "@lambdacurry/medusa-product-reviews": "1.4.0",
    "@perseidesjs/notification-nodemailer": "^2.0.0",
    "@rokmohar/medusa-plugin-meilisearch": "1.4.3",
    "@variablevic/google-analytics-medusa": "^0.1.1",
    "fast-xml-parser": "^5.3.3",
    "medusa-inpost-fulfillment": "^0.4.0",
    "nodemailer": "^7.0.12",
    "sanitize-html": "^2.17.5",
    "slugify": "^1.6.6"
  },
  "devDependencies": {
    "@medusajs/test-utils": "2.17.2",
    "@swc/core": "^1.7.28",
    "jest": "^29.7.0",
    "ts-node": "^10.9.2",
    "typescript": "^5"
  }
}

Node.js version

v22.22.3

Database and its version

PostgreSQL 18.0

Operating system name and version

macOS 26 (Darwin 27.0) — also reproduced on Linux (Docker) in production

Browser name

Microsoft Edge (irrelevant — reproducible with plain cURL, see below)

What happended?

After upgrading to 2.17.2, editing the amount of an existing price list price in the
admin dashboard always fails with a 500. Creating the price works; only updating an
already-saved plain (currency-only, no region/quantity rules) price fails.

Root cause chain (traced in the installed 2.17.2 sources):

  1. Since PR Feat/quantity pricing admin #15258 ("Tiered Pricing for Price List Prices", shipped in 2.17.2), the
    dashboard's edit form always attaches a rules object to every updated price —
    for a plain currency price it is an EMPTY object {}.
    See comparePrices in packages/admin/dashboard/src/routes/price-lists/common/utils.ts
    (in the published bundle: @medusajs/dashboard/dist/chunk-DO2WMVM5.mjs):

    pricesToUpdate.push({
    id, variant_id, currency_code, amount,
    rules: {
    ...(newPrice.regionId ? { region_id: newPrice.regionId } : {}),
    ...
    }, // <- {} for a plain currency price
    })

    In <= 2.17.1 this was rules: regionId ? { region_id } : undefined.

  2. In @medusajs/pricing, PricingModuleService.normalizePrices() only strips the
    rules key from the upsert entry when isPresent(price.rules) is true — and
    isPresent({}) returns FALSE, so for an empty object the stray rules key
    SURVIVES on the entity data:

    const hasRulesInput = isPresent(price.rules)
    if (hasRulesInput) {
    entry.price_rules = rules
    entry.rules_count = rules.length
    delete entry.rules // <- never reached for rules: {}
    }

  3. upsertWithReplace(pricesToUpsert, { relations: ["price_rules"] }) passes the
    entry to MikroORM. Existing rows take the nativeUpdateMany path, which chokes
    on the unknown rules property:

    TypeError: Cannot read properties of undefined (reading 'fieldNames')
    at PostgreSqlDriver.nativeUpdateMany (@mikro-orm/knex/AbstractSqlDriver.js:634:18)
    at MikroOrmAbstractBaseRepository_.upsertMany_ (@medusajs/utils/src/dal/mikro-orm/mikro-orm-repository.ts:1408:16)
    at async MikroOrmAbstractBaseRepository_.upsertWithReplace (@medusajs/utils/src/dal/mikro-orm/mikro-orm-repository.ts:701:11)
    ...
    [update-price-list-prices -> update-price-list-prices (invoke)]
    [batch-price-list-prices -> update-price-list-prices-as-step (invoke)]

    New prices go through the insert path (which maps only known columns), which is
    why CREATE succeeds and only UPDATE fails.

Consistent with the above:

  • update WITHOUT the rules key -> 200 OK
  • update with rules: {} -> 500 (this bug; what the admin UI sends)
  • update with rules: {"region_id": "..."} -> 200 OK (non-empty rules are converted properly)

Expected behavior

Updating the amount of an existing price list price (via the admin dashboard edit
form, or via POST /admin/price-lists/:id/prices/batch with rules: {} in an update
entry) should return 200 and update the price — same as in <= 2.17.1. An empty
rules object should be treated the same as an absent/undefined one.

Actual behavior

HTTP 500 with body:

{"code":"unknown_error","type":"unknown_error","message":"An unknown error occurred."}

and the following server-side error:

TypeError: Cannot read properties of undefined (reading 'fieldNames')
at PostgreSqlDriver.nativeUpdateMany (@mikro-orm/knex/AbstractSqlDriver.js:634:18)

The admin dashboard shows a generic "An unknown error occurred." toast, so price
list prices are effectively not editable from the UI at all (plain currency prices).

Link to reproduction repo

Reproducible on a vanilla create-medusa-app@latest (2.17.2) install with no custom code — two cURL calls below are a complete reproduction, so I hope a repo is not necessary

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