For developers
Declare a setting once. The editor builds the field.
Every section on a Mirin website is a React component with a schema.json beside it. See how the schema becomes the fields owners edit, and which value reaches your component.
On this page
A component and its schema
A section on a Mirin website is a component and a schema. The component draws the section. The schema names it and lists every setting an owner may change, each with a type and a default.
Session Prices
3 items
Salt & Stone Pilates
Private sessions
Fifty five minutes with an instructor who knows your goals. Book one session or a series of five.

- Studio session55 minutes$95
- Duet session55 minutes$70 each
- Intro session45 minutes$60
{ "type": "session_prices", "displayName": "Session Prices", "category": "pricing", "description": "Heading, intro and photo beside a grid of sessions with prices.", "props": { "heading": { "type": "string", "displayName": "Heading", "default": "Private sessions" }, "intro": { "type": "textarea", "displayName": "Intro", "default": "Fifty five minutes with an instructor who knows your goals. Book one session or a series of five." }, "photo": { "type": "image", "displayName": "Photo", "default": { "url": "/images/home-next/salt-stone-pilates.webp", "alt": "Two people doing lunges on reformers in a bright studio with a window over a harbor." } }, "photoSide": { "type": "select", "displayName": "Photo side", "options": [ "Left", "Right" ], "default": "Right" }, "showPrices": { "type": "boolean", "displayName": "Show prices", "default": true }, "columns": { "type": "number", "displayName": "Columns", "min": 1, "max": 3, "step": 1, "default": 3 }, "accentColor": { "type": "color", "displayName": "Accent color", "default": "#3f5539" }, "button": { "type": "object", "displayName": "Button", "default": { "text": "Book a session", "link": "/book" }, "properties": { "text": { "type": "string", "displayName": "Text", "default": "Book a session" }, "link": { "type": "link", "displayName": "Link", "default": "/book" } } }, "sessions": { "type": "array", "displayName": "Sessions", "fields": [ { "name": "name", "type": "string", "displayName": "Name", "default": "New session" }, { "name": "length", "type": "string", "displayName": "Length", "default": "55 minutes" }, { "name": "price", "type": "string", "displayName": "Price", "default": "$0" } ], "default": [ { "name": "Studio session", "length": "55 minutes", "price": "$95" }, { "name": "Duet session", "length": "55 minutes", "price": "$70 each" }, { "name": "Intro session", "length": "45 minutes", "price": "$60" } ] } }}- The component
- A React component, exported as the default export of its file. Mirin renders it to HTML on the server, then hydrates it in the browser.
- The schema
- A schema.json file with the component’s type, displayName, category and description, and a props object. Each key under props is one setting.
- The settings panel
- The editor reads the schema and lists one row per setting, named by its displayName and marked by its type. Opening a row shows the field.
- The saved value
- What an owner enters is saved on the page under the setting’s key. Your component receives it as the prop with that name.
Write the component
Mirin renders React components. Export the component as the default export. Accept one props object whose keys match the keys under props in schema.json.
This is Session Prices, the section in the figure above. It reads every setting the schema declares, and nothing else.
type Photo = { url: string; alt: string };
type Session = { name: string; length: string; price: string };
type SessionPricesProps = {
heading?: string;
intro?: string;
photo?: Photo;
photoSide?: string;
showPrices?: boolean;
columns?: number;
accentColor?: string;
button?: { text: string; link: string };
sessions?: Session[];
};
export default function SessionPrices({
heading = '',
intro = '',
photo,
photoSide = 'Right',
showPrices = true,
columns = 3,
accentColor = '#3f5539',
button,
sessions = [],
}: SessionPricesProps) {
const image = photo?.url ? <img src={photo.url} alt={photo.alt} /> : null;
const list = Array.isArray(sessions) ? sessions : [];
return (
<section className="session-prices">
<div className="session-prices-hero">
{photoSide === 'Left' && image}
<div>
<h2>{heading}</h2>
{intro && <p>{intro}</p>}
{button?.text && (
<a href={button.link} style={{ background: accentColor }}>
{button.text}
</a>
)}
</div>
{photoSide !== 'Left' && image}
</div>
<ul style={{ display: 'grid', gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
{list.map((session, index) => (
<li key={index}>
<strong>{session.name}</strong>
<span>{session.length}</span>
{showPrices && <b>{session.price}</b>}
</li>
))}
</ul>
</section>
);
}{
"type": "session_prices",
"displayName": "Session Prices",
"category": "pricing",
"description": "Heading, intro and photo beside a grid of sessions with prices.",
"props": {
"heading": { "type": "string", "displayName": "Heading", "default": "Private sessions" },
"intro": { "type": "textarea", "displayName": "Intro", "default": "Fifty five minutes with an instructor who knows your goals." },
"photo": { "type": "image", "displayName": "Photo", "default": { "url": "", "alt": "" } },
"photoSide": { "type": "select", "displayName": "Photo side", "options": ["Left", "Right"], "default": "Right" },
"showPrices": { "type": "boolean", "displayName": "Show prices", "default": true },
"columns": { "type": "number", "displayName": "Columns", "min": 1, "max": 3, "step": 1, "default": 3 },
"accentColor": { "type": "color", "displayName": "Accent color", "default": "#3f5539" },
"button": {
"type": "object",
"displayName": "Button",
"default": { "text": "Book a session", "link": "/book" },
"properties": {
"text": { "type": "string", "displayName": "Text", "default": "Book a session" },
"link": { "type": "link", "displayName": "Link", "default": "/book" }
}
},
"sessions": {
"type": "array",
"displayName": "Sessions",
"fields": [
{ "name": "name", "type": "string", "displayName": "Name", "default": "New session" },
{ "name": "length", "type": "string", "displayName": "Length", "default": "55 minutes" },
{ "name": "price", "type": "string", "displayName": "Price", "default": "$0" }
],
"default": [
{ "name": "Studio session", "length": "55 minutes", "price": "$95" },
{ "name": "Intro session", "length": "45 minutes", "price": "$60" }
]
}
}
}Four habits that keep a component easy to live with
- Give every setting a default. A section added to a page starts with the defaults in the schema, so the first render already looks finished.
- Expect empty values. An owner can clear any text, image or list. Render nothing for an empty value instead of a broken element.
- Keep browser code in effects. The first render happens on the server, where window and document do not exist.
- Keep keys stable. Saved content follows the key. A renamed key leaves the old value behind, as Keep settings stable explains below.
Setting types
The type decides which field the editor draws, and the shape of the value your component receives.
- string
- A one line text box.
textdraws the same box. Arrives as a string. - textarea
- A text box over several lines, without formatting. Arrives as a string, line breaks included.
- richtext
- A formatting editor. Arrives as HTML when the page renders.
- number
- A number box with minus and plus buttons. The limits show underneath. Arrives as a number.
- boolean
- A switch. Arrives as true or false.
- color
- A swatch and a color code box that accepts hex or RGB. Arrives as the color the owner entered.
- image
- A preview, Image URL, Alt text and Browse Files. Arrives as an object with url and alt.
- link
- A text box that accepts a full URL or a path starting with /.
urldraws the same box. Arrives as a string. - select
- A dropdown of the options you list. Arrives as the chosen option.
- object
- The settings under
properties, one after another. Arrives as an object with those keys. - array
- A list of entries built from fields, with add, move and delete. Arrives as an array of objects.
- content_list
- Connect a Content List. Arrives as an array of list entries.
Give an image setting an object default
An image setting whose default is a plain URL string stays a string, and the editor hides Alt text for it. Keep an object default such as { "url": "", "alt": "" } so owners can describe the picture.
A rich text setting arrives as HTML, so render it as HTML. The editor draws a one line text box for any type it does not recognize. Content List settings have their own guide, Content Lists in code.
Lists and groups
Use array for anything an owner adds to, reorders or removes. Describe each entry with fields, a list of settings that each carry a name. Every entry is then an object with those names as keys.
Open a row to edit its Text field.
What to Bring
3 items
Before your first class
What to bring
- Grip socks
- A water bottle
- Layers you can move in
{ "props": { "whatToBring": { "type": "array", "displayName": "Checklist items", "fields": [ { "name": "text", "type": "string", "displayName": "Text", "default": "" } ], "default": [ { "text": "Grip socks" }, { "text": "A water bottle" }, { "text": "Layers you can move in" } ] } }}"whatToBring": {
"type": "array",
"displayName": "Checklist items",
"fields": [
{ "name": "text", "type": "string", "displayName": "Text", "default": "" }
],
"default": [
{ "text": "Grip socks" },
{ "text": "A water bottle" }
]
}A row takes its label from the entry’s title, text or name field. Use object to keep settings together, such as a button’s text and link, and list them under properties. An object without properties does not appear in the settings panel.
Store a list of words as objects
Write [{ "text": "Grip socks" }] with a field named text, never ["Grip socks"]. The editor does not read itemSchema, so an array described with it has no fields to edit.
Setting options
Besides its type, a setting takes these keys. The schema itself also carries type, displayName, category and description, which name the component in Design Library.
- displayName
- The name on the setting’s row and above its field. Without it the editor shows the key.
- description
- A line of help under the setting’s name.
- default
- The value a new section starts with. A new list entry starts with each field’s default.
- options
- The choices for
select. Write them as strings, or as objects with label and value. - min, max and step
- Limits for
number. A typed value is held between min and max, and step sets how far minus and plus move it. - fields and properties
- The settings inside each array entry, and the settings inside an object.
Keys the editor does not read
The editor ignores label, required and placeholder. Use displayName for the name, and a default for the starting text.
How designs appear in Design Library
Design Library lists the components on a website. Each design shows its displayName and description, with its category and component version underneath. A category such as pricing reads Pricing.
Design Library
ComponentsHero Banner
Pilates by the water
Strength you can feel from the first class.
Small reformer classes and private sessions in a bright studio with room to breathe.

Search matches a design’s name, description, category, version and type, and the displayName of every setting. Inspect design shows the fields in schema order with their types, and Details shows the type as the Design reference.
Keep settings stable
Each page stores its content by key. When a new release of a theme pack changes a component’s schema, Page updates compares every page that uses the component with the new schema.
- A key you removed or renamed
- Listed under Fields to remove, with the value the page still holds. Remove unused fields deletes it from the page.
- A key you added
- Listed under Fields to add, with its default. Add missing fields with defaults writes that default onto the page.
A rename therefore replaces what an owner typed with the new default. Keep the old key while its content still matters. Owners review these changes in Theme packs.