diff --git a/apps/docs/content/docs/getting-started/index.mdx b/apps/docs/content/docs/getting-started/index.mdx
index b593569c..557e71fe 100644
--- a/apps/docs/content/docs/getting-started/index.mdx
+++ b/apps/docs/content/docs/getting-started/index.mdx
@@ -57,10 +57,11 @@ Create a `.env.local` file in your project root:
```bash
SHOPIFY_STORE_DOMAIN="your-store.myshopify.com"
SHOPIFY_STOREFRONT_ACCESS_TOKEN="your-storefront-access-token"
+SHOPIFY_DEFAULT_CURRENCY="USD"
NEXT_PUBLIC_SITE_NAME="Your Store Name"
```
-You can find the storefront token in **Settings → Apps and sales channels → Headless**. For the full variable list, see [Environment Variables](/docs/reference/env-vars).
+You can find the storefront token in **Settings → Apps and sales channels → Headless**. Set `SHOPIFY_DEFAULT_CURRENCY` to your shop currency, such as `GBP`, when it differs from USD. For the full variable list, see [Environment Variables](/docs/reference/env-vars).
## Step 4: Run locally
diff --git a/apps/docs/content/docs/reference/env-vars.mdx b/apps/docs/content/docs/reference/env-vars.mdx
index 7a549447..3d2f8999 100644
--- a/apps/docs/content/docs/reference/env-vars.mdx
+++ b/apps/docs/content/docs/reference/env-vars.mdx
@@ -16,6 +16,7 @@ type: reference
| Variable | Description |
|----------|-------------|
+| `SHOPIFY_DEFAULT_CURRENCY` | Base storefront currency for single-locale filter UI and markdown fallbacks, e.g. `GBP` or `USD`. Set this when your shop currency is not USD so empty search/collection states still render the correct symbol. |
| `SHOPIFY_STOREFRONT_PRIVATE_TOKEN` | Private Storefront API token for server-side requests. Enables higher rate limits and access to draft content. |
| `SHOPIFY_CUSTOMER_ACCOUNT_URL` | Customer Account API URL. Required when using the enable-shopify-auth skill. |
| `SHOPIFY_CUSTOMER_CLIENT_ID` | Customer Account API client ID. Required when using the enable-shopify-auth skill. |
diff --git a/apps/docs/content/docs/reference/troubleshooting.mdx b/apps/docs/content/docs/reference/troubleshooting.mdx
index 85963e5a..3b263075 100644
--- a/apps/docs/content/docs/reference/troubleshooting.mdx
+++ b/apps/docs/content/docs/reference/troubleshooting.mdx
@@ -65,7 +65,7 @@ To get near-instant updates, set up Shopify webhooks pointed at `/api/webhooks/s
## Locale or currency not changing
-Multi-locale support requires Shopify Markets to be enabled. The template ships as single-locale by default. Run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) to add multi-locale and multi-currency routing.
+Single-locale storefronts use Shopify pricing data where available, but empty search/collection states rely on `SHOPIFY_DEFAULT_CURRENCY` for filter chrome and markdown fallbacks. Set that env var to your shop currency, for example `GBP`, if your store is not USD. Multi-locale support still requires Shopify Markets to be enabled. Run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) to add multi-locale and multi-currency routing.
## Agent can't find context files
diff --git a/apps/template/.env.example b/apps/template/.env.example
index b50052b7..0de66f58 100644
--- a/apps/template/.env.example
+++ b/apps/template/.env.example
@@ -2,5 +2,8 @@
SHOPIFY_STORE_DOMAIN="your-store.myshopify.com"
SHOPIFY_STOREFRONT_ACCESS_TOKEN="your-storefront-access-token"
+# Optional — default currency used for single-locale filter chrome and markdown fallbacks
+SHOPIFY_DEFAULT_CURRENCY="USD"
+
# Store display name (shown in header, metadata, etc.)
NEXT_PUBLIC_SITE_NAME="Your Store Name"
diff --git a/apps/template/app/collections/md/[handle]/route.ts b/apps/template/app/collections/md/[handle]/route.ts
index 3836510f..c7deb619 100644
--- a/apps/template/app/collections/md/[handle]/route.ts
+++ b/apps/template/app/collections/md/[handle]/route.ts
@@ -5,6 +5,7 @@ import {
buildProductFiltersFromParams,
getCollectionProducts,
} from "@/lib/shopify/operations/products";
+import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop";
import { transformShopifyFilters } from "@/lib/shopify/transforms/filters";
import { RESULTS_PER_PAGE, parseFiltersFromSearchParams, searchParamsToRecord } from "@/lib/utils";
@@ -52,11 +53,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ hand
const transformedFilters = transformShopifyFilters(result.filters, { activeFilters });
const hasPriceRange = result.filters.some((filter) => filter.type === "PRICE_RANGE");
+ const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode());
const markdown = collectionToMarkdown({
collection,
products: result.products,
filters: transformedFilters.filters,
priceRange: hasPriceRange ? transformedFilters.priceRange : undefined,
+ currencyCode,
activeFilters,
pageInfo: result.pageInfo,
locale,
diff --git a/apps/template/app/search/md/route.ts b/apps/template/app/search/md/route.ts
index 9f08f2d5..a92bf599 100644
--- a/apps/template/app/search/md/route.ts
+++ b/apps/template/app/search/md/route.ts
@@ -4,6 +4,7 @@ import {
buildProductFiltersFromParams,
getProducts,
} from "@/lib/shopify/operations/products";
+import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop";
import { transformShopifyFilters } from "@/lib/shopify/transforms/filters";
import { RESULTS_PER_PAGE, parseFiltersFromSearchParams, searchParamsToRecord } from "@/lib/utils";
@@ -40,6 +41,7 @@ export async function GET(request: Request) {
const transformedFilters = transformShopifyFilters(result.filters, { activeFilters });
const hasPriceRange = result.filters.some((filter) => filter.type === "PRICE_RANGE");
+ const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode());
const markdown = searchResultsToMarkdown({
query,
collection,
@@ -47,6 +49,7 @@ export async function GET(request: Request) {
total: result.total,
filters: transformedFilters.filters,
priceRange: hasPriceRange ? transformedFilters.priceRange : undefined,
+ currencyCode,
activeFilters,
pageInfo: result.pageInfo,
locale,
diff --git a/apps/template/app/search/page.tsx b/apps/template/app/search/page.tsx
index 8f14ecda..461f2629 100644
--- a/apps/template/app/search/page.tsx
+++ b/apps/template/app/search/page.tsx
@@ -22,6 +22,7 @@ import type { Locale } from "@/lib/i18n";
import { getLocale } from "@/lib/params";
import { buildAlternates, buildOpenGraph } from "@/lib/seo";
import { buildProductFiltersFromParams, getProducts } from "@/lib/shopify/operations/products";
+import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop";
import { transformShopifyFilters } from "@/lib/shopify/transforms/filters";
import { RESULTS_PER_PAGE, parseFiltersFromSearchParams } from "@/lib/utils";
@@ -202,11 +203,13 @@ async function SearchFilterContent({
const transformedFilters = transformShopifyFilters(result.filters, {
activeFilters,
});
+ const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode());
return (
);
diff --git a/apps/template/components/cart/context.tsx b/apps/template/components/cart/context.tsx
index 1bade740..c6427f2a 100644
--- a/apps/template/components/cart/context.tsx
+++ b/apps/template/components/cart/context.tsx
@@ -96,15 +96,23 @@ function computeCartWithPending(
}
if (pendingQuantity > 0) {
+ const currencyCode =
+ pendingLines[0]?.cost.totalAmount.currencyCode ??
+ pendingLines[0]?.merchandise.price?.currencyCode;
+
+ if (!currencyCode) {
+ return null;
+ }
+
return {
id: undefined,
checkoutUrl: "",
totalQuantity: pendingQuantity,
note: null,
cost: {
- subtotalAmount: { amount: "0", currencyCode: "USD" },
- totalAmount: { amount: "0", currencyCode: "USD" },
- totalTaxAmount: { amount: "0", currencyCode: "USD" },
+ subtotalAmount: { amount: "0", currencyCode },
+ totalAmount: { amount: "0", currencyCode },
+ totalTaxAmount: { amount: "0", currencyCode },
},
lines: pendingLines,
shippingCost: null,
diff --git a/apps/template/components/collections/filter-sidebar.tsx b/apps/template/components/collections/filter-sidebar.tsx
index e6be2cd7..806e7c3e 100644
--- a/apps/template/components/collections/filter-sidebar.tsx
+++ b/apps/template/components/collections/filter-sidebar.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useTranslations } from "next-intl";
+import { useLocale, useTranslations } from "next-intl";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useOptimistic, useRef, useState } from "react";
@@ -24,30 +24,64 @@ import {
} from "@/components/ui/filter-sidebar";
import { getActiveFilterBadges } from "@/lib/shopify/transforms/filters";
import type { Filter, PriceRange } from "@/lib/types";
+import { getCurrencySymbol } from "@/lib/utils";
interface CollectionFilterSidebarClientProps {
filters: Filter[];
priceRange?: PriceRange;
+ currencyCode: string;
activeFilters: Record;
}
type FilterState = Record;
const PRICE_PRESETS = [
- { label: "Under $50", min: 0, max: 50 },
- { label: "$50 - $100", min: 50, max: 100 },
- { label: "$100 - $200", min: 100, max: 200 },
- { label: "Over $200", min: 200, max: undefined },
+ { min: 0, max: 50 },
+ { min: 50, max: 100 },
+ { min: 100, max: 200 },
+ { min: 200, max: undefined },
];
-function formatPriceRangeLabel(min: number | null, max: number | null): string {
+function formatCurrencyAmount(amount: number, locale: string, currencyCode: string): string {
+ return new Intl.NumberFormat(locale, {
+ style: "currency",
+ currency: currencyCode,
+ currencyDisplay: "narrowSymbol",
+ minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
+ maximumFractionDigits: Number.isInteger(amount) ? 0 : 2,
+ }).format(amount);
+}
+
+function formatPriceRangeLabel(
+ min: number | null,
+ max: number | null,
+ locale: string,
+ currencyCode: string,
+): string {
if (min !== null && max !== null) {
- return `$${min} - $${max}`;
+ return `${formatCurrencyAmount(min, locale, currencyCode)} - ${formatCurrencyAmount(max, locale, currencyCode)}`;
}
if (min !== null) {
- return `From $${min}`;
+ return `From ${formatCurrencyAmount(min, locale, currencyCode)}`;
+ }
+ return `Up to ${formatCurrencyAmount(max ?? 0, locale, currencyCode)}`;
+}
+
+function formatPricePresetLabel(
+ min: number,
+ max: number | undefined,
+ locale: string,
+ currencyCode: string,
+): string {
+ if (max === undefined) {
+ return `Over ${formatCurrencyAmount(min, locale, currencyCode)}`;
+ }
+
+ if (min === 0) {
+ return `Under ${formatCurrencyAmount(max, locale, currencyCode)}`;
}
- return `Up to $${max}`;
+
+ return `${formatCurrencyAmount(min, locale, currencyCode)} - ${formatCurrencyAmount(max, locale, currencyCode)}`;
}
function getFilterValues(value: string | string[] | undefined): string[] {
@@ -117,8 +151,10 @@ function applyPriceParams(params: URLSearchParams, min: number | null, max: numb
export function CollectionFilterSidebarClient({
filters,
priceRange,
+ currencyCode,
activeFilters,
}: CollectionFilterSidebarClientProps) {
+ const locale = useLocale();
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();
@@ -236,7 +272,7 @@ export function CollectionFilterSidebarClient({
))}
{hasPriceFilter && (
- {formatPriceRangeLabel(urlPriceMin, urlPriceMax)}
+ {formatPriceRangeLabel(urlPriceMin, urlPriceMax, locale, currencyCode)}
)}
@@ -252,6 +288,7 @@ export function CollectionFilterSidebarClient({
onMinChange={setMinInput}
onMaxChange={setMaxInput}
onApply={applyPriceRange}
+ currencySymbol={getCurrencySymbol(currencyCode, locale)}
>
{PRICE_PRESETS.map((preset) => {
@@ -263,11 +300,11 @@ export function CollectionFilterSidebarClient({
return (
applyPricePreset(preset.min, preset.max)}
>
- {preset.label}
+ {formatPricePresetLabel(preset.min, preset.max, locale, currencyCode)}
);
})}
diff --git a/apps/template/components/collections/filters.tsx b/apps/template/components/collections/filters.tsx
index 2a73c98c..fd20ff21 100644
--- a/apps/template/components/collections/filters.tsx
+++ b/apps/template/components/collections/filters.tsx
@@ -14,12 +14,13 @@ async function Render({
}: {
collectionResultsDataPromise: Promise;
}) {
- const { activeFilters, transformedFilters } = await collectionResultsDataPromise;
+ const { activeFilters, currencyCode, transformedFilters } = await collectionResultsDataPromise;
return (
);
diff --git a/apps/template/components/search/results.tsx b/apps/template/components/search/results.tsx
index 2fad2e17..6deb8eb2 100644
--- a/apps/template/components/search/results.tsx
+++ b/apps/template/components/search/results.tsx
@@ -11,6 +11,7 @@ import { ProductCard, ProductCardSkeleton } from "@/components/product-card";
import { Skeleton } from "@/components/ui/skeleton";
import type { Locale } from "@/lib/i18n";
import { buildProductFiltersFromParams, getProducts } from "@/lib/shopify/operations/products";
+import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop";
import { transformShopifyFilters } from "@/lib/shopify/transforms/filters";
import { RESULTS_PER_PAGE } from "@/lib/utils";
@@ -69,6 +70,7 @@ export async function Results({
const transformedFilters = transformShopifyFilters(result.filters, {
activeFilters,
});
+ const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode());
const products = result.products;
return (
@@ -78,6 +80,7 @@ export async function Results({
diff --git a/apps/template/lib/collections/server.ts b/apps/template/lib/collections/server.ts
index c7538e0a..3b55cccf 100644
--- a/apps/template/lib/collections/server.ts
+++ b/apps/template/lib/collections/server.ts
@@ -3,6 +3,7 @@ import {
buildProductFiltersFromParams,
getCollectionProducts,
} from "@/lib/shopify/operations/products";
+import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop";
import { type TransformedFilters, transformShopifyFilters } from "@/lib/shopify/transforms/filters";
import { RESULTS_PER_PAGE, parseFiltersFromSearchParams } from "@/lib/utils";
@@ -15,6 +16,7 @@ export interface CollectionSearchState {
export interface CollectionResultsData {
activeFilters: Record;
cursor?: string;
+ currencyCode: string;
result: Awaited>;
transformedFilters: TransformedFilters;
}
@@ -57,6 +59,7 @@ export async function getCollectionResultsData({
return {
activeFilters,
cursor,
+ currencyCode: result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode()),
result,
transformedFilters: transformShopifyFilters(result.filters, {
activeFilters,
diff --git a/apps/template/lib/i18n/index.ts b/apps/template/lib/i18n/index.ts
index 1e117eb1..a3e0c7e5 100644
--- a/apps/template/lib/i18n/index.ts
+++ b/apps/template/lib/i18n/index.ts
@@ -34,7 +34,8 @@ export function resolveLocale(value: string | null | undefined): Locale {
return value && isEnabledLocale(value) ? value : defaultLocale;
}
-// Currency data per locale
+// Explicit locale → currency mappings are only for configured multi-locale
+// storefronts. Single-locale pricing should come from Shopify responses.
const localeCurrency: Record = {
"en-US": { currency: "USD", symbol: "$" },
};
diff --git a/apps/template/lib/markdown/catalog.ts b/apps/template/lib/markdown/catalog.ts
index fd7fd21b..be74d1c8 100644
--- a/apps/template/lib/markdown/catalog.ts
+++ b/apps/template/lib/markdown/catalog.ts
@@ -1,4 +1,3 @@
-import { getCurrencyCode } from "@/lib/i18n";
import type { Filter, PageInfo, PriceRange, ProductCard } from "@/lib/types";
import { getActiveFilterBadges } from "@/lib/shopify/transforms/filters";
@@ -24,9 +23,7 @@ function getSingleValue(value: string | string[] | undefined): string | undefine
return Array.isArray(value) ? value[0] : value;
}
-function formatPriceRange(priceRange: PriceRange, locale: string): string {
- const currencyCode = getCurrencyCode(locale);
-
+function formatPriceRange(priceRange: PriceRange, locale: string, currencyCode: string): string {
return `${formatPrice({ amount: priceRange.min.toString(), currencyCode }, locale)} - ${formatPrice(
{ amount: priceRange.max.toString(), currencyCode },
locale,
@@ -83,10 +80,12 @@ export function appendAvailableFiltersSection(
{
filters,
priceRange,
+ currencyCode,
locale,
}: {
filters: Filter[];
priceRange?: PriceRange;
+ currencyCode: string;
locale: string;
},
): void {
@@ -98,7 +97,7 @@ export function appendAvailableFiltersSection(
sections.push("");
if (priceRange) {
- sections.push(`- **Price Range**: ${formatPriceRange(priceRange, locale)}`);
+ sections.push(`- **Price Range**: ${formatPriceRange(priceRange, locale, currencyCode)}`);
}
for (const filter of filters) {
diff --git a/apps/template/lib/markdown/collection.ts b/apps/template/lib/markdown/collection.ts
index e45ed0a0..0cd8a553 100644
--- a/apps/template/lib/markdown/collection.ts
+++ b/apps/template/lib/markdown/collection.ts
@@ -14,6 +14,7 @@ export function collectionToMarkdown({
products,
filters,
priceRange,
+ currencyCode,
activeFilters,
pageInfo,
locale,
@@ -23,6 +24,7 @@ export function collectionToMarkdown({
products: ProductCard[];
filters: Filter[];
priceRange?: PriceRange;
+ currencyCode: string;
activeFilters: Record;
pageInfo: PageInfo;
locale: string;
@@ -50,7 +52,7 @@ export function collectionToMarkdown({
}
appendAppliedFiltersSection(sections, { activeFilters, filters });
- appendAvailableFiltersSection(sections, { filters, priceRange, locale });
+ appendAvailableFiltersSection(sections, { filters, priceRange, currencyCode, locale });
appendProductsSection(sections, { products, locale });
appendPaginationSection(sections, pageInfo);
diff --git a/apps/template/lib/markdown/search.ts b/apps/template/lib/markdown/search.ts
index eb09f22a..d7655579 100644
--- a/apps/template/lib/markdown/search.ts
+++ b/apps/template/lib/markdown/search.ts
@@ -16,6 +16,7 @@ export function searchResultsToMarkdown({
total,
filters,
priceRange,
+ currencyCode,
activeFilters,
pageInfo,
locale,
@@ -27,6 +28,7 @@ export function searchResultsToMarkdown({
total: number;
filters: Filter[];
priceRange?: PriceRange;
+ currencyCode: string;
activeFilters: Record;
pageInfo: PageInfo;
locale: string;
@@ -50,7 +52,7 @@ export function searchResultsToMarkdown({
sections.push("");
appendAppliedFiltersSection(sections, { activeFilters, filters });
- appendAvailableFiltersSection(sections, { filters, priceRange, locale });
+ appendAvailableFiltersSection(sections, { filters, priceRange, currencyCode, locale });
appendProductsSection(sections, { products, locale });
appendPaginationSection(sections, pageInfo);
diff --git a/apps/template/lib/shopify/operations/shop.ts b/apps/template/lib/shopify/operations/shop.ts
new file mode 100644
index 00000000..e2ccff7d
--- /dev/null
+++ b/apps/template/lib/shopify/operations/shop.ts
@@ -0,0 +1,3 @@
+export async function getShopDefaultCurrencyCode(): Promise {
+ return process.env.SHOPIFY_DEFAULT_CURRENCY ?? "USD";
+}
diff --git a/apps/template/lib/utils/index.ts b/apps/template/lib/utils/index.ts
index 70fa8689..c8372aba 100644
--- a/apps/template/lib/utils/index.ts
+++ b/apps/template/lib/utils/index.ts
@@ -13,6 +13,18 @@ export function formatPrice(amount: number, currencyCode: string, locale: string
}).format(amount);
}
+export function getCurrencySymbol(currencyCode: string, locale: string): string {
+ return (
+ new Intl.NumberFormat(locale, {
+ style: "currency",
+ currency: currencyCode,
+ currencyDisplay: "narrowSymbol",
+ })
+ .formatToParts(0)
+ .find((part) => part.type === "currency")?.value ?? currencyCode
+ );
+}
+
export function parseFiltersFromSearchParams(
searchParams: Record,
): Record {
diff --git a/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md b/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md
new file mode 100644
index 00000000..82929088
--- /dev/null
+++ b/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md
@@ -0,0 +1,44 @@
+---
+title: Configure single-locale default currency for pricing chrome
+changeKey: shopify-default-currency
+introducedInVersion: 0.1.0
+introducedOn: 2026-04-15
+changeType: fix
+defaultAction: adopt
+appliesTo:
+ - all
+paths:
+ - apps/template/components/cart/context.tsx
+ - apps/template/components/collections/filter-sidebar.tsx
+ - apps/template/lib/collections/server.ts
+ - apps/template/lib/markdown/catalog.ts
+ - apps/template/lib/shopify/operations/shop.ts
+relatedSkills:
+ - /vercel-shop:enable-shopify-markets
+---
+
+## Summary
+
+The template no longer hardcodes USD for single-locale search and collection pricing chrome. Filter presets and markdown price ranges now use product currency when available and fall back to `SHOPIFY_DEFAULT_CURRENCY` for empty-result states.
+
+## Why it matters
+
+Stores whose Shopify base currency is not USD could show mismatched symbols or markdown output even when storefront prices were otherwise correct. This adds an explicit config path so template-generated price UI stays aligned with the storefront's intended default currency.
+
+## Apply when
+
+- Your storefront runs in single-locale mode.
+- Your Shopify shop currency is not USD.
+- Search filters or markdown endpoints showed `$` while Shopify pricing used another currency.
+
+## Safe to skip when
+
+- Your shop currency is already USD and you are fine with the default.
+- You already derive filter and markdown currency directly from Shopify.
+
+## Validation
+
+- Set `SHOPIFY_DEFAULT_CURRENCY` to your shop currency in `.env.local` or your deployment environment.
+- Visit a collection or search page and confirm the price filter symbol matches your configured currency.
+- Request `/collections/` or `/search?q=` with `Accept: text/markdown` and confirm price ranges use the same currency as product prices.
+- Add a product to cart and confirm any optimistic subtotal placeholder matches the variant currency.