> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liteshop.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Catalogue

> Declare products and services and accept Visitor requests the Owner confirms. Payment happens outside Liteshop.

A catalogue lets Visitors request a product or a service from the Site. Each
request names one declared item, an optional quantity, contact details and an
optional message. The Owner confirms or declines it in the **Orders**
dashboard and receives an email with a private dashboard link; the email
contains no Visitor details. Liteshop takes no payment: the Owner arranges
payment and delivery directly with the customer.

The Owner can also add, edit, and remove items and collections, and track
stock, on the **Catalogue** page of the dashboard. Those edits publish a new
Deployment without a developer. Render the catalogue from
`liteshop.catalogue.list()`, so that new items appear on the Site without a
rebuild.

> [!WARNING]
>
> An accepted request is **not** a confirmed order or appointment, and there is
> no cart, online payment or time-slot scheduling. Show success
> only as "Request sent. We'll contact you to confirm." Don't promise a price,
> delivery date, or appointment time on the Owner's behalf.

## Declare the catalogue

Add `capabilities.catalogue` with the items Visitors can request and the
contact fields every request collects. Item IDs are stable lowercase kebab
case. `kind` is `product` or `service`. `maximumQuantity` (1 to 100) is the
largest quantity a Visitor may request at once and defaults to 1. Optional
`description` (up to 2,000 characters per language), `price` (an integer
`amount` in minor units, such as cents, and an ISO 4217 `currency`) and
`images` (up to 8 PNG, JPEG, WebP or AVIF files in the bundle, main image
first) are shown by the Site; the price is display only. A single `image` path
is also accepted, but not together with `images`. Optional `collections` group
items under a name, in order. Owners can also add and remove an item's images
on the **Catalogue** page, which stores the files under `assets/` and publishes
the change, so render images from the listed item rather than fixed paths.

```json theme={null}
{
  "$schema": "https://docs.liteshop.me/schemas/site-v0.1.json",
  "specVersion": "0.1",
  "site": {
    "name": "Example Studio",
    "defaultLocale": "en",
    "supportedLocales": ["en", "fr"],
    "timezone": "Europe/Paris"
  },
  "frontend": { "entry": "index.html" },
  "capabilities": {
    "catalogue": {
      "items": [
        {
          "id": "massage-oil",
          "kind": "product",
          "name": { "en": "Massage oil", "fr": "Huile de massage" },
          "description": { "en": "Calendula and sweet almond.", "fr": "Calendula et amande douce." },
          "price": { "amount": 2400, "currency": "EUR" },
          "maximumQuantity": 5
        },
        {
          "id": "prenatal-session",
          "kind": "service",
          "name": { "en": "Prenatal session", "fr": "Séance prénatale" }
        }
      ],
      "collections": [
        { "id": "care", "name": { "en": "Care", "fr": "Soins" }, "items": ["massage-oil"] }
      ],
      "requestFields": [
        { "id": "name", "type": "text", "label": { "en": "Name", "fr": "Nom" }, "required": true, "maxLength": 120 },
        { "id": "email", "type": "email", "label": { "en": "Email", "fr": "E-mail" }, "required": true, "maxLength": 200 },
        { "id": "phone", "type": "tel", "label": { "en": "Phone", "fr": "Téléphone" }, "required": false, "maxLength": 40 }
      ]
    }
  }
}
```

`requestFields` use the same field types and limits as [Forms](https://docs.liteshop.me/reference/forms.md).
Removing or renaming an item later is allowed: existing requests keep the item
as it was when they were sent, and new requests for a removed item are refused.

## Show the catalogue

`liteshop.catalogue.list()` returns the published items and collections. Each
item has `id`, `kind`, `name` and optional `description` (objects keyed by
language), optional `price`, optional `images` (Site-relative URLs, main image first)
and `image` (the main one),
`maximumQuantity` and `available`. `available` is `false` when the Owner
tracks stock and none is left: show the item as sold out and don't offer a
request.

```js theme={null}
const lang = document.documentElement.lang;
const { items, collections } = await liteshop.catalogue.list();
const price = (p) =>
  new Intl.NumberFormat(lang, { style: "currency", currency: p.currency })
    .format(p.amount / 100);

for (const item of items) {
  const card = document.createElement("article");
  card.append(
    Object.assign(document.createElement("h3"), {
      textContent: item.name[lang] ?? Object.values(item.name)[0],
    }),
  );
  if (item.price) card.append(price(item.price));
  // Add the image, description and a request button for available items.
  document.querySelector("#catalogue").append(card);
}
```

Set text with `textContent`, never `innerHTML`. Prefer rendering from the list
over hard-coding items in HTML: an item the Owner removes in the dashboard
can't be removed while a page names it in a literal `catalogue.request`.

## Send a request from the Site

Load the same-origin SDK and call `liteshop.catalogue.request` with the
`itemId` of a declared item, from the list or written literally:

```js theme={null}
const form = document.querySelector("#request-form");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const data = new FormData(form);
  try {
    await liteshop.catalogue.request({
      itemId: "massage-oil",
      quantity: Number(data.get("quantity")),
      contact: {
        name: data.get("name"),
        email: data.get("email"),
      },
      message: data.get("message") || undefined,
    });
    form.reset();
    // Show "Request sent. We'll contact you to confirm."
  } catch (error) {
    // Show error.message. error.field identifies a rejected field when present.
  }
});
```

Only declared `contact` keys are accepted; omit optional fields rather than
sending empty values. `quantity` is optional and must be a whole number from 1
to the item's `maximumQuantity`. `message` is optional, up to 2,000
characters. The SDK reads the request language from `<html lang="…">`.

Errors use the stable codes `CATALOGUE_QUANTITY_INVALID`,
`CATALOGUE_MESSAGE_TOO_LONG`, `CATALOGUE_OUT_OF_STOCK` (not enough tracked
stock is left), the contact field codes used by Forms,
`CAPABILITY_NOT_DECLARED` and `RATE_LIMITED` (five requests per IP and item per
ten minutes, 100 per Site per hour). French pages receive French messages.

> [!NOTE]
>
> Draft previews disable network requests. Test a real request only after
> Publish on the live Site.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.