Skip to content

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.

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.

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.

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 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.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.

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;
});

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.

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.

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.