upgrade-to-v2.16.0-prompt.md
Paste the content below into an agent running inside the root of a Medusa project (backend, and storefront if present). The agent produces a migration plan for your approval — it does not edit files until you approve.
The breaking changes in scope:
@medusajs/* packages.manager.find now throws on relations that don't exist on an entity instead of silently ignoring them.react-router-dom bumped to 6.30.4 (defensive security update). Admin customizations may break if not updated.@medusajs/eslint-plugin). New projects ship with it; existing projects should add it. Once configured, medusa build and medusa develop run linting by default.require_verification boolean option is removed, replaced by http.authVerificationsPerActor./auth/[actor]/[provider]/verification/request and /auth/[actor]/[provider]/verification/confirm are removed, replaced by /auth/verification/request and /auth/verification/confirm.auth.register, auth.login, auth.verification.request, and auth.verification.confirm.supersecret fallback is gone. In production, the app throws and fails to start if http.jwtSecret / http.cookieSecret are not set.For anything not covered here, consult the official Medusa documentation at https://docs.medusajs.com or the Medusa MCP server before acting. Do not guess at APIs, config keys, or route shapes — verify them. </context>
<inputs> You are given access to the project's working directory. You must discover the following yourself; do not assume: - **Project shape**: standalone Medusa project vs. monorepo (e.g. `apps/backend` + workspaces). Check for a root `package.json` with workspaces and an `apps/` directory. - **Storefront presence**: a separate storefront app/repo or directory that uses `@medusajs/js-sdk`. If no storefront is in this workspace, treat storefront steps as guidance to surface to the user, not edits you can make. - **Current Medusa version**: read from `package.json` dependencies. - **Whether the project uses email verification**: search for `require_verification`, `authVerificationsPerActor`, `/auth/*/verification/`, `sdk.auth.verification`, or `verification_required`. - **Whether secrets are configured**: inspect `medusa-config.ts`/`.js` for `http.jwtSecret` / `http.cookieSecret` and the environment for `JWT_SECRET` / `COOKIE_SECRET`. - **Whether `react-router-dom` is a direct dependency.** - **Whether custom code calls `manager.find` directly** (raw MikroORM access outside the module service abstractions). </inputs> <steps> Work through these in order. For each, record findings and the proposed change in the plan — do not edit yet.Detect project shape and current version. Read the relevant package.json files. Note standalone vs. monorepo and the storefront location (if any). Record the current @medusajs/medusa version.
Plan the package version bump. Identify every @medusajs/* dependency and devDependency across the backend (and admin/plugin packages if monorepo) and target 2.16.0. Note that @medusajs/ui does not follow the 2.x line — if it is a direct dependency anywhere (commonly in admin customizations), target 4.1.16 rather than 2.16.0. If react-router-dom is a direct dependency anywhere (commonly in admin customizations or storefront), target 6.30.4. Plan a single install/upgrade pass and note the package manager in use (yarn/npm/pnpm — detect from lockfile).
Audit JWT and cookie secrets. Check whether http.jwtSecret and http.cookieSecret are set in config or via JWT_SECRET / COOKIE_SECRET env vars. The default supersecret fallback is removed; in production a missing value throws at startup and the app fails to boot. If they are unset or still rely on the default, flag this as a must-fix before deploying item and propose setting them via environment variables. Never invent or hardcode secret values — instruct the user to generate strong secrets and set the env vars.
Audit direct manager.find usage. Search custom code for direct MikroORM manager.find calls that pass fields/populate referencing relations. Under MikroORM 6.6.14 these now throw if a referenced relation/property does not exist on the entity. For each occurrence, plan to validate field/populate paths against the entity metadata before the call (drop paths that don't map to a real property/relation), mirroring how Medusa prunes them internally. If no direct manager.find usage exists, record that this step is N/A.
Plan ESLint plugin setup. This is strongly recommended; once configured, medusa build and medusa develop lint by default and medusa develop fails to start on lint errors.
@medusajs/eslint-plugin, eslint, and jiti. Install at the monorepo root for monorepos, or directly in the project for standalone projects (and in plugins). jiti is required: the config is written in TypeScript (eslint.config.ts), and ESLint 9 uses jiti to load and transpile a TS config file at runtime. Without it, linting fails to load the config.eslint.config.ts, or .js/.mjs):
import { defineConfig } from "eslint/config"
import medusa from "@medusajs/eslint-plugin"
export default defineConfig([...medusa.configs.recommended])
eslint.config.ts): same as above.apps/backend/eslint.config.ts):
import { defineConfig } from "eslint/config"
import base from "../../eslint.config"
export default defineConfig([
{
extends: [base],
rules: {},
},
])
"eslint.config.*" to the exclude array in the backend tsconfig.json to avoid type errors on the config file.lint script to the backend's package.json so the command is easy to run, e.g. "lint": "medusa lint".medusa lint command (supports --fix and --quiet) and the --no-lint flag for medusa build / medusa develop. Recommend running medusa lint --fix after upgrade and surfacing remaining lint errors to the user.Plan email verification config migration (backend). Only if the project uses email verification.
require_verification option from the emailpass provider configuration.http.authVerificationsPerActor under projectConfig in medusa-config.*. Its type is Record<actorType, { entity_type: string; auth_provider: string }[]>. An empty array for an actor type means no verification required. Example:
http: {
authVerificationsPerActor: {
user: [],
customer: [
{ entity_type: "email", auth_provider: "emailpass" },
],
},
}
require_verification: true/false (and any per-actor expectations) onto the new per-actor structure. Confirm with the user which actor types require verification if it is ambiguous.Plan the auth.verification_requested subscriber migration (backend). Only if the project has a subscriber handling the auth.verification_requested event (search for auth.verification_requested or a verificationRequestedHandler). The event payload changed in v2.16.0:
token is renamed to code — use code to build the verification link.provider is renamed to code_provider (defaults to "token").actor_type is removed. Replace the actor_type !== "customer" guard with an entity_type check, e.g. if (entity_type !== "email") return.provider_identity_id is removed.entity_type (e.g. "email") and an optional metadata?: Record<string, unknown> are added. entity_id is still the email/identifier.Migration example:
// Before
export default async function verificationRequestedHandler({
event: { data: { entity_id: email, token, actor_type } },
container,
}: SubscriberArgs<{
entity_id: string
token: string
actor_type: string
provider: string
auth_identity_id: string
provider_identity_id: string
expires_at: string
}>) {
if (actor_type !== "customer") {
return
}
// ...verification_url uses `token`
}
// After
export default async function verificationRequestedHandler({
event: { data: { entity_id: email, entity_type, code } },
container,
}: SubscriberArgs<{
entity_id: string
entity_type: string
code_provider: string
auth_identity_id: string
code: string
expires_at: string
metadata?: Record<string, unknown>
}>) {
// only handle email verifications.
if (entity_type !== "email") {
return
}
// ...verification_url uses `code` instead of `token`
}
Update every reference inside the handler that used token to use code (including the verification_url built for the notification).
Plan verification route migration. Find any backend custom code, middleware, or storefront calls referencing the removed routes:
/auth/[actor]/[provider]/verification/request → /auth/verification/request
{ entity_id: string, entity_type: string, code_provider?: string, metadata?: Record<string, unknown> }. Response: { verification }./auth/[actor]/[provider]/verification/confirm → /auth/verification/confirm
{ code: string, code_provider?: string }. Response: { entity_id, entity_type, code_provider, verified_at }.Plan JS SDK signature migration (storefront / SDK consumers). Update calls to match v2.16.0:
auth.register(actor, method, payload) — the previous options / returnVerification parameter is removed. The register call no longer reports whether verification is required.auth.login(actor, method, payload) — now may return { verification_required: true, verification?, token }. Verification is detected here, not at registration.auth.verification.request(body) — signature changed to a single body object: { entity_id, entity_type, code_provider?, metadata? } (no more actor/method positional args).auth.verification.confirm(body) — signature changed to { code, code_provider? } (token is passed as code).Migration examples:
// Before
const { verification_required } = await sdk.auth.register("customer", "emailpass", payload, { returnVerification: true })
// After
await sdk.auth.register("customer", "emailpass", payload)
const loginResult = await sdk.auth.login("customer", "emailpass", payload)
// loginResult may be { verification_required, token } when verification is needed
// Before
await sdk.auth.verification.request("customer", "emailpass", { entity_id: "[email protected]" })
await sdk.auth.verification.confirm("customer", "emailpass", { token })
// After
await sdk.auth.verification.request({ entity_id: "[email protected]", entity_type: "email" })
await sdk.auth.verification.confirm({ code: token })
Plan storefront verification flow migration. Only if a storefront with email verification exists. The flow moved verification from after registration to after login:
verification_required → call /auth/verification/request → user opens verify page with token → /auth/verification/confirm with { code: token } → login again → create customer if needed → login again.<output_format>). Then stop and wait for the user's approval. Do not edit any files until the user approves. If the user approves, apply the changes in dependency order (version bump and install first, then config, then code), and after each significant change note how to verify it.
</steps>
<error_handling>
medusa-config.ts, tsconfig.json) is missing or has an unexpected shape, report what you found and ask how to proceed instead of forcing an edit.manager.find usage is ambiguous (e.g. dynamic field lists), flag it for manual review in the plan rather than rewriting it blindly.<output_format> Present the plan as Markdown with these sections:
medusa develop).medusa build, medusa lint, register/login/verify a test customer end-to-end).End with an explicit line asking the user to approve the plan before you apply any changes. </output_format>
<success_criteria>
medusa build succeeds, medusa lint reports no errors, and (if email verification is used) a test register → login → verify → login flow completes end-to-end.
</success_criteria>