@softobotics/storefront-config
v0.1.6
Published
Resolves a Vendure Channel's branding/nav/footer/checkout custom fields into a validated storefront config, and a provider that applies it as CSS custom properties.
Keywords
Readme
@softobotics/storefront-config
Resolves a Vendure Channel's branding/nav/footer/checkout custom fields into a
validated, defaulted StorefrontConfig, and a <StorefrontConfigProvider>
that applies the brand tokens as CSS custom properties.
Why this exists
Each tenant storefront should be reskinnable from the Vendure Channel's admin
settings (color, radius, fonts, nav/footer toggles) without a redeploy of the
storefront's own code. This package is the shared resolution/validation layer;
it does not fetch data itself — the consuming app queries the Channel's
custom fields (e.g. via @softobotics/storefront-vendure-client) and passes
the raw result in.
Usage
import {resolveStorefrontConfig} from '@softobotics/storefront-config';
const config = resolveStorefrontConfig(activeChannel.customFields);import {StorefrontConfigProvider} from '@softobotics/storefront-config';
export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html>
<body>
<StorefrontConfigProvider config={config}>{children}</StorefrontConfigProvider>
</body>
</html>
);
}CSS custom properties
StorefrontConfigProvider targets the exact token names
vendure-storefront's globals.css already defines under its @theme inline
block: --primary, --primary-foreground, --radius, --font-sans,
--font-mono, plus the rest of its shadcn palette — --background,
--foreground, --card, --card-foreground, --popover,
--popover-foreground, --secondary, --secondary-foreground, --muted,
--muted-foreground, --accent, --accent-foreground, --destructive,
--border, --input, --ring — not invented --brand-* names. An
unconfigured channel resolves to the same values already hardcoded there
(every one of these was copied byte-for-byte from globals.css's :root
block), so wiring this in is a no-op until a channel sets its own values.
--sidebar-* and --chart-* are deliberately not covered — neither
renders anywhere in vendure-storefront's customer-facing UI (the sidebar
shadcn primitive and the chart component are both unused there), so making
them configurable would be dead surface area.
Dark mode
brand.dark mirrors the same set of tokens (plus primaryColor/
primaryForeground, which do change between light/dark) for the .dark
class block next-themes toggles. StorefrontConfigProvider always renders
both a :root { ... } and a .dark { ... } block; every dark.* field
defaults to the exact value already hardcoded in globals.css's .dark
block, so it's a no-op there too until configured.
The brandPrimaryColor/etc. custom fields
These scalar fields (brandPrimaryColor, brandPrimaryForeground,
brandRadius, brandFontSans, brandFontMono, faviconAssetId) plus the
JSON ones below must exist on the Vendure Channel entity's custom fields
(see vendure-backend/src/vendure-config.ts) and have a migration generated
and applied before a real channel can set them. Until that migration runs,
resolveStorefrontConfig still works against an empty/undefined input and
returns the documented defaults — it does not require the migration to exist
to be built or tested.
storefrontThemeSettings
The rest of the palette (everything under "CSS custom properties" above,
beyond brandPrimaryColor/brandPrimaryForeground) is bundled into one
JSON text custom field, storefrontThemeSettings, rather than ~34
individual scalar fields (16 light + 18 dark tokens) — it's edited as one
palette, not field-by-field, matching the existing
storefrontNavSettings/storefrontFooterSettings/storefrontCheckoutSettings
convention. Shape:
{
"secondaryColor": "oklch(0.9 0.05 200)",
"dark": { "primaryColor": "oklch(0.4 0.2 145)" }
}Any key can be omitted — omitted keys fall back to their own documented default, same as every other field in this package.
Asset ids and logo/favicon URLs
logoAssetId/faviconAssetId are passed through as raw ids, unresolved to a
URL — same as the existing logoAssetId field predating this package. Vendure's
Shop API has no top-level asset(id) query, so resolving an id to a preview
URL is left to the consuming app's existing convention; this package does not
guess at one.
Until that resolution exists, prefer the separate logoUrl/faviconUrl
fields — plain, already-resolved URLs (e.g. an external CDN link, or a Vendure
asset's preview URL copied in manually) that a consumer can render directly
with no extra lookup. resolveStorefrontConfig({}).brand.logoUrl is null
until a channel sets one.
JSON-encoded fields
storefrontNavSettings, storefrontFooterSettings, and
storefrontCheckoutSettings are stored as Vendure text custom fields
containing JSON, matching the partial shape of NavConfig/FooterConfig/
CheckoutConfig. Malformed JSON is ignored (with a console.warn) and falls
back to defaults rather than throwing — a misconfigured channel should degrade
gracefully, not break the storefront build.
