Skip to main content

CMS & Pages

Quark Commerce includes a headless CMS for building dynamic pages with content blocks. Pages are composed of reusable components configured through the Backoffice.

Get a Page​

GET /api/storefront/pages/{slug}
X-Store-Code: us-store
X-Language-Code: en

Response:

{
"id": "guid",
"title": "Home",
"slug": "home",
"contentBlocks": [
{
"id": "guid",
"componentType": "hero-banner",
"sortOrder": 0,
"data": {
"title": "Summer Collection",
"subtitle": "Up to 50% off",
"imageUrl": "https://api.example.com/media/banners/summer.jpg",
"buttonText": "Shop Now",
"buttonLink": "/c/s-new-arrivals"
}
},
{
"id": "guid",
"componentType": "product-grid",
"sortOrder": 1,
"data": {
"title": "Featured Products",
"categoryId": "guid",
"maxItems": 8,
"columns": 4
}
},
{
"id": "guid",
"componentType": "text-block",
"sortOrder": 2,
"data": {
"content": "<p>Welcome to our store...</p>"
}
}
]
}

Content Block Types​

The CMS supports various component types. Your frontend renders each type with the appropriate component:

Component TypeDescription
hero-bannerFull-width banner with image, text, and CTA
product-gridGrid of product cards from a category
text-blockRich text content (HTML)
image-galleryImage carousel/gallery
category-gridGrid of category cards
custom-htmlRaw HTML block

Rendering Strategy​

Build a component registry in your frontend that maps componentType strings to UI components. This pattern allows new block types to be added without code changes — just register a new component for the type.

// Example: Component mapping (adapt to your framework)
const componentMap = {
'hero-banner': HeroBannerComponent,
'product-grid': ProductGridComponent,
'text-block': TextBlockComponent,
};

Page Resolution​

Pages can be resolved by slug. The Backoffice defines which pages exist and their content. Common pages:

  • home — homepage
  • about — about us
  • Custom pages created by the admin
info

The CMS is fully headless — the API provides structured data, and your frontend is responsible for rendering. This means you have complete control over the visual output.

Typed Pages and Platform Targeting​

Besides slug pages, the UI builder's page types have their own endpoints — GET /api/storefront/pages/home, /pages/category/{categoryId}, /pages/product/{productId}, /pages/brand/{brandId}, /pages/search and /pages/global. They return the page's content blocks grouped by zone (zones.main[]), each block with its type, order and data (a JSON string written by the block's editor).

Send the client platform with every page request:

GET /api/storefront/pages/home
X-Store-Code: us-store
X-Language-Code: en
X-Platform: ios

X-Platform is web (the default when the header is absent), ios, android or any other value a channel defines. A page's Platforms setting in the UI builder decides who receives it:

  • no platforms selected — the page serves every platform;
  • platforms selected — only those platforms receive it; mobile covers both ios and android;
  • when both exist, the platform-targeted page wins over the all-platforms page. Store, customer-group and context specificity apply as before among equals.

So an app gets its own Home by creating a second Home page configuration targeted at mobile, while the web storefront keeps the general one. Responses are cached per platform.

Carousel slides may carry an optional videoUrl next to imageUrl: channels that support it play the video muted and looping over the image (its poster); all others show the image.

A carousel block may carry "height": "screen" (the editor's Full screen (opening hero)): every slide should then fill the whole screen, with the client's own header — greeting, search — laid over it rather than above it. It is meant for the first block of a page and for portrait images or video (imageUrlMobile where present). Absent or "auto" means the client's standard carousel band; a client that does not know the option simply shows the band.

Seeing an edit at once

Page responses are served with Cache-Control: max-age=300 and an ETag. A client that should show backoffice edits on its next refresh asks with Cache-Control: no-cache (in fetch: cache: 'no-cache') — the API then answers 304 Not Modified when nothing changed, so it costs a round trip, not a download.