Customizing a template
The 95/5 split: ~95% of merchant changes are content (`lib/brand.ts`) and palette (`app/globals.css`). For the remaining 5% (restructured layouts, industry-specific sections, custom selectors), eject the SDK component or extend the brand schema.
The customisation tiers
| Tier | When | Cost |
|---|---|---|
Edit brand.ts | Copy, links, contact, hero, FAQ, policies | Free updates from the SDK forever |
Edit globals.css @theme | Palette, radius, fonts | Free updates from the SDK forever |
classNames overrides | Restyle a slot inside an SDK component | Free updates; no fork |
| Eject a component | Restructure JSX, change behaviour, add merchant logic | You own the file; no auto-updates |
Extend BrandSchema | Industry-specific fields (lookbook, booking policy) | Type-checked, validated, schema co-evolves |
Edit content via brand.ts
Hardcoding a string in a page or component is the wrong move. Hoist it into the brand object first; everything that reads from brand stays consistent across pages, sitemap, and metadata.
// ❌ wrong: hardcoded in a component
<h1>Akua's Bakery</h1>
// ✅ right: sourced from the brand contract
import { brand } from "@/lib/brand";
<h1>{brand.hero.title}</h1>Eject a component
The SDK ships ~67 ejectable components in its registry (cards, selectors, full pages, primitives). Eject when a classNames override can't reach the part you need to change, or when you're restructuring beyond what the component's props support.
# Browse the registry
cimplify list
# Eject one
cimplify add cart-summary
# → writes ./components/cart-summary.tsx, owned by you
# Replace the SDK import in your page
# import { CartSummary } from "@cimplify/sdk/react"
# import { CartSummary } from "@/components/cart-summary"Once ejected, the file is yours. SDK upgrades won't touch it; you trade off auto-bugfixes for full control.
Don't reinvent the customizer. Variant selection, add-on math, bundle pricing, and composite mode-switching took many iterations to get right. Eject variant-selector, composite-selector, bundle-selector, or add-on-selector and re-style; don't rewrite the cart payload contract. See AGENTS.md for the full doctrine.
Extend the brand schema
Industry-specific fields belong on the schema, not in scattered component props. Use BrandSchema.extend so your additions get the same boot-time validation as the canonical fields.
import { BrandSchema } from "@cimplify/sdk/testing";
import { z } from "zod";
export const ServicesBrandSchema = BrandSchema.extend({
bookingPolicy: z.object({
cancellationWindowHours: z.number().int().positive(),
depositPercent: z.number().int().min(0).max(100),
rescheduleNoticeHours: z.number().int().positive(),
}),
});
export type ServicesBrand = z.infer<typeof ServicesBrandSchema>;import type { ServicesBrand } from "./schema";
export const brand: ServicesBrand = {
// …all the standard Brand fields…
bookingPolicy: {
cancellationWindowHours: 24,
depositPercent: 25,
rescheduleNoticeHours: 12,
},
};import { describe, it, expect } from "vitest";
import { ServicesBrandSchema } from "../lib/schema";
import { brand } from "../lib/brand";
describe("brand", () => {
it("conforms to the services brand contract", () => {
expect(ServicesBrandSchema.safeParse(brand).success).toBe(true);
});
});Add a new section
- Build the section as a Server Component in
components/. - Read merchant-specific copy from
brand; extend the schema if the field isn't there. - Wrap any client interactivity in a
*-client.tsxisland behind<Suspense>to keep the chrome cached. - Compose into the page (
app/page.tsx,app/products/[slug]/page.tsx, …). - Run
bun run check.
Wire a Server Action
"use server";
import { getServerClient, revalidateOrders } from "@cimplify/sdk/server";
export async function cancelMyOrder(orderId: string) {
const r = await getServerClient().orders.cancel(orderId, "customer requested");
if (!r.ok) return { ok: false as const, message: r.error.message };
await revalidateOrders();
return { ok: true as const };
}Don'ts
- Hardcode strings in pages or components.
- Disable
cacheComponents: trueto silence a warning. Wrap in<Suspense>instead. - Use
unstable_cache. Next 16's canonical primitive is'use cache'. - Bypass
getServerClient()by instantiatingcreateCimplifyClientdirectly in a Server Component; you'll lose per-request memoisation.
Next
-
Brand schema Field reference and a full example
-
Testing Catch ejections that broke the contract
Brand schema
Every storefront template ships a `lib/brand.ts` that owns every visible string. Pages, components, JSON-LD, sitemap, `llms.txt`, and metadata all read from `brand`, so a rebrand is one file. The shape is enforced at boot by `BrandSchema`, and at test time by `assertBrand(brand)`.
API Keys
Cimplify uses three families of credentials: **public keys** for the browser SDK, **secret keys** for server-to-server calls, and **developer keys** for the CLI. Each prefix tells you exactly where it belongs.