Storefront & pricing page
The public front door for your product — landing pages, a pricing page generated from your own plans, and a sign-up funnel that lands customers authenticated in your UI.
The front door your customers arrive through
A platform with no way in is not a business. Before anyone can use what you built, they have to find it, understand what it costs, and sign up — which means a public site with landing pages, a pricing page, and a working sign-up funnel.
The platform ships that as a storefront template: a React app, wired to the Control Plane, that you brand, edit and deploy as your own. It is the same shape as the storefront we run for our own products, minus the content.
You receive it as a repository of your own during onboarding, under a licence that lets you modify it freely for your product. The funnel it contains — landing, live pricing, register, email verification, and arrival in a provisioned tenant — is verified working end to end, not a starting sketch.
What ships in it
- Landing and product pages — the skeleton, with placeholder copy for you to replace.
- A pricing page driven by your live plans — see the next section; this is the part worth reading.
- Sign-up, in your own UI — register, verify email, and land authenticated. No sign-in page of ours, ever.
- A marketplace — browse the apps available on your platform, with a detail page per app.
- Case studies, a demo request form and a contact form — wired to your own Control Plane, not to ours.
- Terms, privacy and a 404 — the pages every commercial site needs and nobody enjoys building.
Your pricing page is generated from your plans
This is the piece that does not exist anywhere else, so it is worth being precise about.
When you declare a feature on an app, you give it a code, a type, a unit, a default and — optionally — a customer-facing label and description:
- code: crm.contacts.count
name: Contact limit
type: integer
unit: contacts
default: 10000
marketing_label: Contacts included
marketing_description: How many contact records you can store.
quota:
source: cumulativePublishing the app registers that feature in the plan catalogue. You then set its value per plan, and override it per organization for a deal that does not fit the price list. The storefront's pricing page reads the resulting plans straight from the Control Plane and builds the comparison table from marketing_label and marketing_description, falling back to the engineering label when you have not written marketing copy yet.
The consequence is the useful part: the number on your pricing page and the number enforced at runtime come from one definition. Raise a plan's contact limit and the pricing page reflects it on the next load. There is no second place to update, so the page cannot quietly drift away from what your product actually does — the failure that turns into a refund conversation.
See Plans, features and quotas for the full model, and Subscriptions and billing for what happens after someone picks a plan.
Sign-up happens in your UI
The funnel is register, verify email, then arrive authenticated. Your customers see your pages throughout — they never meet an identity provider's login screen, which is a deliberate platform rule rather than a template choice. See Auth and the five gates for why, and Sign-up and onboarding for the API underneath if you would rather wire your own form.
Setting it up
You need Node 18 or newer, a reachable Control Plane, and the registry token for @egav/app-sdk that comes with your subscription. Then it is npm install, set your Control Plane URL, npm start.
Three things behave deliberately and are worth knowing before you debug them:
- There is no default Control Plane URL. An unconfigured build fails loudly rather than starting. A storefront quietly pointed at the wrong backend would post your visitors' personal data somewhere it does not belong, so this one refuses to guess.
- Your Control Plane must allow the storefront's origin, including the port. If plans do not load and the console shows a CORS error, add the origin in Control Plane settings. It is configuration, not code.
- Serve from a real domain when you test the sign-up flow. Bot-protection keys are registered against specific domains, so a token minted on
localhostis rejected by the server and the failure surfaces as a generic "could not complete signup". Check the serving origin before you go looking for a bug in the form.
The repository README carries the exact commands and environment variables. This page covers what to change and why.
Making it yours
Three files, in this order.
1. Brand — src/brand/brand.config.ts. Nothing else in src/ hardcodes a product name, logo or colour, so a build-time rebrand is one file. Colours flow into CSS variables and then into Tailwind classes like bg-brand, which is why a colour change repaints the whole site rather than just the component library. Mirror the palette in src/index.css so the first paint matches before the app boots.
Branding is two layers, and the Control Plane wins. An operator sets product name, logo, favicon, primary colour, support email and website URL in platform → info → branding, and those apply at runtime with no rebuild — the same mechanism the customer portal uses. brand.config.ts supplies the build-time defaults and anything the Control Plane has not set. The Control Plane exposes a single primary colour; the hover, accent and tint steps are derived from it, so a one-field rebrand stays coherent.
2. Copy — src/content/site.content.ts. Every visible marketing string lives here, shipped as [bracketed] placeholders. npm run check:placeholders fails while any remain, and it is wired into npm run check — so an unedited storefront cannot reach production still saying "[Your product] does [outcome]". Put that check in your CI and the guard travels with you.
3. Assets. Replace public/logos/product-mark.svg and public/favicon.svg.
Extending it
Pages are individual files in src/pages, and src/App.tsx is a short routing file. Adding a page is a component plus a route.
- Your own product pages.
ProductPageis a generic stand-in at/product. Most customers replace it with several pages of their own — one per product or per audience. - The pricing page needs no code. It renders whatever plans your Control Plane serves, using the customer-facing label and description on each feature. Change a plan, add a feature, raise a limit — the page follows on the next load. Editing
PricingPage.tsxis for changing how the table looks, never what it says. - Enquiry forms post to your own Control Plane. Add fields in the page; the endpoint stays the same.
- Case studies, marketplace, legal pages are ordinary content surfaces — extend or delete them freely.
Two things to leave alone unless you mean it. The sign-up sequence (register, verify, callback) mirrors a Control Plane contract; changing the order breaks verification. And the guard that throws when the Control Plane URL is unset exists to stop a misconfigured deploy leaking visitor data — removing it converts a loud failure into a silent one.
Testing what you changed
The template ships with two tiers, and the split matters because one of them needs nothing but your laptop.
- Unit and component tests cover the shell, routing, accessibility and branding, and run with no backend at all — so a copy or brand change is verifiable immediately.
- End-to-end tests cover pricing, registration and verification, and need a reachable Control Plane.
npm run check runs the type check, the tests and the placeholder gate together. Make that your pre-deploy command and an unfinished storefront cannot ship.
It is yours
The storefront is delivered as source that you own and modify, under a licence permitting you to adapt it for your product. Unlike the platform services, it is not something we upgrade underneath you: once you start editing, it is your codebase, and changes we make later do not arrive automatically. We do not support your copy — that is the trade that lets you change anything in it.
What you keep is compatibility. The Control Plane endpoints the storefront calls — plans, sign-up, verification, enquiries — are versioned API surface with the same stability commitment as the rest of the platform. Your storefront keeps working because the contract underneath it holds still, not because you stay on our copy of the code.