Skip to main content
Openfront provides comprehensive multi-regional commerce capabilities, allowing you to sell in multiple countries with localized currencies, pricing, tax rates, and payment/shipping options.

Regional Architecture

The multi-regional system is built around three core concepts:
  1. Regions - Geographic markets with specific settings
  2. Countries - Individual countries assigned to regions
  3. Currencies - Supported currencies for pricing and payments

Region Configuration

A region represents a distinct market with its own currency, tax rules, and available services:
Region {
  name: string                      // Display name (e.g., "North America")
  currencyCode: string              // ISO currency code (e.g., "usd")
  taxRate: float                    // Default tax rate
  taxCode: string                   // Tax jurisdiction code
  giftCardsTaxable: boolean         // Apply tax to gift cards
  automaticTaxes: boolean           // Enable automatic tax calculation
  taxInclusivePricing: boolean      // Prices include tax
  metadata: json                    // Additional configuration
  
  // Relationships
  currency: → Currency
  countries: [→ Country]
  paymentProviders: [→ PaymentProvider]
  fulfillmentProviders: [→ FulfillmentProvider]
  shippingOptions: [→ ShippingOption]
  orders: [→ Order]
  carts: [→ Cart]
  giftCards: [→ GiftCard]
  discounts: [→ Discount]
  taxRates: [→ TaxRate]
}

Creating Regions

Create regions in the admin dashboard or via GraphQL:
mutation CreateRegion {
  createRegion(data: {
    name: "Europe"
    currencyCode: "eur"
    taxRate: 0.20
    automaticTaxes: true
    taxInclusivePricing: true
    countries: {
      connect: [
        { id: "country_de" },
        { id: "country_fr" },
        { id: "country_es" }
      ]
    }
    currency: {
      connect: { code: "EUR" }
    }
  }) {
    id
    name
    currency {
      code
      symbol
    }
    countries {
      name
      iso2
    }
  }
}
Openfront includes pre-configured region templates:
// features/platform/regions/constants/popular-regions.ts

export const POPULAR_REGIONS = [
  {
    name: "United States",
    currencyCode: "USD",
    countries: ["US"],
    taxRate: 0.0,
    taxInclusivePricing: false,
  },
  {
    name: "European Union",
    currencyCode: "EUR",
    countries: ["DE", "FR", "IT", "ES", "NL", "BE"],
    taxRate: 0.20,
    taxInclusivePricing: true,
  },
  {
    name: "United Kingdom",
    currencyCode: "GBP",
    countries: ["GB"],
    taxRate: 0.20,
    taxInclusivePricing: true,
  },
  {
    name: "Canada",
    currencyCode: "CAD",
    countries: ["CA"],
    taxRate: 0.13,
    taxInclusivePricing: false,
  },
];

Currency Management

Currencies define how prices are displayed and processed:
Currency {
  code: string              // ISO 4217 code (USD, EUR, JPY)
  symbol: string            // Display symbol ($, €, ¥)
  symbolNative: string      // Native symbol
  name: string              // Full name
  includesTax: boolean      // Default tax inclusion
  noDivisionCurrency: boolean  // No decimal places (JPY, KRW)
  
  regions: [→ Region]
  orders: [→ Order]
  carts: [→ Cart]
  payments: [→ Payment]
  moneyAmounts: [→ MoneyAmount]
}

Creating Currencies

mutation CreateCurrency {
  createCurrency(data: {
    code: "EUR"
    symbol: "€"
    symbolNative: "€"
    name: "Euro"
    includesTax: true
    noDivisionCurrency: false
  }) {
    id
    code
    symbol
  }
}

No-Division Currencies

Some currencies don’t use decimal places (cents):
const NO_DIVISION_CURRENCIES = [
  "JPY",  // Japanese Yen
  "KRW",  // Korean Won
  "VND",  // Vietnamese Dong
  "CLP",  // Chilean Peso
  "PYG",  // Paraguayan Guarani
  // ... and others
];
Openfront handles these automatically:
  • Amounts stored as whole numbers (not cents)
  • No decimal formatting in display
  • Proper API formatting for payment providers

Regional Pricing

Products can have different prices in each region:
MoneyAmount {
  amount: integer           // Price in smallest currency unit
  currencyCode: string      // Currency for this price
  minQuantity: integer      // Bulk pricing minimum
  maxQuantity: integer      // Bulk pricing maximum
  
  variant: → ProductVariant
  region: → Region
  currency: → Currency
  priceList: → PriceList
}

Setting Regional Prices

mutation UpdateVariantPrices {
  updateProductVariant(
    where: { id: "variant_id" }
    data: {
      prices: {
        create: [
          {
            amount: 9900
            currency: { connect: { code: "USD" } }
            region: { connect: { id: "region_us" } }
          },
          {
            amount: 8900
            currency: { connect: { code: "EUR" } }
            region: { connect: { id: "region_eu" } }
          },
          {
            amount: 7900
            currency: { connect: { code: "GBP" } }
            region: { connect: { id: "region_uk" } }
          }
        ]
      }
    }
  ) {
    id
    prices {
      amount
      currency { code symbol }
      region { name }
    }
  }
}

Calculated Pricing

Openfront provides virtual fields for formatted pricing:
// Product variant with calculated pricing
const variant = {
  id: "variant_123",
  sku: "SHIRT-BLK-M",
  prices: [
    { amount: 9900, currency: "USD" },
    { amount: 8900, currency: "EUR" },
  ],
  // Virtual field
  calculatedPrice: {
    calculatedAmount: 9900,
    originalAmount: 9900,
    currencyCode: "USD",
    formattedPrice: "$99.00",
  }
};

Tax Configuration

Region-Level Taxes

Configure default tax rates at the region level:
Region {
  taxRate: 0.20              // 20% VAT
  taxCode: "EU_VAT"          // Tax jurisdiction
  giftCardsTaxable: false    // Exempt gift cards
  automaticTaxes: true       // Auto-calculate taxes
  taxInclusivePricing: true  // Prices include tax
}

Product-Level Tax Rates

Override taxes for specific products:
mutation CreateTaxRate {
  createTaxRate(data: {
    rate: 0.10
    code: "REDUCED_VAT"
    name: "Reduced VAT Rate"
    region: { connect: { id: "region_eu" } }
    products: {
      connect: [{ id: "product_books" }]
    }
  }) {
    id
    rate
    name
  }
}

Tax Calculation

Taxes are calculated based on region settings:
// Tax-inclusive pricing (UK, EU)
const priceWithTax = 10000;  // £100.00
const taxRate = 0.20;         // 20% VAT
const priceBeforeTax = Math.round(priceWithTax / (1 + taxRate));
// Result: £83.33 + £16.67 VAT = £100.00

// Tax-exclusive pricing (US, CA)
const priceBeforeTax = 10000; // $100.00
const taxRate = 0.0825;       // 8.25% sales tax
const taxAmount = Math.round(priceBeforeTax * taxRate);
// Result: $100.00 + $8.25 tax = $108.25

Country Configuration

Countries are assigned to regions:
Country {
  iso2: string           // ISO 3166-1 alpha-2 (US, GB, DE)
  iso3: string           // ISO 3166-1 alpha-3 (USA, GBR, DEU)
  numCode: integer       // ISO numeric code
  name: string           // Display name
  displayName: string    // Localized name
  
  region: → Region
  taxRates: [→ TaxRate]
}

Creating Countries

mutation CreateCountry {
  createCountry(data: {
    iso2: "DE"
    iso3: "DEU"
    numCode: 276
    name: "Germany"
    displayName: "Deutschland"
    region: { connect: { id: "region_eu" } }
  }) {
    id
    name
    iso2
    region { name }
  }
}

Regional Services

Payment Provider Assignment

Assign payment providers to specific regions:
mutation AssignPaymentProvider {
  updatePaymentProvider(
    where: { id: "pp_stripe" }
    data: {
      regions: {
        connect: [
          { id: "region_us" },
          { id: "region_eu" },
          { id: "region_uk" }
        ]
      }
    }
  ) {
    id
    name
    regions { name }
  }
}

Shipping Options by Region

Configure region-specific shipping:
mutation CreateShippingOption {
  createShippingOption(data: {
    name: "DHL Express"
    priceType: "calculated"
    region: { connect: { id: "region_eu" } }
    provider: { connect: { id: "sp_shippo" } }
    requirements: {
      create: [
        {
          type: "min_subtotal"
          amount: 5000  # Free shipping over €50
        }
      ]
    }
  }) {
    id
    name
    region { name }
  }
}

Storefront Integration

Detect and display correct region in the storefront:

URL Structure

https://store.com/us/products/shirt
https://store.com/gb/products/shirt
https://store.com/de/products/shirt

Region Detection

// app/(storefront)/[countryCode]/layout.tsx

export default async function CountryLayout({
  params,
  children,
}: {
  params: { countryCode: string }
  children: React.ReactNode
}) {
  const { countryCode } = params;
  
  // Get region for country
  const region = await getRegionByCountry(countryCode);
  
  if (!region) {
    notFound();
  }
  
  return (
    <RegionProvider region={region}>
      {children}
    </RegionProvider>
  );
}

Price Display

// Automatically show prices in correct currency
function ProductPrice({ variant, region }) {
  const price = variant.prices.find(
    p => p.region.id === region.id
  );
  
  if (!price) return null;
  
  return (
    <div className="text-2xl font-bold">
      {formatPrice(price.amount, price.currency)}
    </div>
  );
}

function formatPrice(amount: number, currency: Currency) {
  const value = currency.noDivisionCurrency 
    ? amount 
    : amount / 100;
  
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: currency.code,
  }).format(value);
}

Multi-Regional Cart Flow

1
Detect Customer Region
2
Determine region from URL or IP geolocation:
3
const countryCode = params.countryCode; // From URL: /us/
const region = await getRegionByCountry(countryCode);
4
Create Regional Cart
5
Initialize cart with correct region:
6
mutation {
  createCart(data: {
    region: { connect: { id: "region_us" } }
    currency: { connect: { code: "USD" } }
  }) {
    id
    region { name }
    currency { code symbol }
  }
}
7
Add Products with Regional Pricing
8
Products automatically use correct prices:
9
mutation {
  addLineItem(cartId: "cart_id", variantId: "variant_id", quantity: 1) {
    id
    lineItems {
      title
      unitPrice    # Automatically uses USD price
      quantity
      formattedTotal
    }
  }
}
10
Apply Regional Tax
11
Taxes calculated based on region:
12
query {
  cart(where: { id: "cart_id" }) {
    subtotal       # Product total
    tax           # Calculated tax
    total         # Final total
    region {
      taxRate
      taxInclusivePricing
    }
  }
}

Best Practices

Region Strategy

  • Create regions based on business operations, not just geography
  • Group countries with similar tax rules and shipping
  • Consider currency zones (Eurozone, ASEAN, etc.)
  • Plan for region-specific payment providers

Pricing Strategy

  • Set prices based on local market conditions
  • Consider purchasing power parity
  • Account for local taxes in pricing
  • Use price lists for customer-specific pricing

Tax Compliance

  • Research local tax requirements
  • Use tax-inclusive pricing where required (EU, UK)
  • Configure correct tax codes
  • Consider using tax automation services

Currency Display

  • Show prices in customer’s currency
  • Use proper currency formatting
  • Handle no-division currencies correctly
  • Display currency symbols appropriately

Build docs developers (and LLMs) love