Your First Section

Build a Vein section end to end: markup, schema, settings, and blocks.

This page builds one real section — a testimonial strip — from an empty file to something a merchant can add, arrange, and edit. Every section you will ever write follows these same steps.

1. Create the file#

A section is one file in your theme's sections/ folder. The file name is yours; the section's identity is the "name" in its schema. Start with static markup and the smallest valid schema — a name.

vein
{# sections/testimonials.vein #}
<section class="testimonials">
  <h2>What customers say</h2>
</section>
{% schema %}
{ "name": "testimonials" }
{% endschema %}

2. Make it configurable#

Replace the hardcoded heading with a setting. A setting needs an id, a type, and — to be merchant-editable — an "editor" block with a label. Give it a real default: a section should look designed the moment it is added.

vein
<section class="testimonials">
  <h2>{{ settings.heading }}</h2>
</section>
{% schema %}
{
  "name": "testimonials",
  "settings": [
    { "id": "heading", "type": "text", "default": "What customers say",
      "editor": { "label": "Heading", "group": "content" } }
  ]
}
{% endschema %}

3. Add repeatable content: blocks#

Each testimonial is a block: the merchant adds as many as they like, reorders them, hides one, removes one. Declare the allowed block types (each with its own settings), then render settings.blocks in the markup — branch on b.type, read b.settings, skip b.disabled, and wrap each block in the editor markers (no-ops on the live storefront, click-to-select in the editor).

vein
<section class="testimonials">
  <h2>{{ settings.heading }}</h2>
  {% for b in settings.blocks %}
    {% unless b.disabled == true %}
      {% if b.type == 'quote' %}
        {{ b._editor_block_open }}
        <blockquote>
          <p>{{ b.settings.text }}</p>
          <cite>{{ b.settings.author }}</cite>
        </blockquote>
        {{ b._editor_block_close }}
      {% endif %}
    {% endunless %}
  {% else %}
    <p>No testimonials yet.</p>
  {% endfor %}
</section>
{% schema %}
{
  "name": "testimonials",
  "settings": [
    { "id": "heading", "type": "text", "default": "What customers say",
      "editor": { "label": "Heading", "group": "content" } }
  ],
  "blocks": {
    "max": 6,
    "default": ["quote", "quote"],
    "types": [
      { "type": "quote",
        "editor": { "label": "Quote" },
        "settings": [
          { "id": "text", "type": "textarea", "default": "Absolutely love it.",
            "editor": { "label": "Quote", "group": "content" } },
          { "id": "author", "type": "text", "default": "A happy customer",
            "editor": { "label": "Author", "group": "content" } }
        ] }
    ]
  }
}
{% endschema %}

4. What review checks#

When this file is saved or imported, the parser enforces the contract: the schema is valid JSON with a name; every setting type is from the closed set; every block type has at least one editable setting; every type in blocks.default exists in blocks.types; the markup compiles; and any context data you read is declared in ctx_needs. An invalid section fails at review — never at a shopper's render.

Where to go next#

The Template Language is the full markup reference (tags, filters, loops, truthiness). The {% schema %} Contract lists every setting type and rule. Host Filters covers money, t, and placeholder_svg — prices, translations, and placeholder artwork. Theme Locale Files shows how your theme ships its translations.