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
categoryproperty must match one of the categories defined in your theme’ssettings_schema.jsonfile.
Block Schema Properties
Below is a table describing the main properties available in a block schema, along with explanations and examples for each:
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
productandproduct.customboth have typeproduct. - An empty or absent
templatesarray 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.
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
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
detailscontainer of aproduct-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:
- Template —
B.templatesis empty or includes the current template type. - Privacy — if
Thas no whitelist,Bmust not beprivate. - Whitelist — if
Tdeclaresallowed_blocks,Bmust 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, andallowed_blocksfor 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
privateandallowed_blocksto enforce intentional block placement instead of relying on merchant convention. - Test your blocks in different sections to ensure flexibility and compatibility.