medusajs/medusa · error · MedusaError

Product not found

Error message

Product not found

What it means

Thrown by GET /admin/products/:id when refetchProduct returns no record. The product is looked up by idOrFilter with the route's selected fields; an empty result (deleted product, wrong id) triggers NOT_FOUND.

Source

Thrown at packages/medusa/src/api/admin/products/[id]/route.ts:27

import { remapKeysForProduct, remapProductResponse } from "../helpers"
import { MedusaError } from "@medusajs/framework/utils"
import { AdditionalData, HttpTypes } from "@medusajs/framework/types"
import { refetchEntity } from "@medusajs/framework/http"

export const GET = async (
  req: AuthenticatedMedusaRequest<HttpTypes.AdminGetProductParams>,
  res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
  const selectFields = remapKeysForProduct(req.queryConfig.fields ?? [])
  const product = await refetchEntity({
    entity: "product",
    idOrFilter: req.params.id,
    scope: req.scope,
    fields: selectFields,
  })

  if (!product) {
    throw new MedusaError(MedusaError.Types.NOT_FOUND, "Product not found")
  }

  res.status(200).json({ product: remapProductResponse(product) })
}

export const POST = async (
  req: AuthenticatedMedusaRequest<
    HttpTypes.AdminUpdateProduct & AdditionalData,
    HttpTypes.SelectParams
  >,
  res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
  const { additional_data, ...update } = req.validatedBody

  const existingProduct = await refetchEntity({
    entity: "product",
    idOrFilter: req.params.id,
    scope: req.scope,

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Verify the product via GET /admin/products?q=<title> before deep-linking or processing
  2. Handle 404 in consumers (webhooks, importers) by skipping or marking the record deleted
  3. Confirm environment consistency for ids coming from external systems

Example fix

// before
const { product } = await sdk.admin.product.retrieve(prodId)

// after
try {
  const { product } = await sdk.admin.product.retrieve(prodId)
} catch (e) {
  if (e.statusCode === 404) { /* mark record as deleted upstream */ return }
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

const { products } = await sdk.admin.product.list({ id: [prodId], fields: "id" })
if (products.length === 0) throw new Error(`Product ${prodId} not found`)

Type guard

const isProduct = (v: unknown): v is HttpTypes.AdminProduct =>
  !!v && typeof v === "object" && "id" in v && "title" in v

Try / catch

try {
  return await getProduct(prodId)
} catch (e: any) {
  if (e.statusCode === 404) return null // deleted upstream
  throw e
}

Prevention

When it happens

Trigger: Calling GET /admin/products/prod_123 for a product that was deleted, never existed, or belongs to a different store/environment.

Common situations: Deep-linked admin pages to deleted products, webhooks retrying for products removed in the meantime, or stale ids after database re-seeding.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/9f475dbb71d05d56. Report an issue: GitHub.