Skip to Content

Blocks

Learn how to create and customize theme blocks.

Introduction to Blocks

Blocks are modular content elements that can be added to sections in Finqu themes. They allow for flexible and customizable layouts within a section. Most blocks render content directly; some blocks are layout blocks that contain child blocks in named containers (nesting is limited to a single level).

All placement fields described below are opt-in and backward compatible. A block schema with none of them behaves exactly as before.

How Blocks Are Rendered

Blocks are rendered inside sections using the container tag. Each block is defined in the blocks/ directory and rendered in the order specified by the theme editor or default configuration.

You can also render a block statically with the block tag:

{% block 'product-title' %} {% block 'app/part-payment' id: 'product-part-payment' %}

Inline display

For side-by-side block layout (e.g. product card rows), use display: 'inline' on the block tag or in the block schema:

{% block 'product-price', display: 'inline' %}
{ "name": { "en": "Price" }, "display": "inline" }

Inline blocks flow horizontally in the designer instead of stacking vertically.

Block Directory

All block files are stored in the blocks directory of your theme. Each file represents a single block and contains its Liquid markup, logic, and schema.

Block Schema

Each block file defines its configuration using a {% schema %} tag. The schema describes the block’s name, tag, class, category, keywords, and settings. For example:

{% schema %} { "name": { "en": "FAQ Item", "fi": "UKK-kohta" }, "tag": "div", "class": "block block-faq-item", "category": "content", "settings": { // ...block settings... } } {% endschema %}
  • The category property must match one of the categories defined in your theme’s settings_schema.json file.

Block Schema Properties

Below is a table describing the main properties available in a block schema, along with explanations and examples for each:

PropertyDescriptionExample
nameThe display name of the block, usually localized for different languages.{ "en": "FAQ Item", "fi": "UKK-kohta" }
tagThe HTML tag or identifier for the block."div"
classCSS classes applied to the block container."block block-faq-item"
categoryThe category for the block, must match a category in settings_schema.json."content"
settingsConfiguration options for the block, such as text fields, images, etc.{ /* ...block settings... */ }
templatesTemplate types the block is available on. Empty or absent = all templates.["product"]
privateIf true, hidden from the general block picker; only addable where whitelisted.true
containersDeclares this block as a layout block with child drop zones.See Layout blocks
containers[].idContainer id used by {% container 'id' %}."details"
containers[].titleContainer label in the designer.{ "en": "Details" }
containers[].allowed_blocksWhitelist of blocks accepted by the container. Empty = all public blocks.["product-title", "product-price"]

Each property helps define how the block appears and behaves in the theme editor and on the storefront. For more details on settings, see the relevant documentation sections below.

Template Scope (templates)

Both blocks and sections may declare a templates array in their {% schema %}. The block is then only offered on matching template types.

{% schema %} { "name": { "en": "Part-payment calculator" }, "templates": ["product"] } {% endschema %}
  • The template type is the part before the first dot. For example the templates product and product.custom both have type product.
  • An empty or absent templates array means “available on all templates”.
  • Use this as your general placement context. It is coarse and predictable; prefer it over trying to encode fine-grained semantic context.
Use casetemplates
Product part-payment calculator["product"]
Cart-level calculator["cart"]
Generic marketing blockomit (available everywhere)

Section-level templates works the same way. See Sections.

Layout Blocks and Containers

A layout block is a block whose schema declares one or more containers. Each container is a drop zone that can hold child blocks. Nesting is limited to a single level — a child block cannot itself contain further blocks.

Parent (layout) block

blocks/product-card.liquid

<div class="product-card"> <div class="product-card__media"> {% container 'media' %} </div> <div class="product-card__details"> {% container 'details' %} </div> <div class="product-card__actions"> {% container 'actions' %} </div> </div> {% schema %} { "name": { "en": "Product card" }, "templates": ["product", "collection", "index"], "containers": [ { "id": "media", "title": { "en": "Media" }, "allowed_blocks": ["product-image", "product-badge"] }, { "id": "details", "title": { "en": "Details" }, "allowed_blocks": ["product-title", "product-price", "product-rating"] }, { "id": "actions", "title": { "en": "Actions" }, "allowed_blocks": ["buy-button", "wishlist-button"] } ] } {% endschema %}

Container fields

FieldTypeDescription
idstringContainer identifier, referenced by {% container 'id' %}.
titlestring | localized objectLabel shown for the container in the designer.
allowed_blocksstring[]Optional whitelist of block names addable into this container. Empty/absent = any public block.

The {% container %} tag renders the matching child blocks. An unnamed {% container %} holds blocks that have no container assignment.

Private Blocks (private)

Set private: true on a block to remove it from the general block picker. A private block can only be added where it is explicitly listed in an allowed_blocks whitelist (a container, or a section).

This is exactly what you want for child blocks that only make sense inside one specific parent block.

blocks/product-price.liquid

<span class="price">{{ product.price | money }}</span> {% schema %} { "name": { "en": "Price" }, "private": true } {% endschema %}

Because product-price is private and listed in the details container’s allowed_blocks above, it:

  • never appears in the section-level “add block” picker,
  • only appears when adding into the details container of a product-card,
  • cannot be placed anywhere else — by construction, not by convention.

private is the right tool for “child-only” blocks. It differs from a section’s is_creatable: false (which means “cannot be added at all”); a private block can be added, but only where it is whitelisted.

allowed_blocks Whitelist

allowed_blocks is a list of block names that a target accepts. It can be declared in two places:

On a layout block container

Restricts which blocks may be added into that container (see Layout blocks).

On a section schema

Restricts which blocks may be added at the section’s top level.

sections/main-product.liquid

<section class="main-product"> {% container %} </section> {% schema %} { "name": { "en": "Main product" }, "templates": ["product"], "allowed_blocks": ["product-title", "product-price", "product-card", "buy-button"] } {% endschema %}

Resolution rule

A block B may be added to a target T (a container or a section) when all apply:

  1. TemplateB.templates is empty or includes the current template type.
  2. Privacy — if T has no whitelist, B must not be private.
  3. Whitelist — if T declares allowed_blocks, B must be in that list. A whitelisted target shows exactly its listed blocks (private blocks included).

Empty/absent allowed_blocks means no restriction: all public, template-compatible blocks are accepted; private blocks are hidden.

Worked Examples

Product card with private children

product-card templates: ["product","collection","index"] containers: details -> allowed_blocks: ["product-title","product-price"] actions -> allowed_blocks: ["buy-button","wishlist-button"] product-title private: true product-price private: true buy-button (public — also usable elsewhere)

Result: product-title / product-price can only be dropped into the details container of a product card; they never show up anywhere else. buy-button is whitelisted for the actions container but, being public, can also be used in other unrestricted targets.

Cart-level calculator

cart-calculator templates: ["cart"] main-cart (section) allowed_blocks: ["cart-summary","cart-calculator","cart-note"]

Result: cart-calculator is only offered on cart templates, and only inside the main-cart section’s block list.

Third-Party App Blocks

App-provided blocks are merged into the same block registry and use the same schema format as theme blocks. That means an app block can set:

  • templates — to scope itself to product/cart/etc. pages,
  • private — if it is meant to be a child of an app-provided layout block,
  • container allowed_blocks — if the app ships a layout block of its own.

A theme can also constrain app blocks without knowing app internals, by listing (or omitting) them in its own section/container allowed_blocks. This gives themes final say over placement.

For integrations that must appear in an exact spot (e.g. a part-payment calculator pinned to the product card), prefer a static block slot in the theme markup:

{% block 'app/part-payment' id: 'product-part-payment' %}

A static block is owned by the theme layout: the merchant can configure it but cannot drag it elsewhere.

Enforcement

Placement rules are enforced in two places:

  • Designer UI — the block picker is filtered to blocks that pass the resolution rule for the target container/section.
  • Server — the add-block API re-validates templates, private, and allowed_blocks for the resolved target and rejects invalid placements. Do not rely on UI filtering alone; the server is the source of truth.

Current scope: validation runs on block add. Reordering or moving an existing block between containers is not yet re-validated server-side; treat cross-container moves with care until that is added.

Block Best Practices

  • Use clear and descriptive names and categories for your blocks.
  • Organize settings logically for ease of use.
  • Keep block logic simple and focused on a single purpose.
  • Use private and allowed_blocks to enforce intentional block placement instead of relying on merchant convention.
  • Test your blocks in different sections to ensure flexibility and compatibility.