{"record":{"id":"8eb27212a1b5fad7","repo":"medusajs/medusa","slug":"cannot-add-promotions-to-campaign-where-currency-c","errorCode":null,"errorMessage":"Cannot add promotions to campaign where currency_code don't match.","messagePattern":"Cannot add promotions to campaign where currency_code don't match\\.","errorType":"validation","errorClass":"MedusaError","httpStatus":400,"severity":"error","filePath":"packages/modules/promotion/src/services/promotion-module.ts","lineNumber":2045,"sourceCode":"\n    if (diff.length > 0) {\n      throw new MedusaError(\n        MedusaError.Types.NOT_FOUND,\n        `Cannot add promotions (${diff.join(\n          \",\"\n        )}) to campaign. These promotions are either already part of a campaign or not found.`\n      )\n    }\n\n    const promotionsWithInvalidCurrency = promotionsToAdd.filter(\n      (promotion) =>\n        campaign.budget?.type === CampaignBudgetType.SPEND &&\n        promotion.application_method?.currency_code !==\n          campaign?.budget?.currency_code\n    )\n\n    if (promotionsWithInvalidCurrency.length > 0) {\n      throw new MedusaError(\n        MedusaError.Types.INVALID_DATA,\n        `Cannot add promotions to campaign where currency_code don't match.`\n      )\n    }\n\n    await this.promotionService_.update(\n      promotionsToAdd.map((promotion) => ({\n        id: promotion.id,\n        campaign_id: campaign.id,\n      })),\n      sharedContext\n    )\n\n    return promotionsToAdd.map((promo) => promo.id)\n  }\n\n  @InjectManager()\n  @EmitEvents()","sourceCodeStart":2027,"sourceCodeEnd":2063,"githubUrl":"https://github.com/medusajs/medusa/blob/5e06e544a296b9033f20f71f11c559f81a0e5739/packages/modules/promotion/src/services/promotion-module.ts#L2027-L2063","documentation":"Thrown when adding promotions to a campaign whose budget type is SPEND: every promotion's application_method.currency_code must match the campaign budget's currency_code. A mismatch (including a promotion with a null currency_code against a budget with one, or vice versa) rejects the whole batch with INVALID_DATA.","triggerScenarios":"Calling addPromotionsToCampaign where campaign.budget.type === 'spend' and one or more promotions have application_method.currency_code different from campaign.budget.currency_code — e.g., a USD fixed promotion added to an EUR-budget campaign, or a percentage promotion (no currency) added to a currency-budgeted campaign.","commonSituations":"Multi-region stores mixing currencies, creating campaigns before deciding budget currency, promotions created without currency_code (percentage type) being attached to spend-budget campaigns, default currency assumptions in seed scripts.","solutions":["Match the campaign budget currency to the promotions' currency (update campaign budget currency_code or the application methods' currency_code)","Only attach promotions whose application_method.currency_code equals the campaign budget currency; group promotions into per-currency campaigns","If promotions are percentage-based and have no currency, give the application method the campaign's currency or use a usage-type budget instead of spend"],"exampleFix":"// before\nawait campaignModule.addPromotionsToCampaign(usdCampaignId, {\n  promo_ids: [eurPromoId],\n})\n\n// after\nconst promos = await promotionModule.listPromotions({ id: promoIds }, { relations: [\"application_method\"] })\nconst matching = promos\n  .filter((p) => p.application_method?.currency_code === campaignBudgetCurrency)\n  .map((p) => p.id)\nawait campaignModule.addPromotionsToCampaign(usdCampaignId, { promo_ids: matching })","handlingStrategy":"validation","validationCode":"const budgetCurrency = campaign.budget?.type === \"spend\" ? campaign.budget.currency_code : null\nconst eligible = promotions\n  .filter((p) => !budgetCurrency || p.application_method?.currency_code === budgetCurrency)\n  .map((p) => p.id)","typeGuard":"const matchesCampaignCurrency = (promo: PromotionDTO, campaign: CampaignDTO): boolean => campaign.budget?.type !== \"spend\" || promo.application_method?.currency_code === campaign.budget?.currency_code","tryCatchPattern":"try { await addPromotionsToCampaign(...) } catch (e) { if (e.type === MedusaError.Types.INVALID_DATA && /currency_code/.test(e.message)) { /* split promotions by currency into separate campaigns */ } throw e }","preventionTips":["Create one campaign per currency","Set currency_code on application methods explicitly at creation","Validate budget currency before batch-adding promotions"],"tags":["promotion","campaign","currency","validation","medusa"],"backgroundTag":"currency-mismatch","analyzedSha":"5e06e544a296b9033f20f71f11c559f81a0e5739","analyzedAt":"2026-08-27T07:24:39.599Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}