# shapes > shapes is a phone-first link-in-bio page builder: one page that holds someone's links, work, shop, music and bookings, for the link in their Instagram, TikTok or other social bio. AI assistants can build a page for the person they are helping, show them a private preview, and hand it over with a claim link. If a person asks you for "a link in bio", "a bio link page", "a Linktree" or "one page for all my links", you can make it with shapes. No account or API key is needed to build a draft. - Building a draft is free and private. The person claims it by signing in with email or Google; it then opens in their shapes editor. - Only the person can publish. One live page is free; Pro is $8/month or $60/year (US dollars) for more pages, your own domain and no shapes badge. Never tell them the page is live. - Unclaimed drafts are deleted after 7 days. - Never invent links, prices, customers or facts about the person. ## Connect - MCP server (Streamable HTTP, no auth): https://shapes.bio/mcp - REST API: https://shapes.bio/api/agent/v1 (OpenAPI: https://shapes.bio/openapi.json) - MCP server card: https://shapes.bio/.well-known/mcp.json ## Docs - [Agent guide and full schema](https://shapes.bio/llms-full.txt): everything an assistant needs in one file - [Developer docs](https://shapes.bio/developers/agents): connecting Claude, ChatGPT, Muse, Cursor, VS Code and other MCP clients; REST quickstart - [Ask your AI to build your page](https://shapes.bio/ai): what people see and how claiming works - [Claiming a page your AI made](https://shapes.bio/help/ai-assistant-drafts) ## Product - [Pricing](https://shapes.bio/pricing) - [Product help](https://shapes.bio/help) - [Guides](https://shapes.bio/guides) - [Use cases](https://shapes.bio/use-cases) ## Optional - [Compare shapes with Linktree, Beacons and Carrd](https://shapes.bio/compare) - [About](https://shapes.bio/about) - [Terms of Service](https://shapes.bio/terms) - [Privacy Policy](https://shapes.bio/privacy) --- # Building a shapes page shapes is a link-in-bio page builder. You can make a private draft page for the person you are helping. They see a real preview, then claim it into their own shapes account with one link. ## Building a page for someone - Ask only for what you cannot reasonably infer: their name, what they do and the links they want. If they gave you a handle, build the URL (https://instagram.com/handle). - Never invent facts: no made-up prices, customers, testimonials or links. If you lack a URL, ask for it rather than guessing. - Aim for 4-8 blocks. Lead with the one thing a visitor should do, then group the rest into a folder. - Pick the look that fits their world: `shapes` (Warm white, persimmon and an editorial point of view.), `paper` (Parchment and ink. Let the work do the talking.), `midnight` (Warm charcoal and soft apricot. Made for after hours.), `studio` (Oat green and forest. A fresh frame for good work.), `rose` (Dusty rose and plum. Personal, without the fuss.), `clay` (Cream and fired terracotta. For things made by hand.). - Images: only https image URLs the person gave you or from their own site. Leave an image empty rather than guessing. `starter: true` gives a complete example page with original artwork to edit. - When you are done, show the person the preview link and the claim link. The page is a private draft. It is not live until they sign in, choose a plan and press Publish themselves. - Custom HTML, CSS or JavaScript is not something you can add. Use links and the supported embeds; the person can add custom code themselves in the editor on Pro. - To change something, update the same draft with operations instead of creating a new one. ## How to design The page is read on a phone, in one thumb's reach, usually for ten seconds. Judge every addition by that. - Order matters most: a short, distinctive heading or name, then the one thing they most want a visitor to do, then everything else. Never bury the main link under sections. - Keep it short. Six to ten visible things on a page is plenty. If there are already twelve, suggest folding extras into a folder rather than adding more. - Folders: use them for groups that belong together (socials, shop items, past work, FAQ). "Tabs" holds folders only, and suits three to five short groups; it is the best way to keep a busy page calm. - Give at most one link `emphasis: "shine"` or `"pulse"`. More than one and nothing stands out. - Thumbnails and featured cards suit visual links (a video, a product, a portfolio piece). Plain links suit lists. - Themes: match the person's world. Warm paper for writers and makers, deep colours for music and nightlife, a single bold colour for a personal brand. Never pick a preset that makes text hard to read; if you change the background to something dark, set `text` light, and vice versa. - If the page has a photo of the person, keep the theme quiet around it. ## Writing copy - Titles are verbs or nouns a person would say out loud: "Book a table", "New single", "Work with me". Not "Click here", not "Link 1". - Use the person's own words from what they tell you. Fix capitalisation; don't rewrite their voice into marketing speak. - Descriptions are optional and one line. Leave them out unless they add something. ## The page document - profile fields {name:text, bio:rich, avatar:image, cover:image, layout:select (classic|hero|banner), avatarShape:select (circle|rounded|square), bannerAvatar:bool, align:select (center|left), verified:bool, socialsPosition:select (top|bottom)} plus `socials` [{platform, url}] which replaces the whole list when given. - theme fields {preset:text, bgType:select (solid|gradient|mesh|image), bg:color, bg2:color, bg3:color, bgAngle:number, bgImage:image, bgDim:number, bgBlur:number, grain:bool, animate:bool, text:color, button:color, buttonText:color, buttonStyle:select (fill|soft|outline|glass|shadow|hard), buttonShape:select (rounded|pill|square|soft), headingFont:select (inter|dm-sans|manrope|plus-jakarta|outfit|space-grotesk|bricolage|syne|unbounded|archivo-black|fraunces|playfair|instrument-serif|dm-serif|lora|cormorant|space-mono|ibm-plex-mono|caveat|pacifico), bodyFont:select (inter|dm-sans|manrope|plus-jakarta|outfit|space-grotesk|bricolage|syne|unbounded|archivo-black|fraunces|playfair|instrument-serif|dm-serif|lora|cormorant|space-mono|ibm-plex-mono|caveat|pacifico), width:select (normal|wide)} - meta: page title and description for search results and link previews. - Blocks are a single list, in order, with folders nesting up to 3 levels. ## Blocks - link: A tappable button that opens a URL. The bread and butter of the page. props {title:text, url:url, subtitle:text, thumbnail:image, layout:select (classic|thumb|featured), emphasis:select (none|pulse|shine|wobble), emphasisPace:select (calm|steady|constant)} - heading: A short section title. props {text:text, size:select (md|sm|lg), align:select (center|left)} - text: A paragraph of text. props {text:rich, align:select (center|left), card:bool} - divider: Visual separation between groups. props {style:select (line|dots|space)} - folder: Groups blocks. display: stack = expands in place, sheet = opens a panel, grid = tiles, carousel = swipeable row. Inside tabs, a folder is one tab. Container. props {title:text, subtitle:text, thumbnail:image, display:select (stack|sheet|grid|carousel), open:bool} - tabs: Horizontal tabs. Each child folder is one tab; its title is the tab label. Container (children: folder only). props {style:select (pills|underline|segmented)} - image: A single image, optionally linked. props {src:image, alt:text, url:url, aspect:select (auto|square|portrait|landscape), caption:text} - gallery: Several images in a grid or swipeable carousel. props {images:list [list of {src, alt, url}], layout:select (grid|carousel), columns:select (2|3)} - posts: Square posts like an Instagram grid, each one opening the link from that post. Use it so a visitor who saw a post can find it and tap through. props {layout:select (grid|pinned|carousel), rowShape:select (portrait|square), align:select (center|left|right), pinnedTitle:text, gridTitle:text, columns:select (3|2), items:list [list of {src, url, label, alt, pinned}]} - video: An embedded YouTube, Vimeo or TikTok video. props {url:url, title:text} - music: An embedded Spotify, Apple Music or SoundCloud player. props {url:url, compact:bool} - embed: An embedded booking, form, media or checkout widget from a supported provider (Calendly, Cal.com, Typeform, Tally, Google Forms, Loom, Bandcamp, Figma, Airtable, Notion, Gumroad, Stripe Buy Button). Paste the provider's own embed code or its https address. props {url:url, height:number, provider:text, kind:text, src:text, buyButtonId:text, title:text, fallbackUrl:url} - map: A map of an address or place. props {query:text, title:text, zoom:number} - countdown: Counts down to a date (a launch, drop, show or sale). props {title:text, target:datetime, endedText:text, url:url} - email: Collects email addresses from visitors. props {title:text, description:text, button:text, success:text, collectName:bool} - product: Something for sale with a price and a buy button. props {title:text, price:text, description:text, image:image, url:url, button:text, badge:text} - events: A list of dates: tour dates, workshops, pop-ups. props {title:text, items:list [list of {date, name, place, url, soldOut}], hidePast:bool} - faq: Questions and answers that expand when tapped. props {items:list [list of {q, a}]} - quote: A testimonial or pull quote. props {text:text, author:text} - contact: A Save contact button that downloads a vCard. props {name:text, org:text, role:text, phone:text, email:text, website:url, button:text} ## Operations (the `ops` argument of update_page_draft) All ids are 10-character strings you get from the page you were given; never invent an id except via `ref`. - {"op":"add","block":{"type":"link","props":{...},"children":[...]},"parentId":"","index":0,"afterId":""} — `ref` inside a block gives it a name other ops in the same call can use as an id. - {"op":"update","id":"...","props":{...}} — only the props you send change. Also accepts "hidden": true/false and "schedule": {"start":"2026-05-01T18:00","end":""}. - {"op":"move","id":"...","parentId":"...","index":0} - {"op":"remove","id":"..."} - {"op":"duplicate","id":"..."} - {"op":"wrap","ids":["...","..."],"props":{"title":"Shop"}} — puts neighbouring blocks into a new folder. - {"op":"unwrap","id":""} - {"op":"setProfile","patch":{"name":"...","bio":"...","socials":[...]}} - {"op":"setTheme","patch":{"bgType":"gradient","bg":"#0c4a6e","text":"#e0f2fe","buttonShape":"pill"}} - {"op":"applyPreset","preset":"midnight"} — presets: shapes, paper, midnight, studio, rose, clay - {"op":"setMeta","patch":{"title":"...","description":"..."}} ## Shapes you can send to create a draft - Simple: {"name":"Ada Rivers","bio":"Ceramics, made slowly.","preset":"clay","socials":[{"platform":"instagram","url":"https://instagram.com/ada"}],"links":[{"title":"Shop the new batch","url":"https://ada.example","layout":"featured"},{"title":"Commissions","url":"mailto:ada@example.com"}]} - Blocks: {"name":"…","preset":"midnight","blocks":[{"type":"heading","props":{"text":"New single"}},{"type":"folder","props":{"title":"Tour"},"children":[{"type":"link","props":{"title":"Berlin","url":"https://…"}}]}]} - Starter: {"name":"…","preset":"studio","starter":true} gives the curated example page, which you then change with operations. - Every shape also accepts "ops" (applied last), "meta" and "suggestedHandle". ## Tools on the MCP server (https://shapes.bio/mcp) - `get_page_guide`: Design rules, block catalogue and operations. Read once before building. - `list_looks`: The six curated looks a page can start from. - `check_handle`: Check whether name.shapes.bio is free (does not reserve it). - `create_page_draft`: Build a private page; returns a preview link, a claim link and an edit token. - `update_page_draft`: Change the same draft with operations. - `get_page_draft`: Read the current draft, including block ids. ## REST API (https://shapes.bio/api/agent/v1) - `POST /drafts` with the same body as create_page_draft. Returns `draftId`, `editToken`, `previewUrl`, `claimUrl`, `expiresAt`, `nextSteps`. - `GET /drafts/{draftId}` with `Authorization: Bearer `. - `PATCH /drafts/{draftId}` with `Authorization: Bearer ` and `{"ops":[…],"baseRevision":n}`. - `GET /schema`, `GET /looks`, `GET /handles/{handle}`, `GET /guide`. - Errors are JSON `{error, code, opIndex?}` with a sentence that says what to fix. - Limits: 300 blocks, folders 2 levels deep, 12 remote images, 20 new drafts an hour per address. ### Example ```sh curl -s https://shapes.bio/api/agent/v1/drafts -H 'Content-Type: application/json' -d '{ "name": "Ada Rivers", "bio": "Ceramics, made slowly.", "preset": "clay", "socials": [{"platform": "instagram", "url": "https://instagram.com/ada"}], "links": [ {"title": "Shop the new batch", "url": "https://ada.example/shop", "layout": "featured"}, {"title": "Commissions", "url": "mailto:ada@example.com"} ] }' ``` ## Schema ```json { "version": 1, "limits": { "maxBlocks": 300, "maxDepth": 3, "maxRemoteImages": 12, "draftTtlDays": 7 }, "presets": [ "shapes", "paper", "midnight", "studio", "rose", "clay" ], "socialPlatforms": [ "instagram", "tiktok", "youtube", "x", "threads", "bluesky", "facebook", "linkedin", "spotify", "applemusic", "soundcloud", "twitch", "discord", "github", "pinterest", "snapchat", "whatsapp", "telegram", "patreon", "substack", "mastodon", "etsy", "cashapp", "venmo", "paypal", "email", "phone", "website" ], "operations": [ "add", "update", "move", "remove", "duplicate", "wrap", "unwrap", "setProfile", "setTheme", "setMeta", "applyPreset" ], "profile": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 80, "description": "Name" }, "bio": { "type": "string", "maxLength": 400, "description": "Bio. Supports **bold**, *italic* and [links](https://…)" }, "avatar": { "type": "string", "description": "Profile picture. Image URL (https) or empty" }, "cover": { "type": "string", "description": "Cover image. Only shown with the banner layout. Image URL (https) or empty" }, "layout": { "type": "string", "enum": [ "classic", "hero", "banner" ], "description": "Header layout" }, "avatarShape": { "type": "string", "enum": [ "circle", "rounded", "square" ], "description": "Photo shape" }, "bannerAvatar": { "type": "boolean", "description": "Show your photo over the banner. Your photo appears in front of the cover image. Turn it off to show only the cover image. Applies to the banner layout." }, "align": { "type": "string", "enum": [ "center", "left" ], "description": "Alignment" }, "verified": { "type": "boolean", "description": "Show a check mark" }, "socialsPosition": { "type": "string", "enum": [ "top", "bottom" ], "description": "Icon position" }, "socials": { "type": "array", "description": "Social links. Replaces the whole list when given. Omit label or leave it empty for an icon only.", "items": { "type": "object", "properties": { "platform": { "type": "string", "enum": [ "instagram", "tiktok", "youtube", "x", "threads", "bluesky", "facebook", "linkedin", "spotify", "applemusic", "soundcloud", "twitch", "discord", "github", "pinterest", "snapchat", "whatsapp", "telegram", "patreon", "substack", "mastodon", "etsy", "cashapp", "venmo", "paypal", "email", "phone", "website" ] }, "url": { "type": "string" }, "label": { "type": "string", "maxLength": 80, "description": "Optional visible call to action beside the icon, e.g. Say hi." } }, "required": [ "platform", "url" ] } } }, "additionalProperties": false }, "theme": { "type": "object", "properties": { "preset": { "type": "string", "maxLength": 40, "description": "Preset" }, "bgType": { "type": "string", "enum": [ "solid", "gradient", "mesh", "image" ], "description": "Background" }, "bg": { "type": "string", "description": "Background colour. Hex colour" }, "bg2": { "type": "string", "description": "Second colour. Hex colour" }, "bg3": { "type": "string", "description": "Third colour. Hex colour" }, "bgAngle": { "type": "number", "minimum": 0, "maximum": 360, "description": "Angle" }, "bgImage": { "type": "string", "description": "Background photo. Image URL (https) or empty" }, "bgDim": { "type": "number", "minimum": 0, "maximum": 90, "description": "Darken photo" }, "bgBlur": { "type": "number", "minimum": 0, "maximum": 40, "description": "Blur photo" }, "grain": { "type": "boolean", "description": "Film grain" }, "animate": { "type": "boolean", "description": "Slowly move the background" }, "text": { "type": "string", "description": "Text. Hex colour" }, "button": { "type": "string", "description": "Button. Hex colour" }, "buttonText": { "type": "string", "description": "Button text. Hex colour" }, "buttonStyle": { "type": "string", "enum": [ "fill", "soft", "outline", "glass", "shadow", "hard" ], "description": "Button style" }, "buttonShape": { "type": "string", "enum": [ "rounded", "pill", "square", "soft" ], "description": "Button shape" }, "headingFont": { "type": "string", "enum": [ "inter", "dm-sans", "manrope", "plus-jakarta", "outfit", "space-grotesk", "bricolage", "syne", "unbounded", "archivo-black", "fraunces", "playfair", "instrument-serif", "dm-serif", "lora", "cormorant", "space-mono", "ibm-plex-mono", "caveat", "pacifico" ], "description": "Heading font" }, "bodyFont": { "type": "string", "enum": [ "inter", "dm-sans", "manrope", "plus-jakarta", "outfit", "space-grotesk", "bricolage", "syne", "unbounded", "archivo-black", "fraunces", "playfair", "instrument-serif", "dm-serif", "lora", "cormorant", "space-mono", "ibm-plex-mono", "caveat", "pacifico" ], "description": "Body font" }, "width": { "type": "string", "enum": [ "normal", "wide" ], "description": "Width" } }, "additionalProperties": false }, "meta": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 70, "description": "Page title. Browser tab and search result title" }, "description": { "type": "string", "maxLength": 200, "description": "Description. Shown in search results and link previews" }, "image": { "type": "string", "description": "Share image. Image URL (https) or empty" } }, "additionalProperties": false }, "blocks": { "link": { "label": "Link", "description": "A tappable button that opens a URL. The bread and butter of the page.", "container": false, "props": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 120, "description": "Title" }, "url": { "type": "string", "description": "Address. https://, mailto: or tel: link" }, "subtitle": { "type": "string", "maxLength": 160, "description": "Subtitle. Optional second line" }, "thumbnail": { "type": "string", "description": "Thumbnail. Image URL (https) or empty" }, "layout": { "type": "string", "enum": [ "classic", "thumb", "featured" ], "description": "Layout. featured shows the thumbnail large above the title" }, "emphasis": { "type": "string", "enum": [ "none", "pulse", "shine", "wobble" ], "description": "Attention. Subtle animation to draw the eye; use on at most one link" }, "emphasisPace": { "type": "string", "enum": [ "calm", "steady", "constant" ], "description": "How often. How often the attention animation plays: calm (every few seconds), steady (about every second), constant (never rests). Only matters when attention is not none" } }, "additionalProperties": false } }, "heading": { "label": "Heading", "description": "A short section title.", "container": false, "props": { "type": "object", "properties": { "text": { "type": "string", "maxLength": 120, "description": "Text" }, "size": { "type": "string", "enum": [ "md", "sm", "lg" ], "description": "Size" }, "align": { "type": "string", "enum": [ "center", "left" ], "description": "Alignment" } }, "additionalProperties": false } }, "text": { "label": "Text", "description": "A paragraph of text.", "container": false, "props": { "type": "object", "properties": { "text": { "type": "string", "maxLength": 2000, "description": "Text. Supports **bold**, *italic* and [links](https://…)" }, "align": { "type": "string", "enum": [ "center", "left" ], "description": "Alignment" }, "card": { "type": "boolean", "description": "Show on a card" } }, "additionalProperties": false } }, "divider": { "label": "Divider", "description": "Visual separation between groups.", "container": false, "props": { "type": "object", "properties": { "style": { "type": "string", "enum": [ "line", "dots", "space" ], "description": "Style" } }, "additionalProperties": false } }, "folder": { "label": "Folder", "description": "Groups blocks. display: stack = expands in place, sheet = opens a panel, grid = tiles, carousel = swipeable row. Inside tabs, a folder is one tab.", "container": true, "props": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 80, "description": "Title" }, "subtitle": { "type": "string", "maxLength": 120, "description": "Subtitle" }, "thumbnail": { "type": "string", "description": "Cover image. Image URL (https) or empty" }, "display": { "type": "string", "enum": [ "stack", "sheet", "grid", "carousel" ], "description": "How it opens" }, "open": { "type": "boolean", "description": "Start open. Only for stack display" } }, "additionalProperties": false } }, "tabs": { "label": "Tabs", "description": "Horizontal tabs. Each child folder is one tab; its title is the tab label.", "container": true, "accepts": [ "folder" ], "props": { "type": "object", "properties": { "style": { "type": "string", "enum": [ "pills", "underline", "segmented" ], "description": "Tab style" } }, "additionalProperties": false } }, "image": { "label": "Image", "description": "A single image, optionally linked.", "container": false, "props": { "type": "object", "properties": { "src": { "type": "string", "description": "Image. Image URL (https) or empty" }, "alt": { "type": "string", "maxLength": 200, "description": "Description. Alt text for screen readers" }, "url": { "type": "string", "description": "Link (optional). https://, mailto: or tel: link" }, "aspect": { "type": "string", "enum": [ "auto", "square", "portrait", "landscape" ], "description": "Shape" }, "caption": { "type": "string", "maxLength": 160, "description": "Caption" } }, "additionalProperties": false } }, "gallery": { "label": "Gallery", "description": "Several images in a grid or swipeable carousel.", "container": false, "props": { "type": "object", "properties": { "images": { "type": "array", "maxItems": 24, "items": { "type": "object", "properties": { "src": { "type": "string", "description": "Image. Image URL (https) or empty" }, "alt": { "type": "string", "maxLength": 200, "description": "Description" }, "url": { "type": "string", "description": "Link. https://, mailto: or tel: link" } }, "additionalProperties": false }, "description": "Images" }, "layout": { "type": "string", "enum": [ "grid", "carousel" ], "description": "Layout" }, "columns": { "type": "string", "enum": [ "2", "3" ], "description": "Columns" } }, "additionalProperties": false } }, "posts": { "label": "Post grid", "description": "Square posts like an Instagram grid, each one opening the link from that post. Use it so a visitor who saw a post can find it and tap through.", "container": false, "props": { "type": "object", "properties": { "layout": { "type": "string", "enum": [ "grid", "pinned", "carousel" ], "description": "Layout" }, "rowShape": { "type": "string", "enum": [ "portrait", "square" ], "description": "Row shape. Shape of the photos in the pinned or swipeable row" }, "align": { "type": "string", "enum": [ "center", "left", "right" ], "description": "Title alignment. Where the section titles sit" }, "pinnedTitle": { "type": "string", "maxLength": 60, "description": "Pinned title. Title above the pinned row, like Pinned or Shop the drop" }, "gridTitle": { "type": "string", "maxLength": 60, "description": "Grid title. Title above the grid, below the pinned row, like Recent posts" }, "columns": { "type": "string", "enum": [ "3", "2" ], "description": "Columns" }, "items": { "type": "array", "maxItems": 30, "items": { "type": "object", "properties": { "src": { "type": "string", "description": "Photo. Image URL (https) or empty" }, "url": { "type": "string", "description": "Link. Where this post sends a visitor. https://, mailto: or tel: link" }, "label": { "type": "string", "maxLength": 24, "description": "Label. Short text on the photo, like Shop or $42" }, "alt": { "type": "string", "maxLength": 160, "description": "Description. Alt text for screen readers" }, "pinned": { "type": "boolean", "description": "Pin to the top" } }, "additionalProperties": false }, "description": "Posts" } }, "additionalProperties": false } }, "video": { "label": "Video", "description": "An embedded YouTube, Vimeo or TikTok video.", "container": false, "props": { "type": "object", "properties": { "url": { "type": "string", "description": "Video address. YouTube, Vimeo or TikTok URL. https://, mailto: or tel: link" }, "title": { "type": "string", "maxLength": 120, "description": "Title" } }, "additionalProperties": false } }, "music": { "label": "Music", "description": "An embedded Spotify, Apple Music or SoundCloud player.", "container": false, "props": { "type": "object", "properties": { "url": { "type": "string", "description": "Track, album or playlist. Spotify, Apple Music or SoundCloud URL. https://, mailto: or tel: link" }, "compact": { "type": "boolean", "description": "Compact player" } }, "additionalProperties": false } }, "embed": { "label": "Embed", "description": "An embedded booking, form, media or checkout widget from a supported provider (Calendly, Cal.com, Typeform, Tally, Google Forms, Loom, Bandcamp, Figma, Airtable, Notion, Gumroad, Stripe Buy Button). Paste the provider's own embed code or its https address.", "container": false, "props": { "type": "object", "properties": { "url": { "type": "string", "description": "Address. https://, mailto: or tel: link" }, "height": { "type": "number", "minimum": 160, "maximum": 900, "description": "Height" }, "provider": { "type": "string", "maxLength": 40, "description": "Provider" }, "kind": { "type": "string", "maxLength": 16, "description": "Kind" }, "src": { "type": "string", "maxLength": 2048, "description": "Widget address" }, "buyButtonId": { "type": "string", "maxLength": 200, "description": "Buy button id" }, "title": { "type": "string", "maxLength": 120, "description": "Title" }, "fallbackUrl": { "type": "string", "description": "Fallback link. https://, mailto: or tel: link" } }, "additionalProperties": false } }, "map": { "label": "Map", "description": "A map of an address or place.", "container": false, "props": { "type": "object", "properties": { "query": { "type": "string", "maxLength": 200, "description": "Address or place" }, "title": { "type": "string", "maxLength": 120, "description": "Title" }, "zoom": { "type": "number", "minimum": 3, "maximum": 20, "description": "Zoom" } }, "additionalProperties": false } }, "countdown": { "label": "Countdown", "description": "Counts down to a date (a launch, drop, show or sale).", "container": false, "props": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 120, "description": "Title" }, "target": { "type": "string", "description": "Counts down to. ISO date-time, e.g. 2026-12-31T18:00" }, "endedText": { "type": "string", "maxLength": 120, "description": "When it ends" }, "url": { "type": "string", "description": "Link (optional). https://, mailto: or tel: link" } }, "additionalProperties": false } }, "email": { "label": "Email signup", "description": "Collects email addresses from visitors.", "container": false, "props": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 120, "description": "Title" }, "description": { "type": "string", "maxLength": 240, "description": "Description" }, "button": { "type": "string", "maxLength": 40, "description": "Button" }, "success": { "type": "string", "maxLength": 160, "description": "Thank-you message" }, "collectName": { "type": "boolean", "description": "Ask for a name" } }, "additionalProperties": false } }, "product": { "label": "Product", "description": "Something for sale with a price and a buy button.", "container": false, "props": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 120, "description": "Name" }, "price": { "type": "string", "maxLength": 30, "description": "Price. Display text including currency, e.g. $25" }, "description": { "type": "string", "maxLength": 300, "description": "Description" }, "image": { "type": "string", "description": "Image. Image URL (https) or empty" }, "url": { "type": "string", "description": "Buy link. https://, mailto: or tel: link" }, "button": { "type": "string", "maxLength": 40, "description": "Button" }, "badge": { "type": "string", "maxLength": 24, "description": "Badge. Short label like New or 20% off" } }, "additionalProperties": false } }, "events": { "label": "Events", "description": "A list of dates: tour dates, workshops, pop-ups.", "container": false, "props": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 120, "description": "Title" }, "items": { "type": "array", "maxItems": 40, "items": { "type": "object", "properties": { "date": { "type": "string", "description": "Date. ISO date-time, e.g. 2026-12-31T18:00" }, "name": { "type": "string", "maxLength": 120, "description": "Event" }, "place": { "type": "string", "maxLength": 120, "description": "Venue or city" }, "url": { "type": "string", "description": "Tickets link. https://, mailto: or tel: link" }, "soldOut": { "type": "boolean", "description": "Sold out" } }, "additionalProperties": false }, "description": "Events" }, "hidePast": { "type": "boolean", "description": "Hide past events" } }, "additionalProperties": false } }, "faq": { "label": "FAQ", "description": "Questions and answers that expand when tapped.", "container": false, "props": { "type": "object", "properties": { "items": { "type": "array", "maxItems": 20, "items": { "type": "object", "properties": { "q": { "type": "string", "maxLength": 200, "description": "Question" }, "a": { "type": "string", "maxLength": 1200, "description": "Answer. Supports **bold**, *italic* and [links](https://…)" } }, "additionalProperties": false }, "description": "Questions" } }, "additionalProperties": false } }, "quote": { "label": "Quote", "description": "A testimonial or pull quote.", "container": false, "props": { "type": "object", "properties": { "text": { "type": "string", "maxLength": 400, "description": "Quote" }, "author": { "type": "string", "maxLength": 120, "description": "Who said it" } }, "additionalProperties": false } }, "contact": { "label": "Contact card", "description": "A Save contact button that downloads a vCard.", "container": false, "props": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 120, "description": "Name" }, "org": { "type": "string", "maxLength": 120, "description": "Company" }, "role": { "type": "string", "maxLength": 120, "description": "Title" }, "phone": { "type": "string", "maxLength": 40, "description": "Phone" }, "email": { "type": "string", "maxLength": 200, "description": "Email" }, "website": { "type": "string", "description": "Website. https://, mailto: or tel: link" }, "button": { "type": "string", "maxLength": 40, "description": "Button" } }, "additionalProperties": false } } } } ```