Markets: Country-Specific Storefronts
Run one shop, sell in many countries. Per-market currency, language, duty mode, and tax treatment. Primary Market guarantee enforced at three layers.
Overview
Markets group countries that share a currency and language. Cartly routes visitors to the matching market via GeoIP and serves market-specific prices and translated content. Every shop has exactly one Primary Market as a fallback.
Primary Market Guarantee
Enforced at three layers: DB partial unique index, Ent mutation hooks, and service-layer atomic PromoteToPrimary transaction. Cannot be deleted or deactivated. Its default_locale drives the platform translation source language.
Admin API
POST /admin/markets— create (name, countries[], base_currency, default_locale, available_locales[], tax_inclusive, duty_mode, active)PUT /admin/markets/:id— updateDELETE /admin/markets/:id— non-primary onlyPOST /admin/markets/:id/promote— atomic promote-to-primaryPUT /admin/markets/:id/prices— bulk per-variant price overridesPOST /admin/markets/:id/exclusions— exclude product from market
Localization Liquid Context
localization.language, localization.market, localization.available_languages — populated by internal/storefront/storefront_context.go buildLocalizationContext. Endonym names (русский, Deutsch, ქართული) come from internal/markets/endonym.go.
Orphan Locale Handling
When cartly_locale cookie references an inactive locale, the storefront clears it via Set-Cookie: cartly_locale=; Max-Age=0 and falls back to default_locale.
Tax & Duty Modes
tax_inclusive: true = VAT-style (tax extracted from displayed price), false = sales-tax-style (tax added at checkout). duty_mode: DDP = merchant pays duties, DDU = buyer pays.