agentleFS
ShopifyKnown publisherShopify/theme-liquid-docs/ai/claude/CLAUDE.md

theme-liquid-docs / claude

CLAUDE.md110 starsChanged 3 months agoMITView on GitHub

🚨 MANDATORY: YOU MUST CALL "learn_shopify_api" ONCE WHEN WORKING WITH LIQUID THEMES. Use the Shopify AI Toolkit for all Shopify API and platform work. If missing, install it in the agent host per that page (or npx skills add Shopify/shopify-ai-toolkit --list for skill-compatible hosts). Key principles: focus on generating snippets, blocks, and sections; users may create templates using the theme editor

The file

Show its rules and text

We split this CLAUDE.md into the rules it gives an agent

15 rules. About 0 are common, 9 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use the Shopify AI Toolkit for all Shopify API and platform work.

    line 3
  2. Very specific:

    Must include {{ content_for_header }} to inject Shopify scripts in the <head> and {{ content_for_layout }} to render the page content

    line 51
  3. Very specific:

    Keep it here only critical.css and static files necessary for every page, otherwise prefer the usage of the {% stylesheet %} and {% …

    line 61
  4. Very specific:

    Write CSS and JavaScript per components using the {% stylesheet %} and {% javascript %} tags

    line 75
  5. Very specific:

    When defining the {% schema %} tag on sections and blocks, follow these guidelines to use the values:

    line 102
  6. Distinctive:

    If you need to create a mobile layout and you want the merchant to be able to select one or two columns, use a select input:

    line 163
  7. Very specific:

    Use this tag to render section groups as part of the theme's layout content.

    line 946
  8. Distinctive:

    Every user-facing text: must use translation filters.

    line 1013
  9. Distinctive:

    Write clear, concise text.

    line 1062
  10. Distinctive:

    Consider character limits for UI elements.

    line 1065
  11. Distinctive:

    Use interpolation rather than appending strings together.

    line 1068
  12. Distinctive:

    Use descriptive, hierarchical keys

    line 1158
  13. Distinctive:

    Use snake_case for key names

    line 1160
  14. Distinctive:

    Keep text concise for UI elements

    line 1164
  15. Distinctive:

    Use variables for dynamic content

    line 1165

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

🚨 MANDATORY: YOU MUST CALL "learn_shopify_api" ONCE WHEN WORKING WITH LIQUID THEMES.

Use the Shopify AI Toolkit for all Shopify API and platform work. If missing, install it in the agent host per that page (or npx skills add Shopify/shopify-ai-toolkit --list for skill-compatible hosts).

Theme Architecture

Key principles: focus on generating snippets, blocks, and sections; users may create templates using the theme editor

Directory structure

.
├── assets          # Stores static assets (CSS, JS, images, fonts, etc.)
├── blocks          # Reusable, nestable, customizable components
├── config          # Global theme settings and customization options
├── layout          # Top-level wrappers for pages (layout templates)
├── locales         # Translation files for theme internationalization
├── sections        # Modular full-width page components
├── snippets        # Reusable Liquid code or HTML fragments
└── templates       # Templates combining sections and blocks to define page structures

sections

  • Sections are .liquid files that allow you to create reusable modules that can be customized by merchants
  • Sections can include blocks which allow merchants to add, remove, and reorder content within a section
  • Sections are made customizable by including the required {% schema %} tag that exposes settings in the theme editor via a JSON object. Validate that JSON object using the schemas/section.json JSON schema
  • Examples of sections: hero banners, product grids, testimonials, featured collections

blocks

  • Blocks are .liquid files that allow you to create reusable small components that can be customized by merchants (they don't need to fit the full-width of the page)
  • Blocks are ideal for logic that needs to be reused and also edited in the theme editor by merchants
  • Blocks can include other nested blocks which allow merchants to add, remove, and reorder content within a block too
  • Blocks are made customizable by including the required {% schema %} tag that exposes settings in the theme editor via a JSON object. Validate that JSON object using the schemas/theme_block.json JSON schema
  • Blocks must have the {% doc %} tag as the header if you directly/staticly render them in other file via {% content_for 'block', id: '42', type: 'block_name' %}
  • Examples of blocks: individual testimonials, slides in a carousel, feature items

snippets

  • Snippets are reusable code fragments rendered in blocks, sections, and layouts files via the render tag
  • Snippets are ideal for logic that needs to be reused but not directly edited in the theme editor by merchants
  • Snippets accept parameters when rendered for dynamic behavior
  • Snippets must have the {% doc %} tag as the header
  • Examples of sections: buttons, meta-tags, css-variables, and form elements

layout

  • Defines the overall HTML structure of the site, including <head> and <body>, and wraps other templates to provide a consistent frame
  • Contains repeated global elements like navigation, cart drawer, footer, and usually includes CSS/JS assets and meta tags
  • Must include {{ content_for_header }} to inject Shopify scripts in the <head> and {{ content_for_layout }} to render the page content

config

  • config/settings_schema.json is a JSON file that defines schema for global theme settings. Validate the shape shape of this JSON file using the schemas/theme_settings.json JSON schema
  • config/settings_data.json is JSON file that holds the data for the settings defined by config/settings_schema.json

assets

  • Contains static files like CSS, JavaScript, and images—including compiled and optimized assets—referenced in templates via the asset_url filter
  • Keep it here only critical.css and static files necessary for every page, otherwise prefer the usage of the {% stylesheet %} and {% javascript %} tags

locales

  • Stores translation files organized by language code (e.g., en.default.json, fr.json) to localize all user-facing theme content and editor strings
  • Enables multi-language support by providing translations accessible via filters like {{ 'key' | t }} in Liquid for proper internationalization
  • Validate locales JSON files using the schemas/translations.json JSON schema

templates

  • JSON file that define the structure, ordering, and which sections and blocks appear on each page type, allowing merchants to customize layouts without code changes

CSS & JavaScript

  • Write CSS and JavaScript per components using the {% stylesheet %} and {% javascript %} tags
  • Note: {% stylesheet %} and {% javascript %} are only supported in snippets/, blocks/, and sections/

LiquidDoc

Snippets and blocks (when blocks are statically rendered) must include the LiquidDoc header that documents the purpose of the file and required parameters. Example:

{% doc %}
  Renders a responsive image that might be wrapped in a link.

  @param {image} image - The image to be rendered
  @param {string} [url] - An optional destination URL for the image

  @example
  {% render 'image', image: product.featured_image %}
{% enddoc %}

<a href="{{ url | default: '#' }}">{{ image | image_url: width: 200, height: 200 | image_tag }}</a>

The {% schema %} tag on blocks and sections

Key principles: follow the "Good practices" and "Validate the {% schema %} content" using JSON schemas

Good practices

When defining the {% schema %} tag on sections and blocks, follow these guidelines to use the values:

Single property settings: For settings that correspond to a single CSS property, use CSS variables:

<div class="collection" style="--gap: {{ block.settings.gap }}px">
  Example
</div>

{% stylesheet %}
  .collection {
    gap: var(--gap);
  }
{% endstylesheet %}

{% schema %}
{
  "settings": [{
    "type": "range",
    "label": "gap",
    "id": "gap",
    "min": 0,
    "max": 100,
    "unit": "px",
    "default": 0,
  }]
}
{% endschema %}

Multiple property settings: For settings that control multiple CSS properties, use CSS classes:

<div class="collection {{ block.settings.layout }}">
  Example
</div>

{% stylesheet %}
  .collection--full-width {
    /* multiple styles */
  }
  .collection--narrow {
    /* multiple styles */
  }
{% endstylesheet %}

{% schema %}
{
  "settings": [{
    "type": "select",
    "id": "layout",
    "label": "layout",
    "values": [
      { "value": "collection--full-width", "label": "t:options.full" },
      { "value": "collection--narrow", "label": "t:options.narrow" }
    ]
  }]
}
{% endschema %}

Mobile layouts

If you need to create a mobile layout and you want the merchant to be able to select one or two columns, use a select input:

{% schema %}
{
  "type": "select",
  "id": "columns_mobile",
  "label": "Columns on mobile",
  "options": [
    { "value": 1, "label": "1" },
    { "value": "2", "label": "2" }
  ]
}
{% endschema %}

Liquid

Liquid delimiters

  • {{ ... }}: Output – prints a value.
  • {{- ... -}}: Output, trims whitespace around the value.
  • {% ... %}: Logic/control tag (if, for, assign, etc.), does not print anything, no whitespace trim.
  • {%- ... -%}: Logic/control tag, trims whitespace around the tag.

Tip: Adding a dash (-) after {%/{{ or before %}/}} trims spaces or newlines next to the tag.

Examples:

  • {{- product.title -}} → print value, remove surrounding spaces or lines.
  • {%- if available -%}In stock{%- endif -%} → logic, removes extra spaces/lines.

Liquid operators

Comparison operators:

  • ==
  • !=
  • <
  • =

  • <=

Logical operators:

  • or
  • and
  • contains - checks if a string contains a substring, or if an array contains a string

Comparison and comparison tags

Key condition principles:

  • For simplificity, ALWAYS use nested if conditions when the logic requires more than one logical operator
  • Parentheses are not supported in Liquid
  • Ternary conditionals are not supported in Liquid, so always use {% if cond %}

Basic comparison example:

{% if product.title == "Awesome Shoes" %}
  These shoes are awesome!
{% endif %}

Multiple Conditions:

{% if product.type == "Shirt" or product.type == "Shoes" %}
  This is a shirt or a pair of shoes.
{% endif %}

Contains Usage:

  • For strings: {% if product.title contains "Pack" %}
  • For arrays: {% if product.tags contains "Hello" %}
  • Note: contains only works with strings, not objects in arrays

{% elsif %} (used inside if/unless only)

{% if a %}
  ...
{% elsif b %}
  ...
{% endif %}

{% unless %}

{% unless condition %}
  ...
{% endunless %}

{% case %}

{% case variable %}
  {% when 'a' %}
    a
  {% when 'b' %}
    b
  {% else %}
    other
{% endcase %}

{% else %} (used inside if, unless, case, or for)

{% if product.available %}
  In stock
{% else %}
  Sold out
{% endif %}

or inside a for loop:

{% for item in collection.products %}
  {{ item.title }}
{% else %}
  No products found.
{% endfor %}

Variables and variable tags

{% assign my_variable = 'value' %}

{% capture my_variable %}
  Contents of variable
{% endcapture %}

{% increment counter %}
{% decrement counter %}

Liquid filters

You can chain filters in Liquid, passing the result of one filter as the input to the next.

See these filters:

  • upcase: {{ string | upcase }} returns a string
  • split: {{ string | split: string }} returns an array (as we may notice in the docs, split receives a string as its argument)
  • last: {{ array | last }} returns untyped

Each filter can pass its return value to the next filter as long as the types match.

For example, upcase returns a string, which is suitable input for split, which then produces an array for last to use.

Here's how the filters are executed step by step to eventually return "WORLD":

{{ "hello world" | upcase | split: " " | last }}
  • First, "hello world" is converted to uppercase: "HELLO WORLD", which is a string
  • Next, split can act on strings, so it splits the value by space into an array: ["HELLO", "WORLD"]
  • Finally, the last filter work with array, so "WORLD" is returned

Array

  • compact: {{ array | compact }} returns array
  • concat: {{ array | concat: array }} returns array
  • find: {{ array | find: string, string }} returns untyped
  • find_index: {{ array | find_index: string, string }} returns number
  • first: {{ array | first }} returns untyped
  • has: {{ array | has: string, string }} returns boolean
  • join: {{ array | join }} returns string
  • last: {{ array | last }} returns untyped
  • map: {{ array | map: string }} returns array
  • reject: {{ array | reject: string, string }} returns array
  • reverse: {{ array | reverse }} returns array
  • size: {{ variable | size }} returns number
  • sort: {{ array | sort }} returns array
  • sort_natural: {{ array | sort_natural }} returns array
  • sum: {{ array | sum }} returns number
  • uniq: {{ array | uniq }} returns array
  • where: {{ array | where: string, string }} returns array

Cart

  • item_count_for_variant: {{ cart | item_count_for_variant: {variant_id} }} returns number
  • line_items_for: {{ cart | line_items_for: object }} returns array

Collection

  • link_to_type: {{ string | link_to_type }} returns string
  • link_to_vendor: {{ string | link_to_vendor }} returns string
  • sort_by: {{ string | sort_by: string }} returns string
  • url_for_type: {{ string | url_for_type }} returns string
  • url_for_vendor: {{ string | url_for_vendor }} returns string
  • within: {{ string | within: collection }} returns string
  • highlight_active_tag: {{ string | highlight_active_tag }} returns string

Color

  • brightness_difference: {{ string | brightness_difference: string }} returns number
  • color_brightness: {{ string | color_brightness }} returns number
  • color_contrast: {{ string | color_contrast: string }} returns number
  • color_darken: {{ string | color_darken: number }} returns string
  • color_desaturate: {{ string | color_desaturate: number }} returns string
  • color_difference: {{ string | color_difference: string }} returns number
  • color_extract: {{ string | color_extract: string }} returns number
  • color_lighten: {{ string | color_lighten: number }} returns string
  • color_mix: {{ string | color_mix: string, number }} returns string
  • color_modify: {{ string | color_modify: string, number }} returns string
  • color_saturate: {{ string | color_saturate: number }} returns string
  • color_to_hex: {{ string | color_to_hex }} returns string
  • color_to_hsl: {{ string | color_to_hsl }} returns string
  • color_to_oklch: {{ string | color_to_oklch }} returns string
  • color_to_rgb: {{ string | color_to_rgb }} returns string
  • hex_to_rgba: {{ string | hex_to_rgba }} returns string

Customer

  • customer_login_link: {{ string | customer_login_link }} returns string
  • customer_logout_link: {{ string | customer_logout_link }} returns string
  • customer_register_link: {{ string | customer_register_link }} returns string
  • avatar: {{ customer | avatar }} returns string
  • login_button: {{ shop | login_button }} returns string

Date

  • date: {{ date | date: string }} returns string

Default

  • default_errors: {{ string | default_errors }} returns string
  • default: {{ variable | default: variable }} returns untyped
  • default_pagination: {{ paginate | default_pagination }} returns string

Font

  • font_face: {{ font | font_face }} returns string
  • font_modify: {{ font | font_modify: string, string }} returns font
  • font_url: {{ font | font_url }} returns string

Format

  • date: {{ string | date: string }} returns string
  • json: {{ variable | json }} returns string
  • structured_data: {{ variable | structured_data }} returns string
  • unit_price_with_measurement: {{ number | unit_price_with_measurement: unit_price_measurement }} returns string
  • weight_with_unit: {{ number | weight_with_unit }} returns string

Hosted_file

  • asset_img_url: {{ string | asset_img_url }} returns string
  • asset_url: {{ string | asset_url }} returns string
  • file_img_url: {{ string | file_img_url }} returns string
  • file_url: {{ string | file_url }} returns string
  • global_asset_url: {{ string | global_asset_url }} returns string
  • shopify_asset_url: {{ string | shopify_asset_url }} returns string

Html

  • time_tag: {{ string | time_tag: string }} returns string
  • inline_asset_content: {{ asset_name | inline_asset_content }} returns string
  • highlight: {{ string | highlight: string }} returns string
  • link_to: {{ string | link_to: string }} returns string
  • placeholder_svg_tag: {{ string | placeholder_svg_tag }} returns string
  • preload_tag: {{ string | preload_tag: as: string }} returns string
  • script_tag: {{ string | script_tag }} returns string
  • stylesheet_tag: {{ string | stylesheet_tag }} returns string

Localization

  • currency_selector: {{ form | currency_selector }} returns string
  • translate: {{ string | t }} returns string
  • format_address: {{ address | format_address }} returns string

Math

  • abs: {{ number | abs }} returns number
  • at_least: {{ number | at_least }} returns number
  • at_most: {{ number | at_most }} returns number
  • ceil: {{ number | ceil }} returns number
  • divided_by: {{ number | divided_by: number }} returns number
  • floor: {{ number | floor }} returns number
  • minus: {{ number | minus: number }} returns number
  • modulo: {{ number | modulo: number }} returns number
  • plus: {{ number | plus: number }} returns number
  • round: {{ number | round }} returns number
  • times: {{ number | times: number }} returns number

Media

  • external_video_tag: {{ variable | external_video_tag }} returns string
  • external_video_url: {{ media | external_video_url: attribute: string }} returns string
  • image_tag: {{ string | image_tag }} returns string
  • media_tag: {{ media | media_tag }} returns string
  • model_viewer_tag: {{ media | model_viewer_tag }} returns string
  • video_tag: {{ media | video_tag }} returns string
  • article_img_url: {{ variable | article_img_url }} returns string
  • collection_img_url: {{ variable | collection_img_url }} returns string
  • image_url: {{ variable | image_url: width: number, height: number }} returns string
  • img_tag: {{ string | img_tag }} returns string
  • img_url: {{ variable | img_url }} returns string
  • product_img_url: {{ variable | product_img_url }} returns string

Metafield

  • metafield_tag: {{ metafield | metafield_tag }} returns string
  • metafield_text: {{ metafield | metafield_text }} returns string

Money

  • money: {{ number | money }} returns string
  • money_with_currency: {{ number | money_with_currency }} returns string
  • money_without_currency: {{ number | money_without_currency }} returns string
  • money_without_trailing_zeros: {{ number | money_without_trailing_zeros }} returns string

Payment

  • payment_button: {{ form | payment_button }} returns string
  • payment_terms: {{ form | payment_terms }} returns string
  • payment_type_img_url: {{ string | payment_type_img_url }} returns string
  • payment_type_svg_tag: {{ string | payment_type_svg_tag }} returns string

String

  • blake3: {{ string | blake3 }} returns string
  • hmac_sha1: {{ string | hmac_sha1: string }} returns string
  • hmac_sha256: {{ string | hmac_sha256: string }} returns string
  • md5: {{ string | md5 }} returns string
  • sha1: {{ string | sha1: string }} returns string
  • sha256: {{ string | sha256: string }} returns string
  • append: {{ string | append: string }} returns string
  • base64_decode: {{ string | base64_decode }} returns string
  • base64_encode: {{ string | base64_encode }} returns string
  • base64_url_safe_decode: {{ string | base64_url_safe_decode }} returns string
  • base64_url_safe_encode: {{ string | base64_url_safe_encode }} returns string
  • capitalize: {{ string | capitalize }} returns string
  • downcase: {{ string | downcase }} returns string
  • escape: {{ string | escape }} returns string
  • escape_once: {{ string | escape_once }} returns string
  • lstrip: {{ string | lstrip }} returns string
  • newline_to_br: {{ string | newline_to_br }} returns string
  • prepend: {{ string | prepend: string }} returns string
  • remove: {{ string | remove: string }} returns string
  • remove_first: {{ string | remove_first: string }} returns string
  • remove_last: {{ string | remove_last: string }} returns string
  • replace: {{ string | replace: string, string }} returns string
  • replace_first: {{ string | replace_first: string, string }} returns string
  • replace_last: {{ string | replace_last: string, string }} returns string
  • rstrip: {{ string | rstrip }} returns string
  • slice: {{ string | slice }} returns string
  • split: {{ string | split: string }} returns array
  • strip: {{ string | strip }} returns string
  • strip_html: {{ string | strip_html }} returns string
  • strip_newlines: {{ string | strip_newlines }} returns string
  • truncate: {{ string | truncate: number }} returns string
  • truncatewords: {{ string | truncatewords: number }} returns string
  • upcase: {{ string | upcase }} returns string
  • url_decode: {{ string | url_decode }} returns string
  • url_encode: {{ string | url_encode }} returns string
  • camelize: {{ string | camelize }} returns string
  • handleize: {{ string | handleize }} returns string
  • url_escape: {{ string | url_escape }} returns string
  • url_param_escape: {{ string | url_param_escape }} returns string
  • pluralize: {{ number | pluralize: string, string }} returns string

Tag

  • link_to_add_tag: {{ string | link_to_add_tag }} returns string
  • link_to_remove_tag: {{ string | link_to_remove_tag }} returns string
  • link_to_tag: {{ string | link_to_tag }} returns string

Liquid objects

Global objects

  • collections
  • pages
  • all_products
  • articles
  • blogs
  • cart
  • closest
  • content_for_header
  • customer
  • images
  • linklists
  • localization
  • metaobjects
  • request
  • routes
  • shop
  • theme
  • settings
  • template
  • additional_checkout_buttons
  • all_country_option_tags
  • canonical_url
  • content_for_additional_checkout_buttons
  • content_for_index
  • content_for_layout
  • country_option_tags
  • current_page
  • handle
  • page_description
  • page_image
  • page_title
  • powered_by_link
  • scripts

/article page

  • article
  • blog

/blog page

  • blog
  • current_tags

/cart page

  • cart

/checkout page

  • checkout

/collection page

  • collection
  • current_tags

/customers/account page

  • customer

/customers/addresses page

  • customer

/customers/order page

  • customer
  • order

/gift_card.liquid page

  • gift_card
  • recipient

/metaobject page

  • metaobject

/page page

  • page

/product page

  • product
  • remote_product

/robots.txt.liquid page

  • robots

/search page

  • search

Liquid tags

content_for

The content_for tag requires a type parameter to differentiate between rendering a number of theme blocks ('blocks') and a single static block ('block').

Syntax:

{% content_for 'blocks' %}
{% content_for 'block', type: "slide", id: "slide-1" %}

form

Because there are many different form types available in Shopify themes, the form tag requires a type. Depending on the form type, an additional parameter might be required. You can specify the following form types:

Syntax:

{% form 'form_type' %}
  content
{% endform %}

layout

Syntax:

{% layout name %}

assign

You can create variables of any basic type, object, or object property.

Caution: Predefined Liquid objects can be overridden by variables with the same name. To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.

Syntax:

{% assign variable_name = value %}

break

Syntax:

{% break %}

capture

You can create complex strings with Liquid logic and variables.

Caution: Predefined Liquid objects can be overridden by variables with the same name. To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.

Syntax:

{% capture variable %}
  value
{% endcapture %}

case

Syntax:

{% case variable %}
  {% when first_value %}
    first_expression
  {% when second_value %}
    second_expression
  {% else %}
    third_expression
{% endcase %}

comment

Any text inside comment tags won't be output, and any Liquid code will be parsed, but not executed.

Syntax:

{% comment %}
  content
{% endcomment %}

continue

Syntax:

{% continue %}

cycle

The cycle tag must be used inside a for loop.

Tip: Use the cycle tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.

Syntax:

{% cycle string, string, ... %}

decrement

Variables that are declared with decrement are unique to the layout, template, or section file that they're created in. However, the variable is shared across snippets included in the file.

Similarly, variables that are created with decrement are independent from those created with assign and capture. However, decrement and increment share variables.

Syntax:

{% decrement variable_name %}

doc

The doc tag allows developers to include documentation within Liquid templates. Any content inside doc tags is not rendered or outputted. Liquid code inside will be parsed but not executed. This facilitates tooling support for features like code completion, linting, and inline documentation.

For detailed documentation syntax and examples, see the LiquidDoc reference.

Syntax:

{% doc %}
  Renders a message.

  @param {string} foo - A string value.
  @param {string} [bar] - An optional string value.

  @example
  {% render 'message', foo: 'Hello', bar: 'World' %}
{% enddoc %}

echo

Using the echo tag is the same as wrapping an expression in curly brackets ({{ and }}). However, unlike the curly bracket method, you can use the echo tag inside liquid tags.

Tip: You can use filters on expressions inside echo tags.

Syntax:

{% liquid
  echo expression
%}

for

You can do a maximum of 50 iterations with a for loop. If you need to iterate over more than 50 items, then use the paginate tag to split the items over multiple pages.

Tip: Every for loop has an associated forloop object with information about the loop.

Syntax:

{% for variable in array %}
  expression
{% endfor %}

if

Syntax:

{% if condition %}
  expression
{% endif %}

increment

Variables that are declared with increment are unique to the layout, template, or section file that they're created in. However, the variable is shared across snippets included in the file.

Similarly, variables that are created with increment are independent from those created with assign and capture. However, increment and decrement share variables.

Syntax:

{% increment variable_name %}

raw

Syntax:

{% raw %}
  expression
{% endraw %}

render

Inside snippets and app blocks, you can't directly access variables that are created outside of the snippet or app block. However, you can specify variables as parameters to pass outside variables to snippets.

While you can't directly access created variables, you can access global objects, as well as any objects that are directly accessible outside the snippet or app block. For example, a snippet or app block inside the product template can access the product object, and a snippet or app block inside a section can access the section object.

Outside a snippet or app block, you can't access variables created inside the snippet or app block.

Note: When you render a snippet using the render tag, you can't use the include tag inside the snippet.

Syntax:

{% render 'filename' %}

tablerow

The tablerow tag must be wrapped in HTML <table> and </table> tags.

Tip: Every tablerow loop has an associated tablerowloop object with information about the loop.

Syntax:

{% tablerow variable in array %}
  expression
{% endtablerow %}

unless

Tip: Similar to the if tag, you can use elsif to add more conditions to an unless tag.

Syntax:

{% unless condition %}
  expression
{% endunless %}

paginate

Because for loops are limited to 50 iterations per page, you need to use the paginate tag to iterate over an array that has more than 50 items. The following arrays can be paginated:

Within the paginate tag, you have access to the paginate object. You can use this object, or the default_pagination filter, to build page navigation.

Note: The paginate tag allows the user to paginate to the 25,000th item in the array and no further. To reach items further in the array the array should be filtered further before paginating. See Pagination Limits for more information.

Syntax:

{% paginate array by page_size %}
  {% for item in array %}
    forloop_content
  {% endfor %}
{% endpaginate %}

javascript

Each section, block or snippet can have only one {% javascript %} tag.

To learn more about how JavaScript that's defined between the javascript tags is loaded and run, refer to the documentation for javascript tags.

Caution: Liquid isn't rendered inside of {% javascript %} tags. Including Liquid code can cause syntax errors.

Syntax:

{% javascript %}
  javascript_code
{% endjavascript %}

section

Rendering a section with the section tag renders a section statically. To learn more about sections and how to use them in your theme, refer to Render a section.

Syntax:

{% section 'name' %}

stylesheet

Each section, block or snippet can have only one {% stylesheet %} tag.

To learn more about how CSS that's defined between the stylesheet tags is loaded and run, refer to the documentation for stylesheet tags.

Caution: Liquid isn't rendered inside of {% stylesheet %} tags. Including Liquid code can cause syntax errors.

Syntax:

{% stylesheet %}
  css_styles
{% endstylesheet %}

sections

Use this tag to render section groups as part of the theme's layout content. Place the sections tag where you want to render it in the layout.

To learn more about section groups and how to use them in your theme, refer to Section groups.

Syntax:

{% sections 'name' %}

style

Note: If you reference color settings inside style tags, then the associated CSS rules will update as the setting is changed in the theme editor, without a page refresh.

Syntax:

{% style %}
  CSS_rules
{% endstyle %}

else

You can use the else tag with the following tags:

Syntax:

{% else %}
  expression

else

Syntax:

{% for variable in array %}
  first_expression
{% else %}
  second_expression
{% endfor %}

liquid

Because the tags don't have delimeters, each tag needs to be on its own line.

Tip: Use the echo tag to output an expression inside liquid tags.

Syntax:

{% liquid
  expression
%}

Translation development standards

Translation requirements

  • Every user-facing text must use translation filters.
  • Update locales/en.default.json with all new keys.
  • Use descriptive, hierarchical keys for organization.
  • Only add English text; translators handle other languages.

Translation filter usage

Use {{ 'key' | t }} for all text:

<!-- Good -->
<h2>{{ 'sections.featured_collection.title' | t }}</h2>
<p>{{ 'sections.featured_collection.description' | t }}</p>
<button>{{ 'products.add_to_cart' | t }}</button>

<!-- Bad -->
<h2>Featured Collection</h2>
<p>Check out our best products</p>
<button>Add to cart</button>

Translation with variables

Use variables for interpolation:

<!-- Liquid template -->
<p>{{ 'products.price_range' | t: min: product.price_min | money, max: product.price_max | money }}</p>
<p>{{ 'general.pagination.page' | t: page: paginate.current_page, pages: paginate.pages }}</p>

Corresponding keys in locale files:

{
  "products": {
    "price_range": "From {{ min }} to {{ max }}"
  },
  "general": {
    "pagination": {
      "page": "Page {{ page }} of {{ pages }}"
    }
  }
}

Best practices

Content guidelines:

  • Write clear, concise text.
  • Use sentence case for all user-facing text, including titles, headings, and button labels (capitalize only the first word and proper nouns; e.g., Featured collection → Featured collection, not Featured Collection).
  • Be consistent with terminology.
  • Consider character limits for UI elements.

Variable usage:

  • Use interpolation rather than appending strings together.
  • Prioritize clarity over brevity for variable naming.
  • Escape variables unless they output HTML: {{ variable | escape }}.

Localization standards

Auto-attached when working in locales/ directory.

File structure

locales/
├── en.default.json          # English (required)
├── en.default.schema.json   # English (required)
├── es.json                  # Spanish
├── est.schema.json          # Spanish
├── fr.json                  # French
├── frt.schema.json          # French
└── pt-BR.json               # Portuguese
└── pt-BR..schema.json       # Portuguese

Locale files

Locale files are JSON files containing translations for all the text strings used throughout a Shopify theme and its editor. They let merchants easily update and localize repeated words and phrases, making it possible to translate store content and settings into multiple languages for international customers. These files provide a centralized way to manage and edit translations.

Example:

{
  "general": {
    "cart": "Cart",
    "checkout": "Checkout"
  },
  "products": {
    "add_to_cart": "Add to Cart"
  }
}

Schema locale files

Schema locale files, saved with a .schema.json extension, store translation strings specifically for theme editor setting schemas. They follow a structured organization—category, group, and description—to give context to each translation, enabling accurate localization of editor content. Schema locale files must use the IETF language tag format in their naming, such as en-GB.schema.json for British English or fr-CA.schema.json for Canadian French.

Example:

{
  "products": {
    "card": {
      "description": "Product card layout"
    }
  }
}

Key organization

Hierarchical structure:

{
  "general": {
    "meta": {
      "title": "{{ shop_name }}",
      "description": "{{ shop_description }}"
    },
    "accessibility": {
      "skip_to_content": "Skip to content",
      "close": "Close"
    }
  },
  "products": {
    "add_to_cart": "Add to cart",
    "quick_view": "Quick view",
    "price": {
      "regular": "Regular price",
      "sale": "Sale price",
      "unit": "Unit price"
    }
  }
}

Usage

{{ 'general.meta.title' | t: shop_name: shop.name }}
{{ 'general.meta.description' | t: shop_description: shop.description }}

Translation guidelines

Key naming:

  • Use descriptive, hierarchical keys
  • Maximum 3 levels deep
  • Use snake_case for key names
  • Group related translations

Content rules:

  • Keep text concise for UI elements
  • Use variables for dynamic content
  • Consider character limits
  • Maintain consistent terminology

Examples per kind of asset

snippet

{% doc %}
  Renders a responsive image that might be wrapped in a link.

  When `width`, `height` and `crop` are provided, the image will be rendered
  with a fixed aspect ratio.

  Serves as an example of how to use the `image_url` filter and `image_tag` filter
  as well as how you can use LiquidDoc to document your code.

  @param {image} image - The image to be rendered
  @param {string} [url] - An optional destination URL for the image
  @param {string} [css_class] - Optional class to be added to the image wrapper
  @param {number} [width] - The highest resolution width of the image to be rendered
  @param {number} [height] - The highest resolution height of the image to be rendered
  @param {string} [crop] - The crop position of the image

  @example
  {% render 'image', image: product.featured_image %}
  {% render 'image', image: product.featured_image, url: product.url %}
  {% render 'image',
    css_class: 'product__image',
    image: product.featured_image,
    url: product.url,
    width: 1200,
    height: 800,
    crop: 'center',
  %}
{% enddoc %}

{% liquid
  unless height
    assign width = width | default: image.width
  endunless

  if url
    assign wrapper = 'a'
  else
    assign wrapper = 'div'
  endif
%}

<{{ wrapper }}
  class="image {{ css_class }}"
  {% if url %}
    href="{{ url }}"
  {% endif %}
>
  {{ image | image_url: width: width, height: height, crop: crop | image_tag }}
</{{ wrapper }}>

{% stylesheet %}
  .image {
    display: block;
    position: relative;
    overflow: hidden;
    width: 100%;
    height: auto;
  }

  .image > img {
    width: 100%;
    height: auto;
  }
{% endstylesheet %}

{% javascript %}
  function doSomething() {
    // example
  }
  doSomething()
{% endjavascript %}

block

Text

{% doc %}
  Renders a text block.

  @example
  {% content_for 'block', type: 'text', id: 'text' %}
{% enddoc %}

<div
  class="text {{ block.settings.text_style }}"
  style="--text-align: {{ block.settings.alignment }}"
  {{ block.shopify_attributes }}
>
  {{ block.settings.text }}
</div>

{% stylesheet %}
  .text {
    text-align: var(--text-align);
  }
  .text--title {
    font-size: 2rem;
    font-weight: 700;
  }
  .text--subtitle {
    font-size: 1.5rem;
  }
{% endstylesheet %}

{% schema %}
{
  "name": "t:general.text",
  "settings": [
    {
      "type": "text",
      "id": "text",
      "label": "t:labels.text",
      "default": "Text"
    },
    {
      "type": "select",
      "id": "text_style",
      "label": "t:labels.text_style",
      "options": [
        { "value": "text--title", "label": "t:options.text_style.title" },
        { "value": "text--subtitle", "label": "t:options.text_style.subtitle" },
        { "value": "text--normal", "label": "t:options.text_style.normal" }
      ],
      "default": "text--title"
    },
    {
      "type": "text_alignment",
      "id": "alignment",
      "label": "t:labels.alignment",
      "default": "left"
    }
  ],
  "presets": [{ "name": "t:general.text" }]
}
{% endschema %}

Group

{% doc %}
  Renders a group of blocks with configurable layout direction, gap and
  alignment.

  All settings apply to only one dimension to reduce configuration complexity.

  This component is a wrapper concerned only with rendering its children in
  the specified layout direction with appropriate padding and alignment.

  @example
  {% content_for 'block', type: 'group', id: 'group' %}
{% enddoc %}

<div
  class="group {{ block.settings.layout_direction }}"
  style="
    --padding: {{ block.settings.padding }}px;
    --alignment: {{ block.settings.alignment }};
  "
  {{ block.shopify_attributes }}
>
  {% content_for 'blocks' %}
</div>

{% stylesheet %}
  .group {
    display: flex;
    flex-wrap: nowrap;
    overflow: hidden;
    width: 100%;
  }
  .group--horizontal {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 0 var(--padding);
  }
  .group--vertical {
    flex-direction: column;
    align-items: var(--alignment);
    padding: var(--padding) 0;
  }
{% endstylesheet %}

{% schema %}
{
  "name": "t:general.group",
  "blocks": [{ "type": "@theme" }],
  "settings": [
    {
      "type": "select",
      "id": "layout_direction",
      "label": "t:labels.layout_direction",
      "default": "group--vertical",
      "options": [
        { "value": "group--horizontal", "label": "t:options.direction.horizontal" },
        { "value": "group--vertical", "label": "t:options.direction.vertical" }
      ]
    },
    {
      "visible_if": "{{ block.settings.layout_direction == 'group--vertical' }}",
      "type": "select",
      "id": "alignment",
      "label": "t:labels.alignment",
      "default": "flex-start",
      "options": [
        { "value": "flex-start", "label": "t:options.alignment.left" },
        { "value": "center", "label": "t:options.alignment.center" },
        { "value": "flex-end", "label": "t:options.alignment.right" }
      ]
    },
    {
      "type": "range",
      "id": "padding",
      "label": "t:labels.padding",
      "default": 0,
      "min": 0,
      "max": 200,
      "step": 2,
      "unit": "px"
    }
  ],
  "presets": [
    {
      "name": "t:general.column",
      "category": "t:general.layout",
      "settings": {
        "layout_direction": "group--vertical",
        "alignment": "flex-start",
        "padding": 0
      }
    },
    {
      "name": "t:general.row",
      "category": "t:general.layout",
      "settings": {
        "layout_direction": "group--horizontal",
        "padding": 0
      }
    }
  ]
}
{% endschema %}

section

<div class="example-section full-width">
  {% if section.settings.background_image %}
    <div class="example-section__background">
      {{ section.settings.background_image | image_url: width: 2000 | image_tag }}
    </div>
  {% endif %}

  <div class="custom-section__content">
    {% content_for 'blocks' %}
  </div>
</div>

{% stylesheet %}
  .example-section {
    position: relative;
    overflow: hidden;
    width: 100%;
  }
  .example-section__background {
    position: absolute;
    width: 100%;
    height: 100%;
    z-index: -1;
    overflow: hidden;
  }
  .example-section__background img {
    position: absolute;
    width: 100%;
    height: auto;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
  .example-section__content {
    display: grid;
    grid-template-columns: var(--content-grid);
  }

  .example-section__content > * {
    grid-column: 2;
  }
{% endstylesheet %}

{% schema %}
{
  "name": "t:general.custom_section",
  "blocks": [{ "type": "@theme" }],
  "settings": [
    {
      "type": "image_picker",
      "id": "background_image",
      "label": "t:labels.background"
    }
  ],
  "presets": [
    {
      "name": "t:general.custom_section"
    }
  ]
}
{% endschema %}

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.