medusajs/medusa · error · MedusaError

Promotions with ids (${diff.join(",")}) not found.

Error message

Promotions with ids (${diff.join(",")}) not found.

What it means

Thrown by removePromotionsFromCampaign: some promotion ids supplied in promotionsToRemove do not exist in the resolved promotionIds set (deleted, never attached to this campaign, or invalid). The arrayDifference is non-empty so the removal request is rejected with NOT_FOUND.

Source

Thrown at packages/modules/promotion/src/services/promotion-module.ts:2093

    data: PromotionTypes.AddPromotionsToCampaignDTO,
    @MedusaContext() sharedContext: Context = {}
  ) {
    const { id, promotion_ids: promotionIds = [] } = data

    await this.campaignService_.retrieve(id, {}, sharedContext)
    const promotionsToRemove = await this.promotionService_.list(
      { id: promotionIds },
      {},
      sharedContext
    )

    const diff = arrayDifference(
      promotionsToRemove.map((p) => p.id),
      promotionIds
    )

    if (diff.length > 0) {
      throw new MedusaError(
        MedusaError.Types.NOT_FOUND,
        `Promotions with ids (${diff.join(",")}) not found.`
      )
    }

    await this.promotionService_.update(
      promotionsToRemove.map((promotion) => ({
        id: promotion.id,
        campaign_id: null,
      })),
      sharedContext
    )

    return promotionsToRemove.map((promo) => promo.id)
  }
}

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Re-fetch the campaign's promotions (campaign promotion list) and intersect with the ids you intend to remove
  2. Make removal idempotent client-side: ignore/remove already-absent ids before calling
  3. Guard against double submits in the UI

Example fix

// before
await campaignModule.removePromotionsFromCampaign(campaignId, {
  promo_ids: ["promo_already_removed"],
})

// after
const current = await promotionModule.listPromotions({ campaign_id: campaignId })
const toRemove = requestedIds.filter((id) => current.some((p) => p.id === id))
if (toRemove.length) {
  await campaignModule.removePromotionsFromCampaign(campaignId, { promo_ids: toRemove })
}
Defensive patterns

Strategy: validation

Validate before calling

const attached = await promotionModule.listPromotions({ campaign_id: campaignId })
const attachedIds = new Set(attached.map((p) => p.id))
const toRemove = requestedIds.filter((id) => attachedIds.has(id))

Type guard

const isAttachedToCampaign = (id: string, attachedIds: Set<string>): boolean => attachedIds.has(id)

Try / catch

try { await removePromotionsFromCampaign(...) } catch (e) { if (e.type === MedusaError.Types.NOT_FOUND) { /* refetch campaign promotions, filter, retry */ } throw e }

Prevention

When it happens

Trigger: Calling removePromotionsFromCampaign with ids that are not currently linked to the campaign, were already removed (double-submit), were hard-deleted, or are typos.

Common situations: Double-clicking a remove button firing two requests, UI state desync after another admin removed the promotion, deleted promotions lingering in a client-side list.

Related errors


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