Vein

The templating language for Tringify storefronts.

Vein is how a Tringify theme renders. If you have built storefront themes before, you already know the shape: template files with {{ output }} and {% tags %}, a schema block that declares merchant-editable settings, and a store editor that renders those settings as controls. This page is the whole model — learn it once and every part of a theme reads the same way.

The whole model, in one breath#

A storefront surface (home, product, cart, the header, …) holds an ordered list of sections. Each section is a Vein file — markup plus a {% schema %} block. A section has settings (its controls) and, optionally, blocks: repeatable content units the merchant adds, reorders, and removes. That is the entire system, repeated for every surface.

text
surface
  └─ section[]                    an ordered list, arranged by the merchant
       └─ a .vein file            markup + {% schema %}
            ├─ settings           merchant-editable controls
            └─ blocks[]           repeatable content units

The schema drives everything#

The {% schema %} you write is the single source for a section: it generates the editor controls the merchant sees, validates what they save, and shapes what your markup reads. Declare a setting once and it appears in the editor automatically — what the merchant configures there is exactly what the store ships. No platform code ever knows your section's name.

vein
<section class="promo">
  <h2>{{ settings.heading }}</h2>
  <p>{{ settings.body }}</p>
</section>
{% schema %}
{
  "name": "promo",
  "settings": [
    { "id": "heading", "type": "text", "default": "Summer sale",
      "editor": { "label": "Heading", "group": "content" } },
    { "id": "body", "type": "textarea", "default": "Up to 50% off.",
      "editor": { "label": "Body", "group": "content" } }
  ]
}
{% endschema %}

Declared, not ambient#

A section states what it uses. Context data (the product, the cart, the shop) must be declared in ctx_needs before it can be read — an undeclared read is an error at review, not a silent blank at render. Settings are declared in the schema; stored values outside the schema are rejected on save. The result: a section is self-describing, and the platform can verify it before it ever reaches a storefront.

Safe by construction#

Output is always auto-escaped. There is no raw filter and no {% raw %} block — a value can never inject markup. Rich HTML (a product description, a richtext setting) renders correctly because the platform sanitizes those specific fields and marks them trusted before your section sees them. Prices, translations, and placeholder artwork come from platform host filters (money, t, placeholder_svg) — your template declares intent; the platform computes the correct result for every buyer, locale, and currency.

Where things live#

Piece

Where

Sections

sections/.vein in the theme bundle

Theme manifest + default locale

theme.json

Design tokens

tokens.json

Global theme settings schema

config/settings_schema.json

Translations

locales/.json + locales/.schema.json

Page templates

templates/*.json