skills/shopify/liquid-patterns/SKILL.md
Common Liquid code patterns for Shopify theme development. Use when writing Liquid templates, handling translations, product displays, or theme customizations.
npx skillsauth add javeedishaq/ai-workflow-orchestrator liquid-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
{% comment %} Use locale files for hardcoded text {% endcomment %}
{{ 'products.benefits.lifetime_guarantee' | t }}
{% comment %} With default fallback {% endcomment %}
{{ 'products.benefits.lifetime_description' | t: default: 'Fallback text' }}
{% comment %} Dynamic key translation {% endcomment %}
{% assign key = 'products.' | append: product.type | append: '.title' %}
{{ key | t }}
<div class="product-card" data-product-id="{{ product.id }}">
{% if product.featured_image %}
<img
src="{{ product.featured_image | image_url: width: 400 }}"
alt="{{ product.featured_image.alt | escape }}"
loading="lazy"
width="400"
height="{{ 400 | divided_by: product.featured_image.aspect_ratio | round }}"
>
{% endif %}
<h3>{{ product.title }}</h3>
{% if product.compare_at_price > product.price %}
<span class="price--sale">{{ product.price | money }}</span>
<span class="price--compare">{{ product.compare_at_price | money }}</span>
{% else %}
<span class="price">{{ product.price | money }}</span>
{% endif %}
</div>
{% for option in product.options_with_values %}
<div class="option-selector">
<label for="option-{{ forloop.index }}">{{ option.name }}</label>
<select id="option-{{ forloop.index }}" name="options[{{ option.name }}]">
{% for value in option.values %}
<option
value="{{ value }}"
{% if option.selected_value == value %}selected{% endif %}
>
{{ value }}
</option>
{% endfor %}
</select>
</div>
{% endfor %}
{% comment %} Responsive images with srcset {% endcomment %}
{% assign image = product.featured_image %}
<img
src="{{ image | image_url: width: 600 }}"
srcset="{{ image | image_url: width: 300 }} 300w,
{{ image | image_url: width: 600 }} 600w,
{{ image | image_url: width: 900 }} 900w"
sizes="(max-width: 600px) 300px, (max-width: 900px) 600px, 900px"
alt="{{ image.alt | escape }}"
loading="lazy"
>
{% paginate collection.products by 12 %}
<div class="product-grid">
{% for product in collection.products %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
{% if paginate.pages > 1 %}
{% render 'pagination', paginate: paginate %}
{% endif %}
{% endpaginate %}
{% comment %} Product metafield {% endcomment %}
{% if product.metafields.custom.badge_type %}
<span class="badge-type">{{ product.metafields.custom.badge_type.value }}</span>
{% endif %}
{% comment %} Shop metafield {% endcomment %}
{{ shop.metafields.global.announcement | metafield_tag }}
{% comment %} Check for specific template {% endcomment %}
{% if template.name == 'product' %}
{% comment %} Product-specific code {% endcomment %}
{% endif %}
{% comment %} Check customer status {% endcomment %}
{% if customer %}
{{ 'account.welcome' | t: name: customer.first_name }}
{% else %}
{{ 'account.login_prompt' | t }}
{% endif %}
{% form 'product', product %}
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<div class="quantity">
<label for="quantity">{{ 'products.quantity' | t }}</label>
<input type="number" id="quantity" name="quantity" value="1" min="1">
</div>
<button
type="submit"
name="add"
{% unless product.available %}disabled{% endunless %}
>
{% if product.available %}
{{ 'products.add_to_cart' | t }}
{% else %}
{{ 'products.sold_out' | t }}
{% endif %}
</button>
{% endform %}
{% schema %}
{
"name": "Custom Section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Section Heading"
},
{
"type": "image_picker",
"id": "image",
"label": "Image"
}
],
"presets": [
{
"name": "Custom Section"
}
]
}
{% endschema %}
Always use:
{% comment %} Correct {% endcomment %}
{{ 'products.badge.title' | t }}
{% comment %} Wrong - never use {% endcomment %}
{{ 'products.ecusson.title' | t }}
tools
# Test Patterns Testing patterns for reliable, maintainable, and fast tests. > **Template Usage:** Customize for your test framework (Vitest, Jest, Playwright, etc.) and assertion library. ## Test Structure ```typescript // user.test.ts import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { userService } from '@/services/user.service'; import { createTestUser, cleanupTestData } from '@/tests/helpers'; describe('UserService', () => { let testUserId: string; befor
tools
# State Management Patterns Client-side state management patterns for modern applications. > **Template Usage:** Customize for your state library (React Query, Zustand, Jotai, Redux, etc.). ## State Categories | Type | Description | Solution | |------|-------------|----------| | **Server State** | Data from API/database | React Query, SWR | | **Client State** | UI state, user preferences | Zustand, Jotai, useState | | **Form State** | Form inputs, validation | React Hook Form, Formik | | **U
development
# Service Patterns Service layer patterns for clean architecture with proper error handling, logging, and type safety. > **Template Usage:** Customize for your ORM (Prisma, Drizzle, TypeORM, etc.) and logging solution. ## Result Type Pattern Never throw exceptions from services. Always return a Result type. ```typescript // lib/result.ts export type Result<T, E = Error> = | { success: true; data: T } | { success: false; error: E }; export function ok<T>(data: T): Result<T, never> { r
testing
# Row-Level Security Patterns Database security patterns for multi-tenant and user-scoped data. > **Template Usage:** Customize for your database (PostgreSQL, Supabase, etc.) and auth system. ## RLS Fundamentals ### Enable RLS on Tables ```sql -- Enable RLS (required before policies take effect) ALTER TABLE users ENABLE ROW LEVEL SECURITY; ALTER TABLE posts ENABLE ROW LEVEL SECURITY; ALTER TABLE comments ENABLE ROW LEVEL SECURITY; -- Force RLS for table owners too (recommended) ALTER TABLE