Skip to main content
Not live yet. Storefront blocks open with the Tassyir app marketplace. Until then the API refuses [[storefront.blocks]] in a manifest. The block language is published now, in @tassyir/storefront-blocks, so you can write and test blocks before the opening. This guide describes it; details may still change until the opening. A block is a piece of a store page your app provides: a rating under the product’s price, a size chart, a trust badge, a delivery estimate. The merchant places it in the store builder, like any section. Your app ships the block’s template and stylesheet with its version. Tassyir checks them at upload, then renders the template on its own servers for every page view, with the data your app stored. A block runs no JavaScript and has no form. The store page holds the order form, with the buyer’s name, phone and address, so a block can only ever be markup and styles: Tassyir escapes every value, rebuilds the HTML from an allowlist, and scopes the stylesheet to the block.

Declare a block

In tassyir.app.toml, with the write_storefront scope:
  • target: product for the product page (the template reads product), or any (the default) for any page but checkout.
  • data: the app data keys the block reads, as <owner>.<key>. Each must be declared with storefront visibility. Owners: product (target product only), store, business. Order and customer data never reach a store page.
  • settings: what the merchant sets in the builder. Types: string, text, boolean, integer, number, select, color, image, url, product, collection. A value that no longer fits the declaration falls back to the default when the block renders.

The template

A block template is a .liquid file in a subset of Liquid. If you know Shopify’s Liquid, the syntax is the same: {{ value | filter: argument }} outputs, {% tag %} logic, {{- and -}} to trim whitespace.

Objects

Nothing about the buyer or the cart is ever available. Any value answers size, first and last; a record answers its own keys only. A name that is not one of these objects or a variable the template assigns is an error at upload.

Tags

Not available, each refused at upload with a fix: include, render, section, layout, block, raw, liquid, echo, form, schema, style, stylesheet, javascript, paginate, tablerow, cycle, increment, decrement. A block is one self-contained template.

Filters

Not available, each refused with a fix: raw (there is no unescaped output), date (a block has no clock: store the date as text), json, img_url (use image_url), money_with_currency (use money), link_to, script_tag, stylesheet_tag, hashes and base64.

The HTML a block may use

The rendered HTML is parsed and written out again from an allowlist. What the template writes outside the allowlist is an error at upload; what data brings in is removed at render.
  • Elements: a, abbr, article, aside, b, bdi, bdo, blockquote, br, caption, cite, code, dd, del, details, dfn, div, dl, dt, em, figcaption, figure, footer, h2 to h6, header, hr, i, img, ins, kbd, li, mark, meter, ol, p, pre, progress, q, s, samp, section, small, span, strong, sub, summary, sup, the table elements, time, u, ul, var, wbr. No h1: the page has its own.
  • SVG icons: svg with g, path, circle, ellipse, line, polygon, polyline, rect and a text-only title inside it, with their shape and paint attributes (fill="currentColor" follows the text colour).
  • Attributes: class, title, lang, dir, role, hidden, translate, a set of aria-*, and each element’s own (href and target on links; src, alt, width, height on images; colspan, datetime, open, …). No id or name: an element with an id becomes a global variable of the page, which can break the store’s own scripts. No style (use the stylesheet), no data-*, no on… handler.
  • Links: https:// addresses (Tassyir adds rel="nofollow noopener noreferrer"), paths on the store (/collections/all) and fragments.
  • Images: an <img> takes its src from {{ … | image_url: <width> }} only, so every image comes from Tassyir’s CDN. Images load lazily.
  • Text: bidirectional override and isolate characters, zero-width and control characters are removed from values; Arabic’s marks and joiners stay.
Liquid may fill a quoted attribute value or an element’s content. It may not make an element or attribute name (<{{ x }}>), nor fill an unquoted value.

The stylesheet

The optional .css file is scoped to the block’s section, so its selectors cannot reach the rest of the page, and the block is drawn inside its section’s box. At upload it is refused when it holds:
  • an at-rule other than @media, @supports and @keyframes <plain name> (no @import, @font-face, @layer), or a nested rule;
  • a url() outside https://cdn.tassyir.io/, or a function that loads something (image-set(), src());
  • position other than static, relative, absolute or sticky, written as the word itself (never fixed, never through var()); behavior, -moz-binding, expression();
  • a backslash escape outside a quoted string, <, a bidi or invisible character.
Match the store’s theme with its variables: Other --st-* variables may change without notice and are refused. Each color setting is published on the block as --block-<key>, underscores as hyphens: accent_color is var(--block-accent-color).

Limits

A template is at most 16 KB, a stylesheet 8 KB, a version at most 20 blocks and 100 KB of block files. A render reads at most 32 KB of app data and writes at most 32 KB of HTML. A for loop runs at most 50 times, and at most 3 loops nest. Render work is counted in steps, never timed: every tag, output, loop iteration and condition is a step, and every filter or comparison costs one more per 256 characters it touches, up to 20,000 steps. A block over a limit renders nothing: the page shows without it.

Test a block on your machine

The package runs the upload check and renders previews with sample data: the store “Dar Amina”, a product with a price, your settings’ defaults, and the app data you pass.
validateBlock is the check the API runs on upload: it compiles the template against the declaration, checks the stylesheet, and renders the block with sample data in Arabic, French and English and with no app data, since a block must render before your app has stored anything.

Errors

Every error has a stable code, a source (template, stylesheet or render), a 1-based line and column (0 for a render limit), a message and a fix. On upload, the API refuses a version with an invalid block with BLOCK_TEMPLATE_INVALID, naming the block and listing these errors. The package’s reference lists every code. The ones you will meet first: