Skip to content
New Webhooks added: Inventory and Order modifications. Check the changelog →
Cartly Developers

Theme Section Blocks

How to design, render, and edit section blocks in Cartly themes — the developer reference for theme authoring.

Overview

A section is a top-level page unit (hero, featured-collection, footer). A block is a sub-unit within a section — one editorial card, one footer column, one accordion panel. Blocks exist because sections often need a repeating UI of variable count where structure is tightly coupled to the section's own layout.

When to use what: use a setting for a single toggle; use a block for repeating UI inside one section; use a separate section for content the merchant might add or remove independently.

Schema Declaration

Blocks are declared in {% schema %} alongside section settings:

{
  "name": "Testimonials",
  "blocks": [
    {
      "type": "testimonial",
      "name": "Testimonial",
      "limit": 6,
      "settings": [
        { "type": "text", "id": "author", "label": "Author name" },
        { "type": "textarea", "id": "quote", "label": "Quote" }
      ]
    }
  ],
  "max_blocks": 6,
  "presets": [{ "name": "Testimonials", "blocks": [{ "type": "testimonial" }] }]
}

Each block schema entry has: type (discriminator), name (display label in the editor accordion), settings (same shape as section settings), and optional limit (per-type max). max_blocks caps total count across all types. presets[].blocks pre-populates blocks when the section is first added.

Instance JSON Shape

Section instances in templates/*.json carry:

{
  "sections": {
    "testimonials": {
      "type": "testimonials",
      "settings": {},
      "blocks": {
        "block_1": { "type": "testimonial", "settings": { "author": "Jane D.", "quote": "Best store!" } },
        "block_2": { "type": "testimonial", "settings": { "author": "Mark T.", "quote": "Great service!" } }
      },
      "block_order": ["block_1", "block_2"]
    }
  }
}

block_order (singular, Shopify-compatible) controls render order. The visual editor always writes block_order; it reads either for backwards compatibility.

Liquid Rendering

{% for block in section.blocks %}
  {% if block.type == 'testimonial' %}
  <div {{ block.shopify_attributes }}>
    <p>{{ block.settings.quote | escape }}</p>
    <cite>{{ block.settings.author | escape }}</cite>
  </div>
  {% endif %}
{% endfor %}

The renderer assembles section.blocks from the instance's blocks map in block_order. Blocks not listed in block_order are appended at the tail (lossless). Discriminate on block.type when a section accepts multiple block types. Always emit {{ block.shopify_attributes }} on the block's outer element — the visual editor's bridge uses this attribute to locate and highlight the block.

Visual Editor — Block Panel

When a merchant selects a section in the visual editor, the right panel shows an accordion below the section settings — one row per block in block_order. Expanding a row reveals the block's settings rendered through the same field-renderer as section settings. The expanded row dispatches a cartly:block:select postMessage to the preview iframe; editor-preview.js catches it, finds the matching [data-block-id] inside the section container, scrolls to it, and applies the .cartly-block-highlight CSS animation.

Lazy mounting: only the expanded row's settings tree exists in the DOM. Collapsed rows have zero DOM overhead.

What the visual editor supports: editing any block's settings fields, live preview of changes.

What is in the code editor only: adding blocks, deleting blocks, reordering blocks, changing block types, adjusting block_order.

Code Editor — Structural Changes

Open /admin/themes/<id>/code and edit the template JSON directly:

  • Add a block: add an entry to sections.<id>.blocks map and push the key to block_order.
  • Remove a block: delete from both the blocks map and the order array.
  • Reorder blocks: rearrange entries in block_order.
  • Visibility per block: not natively supported at the renderer level. Use {% if block.settings.show %} inside the Liquid loop with a checkbox setting.

Depth Limit

Cartly Liquid does not support blocks-within-blocks. If your schema accidentally declares blocks inside a block entry, the parser logs a console.warn and ignores the nested declaration. There is no runtime error — the mistake surfaces during local theme development in the browser console.

Best Practices

  • Always ship at least one preset with default block content matching the design mockup.
  • Pair max_blocks with the Liquid template's UI capacity (e.g. an editorial-pair section with a 2-column grid: max_blocks: 2).
  • Emit {{ block.shopify_attributes }} on every block's outer element — required for the visual editor highlight bridge.
  • Apply | escape to all merchant-controlled string values interpolated into HTML attributes (THEME-AUTHORING-STANDARDS M-6).
  • Do not nest blocks — use separate sections for independently manageable content.