Shared types
The small objects that the other data pages reuse, such as Money, Category, Brand, MenuItem, Language and paginated results.
Use this page when a field on the store, product or cart points to a smaller shape (an image, a category, a price object) and you need its fields.
All types import from @salla.sa/twilight-theme-engine/types, except where a section says otherwise:
import type { Money, Category, MenuItem } from '@salla.sa/twilight-theme-engine/types';The examples marked "Public store API" are real answers from the public demo store.
Money
Where it appears: product variants (skus[].price), base_currency_price on a product, and cart lines (price, total and more).
Example (public store API, a product's base_currency_price):
{ "currency": "SAR", "amount": 3 }| Field | Type | Description |
|---|---|---|
amount | number | The amount as a number. |
currency | string | Currency code. |
formatted? | string | Ready-made text, when the API sends one. |
Many price fields are plain numbers instead: on a product, price, regular_price and sale_price are numbers (199), and currency is a separate field. Cart lines can be either. Turn both into a number first, then format it with useMoney:
import type { Money } from '@salla.sa/twilight-theme-engine/types';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
function Price({ value }: { value: number | Money }) {
const { format } = useMoney();
const amount = typeof value === 'number' ? value : value.amount;
return <>{format(amount, { currency: typeof value === 'number' ? undefined : value.currency })}</>;
}Images
Where it appears: Product.image and Product.images.
Example (public store API):
{ "url": "https://cdn.salla.sa/mQgZlG/c58e19d6-...-500x500.jpg", "alt": "Chair" }| Field | Type | Description |
|---|---|---|
id? | number | Image id. |
url? | string | Image URL. |
alt? | string | Alt text; usually the product name. |
video_url? | string | Video URL when the item is a video, otherwise null. |
video_type? | "video" | "youtube" | null | youtube for a YouTube link, video for an uploaded file, null for an image. |
type? | "image" | "video" | Media kind. The API also sends '3d-image' for a 3D main image. |
three_d_image_url? | string | .glb 3D model URL, or "" when none. |
main? | boolean | True for the main image. |
sort? | number | Position in the gallery. |
images can be null when a product has a single image: read image first, and use images ?? []. Images on categories, brands, cart lines and menu items are plain URL strings, not this object.
Category
Where it appears: category lists and pages (category API), and on a product as a smaller ProductCategory (name, url, optional id, icon, image).
Example (public store API, GET categories):
| Field | Type | Description |
|---|---|---|
id | string | number | Id as the API sends it, a hashed string such as "xzjvNQ". |
id_? | number | The raw numeric id, e.g. 868669273. |
name | string | Category name. |
description? | string | Category description. |
url | string | Full URL of the category page. |
icon? | string | null | CSS icon class, e.g. "sicon-store", or null. |
image? | string | null | Flat image URL string (NOT a nested ProductImage object), or null. Aligns with both the API response and Twig. |
products_count? | number | Number of products in the category. |
sub_categories? | Category[] | Child categories, possibly empty. |
items? | Category[] | null | Mirror of sub_categories on parent categories (an array, possibly empty); null on some root categories without children. |
Brand
Where it appears: the brands list, brand pages and the home brands block. The engine has two Brand types:
Brandfrom@salla.sa/twilight-theme-engine/types:id: number,name,logo,url. This is the small card shape.Brandfrom@salla.sa/twilight-theme-engine/routes: the full record used by the brands pages and thebrandAPI.
Example (public store API, GET brands, description shortened):
| Field | Type | Description |
|---|---|---|
id | string | number | Brand id. Numeric on the Store API (brands and brands/{id}); string in older fixtures. |
url | string | Brand page URL. |
name | string | Brand name. |
label | string | Display label. |
description | string | Brand description. |
banner? | string | null | Banner image URL, or null. |
logo | string | Logo image URL. Always present; required to match Twig template usage. |
status | boolean | Whether the brand is active. |
ar_char | string | First Arabic letter, used to group the brands directory. |
en_char | string | First Latin letter, used to group the brands directory. |
channels? | string[] | Sales channels the brand is available on. |
metadata? | { title: string; description: string; url: string; } | SEO data. |
metadata
| Field | Type | Description |
|---|---|---|
title | string | SEO title. |
description | string | SEO description. |
url | string | SEO URL. |
id is typed string | number and the API sends a number. Compare with String(brand.id).
MenuItem
Where it appears: the header and footer menus (menu API), usually passed to your header and footer components.
Example (public store API, GET menus/header, one item with one child):
| Field | Type | Description |
|---|---|---|
id | string | number | Item id. Built-in links use a word, such as "offers" or "blog". |
title | string | Text to show. |
url | string | Link URL. |
image? | string | null | Root-level image the merchant attached in the menu builder, when there is one. |
children? | MenuItem[] | Sub-items. Missing on some items: use children ?? []. |
has_children? | boolean | Whether there are sub-items. Missing on some items. |
target? | string | Link target, e.g. "_self". |
order? | number | Sort position. |
attrs? | string | Raw HTML attribute string for the item element (e.g. id="offers"). |
link_attrs? | string | Raw HTML attribute string for the anchor (e.g. target="_self" class="offers-link"). |
Breadcrumb
Where it appears: useBreadcrumbs(page) returns a list of them for the trail above a page.
Example (illustrative values):
[
{ "name": "Home", "url": "/" },
{ "name": "Accessories", "url": "/accessories/c1309167683" }
]| Field | Type | Description |
|---|---|---|
name | string | Label. |
url | string | Link target. |
import { useBreadcrumbs } from '@salla.sa/twilight-theme-engine/hooks';
const crumbs = useBreadcrumbs(page); // [] on the home pageIf the page has no trail of its own, the hook returns Home plus the page title. On a product page it inserts the list the customer came from.
Page
Where it appears: every route loader returns a page object, which components pass to useBreadcrumbs and use for the title.
Example (illustrative values):
{ "slug": "product.single", "title": "Chair", "id": 677709975, "url": "/chair/p677709975" }| Field | Type | Description |
|---|---|---|
title | string | Page title. |
slug | string | Route id of the page, such as "product.single". |
description? | string | Page description. |
id? | string | number | Entity ID associated with the page (e.g. category ID on a category page). |
url? | string | URL of the current page. |
parent? | { id: string | number; name: string; url: string; } | null | Parent entity, if any. |
breadcrumbs? | Breadcrumb[] | Breadcrumb trail built by the route loader. If omitted, useBreadcrumbs provides a fallback. |
parent
| Field | Type | Description |
|---|---|---|
id | string | number | Parent entity id. |
name | string | Parent entity name. |
url | string | Parent entity URL. |
breadcrumbs
| Field | Type | Description |
|---|---|---|
name | string | Label. |
url | string | Link target. |
A merchant's custom page is a Page plus content (HTML), metadata and created_at, called StaticPage. The page API returns the raw record, for example from the public store: { "id": 153787377, "name": "Our team", "content": "<p>...</p>", "type": "general" }.
Language
Where it appears: useTwilight().settings.languages, the list behind a language switcher.
Example (public store API, languages.EN, plus the iso_code the engine adds):
| Field | Type | Description |
|---|---|---|
name | string | Language name written in that language. |
code | string | Language code. |
iso_code? | string | Same as code — the SDK's salla-localization-modal reads iso_code, not code. |
url | string | Flag icon URL (e.g., "https://assets.salla.sa/images/flags/ar.svg") - NOT the store URL. |
is_rtl | boolean | Whether the language is written right to left. |
country_code | string | Country code used to pick the flag. |
has_custom_translations? | boolean | Whether this store has its own wording for this language. Written by Languages-Module when a merchant saves a translation, so a theme can tell without fetching whether translations/<storeId>/<iso>.json is worth asking for — most stores have none, and a miss is an uncached round trip on every render. Optional because it is new: a store nobody has written it for carries no key at all, and absent has to read as false. Do not treat a missing flag as "ask anyway". |
For the current language, use useTranslation() (locale, direction).
Pagination
Where it appears: lists such as product.list return PaginatedResult<T> from @salla.sa/twilight-theme-engine/api/types.
interface PaginatedResult<T> {
items: T[];
next: string | null; // null on the last page
}Example (illustrative values):
{ "items": [ /* Product, Product, ... */ ], "next": "<opaque cursor>" }| Field | Type | Description |
|---|---|---|
items | T[] | The items of this page. |
next | string | null | Cursor for the next page, null on the last. Pass it back as cursor; do not parse it. |
The raw API also sends a cursor object (the Pagination type). Product list results keep it as cursor next to items; cursor.current is the page number for display only.
| Field | Type | Description |
|---|---|---|
next? | string | null | Cursor for the next page, or null on the last page. |
previous? | string | null | Cursor for the previous page, or null on the first page. |
current | number | Current page number, for UI display only. |
const first = await product.list({ source: 'latest' });
const second = first.next ? await product.list({ source: 'latest', cursor: first.next }) : null;Testimonial and Slider
The types exist for theme code that renders store testimonials and home sliders. The engine declares them but does not load them from a named API module, so fill them from the data your own loader or the home block gives you.
Testimonial
| Field | Type | Description |
|---|---|---|
id | number | Testimonial id. |
name | string | Name of the customer who wrote it. |
content | string | Testimonial text. |
rating? | number | Star rating the customer gave. |
image? | string | Customer image URL. |
Slider
| Field | Type | Description |
|---|---|---|
id | number | Slide id. |
image | string | Slide image URL. |
link? | string | URL the slide links to. |
title? | string | Slide title. |
description? | string | Slide text. |