The rules
What a custom template can and can't do — and the single most common mistake.
Your template is one JSX expression — the thing you'd put after return ( in a React component. Nothing more.
Behind the scenes the app wraps what you type as (props) => ( … your code … ) and compiles that. Only two names are handed to your code: props and React. Everything below follows from that.
Allowed
- Any JSX elements and attributes
props.anything.map(), ternaries (cond ? a : b),&&, string concatenation, arithmetic, and standard JavaScript built-ins such asMath,Array.fromandJSON.stringify- Inline arrow functions in event handlers:
onClick={() => props.onAddToKit()} - Statements inside a function body — an event handler like
onClick={(e) => { e.preventDefault(); … }}, or a.map()callback with its ownconst React.Fragment, or the<>…</>shorthand- Inline
style={{ … }}objects
Won't compile
import— you have no access to other files- Statements at the top level:
const x = …,if (…) { … },return …,function … - A semicolon after the closing tag
- Multiple top-level elements without a wrapper — wrap them in
<>…</>
The switch stays off and a red Compile failed banner tells you where. See Troubleshooting.
Not supported
These compile, but they fail when the template renders, and the app falls back to the built-in design:
- Hooks (
useState,useEffect, …) — the template holds no state, and bare hook names aren't defined require— not defined either- The app's own components. You can't import or name them; you place the finished elements the app hands you on
propsinstead, such asprops.slots.sidebar - A bare
<Fragment>— write<React.Fragment>or<>…</>
fetch, window, document, timers, and other browser APIs are a different case. Nothing in the app blocks them — a template isn't sandboxed — but loading data and holding state are the app's job, and nothing here supports them. Treat them as off-limits.
If you need a value that isn't in props, that's a gap in what the app provides, not something to work around. Contact support and ask for it.
The single most common mistake
Every value you need has to be computed inline, inside { }, because the whole template is one expression with no room for a line that assigns to a variable first. The wrong version stops with Compile failed and an Unexpected token message pointing at the const line.
When you do need a variable, put it inside a function body, where statements are allowed:
Syntax notes
classisclassName. Write<div className="my-card">, notclass.classcompiles and still reaches the page, but it isn't valid JSX and React warns about it in development builds. The same goes forfor— usehtmlFor.- Event names are camelCase.
onClick, notonclick. A lowercase handler is ignored, so the click does nothing. - Styles are objects with camelCase keys.
style={{ backgroundColor: "red", fontSize: "14px" }}, not a CSS string. A string such asstyle="color: red"compiles, but throws when the template renders — and the app falls back to the built-in design.
How your code runs
- In the admin, the compiler turns your JSX into plain
React.createElement(…)calls. The compiled code is saved with the builder; your original source stays in the admin. - On the storefront, only the compiled code is sent. The app turns it into a function and renders it with your
props.Reactis the only thing passed in, which is whyReact.Fragmentworks and nothing else from the app does. - If building or rendering the template throws, the app retires it for the rest of that page view and shows the built-in design instead.
Two things follow from this. A template can only lay out what the app gives it — that's what makes a mistake harmless, because a compile error is caught before anything reaches your store and a runtime error falls back to the built-in design. And the compiled code is readable by anyone who views your storefront, so keep anything private out of it. See Troubleshooting for what each kind of failure looks like and how to fix it.
Next
CSS variables and styling — keep your Design-tab settings working inside your custom markup.