Shop template reference
This reference describes template format version 1. Use it with the walkthrough. Bottle has no server-side template tags: layout and interactions use standard HTML, CSS and JavaScript.
Manifest fields
Section titled “Manifest fields”Unknown manifest fields are rejected. Field names and values are case-sensitive.
| Field | Type and allowed values | Required or default |
|---|---|---|
format |
String, exactly bottle-template |
Required |
schemaVersion |
Number, exactly 1 |
Required |
name |
Non-empty trimmed string, maximum 80 characters | Required |
description |
Trimmed string, maximum 240 characters | Empty string if omitted |
pages.home |
Exactly index.html |
Required |
pages.products |
Relative .html filename |
Optional; native catalogue if omitted |
pages.category |
Relative .html filename |
Optional; native category layout if omitted |
pages.page |
Relative .html filename |
Optional; native custom content pages if omitted |
styles |
Array of up to four unique .css filenames |
Empty array if omitted |
scripts |
Array of up to four unique .js filenames |
Empty array if omitted |
Every referenced file must exist. Styles and scripts load in array order. The same HTML file may serve several layouts. There are no theme, template, plugins, dependencies, custom manifest settings or per-page script fields in this format. Your own design constants belong in CSS or JavaScript.
Paths are at most 100 characters, lowercase, and contain letters, digits, hyphens or underscores. Up to three directory segments may precede the filename. Supported extensions are .html, .css, .js, .png, .jpg, .jpeg and .webp. The root manifest is the only supported JSON file. Absolute paths, spaces, dot-prefixed files, .., URL references and query strings are not accepted.
Public data shape
Section titled “Public data shape”Bottle.ready(callback) calls your callback with a read-only snapshot of public shop data. This is not the authenticated Bottle API. Treat it as immutable; make your own arrays when sorting or filtering.
type ShopData = { shop: { name: string; phone: string; email: string; currency: 'GBP' }; products: Product[]; categories: { slug: string; name: string; path: string }[]; navigation: { label: string; path: string; children: { label: string; path: string }[]; }[]; page: { type: 'home' | 'products' | 'category' | 'page'; path: string; title: string; categorySlug: string | null; html: string | null; }; links: { home: '/'; products: '/products'; basket: '/basket'; account: '/account/sign-in' }; catalogueTruncated: boolean;};
type Product = { id: string; name: string; path: string; description: string | null; pricePence: number; availability: 'in_stock' | 'low' | 'out' | 'backorder'; kind: | 'cylinder' | 'deposit' | 'hire' | 'accessory' | 'appliance' | 'fuel' | 'consumable' | 'delivery' | 'other'; categorySlugs: string[]; images: { src: string; alt: string; width: number; height: number }[];};This TypeScript block is a reference, not an uploadable .ts file. Uploaded scripts must be finished browser JavaScript.
Shop and product fields
Section titled “Shop and product fields”| Field | Meaning and handling |
|---|---|
shop.name |
Public business name, up to 200 characters |
shop.phone, shop.email |
Public contact text; may be empty. They do not make tel: or mailto: navigation supported |
shop.currency |
Currently GBP; use with Intl.NumberFormat |
product.id |
Public identifier; useful as a render key, not permission to call private APIs |
product.name |
Product name, up to 200 characters; render as text |
product.path |
Bottle-generated path to the protected product page; use directly |
product.description |
Plain text up to 1,000 characters, or null; not rich HTML |
product.pricePence |
Public product price in integer pence, in the shop’s public VAT basis. It is not a final quote: delivery, cylinder charges and account-specific pricing are handled on Bottle’s pages |
product.availability |
Stock state, not an exact count or promised delivery date |
product.kind |
Product classification; use friendly labels if displaying it |
product.categorySlugs |
Up to 20 category slugs; use for client-side presentation |
product.images |
Zero to four public pictures. src is an approved absolute URL; use unchanged. alt is supplied text; dimensions help reserve image space |
Availability labels can be In stock, Low stock, Out of stock and Available to order. Do not translate backorder into “Next-day delivery”: no delivery date is supplied. Do not hide products solely because they are out of stock unless that is a deliberate design decision.
The shared public product contract also permits deposit, hire and delivery charge kinds. These are normally handled separately from saleable catalogue products; if displaying a kind, handle every allowed value rather than assuming all items are cylinders. None of these values grants a template permission to calculate or collect the charges itself.
Categories and navigation
Section titled “Categories and navigation”Categories have public slugs, names of up to 160 characters and supplied shop paths. The interface includes at most 100 categories.
navigation reflects supported links from the shop’s configured menu, including one level of children. External, unpublished or unsupported links are not exposed. Build your menu from these entries if you want it to follow the shop editor. Do not assume every entry has children or that a menu is non-empty.
Page context
Section titled “Page context”page.type |
What products contains |
Content |
|---|---|---|
home |
Up to 500 public products | Build your own homepage |
products |
Up to 500 public products | Build your catalogue |
category |
Up to 500 products in page.categorySlug |
Filtering happens before the limit |
page |
Public products, if needed by your layout | page.title and sanitised text-section page.html |
page.html is at most 32 KB and contains published text/text-with-picture section text, not the full section-editor layout. It may be null or empty. Picture, gallery and product widgets are not reconstructed. Omit pages.page if you want Bottle to render those sections unchanged.
catalogueTruncated is true when product or category limits leave out data. There is no pagination or extra-data fetch interface in version 1. Show a clear notice and direct shoppers to narrower categories. Search and sort only the supplied snapshot; do not label them a complete search of a truncated catalogue.
Browser methods
Section titled “Browser methods”Bottle.ready(callback)
Section titled “Bottle.ready(callback)”Register a function receiving ShopData. Initialise your DOM and event handlers inside it. There is no Promise return contract; the method and its snapshot do not give you a session, API key, private customer data or a basket.
Bottle.ready(function (data) { document.getElementById('shop-name').textContent = data.shop.name;});Bottle.navigate(path)
Section titled “Bottle.navigate(path)”Ask the trusted shop page to open an approved path. Use supplied product, category, navigation or links paths. Return values are not an API response; do not rely on a success result. Unknown paths are ignored.
Bottle.ready(function (data) { document.getElementById('basket-link').addEventListener('click', function (event) { event.preventDefault(); Bottle.navigate(data.links.basket); });});Known built-in destinations include /, /products, /basket, /account/sign-in, /delivery, /about, /contact, /help, /privacy, /terms, /cookies and /blog. Actual product, category and published custom-page paths are also supported. Arbitrary external URLs, API routes, query strings and account subroutes are not supported. Do not use window.location, parent, top or normal self-navigation to bypass the interface.
Same-document anchors such as href="#products" are normal HTML: leave their browser behaviour intact rather than passing them to Bottle.navigate.
HTML and CSS support
Section titled “HTML and CSS support”Use semantic HTML. The parser rejects script/style/link/meta/base tags, frames, embeds, SVG/MathML, HTML template tags and data-entry forms. Inputs may be search, radio or checkbox; other input types, textarea, select and option are rejected. For sort/filter choices, use labelled buttons or radio inputs instead of a select.
Inline event handlers, style, srcset, action, formaction, ping, srcdoc and background attributes are rejected. Static image src values must reference bundled JPEG/PNG/WebP files. Static href values support simple internal paths and anchors, not external URLs. A syntactically accepted path still needs to be an actual known shop destination when passed to the interface.
Use system fonts. Supported CSS at-rules are @media, @supports, @container, @keyframes, @-webkit-keyframes, @layer and @starting-style. @import, @font-face, remote resources, CSS escapes, image-set, executable expressions and ambiguous resource URLs are rejected. CSS url(...) may reference bundled supported pictures with safe relative paths. Do not use ../ references.
Scripts run as classic browser scripts in manifest order, not ES modules. No import from a CDN, eval, new Function, remote library, fetch, worker or server execution is available. If you bundle a framework, its output must still respect these rules and the size limits; avoid tools requiring remote chunks or runtime code generation.
State, safety and limits
Section titled “State, safety and limits”The template has no persistent browser storage. Keep temporary search/filter state in JavaScript variables. Navigation may create a new frame and reset it. Never collect passwords, addresses or payment details in a template. Use Bottle’s supported pages instead.
The parent automatically measures template height. Use normal document flow; do not turn the whole page into a fixed-height scroll container or force height: 100vh on it. See the safety and size limits and troubleshooting before shipping.