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
- Verify the product via GET /admin/products?q=<title> before deep-linking or processing
- Handle 404 in consumers (webhooks, importers) by skipping or marking the record deleted
- 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
- Webhook consumers must treat product 404 as deletion signal
- Don't cache product ids across re-seeds
- Guard deep links with an existence check
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
- Product with id "${req.params.id}" not found
- Invite with id: ${id} was not found
- Price list with id: ${id} was not found
- Product category with id: ${req.params.id} was not found
- Product option value with id "${valueId}" was not found for
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/9f475dbb71d05d56.
Report an issue: GitHub.