@augeo/smelt 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.claude/settings.json +11 -0
  2. package/.github/workflows/verify.yml +64 -0
  3. package/.gitmodules +3 -0
  4. package/.prettierignore +25 -0
  5. package/.prettierrc.cjs +9 -0
  6. package/.zed/settings.json +21 -0
  7. package/AGENTS.md +232 -0
  8. package/LICENSE +21 -0
  9. package/README.md +266 -0
  10. package/biome.json +58 -0
  11. package/dist/cli.d.mts +1 -0
  12. package/dist/cli.mjs +350 -0
  13. package/dist/schema.d.mts +265 -0
  14. package/dist/schema.mjs +21 -0
  15. package/docs/TESTING.md +293 -0
  16. package/docs/assets-plan.md +197 -0
  17. package/docs/build-spec.md +466 -0
  18. package/docs/library-conversion-plan.md +419 -0
  19. package/example/.gitattributes +7 -0
  20. package/example/.shopifyignore +28 -0
  21. package/example/.theme-check.yml +7 -0
  22. package/example/blocks/_built--sections--hero--blocks--feature.liquid +52 -0
  23. package/example/config/settings_schema.json +10 -0
  24. package/example/layout/theme.liquid +25 -0
  25. package/example/locales/en.default.json +1 -0
  26. package/example/package-lock.json +51 -0
  27. package/example/package.json +20 -0
  28. package/example/sections/built--sections--hero.liquid +83 -0
  29. package/example/snippets/built--components--button.liquid +38 -0
  30. package/example/snippets/built--components--card.liquid +33 -0
  31. package/example/src/components/button/button.css +13 -0
  32. package/example/src/components/card/card.css +16 -0
  33. package/example/src/components/card/card.liquid +9 -0
  34. package/example/src/sections/hero/blocks/feature/feature.css +11 -0
  35. package/example/src/sections/hero/blocks/feature/feature.liquid +9 -0
  36. package/example/src/sections/hero/blocks/feature/feature.schema.ts +14 -0
  37. package/example/src/sections/hero/hero.css +15 -0
  38. package/example/src/sections/hero/hero.liquid +16 -0
  39. package/example/src/sections/hero/hero.schema.ts +26 -0
  40. package/example/src/sections/hero/hero.test.ts +43 -0
  41. package/example/src/utilities/labels.ts +5 -0
  42. package/example/templates/index.liquid +1 -0
  43. package/example/tsconfig.json +10 -0
  44. package/example/vitest.config.ts +6 -0
  45. package/lib/build/build.test.ts +475 -0
  46. package/lib/build/build.ts +314 -0
  47. package/lib/build/command.ts +27 -0
  48. package/lib/build/index.ts +1 -0
  49. package/lib/cli.ts +17 -0
  50. package/lib/dev/command.ts +25 -0
  51. package/lib/dev/index.ts +1 -0
  52. package/lib/dev/watch.ts +52 -0
  53. package/lib/resolver.test.ts +275 -0
  54. package/lib/resolver.ts +156 -0
  55. package/lib/schema.ts +37 -0
  56. package/package.json +59 -0
  57. package/scripts/codegen-schema.ts +66 -0
  58. package/src/components/button/button.css +13 -0
  59. package/src/components/button/button.liquid +5 -0
  60. package/src/components/button/button.ts +5 -0
  61. package/src/tsconfig.json +10 -0
  62. package/tests/example.test.ts +101 -0
  63. package/tsconfig.json +20 -0
  64. package/tsdown.config.ts +14 -0
  65. package/vendor/theme-liquid-docs/.gitattributes +10 -0
  66. package/vendor/theme-liquid-docs/.github/CODEOWNERS +1 -0
  67. package/vendor/theme-liquid-docs/.github/CODE_OF_CONDUCT.md +73 -0
  68. package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/bug_report.md +17 -0
  69. package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  70. package/vendor/theme-liquid-docs/.github/dependabot.yaml +6 -0
  71. package/vendor/theme-liquid-docs/.github/workflows/ci.yml +33 -0
  72. package/vendor/theme-liquid-docs/.github/workflows/cla.yml +27 -0
  73. package/vendor/theme-liquid-docs/.github/workflows/shopify-dev-preview-automation.yml +86 -0
  74. package/vendor/theme-liquid-docs/.github/workflows/update-latest.yml +56 -0
  75. package/vendor/theme-liquid-docs/.prettierrc.json +16 -0
  76. package/vendor/theme-liquid-docs/.vscode/settings.json +28 -0
  77. package/vendor/theme-liquid-docs/LICENSE.md +7 -0
  78. package/vendor/theme-liquid-docs/README.md +48 -0
  79. package/vendor/theme-liquid-docs/ai/claude/CLAUDE.md +1485 -0
  80. package/vendor/theme-liquid-docs/ai/cursor/rules/assets.mdc +15 -0
  81. package/vendor/theme-liquid-docs/ai/cursor/rules/blocks.mdc +339 -0
  82. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-group.mdc +103 -0
  83. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-text.mdc +59 -0
  84. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/section-example.mdc +61 -0
  85. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/snippet-example.mdc +72 -0
  86. package/vendor/theme-liquid-docs/ai/cursor/rules/liquid.mdc +837 -0
  87. package/vendor/theme-liquid-docs/ai/cursor/rules/locales.mdc +100 -0
  88. package/vendor/theme-liquid-docs/ai/cursor/rules/localization.mdc +67 -0
  89. package/vendor/theme-liquid-docs/ai/cursor/rules/mcp.mdc +2 -0
  90. package/vendor/theme-liquid-docs/ai/cursor/rules/schemas.mdc +184 -0
  91. package/vendor/theme-liquid-docs/ai/cursor/rules/sections.mdc +84 -0
  92. package/vendor/theme-liquid-docs/ai/cursor/rules/settings-schema.mdc +51 -0
  93. package/vendor/theme-liquid-docs/ai/cursor/rules/snippets.mdc +119 -0
  94. package/vendor/theme-liquid-docs/ai/github/copilot-instructions.md +1485 -0
  95. package/vendor/theme-liquid-docs/ai/liquid.mdc +638 -0
  96. package/vendor/theme-liquid-docs/data/filters.json +6148 -0
  97. package/vendor/theme-liquid-docs/data/latest.json +2 -0
  98. package/vendor/theme-liquid-docs/data/objects.json +20594 -0
  99. package/vendor/theme-liquid-docs/data/shopify_system_translations.json +2586 -0
  100. package/vendor/theme-liquid-docs/data/tags.json +1276 -0
  101. package/vendor/theme-liquid-docs/package.json +20 -0
  102. package/vendor/theme-liquid-docs/schemas/manifest_schema.json +31 -0
  103. package/vendor/theme-liquid-docs/schemas/manifest_theme.json +19 -0
  104. package/vendor/theme-liquid-docs/schemas/manifest_theme_app_extension.json +10 -0
  105. package/vendor/theme-liquid-docs/schemas/theme/app_block_entry.json +13 -0
  106. package/vendor/theme-liquid-docs/schemas/theme/default_setting_values.json +24 -0
  107. package/vendor/theme-liquid-docs/schemas/theme/local_block_entry.json +25 -0
  108. package/vendor/theme-liquid-docs/schemas/theme/preset.json +72 -0
  109. package/vendor/theme-liquid-docs/schemas/theme/preset_blocks.json +91 -0
  110. package/vendor/theme-liquid-docs/schemas/theme/section.json +208 -0
  111. package/vendor/theme-liquid-docs/schemas/theme/setting.json +1413 -0
  112. package/vendor/theme-liquid-docs/schemas/theme/settings.json +10 -0
  113. package/vendor/theme-liquid-docs/schemas/theme/targetted_block_entry.json +15 -0
  114. package/vendor/theme-liquid-docs/schemas/theme/theme_block.json +91 -0
  115. package/vendor/theme-liquid-docs/schemas/theme/theme_block_entry.json +14 -0
  116. package/vendor/theme-liquid-docs/schemas/theme/theme_settings.json +83 -0
  117. package/vendor/theme-liquid-docs/schemas/theme/translations.json +63 -0
  118. package/vendor/theme-liquid-docs/schemas/update/update_extension_schema_v1.json +186 -0
  119. package/vendor/theme-liquid-docs/tests/fixtures/section-nested-blocks.json +18 -0
  120. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-1.json +90 -0
  121. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-2.json +201 -0
  122. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-3.json +29 -0
  123. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-4.json +315 -0
  124. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-5.json +114 -0
  125. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-6.json +63 -0
  126. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-conditional-settings.json +145 -0
  127. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-preset-blocks-as-hash.json +60 -0
  128. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-static-block-preset.json +76 -0
  129. package/vendor/theme-liquid-docs/tests/fixtures/section-settings.json +34 -0
  130. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-1.json +234 -0
  131. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-2.json +253 -0
  132. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-basics.json +48 -0
  133. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-conditional-settings.json +202 -0
  134. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-presets-as-hash.json +50 -0
  135. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-settings.json +34 -0
  136. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-all-settings.json +313 -0
  137. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-dawn.json +1469 -0
  138. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-metadata.json +10 -0
  139. package/vendor/theme-liquid-docs/tests/fixtures/translations-1.json +14 -0
  140. package/vendor/theme-liquid-docs/tests/section.spec.ts +367 -0
  141. package/vendor/theme-liquid-docs/tests/test-constants.ts +58 -0
  142. package/vendor/theme-liquid-docs/tests/test-helpers.ts +104 -0
  143. package/vendor/theme-liquid-docs/tests/theme-settings/color_palette.spec.ts +184 -0
  144. package/vendor/theme-liquid-docs/tests/theme-settings/color_scheme_group.spec.ts +143 -0
  145. package/vendor/theme-liquid-docs/tests/theme-settings/general.spec.ts +192 -0
  146. package/vendor/theme-liquid-docs/tests/theme-settings/metaobject.spec.ts +94 -0
  147. package/vendor/theme-liquid-docs/tests/theme-settings/resource_list.spec.ts +58 -0
  148. package/vendor/theme-liquid-docs/tests/theme-settings/theme-metadata.spec.ts +59 -0
  149. package/vendor/theme-liquid-docs/tests/theme_block.spec.ts +266 -0
  150. package/vendor/theme-liquid-docs/tests/translations_schema.spec.ts +31 -0
  151. package/vendor/theme-liquid-docs/yarn.lock +543 -0
  152. package/vitest.config.ts +7 -0
@@ -0,0 +1,1485 @@
1
+ 🚨 MANDATORY: YOU MUST CALL "learn_shopify_api" ONCE WHEN WORKING WITH LIQUID THEMES.
2
+
3
+ ## Theme Architecture
4
+
5
+ **Key principles: focus on generating snippets, blocks, and sections; users may create templates using the theme editor**
6
+
7
+ ### Directory structure
8
+
9
+ ```
10
+ .
11
+ ├── assets # Stores static assets (CSS, JS, images, fonts, etc.)
12
+ ├── blocks # Reusable, nestable, customizable components
13
+ ├── config # Global theme settings and customization options
14
+ ├── layout # Top-level wrappers for pages (layout templates)
15
+ ├── locales # Translation files for theme internationalization
16
+ ├── sections # Modular full-width page components
17
+ ├── snippets # Reusable Liquid code or HTML fragments
18
+ └── templates # Templates combining sections and blocks to define page structures
19
+ ```
20
+
21
+ #### `sections`
22
+
23
+ - Sections are `.liquid` files that allow you to create reusable modules that can be customized by merchants
24
+ - Sections can include blocks which allow merchants to add, remove, and reorder content within a section
25
+ - 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
26
+ - Examples of sections: hero banners, product grids, testimonials, featured collections
27
+
28
+ #### `blocks`
29
+
30
+ - 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)
31
+ - Blocks are ideal for logic that needs to be reused and also edited in the theme editor by merchants
32
+ - Blocks can include other nested blocks which allow merchants to add, remove, and reorder content within a block too
33
+ - 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
34
+ - 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' %}`
35
+ - Examples of blocks: individual testimonials, slides in a carousel, feature items
36
+
37
+ #### `snippets`
38
+
39
+ - Snippets are reusable code fragments rendered in blocks, sections, and layouts files via the `render` tag
40
+ - Snippets are ideal for logic that needs to be reused but not directly edited in the theme editor by merchants
41
+ - Snippets accept parameters when rendered for dynamic behavior
42
+ - Snippets must have the `{% doc %}` tag as the header
43
+ - Examples of sections: buttons, meta-tags, css-variables, and form elements
44
+
45
+ #### `layout`
46
+
47
+ - Defines the overall HTML structure of the site, including `<head>` and `<body>`, and wraps other templates to provide a consistent frame
48
+ - Contains repeated global elements like navigation, cart drawer, footer, and usually includes CSS/JS assets and meta tags
49
+ - Must include `{{ content_for_header }}` to inject Shopify scripts in the `<head>` and `{{ content_for_layout }}` to render the page content
50
+
51
+ #### `config`
52
+
53
+ - `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
54
+ - `config/settings_data.json` is JSON file that holds the data for the settings defined by `config/settings_schema.json`
55
+
56
+ #### `assets`
57
+
58
+ - Contains static files like CSS, JavaScript, and images—including compiled and optimized assets—referenced in templates via the `asset_url` filter
59
+ - Keep it here only `critical.css` and static files necessary for every page, otherwise prefer the usage of the `{% stylesheet %}` and `{% javascript %}` tags
60
+
61
+ #### `locales`
62
+
63
+ - Stores translation files organized by language code (e.g., `en.default.json`, `fr.json`) to localize all user-facing theme content and editor strings
64
+ - Enables multi-language support by providing translations accessible via filters like `{{ 'key' | t }}` in Liquid for proper internationalization
65
+ - Validate `locales` JSON files using the `schemas/translations.json` JSON schema
66
+
67
+ #### `templates`
68
+
69
+ - 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
70
+
71
+ ### CSS & JavaScript
72
+
73
+ - Write CSS and JavaScript per components using the `{% stylesheet %}` and `{% javascript %}` tags
74
+ - Note: `{% stylesheet %}` and `{% javascript %}` are only supported in `snippets/`, `blocks/`, and `sections/`
75
+
76
+ ### LiquidDoc
77
+
78
+ Snippets and blocks (when blocks are statically rendered) must include the LiquidDoc header that documents the purpose of the file and required parameters. Example:
79
+
80
+ ```liquid
81
+ {% doc %}
82
+ Renders a responsive image that might be wrapped in a link.
83
+
84
+ @param {image} image - The image to be rendered
85
+ @param {string} [url] - An optional destination URL for the image
86
+
87
+ @example
88
+ {% render 'image', image: product.featured_image %}
89
+ {% enddoc %}
90
+
91
+ <a href="{{ url | default: '#' }}">{{ image | image_url: width: 200, height: 200 | image_tag }}</a>
92
+ ```
93
+
94
+ ## The `{% schema %}` tag on blocks and sections
95
+
96
+ **Key principles: follow the "Good practices" and "Validate the `{% schema %}` content" using JSON schemas**
97
+
98
+ ### Good practices
99
+
100
+ When defining the `{% schema %}` tag on sections and blocks, follow these guidelines to use the values:
101
+
102
+ **Single property settings**: For settings that correspond to a single CSS property, use CSS variables:
103
+ ```liquid
104
+ <div class="collection" style="--gap: {{ block.settings.gap }}px">
105
+ Example
106
+ </div>
107
+
108
+ {% stylesheet %}
109
+ .collection {
110
+ gap: var(--gap);
111
+ }
112
+ {% endstylesheet %}
113
+
114
+ {% schema %}
115
+ {
116
+ "settings": [{
117
+ "type": "range",
118
+ "label": "gap",
119
+ "id": "gap",
120
+ "min": 0,
121
+ "max": 100,
122
+ "unit": "px",
123
+ "default": 0,
124
+ }]
125
+ }
126
+ {% endschema %}
127
+ ```
128
+
129
+ **Multiple property settings**: For settings that control multiple CSS properties, use CSS classes:
130
+ ```liquid
131
+ <div class="collection {{ block.settings.layout }}">
132
+ Example
133
+ </div>
134
+
135
+ {% stylesheet %}
136
+ .collection--full-width {
137
+ /* multiple styles */
138
+ }
139
+ .collection--narrow {
140
+ /* multiple styles */
141
+ }
142
+ {% endstylesheet %}
143
+
144
+ {% schema %}
145
+ {
146
+ "settings": [{
147
+ "type": "select",
148
+ "id": "layout",
149
+ "label": "layout",
150
+ "values": [
151
+ { "value": "collection--full-width", "label": "t:options.full" },
152
+ { "value": "collection--narrow", "label": "t:options.narrow" }
153
+ ]
154
+ }]
155
+ }
156
+ {% endschema %}
157
+ ```
158
+
159
+ #### Mobile layouts
160
+
161
+ 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:
162
+
163
+ ```liquid
164
+ {% schema %}
165
+ {
166
+ "type": "select",
167
+ "id": "columns_mobile",
168
+ "label": "Columns on mobile",
169
+ "options": [
170
+ { "value": 1, "label": "1" },
171
+ { "value": "2", "label": "2" }
172
+ ]
173
+ }
174
+ {% endschema %}
175
+ ```
176
+
177
+ ## Liquid
178
+
179
+ ### Liquid delimiters
180
+
181
+ - **`{{ ... }}`**: Output – prints a value.
182
+ - **`{{- ... -}}`**: Output, trims whitespace around the value.
183
+ - **`{% ... %}`**: Logic/control tag (if, for, assign, etc.), does not print anything, no whitespace trim.
184
+ - **`{%- ... -%}`**: Logic/control tag, trims whitespace around the tag.
185
+
186
+ **Tip:**
187
+ Adding a dash (`-`) after `{%`/`{{` or before `%}`/`}}` trims spaces or newlines next to the tag.
188
+
189
+ **Examples:**
190
+ - `{{- product.title -}}` → print value, remove surrounding spaces or lines.
191
+ - `{%- if available -%}In stock{%- endif -%}` → logic, removes extra spaces/lines.
192
+
193
+ ### Liquid operators
194
+
195
+ **Comparison operators:**
196
+ - ==
197
+ - !=
198
+ - >
199
+ - <
200
+ - >=
201
+ - <=
202
+
203
+ **Logical operators:**
204
+ - `or`
205
+ - `and`
206
+ - `contains` - checks if a string contains a substring, or if an array contains a string
207
+
208
+ #### Comparison and comparison tags
209
+
210
+ **Key condition principles:**
211
+ - For simplificity, ALWAYS use nested `if` conditions when the logic requires more than one logical operator
212
+ - Parentheses are not supported in Liquid
213
+ - Ternary conditionals are not supported in Liquid, so always use `{% if cond %}`
214
+
215
+ **Basic comparison example:**
216
+ ```liquid
217
+ {% if product.title == "Awesome Shoes" %}
218
+ These shoes are awesome!
219
+ {% endif %}
220
+ ```
221
+
222
+ **Multiple Conditions:**
223
+ ```liquid
224
+ {% if product.type == "Shirt" or product.type == "Shoes" %}
225
+ This is a shirt or a pair of shoes.
226
+ {% endif %}
227
+ ```
228
+
229
+ **Contains Usage:**
230
+ - For strings: `{% if product.title contains "Pack" %}`
231
+ - For arrays: `{% if product.tags contains "Hello" %}`
232
+ - Note: `contains` only works with strings, not objects in arrays
233
+
234
+ **{% elsif %} (used inside if/unless only)**
235
+ ```liquid
236
+ {% if a %}
237
+ ...
238
+ {% elsif b %}
239
+ ...
240
+ {% endif %}
241
+ ```
242
+
243
+ **{% unless %}**
244
+ ```liquid
245
+ {% unless condition %}
246
+ ...
247
+ {% endunless %}
248
+ ```
249
+
250
+ **{% case %}**
251
+ ```liquid
252
+ {% case variable %}
253
+ {% when 'a' %}
254
+ a
255
+ {% when 'b' %}
256
+ b
257
+ {% else %}
258
+ other
259
+ {% endcase %}
260
+ ```
261
+
262
+ **{% else %} (used inside if, unless, case, or for)**
263
+ ```liquid
264
+ {% if product.available %}
265
+ In stock
266
+ {% else %}
267
+ Sold out
268
+ {% endif %}
269
+ ```
270
+ _or inside a for loop:_
271
+ ```liquid
272
+ {% for item in collection.products %}
273
+ {{ item.title }}
274
+ {% else %}
275
+ No products found.
276
+ {% endfor %}
277
+ ```
278
+
279
+ #### Variables and variable tags
280
+
281
+ ```liquid
282
+ {% assign my_variable = 'value' %}
283
+
284
+ {% capture my_variable %}
285
+ Contents of variable
286
+ {% endcapture %}
287
+
288
+ {% increment counter %}
289
+ {% decrement counter %}
290
+ ```
291
+
292
+ ### Liquid filters
293
+
294
+ You can chain filters in Liquid, passing the result of one filter as the input to the next.
295
+
296
+ See these filters:
297
+
298
+ - `upcase`: `{{ string | upcase }}` returns a **string**
299
+ - `split`: `{{ string | split: string }}` returns an **array** (as we may notice in the docs, `split` receives a string as its argument)
300
+ - `last`: `{{ array | last }}` returns **untyped**
301
+
302
+ Each filter can pass its return value to the next filter as long as the types match.
303
+
304
+ For example, `upcase` returns a string, which is suitable input for `split`, which then produces an array for `last` to use.
305
+
306
+ Here's how the filters are executed step by step to eventually return `"WORLD"`:
307
+
308
+ ```liquid
309
+ {{ "hello world" | upcase | split: " " | last }}
310
+ ```
311
+
312
+ - First, `"hello world"` is converted to uppercase: `"HELLO WORLD"`, which is a string
313
+ - Next, `split` can act on strings, so it splits the value by space into an array: `["HELLO", "WORLD"]`
314
+ - Finally, the `last` filter work with array, so `"WORLD"` is returned
315
+
316
+ #### Array
317
+ - `compact`: `{{ array | compact }}` returns `array`
318
+ - `concat`: `{{ array | concat: array }}` returns `array`
319
+ - `find`: `{{ array | find: string, string }}` returns `untyped`
320
+ - `find_index`: `{{ array | find_index: string, string }}` returns `number`
321
+ - `first`: `{{ array | first }}` returns `untyped`
322
+ - `has`: `{{ array | has: string, string }}` returns `boolean`
323
+ - `join`: `{{ array | join }}` returns `string`
324
+ - `last`: `{{ array | last }}` returns `untyped`
325
+ - `map`: `{{ array | map: string }}` returns `array`
326
+ - `reject`: `{{ array | reject: string, string }}` returns `array`
327
+ - `reverse`: `{{ array | reverse }}` returns `array`
328
+ - `size`: `{{ variable | size }}` returns `number`
329
+ - `sort`: `{{ array | sort }}` returns `array`
330
+ - `sort_natural`: `{{ array | sort_natural }}` returns `array`
331
+ - `sum`: `{{ array | sum }}` returns `number`
332
+ - `uniq`: `{{ array | uniq }}` returns `array`
333
+ - `where`: `{{ array | where: string, string }}` returns `array`
334
+
335
+ #### Cart
336
+ - `item_count_for_variant`: `{{ cart | item_count_for_variant: {variant_id} }}` returns `number`
337
+ - `line_items_for`: `{{ cart | line_items_for: object }}` returns `array`
338
+
339
+ #### Collection
340
+ - `link_to_type`: `{{ string | link_to_type }}` returns `string`
341
+ - `link_to_vendor`: `{{ string | link_to_vendor }}` returns `string`
342
+ - `sort_by`: `{{ string | sort_by: string }}` returns `string`
343
+ - `url_for_type`: `{{ string | url_for_type }}` returns `string`
344
+ - `url_for_vendor`: `{{ string | url_for_vendor }}` returns `string`
345
+ - `within`: `{{ string | within: collection }}` returns `string`
346
+ - `highlight_active_tag`: `{{ string | highlight_active_tag }}` returns `string`
347
+
348
+ #### Color
349
+ - `brightness_difference`: `{{ string | brightness_difference: string }}` returns `number`
350
+ - `color_brightness`: `{{ string | color_brightness }}` returns `number`
351
+ - `color_contrast`: `{{ string | color_contrast: string }}` returns `number`
352
+ - `color_darken`: `{{ string | color_darken: number }}` returns `string`
353
+ - `color_desaturate`: `{{ string | color_desaturate: number }}` returns `string`
354
+ - `color_difference`: `{{ string | color_difference: string }}` returns `number`
355
+ - `color_extract`: `{{ string | color_extract: string }}` returns `number`
356
+ - `color_lighten`: `{{ string | color_lighten: number }}` returns `string`
357
+ - `color_mix`: `{{ string | color_mix: string, number }}` returns `string`
358
+ - `color_modify`: `{{ string | color_modify: string, number }}` returns `string`
359
+ - `color_saturate`: `{{ string | color_saturate: number }}` returns `string`
360
+ - `color_to_hex`: `{{ string | color_to_hex }}` returns `string`
361
+ - `color_to_hsl`: `{{ string | color_to_hsl }}` returns `string`
362
+ - `color_to_oklch`: `{{ string | color_to_oklch }}` returns `string`
363
+ - `color_to_rgb`: `{{ string | color_to_rgb }}` returns `string`
364
+ - `hex_to_rgba`: `{{ string | hex_to_rgba }}` returns `string`
365
+
366
+ #### Customer
367
+ - `customer_login_link`: `{{ string | customer_login_link }}` returns `string`
368
+ - `customer_logout_link`: `{{ string | customer_logout_link }}` returns `string`
369
+ - `customer_register_link`: `{{ string | customer_register_link }}` returns `string`
370
+ - `avatar`: `{{ customer | avatar }}` returns `string`
371
+ - `login_button`: `{{ shop | login_button }}` returns `string`
372
+
373
+ #### Date
374
+ - `date`: `{{ date | date: string }}` returns `string`
375
+
376
+ #### Default
377
+ - `default_errors`: `{{ string | default_errors }}` returns `string`
378
+ - `default`: `{{ variable | default: variable }}` returns `untyped`
379
+ - `default_pagination`: `{{ paginate | default_pagination }}` returns `string`
380
+
381
+ #### Font
382
+ - `font_face`: `{{ font | font_face }}` returns `string`
383
+ - `font_modify`: `{{ font | font_modify: string, string }}` returns `font`
384
+ - `font_url`: `{{ font | font_url }}` returns `string`
385
+
386
+ #### Format
387
+ - `date`: `{{ string | date: string }}` returns `string`
388
+ - `json`: `{{ variable | json }}` returns `string`
389
+ - `structured_data`: `{{ variable | structured_data }}` returns `string`
390
+ - `unit_price_with_measurement`: `{{ number | unit_price_with_measurement: unit_price_measurement }}` returns `string`
391
+ - `weight_with_unit`: `{{ number | weight_with_unit }}` returns `string`
392
+
393
+ #### Hosted_file
394
+ - `asset_img_url`: `{{ string | asset_img_url }}` returns `string`
395
+ - `asset_url`: `{{ string | asset_url }}` returns `string`
396
+ - `file_img_url`: `{{ string | file_img_url }}` returns `string`
397
+ - `file_url`: `{{ string | file_url }}` returns `string`
398
+ - `global_asset_url`: `{{ string | global_asset_url }}` returns `string`
399
+ - `shopify_asset_url`: `{{ string | shopify_asset_url }}` returns `string`
400
+
401
+ #### Html
402
+ - `time_tag`: `{{ string | time_tag: string }}` returns `string`
403
+ - `inline_asset_content`: `{{ asset_name | inline_asset_content }}` returns `string`
404
+ - `highlight`: `{{ string | highlight: string }}` returns `string`
405
+ - `link_to`: `{{ string | link_to: string }}` returns `string`
406
+ - `placeholder_svg_tag`: `{{ string | placeholder_svg_tag }}` returns `string`
407
+ - `preload_tag`: `{{ string | preload_tag: as: string }}` returns `string`
408
+ - `script_tag`: `{{ string | script_tag }}` returns `string`
409
+ - `stylesheet_tag`: `{{ string | stylesheet_tag }}` returns `string`
410
+
411
+ #### Localization
412
+ - `currency_selector`: `{{ form | currency_selector }}` returns `string`
413
+ - `translate`: `{{ string | t }}` returns `string`
414
+ - `format_address`: `{{ address | format_address }}` returns `string`
415
+
416
+ #### Math
417
+ - `abs`: `{{ number | abs }}` returns `number`
418
+ - `at_least`: `{{ number | at_least }}` returns `number`
419
+ - `at_most`: `{{ number | at_most }}` returns `number`
420
+ - `ceil`: `{{ number | ceil }}` returns `number`
421
+ - `divided_by`: `{{ number | divided_by: number }}` returns `number`
422
+ - `floor`: `{{ number | floor }}` returns `number`
423
+ - `minus`: `{{ number | minus: number }}` returns `number`
424
+ - `modulo`: `{{ number | modulo: number }}` returns `number`
425
+ - `plus`: `{{ number | plus: number }}` returns `number`
426
+ - `round`: `{{ number | round }}` returns `number`
427
+ - `times`: `{{ number | times: number }}` returns `number`
428
+
429
+ #### Media
430
+ - `external_video_tag`: `{{ variable | external_video_tag }}` returns `string`
431
+ - `external_video_url`: `{{ media | external_video_url: attribute: string }}` returns `string`
432
+ - `image_tag`: `{{ string | image_tag }}` returns `string`
433
+ - `media_tag`: `{{ media | media_tag }}` returns `string`
434
+ - `model_viewer_tag`: `{{ media | model_viewer_tag }}` returns `string`
435
+ - `video_tag`: `{{ media | video_tag }}` returns `string`
436
+ - `article_img_url`: `{{ variable | article_img_url }}` returns `string`
437
+ - `collection_img_url`: `{{ variable | collection_img_url }}` returns `string`
438
+ - `image_url`: `{{ variable | image_url: width: number, height: number }}` returns `string`
439
+ - `img_tag`: `{{ string | img_tag }}` returns `string`
440
+ - `img_url`: `{{ variable | img_url }}` returns `string`
441
+ - `product_img_url`: `{{ variable | product_img_url }}` returns `string`
442
+
443
+ #### Metafield
444
+ - `metafield_tag`: `{{ metafield | metafield_tag }}` returns `string`
445
+ - `metafield_text`: `{{ metafield | metafield_text }}` returns `string`
446
+
447
+ #### Money
448
+ - `money`: `{{ number | money }}` returns `string`
449
+ - `money_with_currency`: `{{ number | money_with_currency }}` returns `string`
450
+ - `money_without_currency`: `{{ number | money_without_currency }}` returns `string`
451
+ - `money_without_trailing_zeros`: `{{ number | money_without_trailing_zeros }}` returns `string`
452
+
453
+ #### Payment
454
+ - `payment_button`: `{{ form | payment_button }}` returns `string`
455
+ - `payment_terms`: `{{ form | payment_terms }}` returns `string`
456
+ - `payment_type_img_url`: `{{ string | payment_type_img_url }}` returns `string`
457
+ - `payment_type_svg_tag`: `{{ string | payment_type_svg_tag }}` returns `string`
458
+
459
+ #### String
460
+ - `blake3`: `{{ string | blake3 }}` returns `string`
461
+ - `hmac_sha1`: `{{ string | hmac_sha1: string }}` returns `string`
462
+ - `hmac_sha256`: `{{ string | hmac_sha256: string }}` returns `string`
463
+ - `md5`: `{{ string | md5 }}` returns `string`
464
+ - `sha1`: `{{ string | sha1: string }}` returns `string`
465
+ - `sha256`: `{{ string | sha256: string }}` returns `string`
466
+ - `append`: `{{ string | append: string }}` returns `string`
467
+ - `base64_decode`: `{{ string | base64_decode }}` returns `string`
468
+ - `base64_encode`: `{{ string | base64_encode }}` returns `string`
469
+ - `base64_url_safe_decode`: `{{ string | base64_url_safe_decode }}` returns `string`
470
+ - `base64_url_safe_encode`: `{{ string | base64_url_safe_encode }}` returns `string`
471
+ - `capitalize`: `{{ string | capitalize }}` returns `string`
472
+ - `downcase`: `{{ string | downcase }}` returns `string`
473
+ - `escape`: `{{ string | escape }}` returns `string`
474
+ - `escape_once`: `{{ string | escape_once }}` returns `string`
475
+ - `lstrip`: `{{ string | lstrip }}` returns `string`
476
+ - `newline_to_br`: `{{ string | newline_to_br }}` returns `string`
477
+ - `prepend`: `{{ string | prepend: string }}` returns `string`
478
+ - `remove`: `{{ string | remove: string }}` returns `string`
479
+ - `remove_first`: `{{ string | remove_first: string }}` returns `string`
480
+ - `remove_last`: `{{ string | remove_last: string }}` returns `string`
481
+ - `replace`: `{{ string | replace: string, string }}` returns `string`
482
+ - `replace_first`: `{{ string | replace_first: string, string }}` returns `string`
483
+ - `replace_last`: `{{ string | replace_last: string, string }}` returns `string`
484
+ - `rstrip`: `{{ string | rstrip }}` returns `string`
485
+ - `slice`: `{{ string | slice }}` returns `string`
486
+ - `split`: `{{ string | split: string }}` returns `array`
487
+ - `strip`: `{{ string | strip }}` returns `string`
488
+ - `strip_html`: `{{ string | strip_html }}` returns `string`
489
+ - `strip_newlines`: `{{ string | strip_newlines }}` returns `string`
490
+ - `truncate`: `{{ string | truncate: number }}` returns `string`
491
+ - `truncatewords`: `{{ string | truncatewords: number }}` returns `string`
492
+ - `upcase`: `{{ string | upcase }}` returns `string`
493
+ - `url_decode`: `{{ string | url_decode }}` returns `string`
494
+ - `url_encode`: `{{ string | url_encode }}` returns `string`
495
+ - `camelize`: `{{ string | camelize }}` returns `string`
496
+ - `handleize`: `{{ string | handleize }}` returns `string`
497
+ - `url_escape`: `{{ string | url_escape }}` returns `string`
498
+ - `url_param_escape`: `{{ string | url_param_escape }}` returns `string`
499
+ - `pluralize`: `{{ number | pluralize: string, string }}` returns `string`
500
+
501
+ #### Tag
502
+ - `link_to_add_tag`: `{{ string | link_to_add_tag }}` returns `string`
503
+ - `link_to_remove_tag`: `{{ string | link_to_remove_tag }}` returns `string`
504
+ - `link_to_tag`: `{{ string | link_to_tag }}` returns `string`
505
+
506
+ ### Liquid objects
507
+
508
+ #### Global objects
509
+ - `collections`
510
+ - `pages`
511
+ - `all_products`
512
+ - `articles`
513
+ - `blogs`
514
+ - `cart`
515
+ - `closest`
516
+ - `content_for_header`
517
+ - `customer`
518
+ - `images`
519
+ - `linklists`
520
+ - `localization`
521
+ - `metaobjects`
522
+ - `request`
523
+ - `routes`
524
+ - `shop`
525
+ - `theme`
526
+ - `settings`
527
+ - `template`
528
+ - `additional_checkout_buttons`
529
+ - `all_country_option_tags`
530
+ - `canonical_url`
531
+ - `content_for_additional_checkout_buttons`
532
+ - `content_for_index`
533
+ - `content_for_layout`
534
+ - `country_option_tags`
535
+ - `current_page`
536
+ - `handle`
537
+ - `page_description`
538
+ - `page_image`
539
+ - `page_title`
540
+ - `powered_by_link`
541
+ - `scripts`
542
+
543
+ #### `/article` page
544
+ - `article`
545
+ - `blog`
546
+
547
+ #### `/blog` page
548
+ - `blog`
549
+ - `current_tags`
550
+
551
+ #### `/cart` page
552
+ - `cart`
553
+
554
+ #### `/checkout` page
555
+ - `checkout`
556
+
557
+ #### `/collection` page
558
+ - `collection`
559
+ - `current_tags`
560
+
561
+ #### `/customers/account` page
562
+ - `customer`
563
+
564
+ #### `/customers/addresses` page
565
+ - `customer`
566
+
567
+ #### `/customers/order` page
568
+ - `customer`
569
+ - `order`
570
+
571
+ #### `/gift_card.liquid` page
572
+ - `gift_card`
573
+ - `recipient`
574
+
575
+ #### `/metaobject` page
576
+ - `metaobject`
577
+
578
+ #### `/page` page
579
+ - `page`
580
+
581
+ #### `/product` page
582
+ - `product`
583
+ - `remote_product`
584
+
585
+ #### `/robots.txt.liquid` page
586
+ - `robots`
587
+
588
+ #### `/search` page
589
+ - `search`
590
+ ### Liquid tags
591
+
592
+
593
+ #### content_for
594
+ The `content_for` tag requires a type parameter to differentiate between rendering a number of theme blocks (`'blocks'`) and a single static block (`'block'`).
595
+
596
+
597
+ Syntax:
598
+ ```
599
+ {% content_for 'blocks' %}
600
+ {% content_for 'block', type: "slide", id: "slide-1" %}
601
+ ```
602
+
603
+ #### form
604
+ Because there are many different form types available in Shopify themes, the `form` tag requires a type. Depending on the
605
+ form type, an additional parameter might be required. You can specify the following form types:
606
+
607
+ - [`activate_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-activate_customer_password)
608
+ - [`cart`](https://shopify.dev/docs/api/liquid/tags/form#form-cart)
609
+ - [`contact`](https://shopify.dev/docs/api/liquid/tags/form#form-contact)
610
+ - [`create_customer`](https://shopify.dev/docs/api/liquid/tags/form#form-create_customer)
611
+ - [`currency`](https://shopify.dev/docs/api/liquid/tags/form#form-currency)
612
+ - [`customer`](https://shopify.dev/docs/api/liquid/tags/form#form-customer)
613
+ - [`customer_address`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_address)
614
+ - [`customer_login`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_login)
615
+ - [`guest_login`](https://shopify.dev/docs/api/liquid/tags/form#form-guest_login)
616
+ - [`localization`](https://shopify.dev/docs/api/liquid/tags/form#form-localization)
617
+ - [`new_comment`](https://shopify.dev/docs/api/liquid/tags/form#form-new_comment)
618
+ - [`product`](https://shopify.dev/docs/api/liquid/tags/form#form-product)
619
+ - [`recover_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-recover_customer_password)
620
+ - [`reset_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-reset_customer_password)
621
+ - [`storefront_password`](https://shopify.dev/docs/api/liquid/tags/form#form-storefront_password)
622
+
623
+
624
+ Syntax:
625
+ ```
626
+ {% form 'form_type' %}
627
+ content
628
+ {% endform %}
629
+ ```
630
+
631
+ #### layout
632
+
633
+ Syntax:
634
+ ```
635
+ {% layout name %}
636
+ ```
637
+
638
+ #### assign
639
+ You can create variables of any [basic type](https://shopify.dev/docs/api/liquid/basics#types), [object](https://shopify.dev/docs/api/liquid/objects), or object property.
640
+
641
+ > Caution:
642
+ > Predefined Liquid objects can be overridden by variables with the same name.
643
+ > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
644
+
645
+
646
+ Syntax:
647
+ ```
648
+ {% assign variable_name = value %}
649
+ ```
650
+
651
+ #### break
652
+
653
+ Syntax:
654
+ ```
655
+ {% break %}
656
+ ```
657
+
658
+ #### capture
659
+ You can create complex strings with Liquid logic and variables.
660
+
661
+ > Caution:
662
+ > Predefined Liquid objects can be overridden by variables with the same name.
663
+ > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
664
+
665
+
666
+ Syntax:
667
+ ```
668
+ {% capture variable %}
669
+ value
670
+ {% endcapture %}
671
+ ```
672
+
673
+ #### case
674
+
675
+ Syntax:
676
+ ```
677
+ {% case variable %}
678
+ {% when first_value %}
679
+ first_expression
680
+ {% when second_value %}
681
+ second_expression
682
+ {% else %}
683
+ third_expression
684
+ {% endcase %}
685
+ ```
686
+
687
+ #### comment
688
+ Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
689
+
690
+
691
+ Syntax:
692
+ ```
693
+ {% comment %}
694
+ content
695
+ {% endcomment %}
696
+ ```
697
+
698
+ #### continue
699
+
700
+ Syntax:
701
+ ```
702
+ {% continue %}
703
+ ```
704
+
705
+ #### cycle
706
+ The `cycle` tag must be used inside a `for` loop.
707
+
708
+ > Tip:
709
+ > Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.
710
+
711
+
712
+ Syntax:
713
+ ```
714
+ {% cycle string, string, ... %}
715
+ ```
716
+
717
+ #### decrement
718
+ Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
719
+ or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
720
+ [snippets](/themes/architecture/snippets) included in the file.
721
+
722
+ Similarly, variables that are created with `decrement` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign)
723
+ and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](https://shopify.dev/docs/api/liquid/tags/increment) share
724
+ variables.
725
+
726
+
727
+ Syntax:
728
+ ```
729
+ {% decrement variable_name %}
730
+ ```
731
+
732
+ #### doc
733
+ The `doc` tag allows developers to include documentation within Liquid
734
+ templates. Any content inside `doc` tags is not rendered or outputted.
735
+ Liquid code inside will be parsed but not executed. This facilitates
736
+ tooling support for features like code completion, linting, and inline
737
+ documentation.
738
+
739
+ For detailed documentation syntax and examples, see the
740
+ [`LiquidDoc` reference](https://shopify.dev/docs/storefronts/themes/tools/liquid-doc).
741
+
742
+
743
+ Syntax:
744
+ ```
745
+ {% doc %}
746
+ Renders a message.
747
+
748
+ @param {string} foo - A string value.
749
+ @param {string} [bar] - An optional string value.
750
+
751
+ @example
752
+ {% render 'message', foo: 'Hello', bar: 'World' %}
753
+ {% enddoc %}
754
+ ```
755
+
756
+ #### echo
757
+ Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
758
+ bracket method, you can use the `echo` tag inside [`liquid` tags](https://shopify.dev/docs/api/liquid/tags/liquid).
759
+
760
+ > Tip:
761
+ > You can use [filters](https://shopify.dev/docs/api/liquid/filters) on expressions inside `echo` tags.
762
+
763
+
764
+ Syntax:
765
+ ```
766
+ {% liquid
767
+ echo expression
768
+ %}
769
+ ```
770
+
771
+ #### for
772
+ 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
773
+ [`paginate` tag](https://shopify.dev/docs/api/liquid/tags/paginate) to split the items over multiple pages.
774
+
775
+ > Tip:
776
+ > Every `for` loop has an associated [`forloop` object](https://shopify.dev/docs/api/liquid/objects/forloop) with information about the loop.
777
+
778
+
779
+ Syntax:
780
+ ```
781
+ {% for variable in array %}
782
+ expression
783
+ {% endfor %}
784
+ ```
785
+
786
+ #### if
787
+
788
+ Syntax:
789
+ ```
790
+ {% if condition %}
791
+ expression
792
+ {% endif %}
793
+ ```
794
+
795
+ #### increment
796
+ Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
797
+ or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
798
+ [snippets](/themes/architecture/snippets) included in the file.
799
+
800
+ Similarly, variables that are created with `increment` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign)
801
+ and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](https://shopify.dev/docs/api/liquid/tags/decrement) share
802
+ variables.
803
+
804
+
805
+ Syntax:
806
+ ```
807
+ {% increment variable_name %}
808
+ ```
809
+
810
+ #### raw
811
+
812
+ Syntax:
813
+ ```
814
+ {% raw %}
815
+ expression
816
+ {% endraw %}
817
+ ```
818
+
819
+ #### render
820
+ Inside snippets and app blocks, you can't directly access variables that are [created](https://shopify.dev/docs/api/liquid/tags/variable-tags) outside
821
+ of the snippet or app block. However, you can [specify variables as parameters](https://shopify.dev/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
822
+ to pass outside variables to snippets.
823
+
824
+ While you can't directly access created variables, you can access global objects, as well as any objects that are
825
+ directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
826
+ can access the [`product` object](https://shopify.dev/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
827
+ can access the [`section` object](https://shopify.dev/docs/api/liquid/objects/section).
828
+
829
+ Outside a snippet or app block, you can't access variables created inside the snippet or app block.
830
+
831
+ > Note:
832
+ > When you render a snippet using the `render` tag, you can't use the [`include` tag](https://shopify.dev/docs/api/liquid/tags/include)
833
+ > inside the snippet.
834
+
835
+
836
+ Syntax:
837
+ ```
838
+ {% render 'filename' %}
839
+ ```
840
+
841
+ #### tablerow
842
+ The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
843
+
844
+ > Tip:
845
+ > Every `tablerow` loop has an associated [`tablerowloop` object](https://shopify.dev/docs/api/liquid/objects/tablerowloop) with information about the loop.
846
+
847
+
848
+ Syntax:
849
+ ```
850
+ {% tablerow variable in array %}
851
+ expression
852
+ {% endtablerow %}
853
+ ```
854
+
855
+ #### unless
856
+ > Tip:
857
+ > Similar to the [`if` tag](https://shopify.dev/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
858
+
859
+
860
+ Syntax:
861
+ ```
862
+ {% unless condition %}
863
+ expression
864
+ {% endunless %}
865
+ ```
866
+
867
+ #### paginate
868
+ Because [`for` loops](https://shopify.dev/docs/api/liquid/tags/for) are limited to 50 iterations per page, you need to use the `paginate` tag to
869
+ iterate over an array that has more than 50 items. The following arrays can be paginated:
870
+
871
+ - [`article.comments`](https://shopify.dev/docs/api/liquid/objects/article#article-comments)
872
+ - [`blog.articles`](https://shopify.dev/docs/api/liquid/objects/blog#blog-articles)
873
+ - [`collections`](https://shopify.dev/docs/api/liquid/objects/collections)
874
+ - [`collection.products`](https://shopify.dev/docs/api/liquid/objects/collection#collection-products)
875
+ - [`customer.addresses`](https://shopify.dev/docs/api/liquid/objects/customer#customer-addresses)
876
+ - [`customer.orders`](https://shopify.dev/docs/api/liquid/objects/customer#customer-orders)
877
+ - [`metaobject_definition.values`](https://shopify.dev/docs/api/liquid/objects/metaobject_definition#metaobject_definition-values)
878
+ - [`pages`](https://shopify.dev/docs/api/liquid/objects/pages)
879
+ - [`product.variants`](https://shopify.dev/docs/api/liquid/objects/product#variants)
880
+ - [`search.results`](https://shopify.dev/docs/api/liquid/objects/search#search-results)
881
+ - [`article_list` settings](/themes/architecture/settings/input-settings#article_list)
882
+ - [`collection_list` settings](/themes/architecture/settings/input-settings#collection_list)
883
+ - [`product_list` settings](/themes/architecture/settings/input-settings#product_list)
884
+
885
+ Within the `paginate` tag, you have access to the [`paginate` object](https://shopify.dev/docs/api/liquid/objects/paginate). You can use this
886
+ object, or the [`default_pagination` filter](https://shopify.dev/docs/api/liquid/filters/default_pagination), to build page navigation.
887
+
888
+ > Note:
889
+ > The `paginate` tag allows the user to paginate to the 25,000th item in the array and no further. To reach items further in
890
+ > the array the array should be filtered further before paginating. See
891
+ > [Pagination Limits](/themes/best-practices/performance/platform#pagination-limits) for more information.
892
+
893
+
894
+ Syntax:
895
+ ```
896
+ {% paginate array by page_size %}
897
+ {% for item in array %}
898
+ forloop_content
899
+ {% endfor %}
900
+ {% endpaginate %}
901
+ ```
902
+
903
+ #### javascript
904
+ Each section, block or snippet can have only one `{% javascript %}` tag.
905
+
906
+ To learn more about how JavaScript that's defined between the `javascript` tags is loaded and run, refer to the documentation for [javascript tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#javascript).
907
+ > Caution:
908
+ > Liquid isn't rendered inside of `{% javascript %}` tags. Including Liquid code can cause syntax errors.
909
+
910
+
911
+ Syntax:
912
+ ```
913
+ {% javascript %}
914
+ javascript_code
915
+ {% endjavascript %}
916
+ ```
917
+
918
+ #### section
919
+ Rendering a section with the `section` tag renders a section statically. To learn more about sections and how to use
920
+ them in your theme, refer to [Render a section](/themes/architecture/sections#render-a-section).
921
+
922
+
923
+ Syntax:
924
+ ```
925
+ {% section 'name' %}
926
+ ```
927
+
928
+ #### stylesheet
929
+ Each section, block or snippet can have only one `{% stylesheet %}` tag.
930
+
931
+ To learn more about how CSS that's defined between the `stylesheet` tags is loaded and run, refer to the documentation for [stylesheet tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#stylesheet).
932
+ > Caution:
933
+ > Liquid isn't rendered inside of `{% stylesheet %}` tags. Including Liquid code can cause syntax errors.
934
+
935
+
936
+ Syntax:
937
+ ```
938
+ {% stylesheet %}
939
+ css_styles
940
+ {% endstylesheet %}
941
+ ```
942
+
943
+ #### sections
944
+ Use this tag to render section groups as part of the theme's [layout](/themes/architecture/layouts) content. Place the `sections` tag where you want to render it in the layout.
945
+
946
+ To learn more about section groups and how to use them in your theme, refer to [Section groups](/themes/architecture/section-groups#usage).
947
+
948
+
949
+ Syntax:
950
+ ```
951
+ {% sections 'name' %}
952
+ ```
953
+
954
+ #### style
955
+ > Note:
956
+ > If you reference [color settings](/themes/architecture/settings/input-settings#color) inside `style` tags, then
957
+ > the associated CSS rules will update as the setting is changed in the theme editor, without a page refresh.
958
+
959
+
960
+ Syntax:
961
+ ```
962
+ {% style %}
963
+ CSS_rules
964
+ {% endstyle %}
965
+ ```
966
+
967
+ #### else
968
+ You can use the `else` tag with the following tags:
969
+
970
+ - [`case`](https://shopify.dev/docs/api/liquid/tags/case)
971
+ - [`if`](https://shopify.dev/docs/api/liquid/tags/if)
972
+ - [`unless`](https://shopify.dev/docs/api/liquid/tags/unless)
973
+
974
+
975
+ Syntax:
976
+ ```
977
+ {% else %}
978
+ expression
979
+ ```
980
+
981
+ #### else
982
+
983
+ Syntax:
984
+ ```
985
+ {% for variable in array %}
986
+ first_expression
987
+ {% else %}
988
+ second_expression
989
+ {% endfor %}
990
+ ```
991
+
992
+ #### liquid
993
+ Because the tags don't have delimeters, each tag needs to be on its own line.
994
+
995
+ > Tip:
996
+ > Use the [`echo` tag](https://shopify.dev/docs/api/liquid/tags/echo) to output an expression inside `liquid` tags.
997
+
998
+
999
+ Syntax:
1000
+ ```
1001
+ {% liquid
1002
+ expression
1003
+ %}
1004
+ ```
1005
+
1006
+
1007
+ ## Translation development standards
1008
+
1009
+ ### Translation requirements
1010
+
1011
+ - **Every user-facing text** must use translation filters.
1012
+ - **Update `locales/en.default.json`** with all new keys.
1013
+ - **Use descriptive, hierarchical keys** for organization.
1014
+ - **Only add English text**; translators handle other languages.
1015
+
1016
+ ### Translation filter usage
1017
+
1018
+ **Use `{{ 'key' | t }}` for all text:**
1019
+
1020
+ ```liquid
1021
+ <!-- Good -->
1022
+ <h2>{{ 'sections.featured_collection.title' | t }}</h2>
1023
+ <p>{{ 'sections.featured_collection.description' | t }}</p>
1024
+ <button>{{ 'products.add_to_cart' | t }}</button>
1025
+
1026
+ <!-- Bad -->
1027
+ <h2>Featured Collection</h2>
1028
+ <p>Check out our best products</p>
1029
+ <button>Add to cart</button>
1030
+ ```
1031
+
1032
+ ### Translation with variables
1033
+
1034
+ **Use variables for interpolation:**
1035
+
1036
+ ```liquid
1037
+ <!-- Liquid template -->
1038
+ <p>{{ 'products.price_range' | t: min: product.price_min | money, max: product.price_max | money }}</p>
1039
+ <p>{{ 'general.pagination.page' | t: page: paginate.current_page, pages: paginate.pages }}</p>
1040
+ ```
1041
+
1042
+ **Corresponding keys in locale files:**
1043
+
1044
+ ```json
1045
+ {
1046
+ "products": {
1047
+ "price_range": "From {{ min }} to {{ max }}"
1048
+ },
1049
+ "general": {
1050
+ "pagination": {
1051
+ "page": "Page {{ page }} of {{ pages }}"
1052
+ }
1053
+ }
1054
+ }
1055
+ ```
1056
+
1057
+ ### Best practices
1058
+
1059
+ **Content guidelines:**
1060
+ - Write clear, concise text.
1061
+ - **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`).
1062
+ - Be consistent with terminology.
1063
+ - Consider character limits for UI elements.
1064
+
1065
+ **Variable usage:**
1066
+ - Use interpolation rather than appending strings together.
1067
+ - Prioritize clarity over brevity for variable naming.
1068
+ - Escape variables unless they output HTML: `{{ variable | escape }}`.
1069
+
1070
+
1071
+ ## Localization standards
1072
+
1073
+ Auto-attached when working in `locales/` directory.
1074
+
1075
+ ### File structure
1076
+
1077
+ ```
1078
+ locales/
1079
+ ├── en.default.json # English (required)
1080
+ ├── en.default.schema.json # English (required)
1081
+ ├── es.json # Spanish
1082
+ ├── est.schema.json # Spanish
1083
+ ├── fr.json # French
1084
+ ├── frt.schema.json # French
1085
+ └── pt-BR.json # Portuguese
1086
+ └── pt-BR..schema.json # Portuguese
1087
+ ```
1088
+
1089
+ #### Locale files
1090
+
1091
+ 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.
1092
+
1093
+ **Example:**
1094
+ ```json
1095
+ {
1096
+ "general": {
1097
+ "cart": "Cart",
1098
+ "checkout": "Checkout"
1099
+ },
1100
+ "products": {
1101
+ "add_to_cart": "Add to Cart"
1102
+ }
1103
+ }
1104
+ ```
1105
+
1106
+ #### Schema locale files
1107
+
1108
+ 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.
1109
+
1110
+ **Example:**
1111
+ ```json
1112
+ {
1113
+ "products": {
1114
+ "card": {
1115
+ "description": "Product card layout"
1116
+ }
1117
+ }
1118
+ }
1119
+ ```
1120
+
1121
+ ### Key organization
1122
+
1123
+ **Hierarchical structure:**
1124
+ ```json
1125
+ {
1126
+ "general": {
1127
+ "meta": {
1128
+ "title": "{{ shop_name }}",
1129
+ "description": "{{ shop_description }}"
1130
+ },
1131
+ "accessibility": {
1132
+ "skip_to_content": "Skip to content",
1133
+ "close": "Close"
1134
+ }
1135
+ },
1136
+ "products": {
1137
+ "add_to_cart": "Add to cart",
1138
+ "quick_view": "Quick view",
1139
+ "price": {
1140
+ "regular": "Regular price",
1141
+ "sale": "Sale price",
1142
+ "unit": "Unit price"
1143
+ }
1144
+ }
1145
+ }
1146
+ ```
1147
+ **Usage**
1148
+ ```liquid
1149
+ {{ 'general.meta.title' | t: shop_name: shop.name }}
1150
+ {{ 'general.meta.description' | t: shop_description: shop.description }}
1151
+ ```
1152
+
1153
+ ### Translation guidelines
1154
+
1155
+ **Key naming:**
1156
+ - Use descriptive, hierarchical keys
1157
+ - Maximum 3 levels deep
1158
+ - Use snake_case for key names
1159
+ - Group related translations
1160
+
1161
+ **Content rules:**
1162
+ - Keep text concise for UI elements
1163
+ - Use variables for dynamic content
1164
+ - Consider character limits
1165
+ - Maintain consistent terminology
1166
+
1167
+ ## Examples per kind of asset
1168
+
1169
+ ### `snippet`
1170
+
1171
+ ```liquid
1172
+ {% doc %}
1173
+ Renders a responsive image that might be wrapped in a link.
1174
+
1175
+ When `width`, `height` and `crop` are provided, the image will be rendered
1176
+ with a fixed aspect ratio.
1177
+
1178
+ Serves as an example of how to use the `image_url` filter and `image_tag` filter
1179
+ as well as how you can use LiquidDoc to document your code.
1180
+
1181
+ @param {image} image - The image to be rendered
1182
+ @param {string} [url] - An optional destination URL for the image
1183
+ @param {string} [css_class] - Optional class to be added to the image wrapper
1184
+ @param {number} [width] - The highest resolution width of the image to be rendered
1185
+ @param {number} [height] - The highest resolution height of the image to be rendered
1186
+ @param {string} [crop] - The crop position of the image
1187
+
1188
+ @example
1189
+ {% render 'image', image: product.featured_image %}
1190
+ {% render 'image', image: product.featured_image, url: product.url %}
1191
+ {% render 'image',
1192
+ css_class: 'product__image',
1193
+ image: product.featured_image,
1194
+ url: product.url,
1195
+ width: 1200,
1196
+ height: 800,
1197
+ crop: 'center',
1198
+ %}
1199
+ {% enddoc %}
1200
+
1201
+ {% liquid
1202
+ unless height
1203
+ assign width = width | default: image.width
1204
+ endunless
1205
+
1206
+ if url
1207
+ assign wrapper = 'a'
1208
+ else
1209
+ assign wrapper = 'div'
1210
+ endif
1211
+ %}
1212
+
1213
+ <{{ wrapper }}
1214
+ class="image {{ css_class }}"
1215
+ {% if url %}
1216
+ href="{{ url }}"
1217
+ {% endif %}
1218
+ >
1219
+ {{ image | image_url: width: width, height: height, crop: crop | image_tag }}
1220
+ </{{ wrapper }}>
1221
+
1222
+ {% stylesheet %}
1223
+ .image {
1224
+ display: block;
1225
+ position: relative;
1226
+ overflow: hidden;
1227
+ width: 100%;
1228
+ height: auto;
1229
+ }
1230
+
1231
+ .image > img {
1232
+ width: 100%;
1233
+ height: auto;
1234
+ }
1235
+ {% endstylesheet %}
1236
+
1237
+ {% javascript %}
1238
+ function doSomething() {
1239
+ // example
1240
+ }
1241
+ doSomething()
1242
+ {% endjavascript %}
1243
+
1244
+ ```
1245
+
1246
+ ### `block`
1247
+
1248
+ #### Text
1249
+
1250
+ ```liquid
1251
+ {% doc %}
1252
+ Renders a text block.
1253
+
1254
+ @example
1255
+ {% content_for 'block', type: 'text', id: 'text' %}
1256
+ {% enddoc %}
1257
+
1258
+ <div
1259
+ class="text {{ block.settings.text_style }}"
1260
+ style="--text-align: {{ block.settings.alignment }}"
1261
+ {{ block.shopify_attributes }}
1262
+ >
1263
+ {{ block.settings.text }}
1264
+ </div>
1265
+
1266
+ {% stylesheet %}
1267
+ .text {
1268
+ text-align: var(--text-align);
1269
+ }
1270
+ .text--title {
1271
+ font-size: 2rem;
1272
+ font-weight: 700;
1273
+ }
1274
+ .text--subtitle {
1275
+ font-size: 1.5rem;
1276
+ }
1277
+ {% endstylesheet %}
1278
+
1279
+ {% schema %}
1280
+ {
1281
+ "name": "t:general.text",
1282
+ "settings": [
1283
+ {
1284
+ "type": "text",
1285
+ "id": "text",
1286
+ "label": "t:labels.text",
1287
+ "default": "Text"
1288
+ },
1289
+ {
1290
+ "type": "select",
1291
+ "id": "text_style",
1292
+ "label": "t:labels.text_style",
1293
+ "options": [
1294
+ { "value": "text--title", "label": "t:options.text_style.title" },
1295
+ { "value": "text--subtitle", "label": "t:options.text_style.subtitle" },
1296
+ { "value": "text--normal", "label": "t:options.text_style.normal" }
1297
+ ],
1298
+ "default": "text--title"
1299
+ },
1300
+ {
1301
+ "type": "text_alignment",
1302
+ "id": "alignment",
1303
+ "label": "t:labels.alignment",
1304
+ "default": "left"
1305
+ }
1306
+ ],
1307
+ "presets": [{ "name": "t:general.text" }]
1308
+ }
1309
+ {% endschema %}
1310
+ ```
1311
+
1312
+ #### Group
1313
+
1314
+ ```liquid
1315
+ {% doc %}
1316
+ Renders a group of blocks with configurable layout direction, gap and
1317
+ alignment.
1318
+
1319
+ All settings apply to only one dimension to reduce configuration complexity.
1320
+
1321
+ This component is a wrapper concerned only with rendering its children in
1322
+ the specified layout direction with appropriate padding and alignment.
1323
+
1324
+ @example
1325
+ {% content_for 'block', type: 'group', id: 'group' %}
1326
+ {% enddoc %}
1327
+
1328
+ <div
1329
+ class="group {{ block.settings.layout_direction }}"
1330
+ style="
1331
+ --padding: {{ block.settings.padding }}px;
1332
+ --alignment: {{ block.settings.alignment }};
1333
+ "
1334
+ {{ block.shopify_attributes }}
1335
+ >
1336
+ {% content_for 'blocks' %}
1337
+ </div>
1338
+
1339
+ {% stylesheet %}
1340
+ .group {
1341
+ display: flex;
1342
+ flex-wrap: nowrap;
1343
+ overflow: hidden;
1344
+ width: 100%;
1345
+ }
1346
+ .group--horizontal {
1347
+ flex-direction: row;
1348
+ justify-content: space-between;
1349
+ align-items: center;
1350
+ padding: 0 var(--padding);
1351
+ }
1352
+ .group--vertical {
1353
+ flex-direction: column;
1354
+ align-items: var(--alignment);
1355
+ padding: var(--padding) 0;
1356
+ }
1357
+ {% endstylesheet %}
1358
+
1359
+ {% schema %}
1360
+ {
1361
+ "name": "t:general.group",
1362
+ "blocks": [{ "type": "@theme" }],
1363
+ "settings": [
1364
+ {
1365
+ "type": "select",
1366
+ "id": "layout_direction",
1367
+ "label": "t:labels.layout_direction",
1368
+ "default": "group--vertical",
1369
+ "options": [
1370
+ { "value": "group--horizontal", "label": "t:options.direction.horizontal" },
1371
+ { "value": "group--vertical", "label": "t:options.direction.vertical" }
1372
+ ]
1373
+ },
1374
+ {
1375
+ "visible_if": "{{ block.settings.layout_direction == 'group--vertical' }}",
1376
+ "type": "select",
1377
+ "id": "alignment",
1378
+ "label": "t:labels.alignment",
1379
+ "default": "flex-start",
1380
+ "options": [
1381
+ { "value": "flex-start", "label": "t:options.alignment.left" },
1382
+ { "value": "center", "label": "t:options.alignment.center" },
1383
+ { "value": "flex-end", "label": "t:options.alignment.right" }
1384
+ ]
1385
+ },
1386
+ {
1387
+ "type": "range",
1388
+ "id": "padding",
1389
+ "label": "t:labels.padding",
1390
+ "default": 0,
1391
+ "min": 0,
1392
+ "max": 200,
1393
+ "step": 2,
1394
+ "unit": "px"
1395
+ }
1396
+ ],
1397
+ "presets": [
1398
+ {
1399
+ "name": "t:general.column",
1400
+ "category": "t:general.layout",
1401
+ "settings": {
1402
+ "layout_direction": "group--vertical",
1403
+ "alignment": "flex-start",
1404
+ "padding": 0
1405
+ }
1406
+ },
1407
+ {
1408
+ "name": "t:general.row",
1409
+ "category": "t:general.layout",
1410
+ "settings": {
1411
+ "layout_direction": "group--horizontal",
1412
+ "padding": 0
1413
+ }
1414
+ }
1415
+ ]
1416
+ }
1417
+ {% endschema %}
1418
+ ```
1419
+
1420
+ ### `section`
1421
+
1422
+ ```liquid
1423
+ <div class="example-section full-width">
1424
+ {% if section.settings.background_image %}
1425
+ <div class="example-section__background">
1426
+ {{ section.settings.background_image | image_url: width: 2000 | image_tag }}
1427
+ </div>
1428
+ {% endif %}
1429
+
1430
+ <div class="custom-section__content">
1431
+ {% content_for 'blocks' %}
1432
+ </div>
1433
+ </div>
1434
+
1435
+ {% stylesheet %}
1436
+ .example-section {
1437
+ position: relative;
1438
+ overflow: hidden;
1439
+ width: 100%;
1440
+ }
1441
+ .example-section__background {
1442
+ position: absolute;
1443
+ width: 100%;
1444
+ height: 100%;
1445
+ z-index: -1;
1446
+ overflow: hidden;
1447
+ }
1448
+ .example-section__background img {
1449
+ position: absolute;
1450
+ width: 100%;
1451
+ height: auto;
1452
+ top: 50%;
1453
+ left: 50%;
1454
+ transform: translate(-50%, -50%);
1455
+ }
1456
+ .example-section__content {
1457
+ display: grid;
1458
+ grid-template-columns: var(--content-grid);
1459
+ }
1460
+
1461
+ .example-section__content > * {
1462
+ grid-column: 2;
1463
+ }
1464
+ {% endstylesheet %}
1465
+
1466
+ {% schema %}
1467
+ {
1468
+ "name": "t:general.custom_section",
1469
+ "blocks": [{ "type": "@theme" }],
1470
+ "settings": [
1471
+ {
1472
+ "type": "image_picker",
1473
+ "id": "background_image",
1474
+ "label": "t:labels.background"
1475
+ }
1476
+ ],
1477
+ "presets": [
1478
+ {
1479
+ "name": "t:general.custom_section"
1480
+ }
1481
+ ]
1482
+ }
1483
+ {% endschema %}
1484
+ ```
1485
+