The shape of the object the storefront passes to a BYOT theme's mount(ctx) call.Top-level#
interface MountContext {
store: StoreData;
themeSettings: ThemeSettingsV3;
page: PageContext;
locale: "en" | "ar";
direction: "ltr" | "rtl";
currency: string; // e.g. "EGP", "USD"
}
The fields below are the values you'll actually destructure most often.store#
interface StoreData {
id: string;
name: string;
subdomain: string;
default_language: "en" | "ar";
currency: string;
logo_url: string | null;
favicon_url: string | null;
contact_email: string | null;
contact_phone: string | null;
settings: Record<string, unknown>; // free-form, includes payment + tax + analytics
}
themeSettings#
Resolved customizer state. The shape mirrors the merchant's customizer JSON after resolveThemeSettings() normalizes legacy fields:interface ThemeSettingsV3 {
theme_id: string; // e.g. "bazar" for built-in, or your published theme id
external_theme?: {
bundle_url: string;
css_url?: string;
};
settings: Record<string, unknown>; // resolved per settings_schema.json
templates?: {
home?: PageTemplate;
product?: PageTemplate;
collection?: PageTemplate;
cart?: PageTemplate;
[k: string]: PageTemplate | undefined;
};
// ... locales, presets, variants (theme color palettes), etc.
}
interface PageTemplate {
sections: Record<string, SectionInstance>;
order: string[]; // section ids in render order
}
interface SectionInstance {
type: string; // matches schemas/sections/<type>.json
settings: Record<string, unknown>;
blocks?: Record<string, BlockInstance>;
block_order?: string[];
disabled?: boolean;
}
Themes don't usually iterate templates directly — they receive resolved data through context (e.g. useSection() provides current section settings). But you can read themeSettings directly for global values like color schemes.page per route#
The storefront sets page.type per route so themes can dispatch. page.data carries everything else the bundle needs to render that route without an extra API call.Home#
{ type: "home", title?: string }
No data. Themes call useProducts(), useCollections(), etc. for the home layout.Product detail#
{
type: "product",
title: product.name,
handle: slug,
data: { product: Product } // includes options[] + variants[] post-Phase 8.1
}
The product object includes options (axis definitions) and variants (per-combination price/SKU/stock).Collection / category#
{
type: "collection",
title: collection.name,
handle: slug,
data: { collection: Category, products: Product[], pagination: {...} }
}
Cart#
{ type: "cart", title: "Cart" }
No pre-fetch. Themes call useCart() which hits /api/cart from the bundle.Checkout (multi-step, Phase 7)#
page.type | page.data |
|---|
checkout_contact | { cart, customer } |
checkout_shipping | { cart, address, rates, pickup_locations } |
checkout_payment | { cart, address, methods, saved_cards } |
checkout_review | { cart, address, rate, method } |
checkout_processing | { order_id } |
checkout_thank_you | { order } |
Themes that want to drive checkout programmatically use useCheckout() from the SDK instead of routing through these page types.Account routes#
page.type | page.data |
|---|
account (dashboard) | { customer } |
account_login | {} |
account_register | {} |
account_recover | {} |
account_reset | { token: string } |
account_profile | { customer } |
account_orders | { customer, orders: Order[], pagination } |
account_order | { customer, order: Order } |
account_addresses | { customer, addresses: Address[] } |
account_gift_cards | { customer } |
account_wishlist | { customer, items } |
Content routes#
page.type | page.data |
|---|
page | { page: { title, body_html, handle } } |
policies | { policy: { handle, title, body_html } } |
blogs | { blogs: Blog[] } |
blog | { blog: Blog, articles: Article[] } |
article | { blog: Blog, article: Article } |
Search#
{
type: "search",
data: { query: string, products: Product[], collections: Category[], pagination }
}
Special#
page.type | When |
|---|
password | Store is password-locked (pre-launch) |
404 | Route didn't match anything |
Common entity shapes#
Product#
interface Product {
id: string;
name: string;
slug: string;
description: string;
short_description: string | null;
product_type: "physical" | "digital" | "gift_card";
status: "active" | "draft" | "archived";
price: number; // major units, e.g. 99.00
compare_at_price?: number;
currency: string;
sku: string | null;
quantity: number | null; // sum across variants
in_stock: boolean;
is_low_stock: boolean;
is_on_sale: boolean;
images: Array<{ url: string; alt?: string }>;
tags: string[];
category_id: string | null;
attributes: Record<string, unknown>; // free-form
options: ProductOption[]; // Phase 8.1 axes
variants: ProductVariant[]; // Phase 8.1
}
interface ProductOption {
name: string; // e.g. "Size"
position: number;
values?: string[]; // e.g. ["S", "M", "L"]
}
interface ProductVariant {
id: string;
option_values: Record<string, string>; // { Size: "M", Color: "Red" }
price: string; // decimal string
compare_at_price: string | null;
sku: string | null;
inventory_quantity: number;
is_in_stock: boolean;
image_url: string | null;
}
Cart (from useCart())#
interface Cart {
items: CartLineItem[];
item_count: number;
total_quantity: number;
subtotal: number; // cents
currency: string;
applied_promotion?: { code?: string; label?: string; amount: number } | null;
}
interface CartLineItem {
id: string; // line item key
product_id: string;
variant_id: string | null;
product_name: string;
variant_name: string | null; // "Size: M / Color: Red"
sku: string | null;
quantity: number;
unit_price: number; // cents, snapshotted at add-time
total_price: number; // cents
current_price: number; // cents, LIVE
price_changed: boolean; // current vs unit
image_url: string | null;
in_stock: boolean;
available_now: number | null;
sold_out_now: boolean;
}
Order (from useOrders() / useOrder())#
interface Order {
id: string;
order_number: string;
status: "pending" | "pending_deposit" | "confirmed" | "processing" |
"shipped" | "delivered" | "cancelled" | "returned";
payment_status: "pending" | "paid" | "refunded" | "partially_refunded" | "failed";
fulfillment_status: "unfulfilled" | "partial" | "fulfilled";
currency: string;
subtotal: number; // cents
shipping_cost: number;
tax_amount: number;
discount_amount: number;
total: number;
line_items: OrderLineItem[];
shipping_address: Address;
billing_address: Address;
payment_method: string;
shipping_method: string | null;
coupon_code: string | null;
customer_notes: string | null;
created_at: string;
shipped_at?: string;
delivered_at?: string;
metadata?: {
gift_cards_applied?: Array<{ gift_card_id: string; last_four: string; amount_cents: number }>;
gift_card_tender_total_cents?: number;
tax_breakdown?: { rate: number; inclusive: boolean; added_cents: number; included_cents: number };
};
}
Customer#
interface Customer {
id: string;
email: string;
first_name: string | null;
last_name: string | null;
phone: string | null;
total_orders: number;
total_spent: number;
created_at: string;
default_address_id: string | null;
}
Defaults when fields are missing#
The storefront's API client (normalizeProduct, etc.) backfills certain fields:currency → falls back to price_currency then to store.currency
in_stock → falls back to is_in_stock
images → defaults to [] so themes can iterate safely
variants / options → default to [] for products with no variants
If a field is required but missing (e.g. product.id), expect the page to 404 — themes should never see partial entities. Modified at 2026-09-19 15:52:00