shopify-liquid — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited shopify-liquid (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Build and modify Shopify Online Store 2.0 themes with Liquid templating, section schemas, and safe deployment workflows.
theme/
├── layout/theme.liquid # Base HTML wrapper (required)
├── templates/*.json # Page composition (section order + saved settings)
├── sections/*.liquid # Reusable modules with schema
├── snippets/*.liquid # Reusable fragments ({% render 'name' %})
├── assets/ # CSS, JS, images ({{ 'file' | asset_url }})
├── config/settings_schema.json # Global theme settings for Shopify Admin
��── locales/*.json # i18n translationsEvery section follows this pattern:
<section class="my-section">
{% if section.settings.eyebrow != blank %}
<span class="eyebrow">{{ section.settings.eyebrow }}</span>
{% endif %}
<h2>{{ section.settings.heading }}</h2>
{% for block in section.blocks %}
<div class="block-item" {{ block.shopify_attributes }}>
<h3>{{ block.settings.title }}</h3>
<p>{{ block.settings.text }}</p>
</div>
{% endfor %}
</section>
{% schema %}
{
"name": "My Section",
"settings": [
{ "type": "text", "id": "eyebrow", "label": "Eyebrow" },
{ "type": "text", "id": "heading", "label": "Heading", "default": "Section Title" }
],
"blocks": [
{
"type": "item",
"name": "Item",
"settings": [
{ "type": "text", "id": "title", "label": "Title" },
{ "type": "textarea", "id": "text", "label": "Text" }
]
}
],
"presets": [
{ "name": "My Section" }
]
}
{% endschema %}| Type | Usage | Notes |
|---|---|---|
text | Single line input | |
textarea | Multi-line input | |
richtext | HTML editor | Returns HTML string |
image_picker | Image from file library | Use with image_url filter |
product | Product selector | Returns product object |
collection | Collection selector | Returns collection object |
select | Dropdown | Requires options array |
range | Numeric slider | Requires min, max, step |
checkbox | Boolean toggle | |
color | Color picker | Returns hex string |
url | URL input | |
header | Section divider | Label only, no id |
Access: section.settings.field_id (section level), block.settings.field_id (block level)
Templates define which sections appear on a page and their order:
{
"sections": {
"hero": {
"type": "hero-banner",
"settings": {
"heading": "Welcome"
}
},
"features": {
"type": "features-grid",
"settings": {}
}
},
"order": ["hero", "features"]
}CRITICAL: Template JSON contains merchant-saved data (images, text overrides). Never push templates without pulling first — it erases merchant customizations.
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ page_title }}{% if current_tags %} — {{ current_tags | join: ', ' }}{% endif %}</title>
{{ 'theme.css' | asset_url | stylesheet_tag }}
{{ content_for_header }}
</head>
<body>
{% section 'header' %}
<main>{{ content_for_layout }}</main>
{% section 'footer' %}
<script src="{{ 'theme.js' | asset_url }}" defer></script>
</body>
</html>content_for_header — required (Shopify analytics, editor JS)content_for_layout — where template sections renderAssets:
{{ 'file.css' | asset_url }} — CDN URL for theme asset{{ image | image_url: width: 800 }} — resized image URL{{ 'key' | t }} — i18n translation from localesDisplay:
{{ price | money }} — format as currency{{ string | strip }} — trim whitespace{{ value | default: 'fallback' }} — fallback if empty{{ string | escape }} — HTML escape{{ string | truncate: 100 }} — truncate with ellipsisWhitespace: Use {%- tag -%} (hyphens) to strip surrounding whitespace.
# Install
brew install shopify-cli
# Local dev (live preview, no live store affected)
shopify theme dev --store mystore.myshopify.com
# Pull remote changes (merchant edits) BEFORE pushing
shopify theme pull --store mystore.myshopify.com
# Safe push (sections + assets only, never delete remote files)
shopify theme push --store mystore.myshopify.com \
--allow-live \
--nodelete \
--only "sections/*:assets/*:layout/*:snippets/*:locales/*:config/settings_schema.json"
# Push specific template (only when intentional)
shopify theme push --only "templates/page.about.json"
# Create new unpublished theme
shopify theme push --store mystore.myshopify.com --theme-name "New Theme"Flags:
--allow-live — update the live (published) theme--nodelete — don't remove files missing locally--only — colon-separated glob patterns (not comma)image_url filter on nil crashes silently. Always guard: {% if section.settings.image != blank %}{{ block.shopify_attributes }} to block wrapper divs. Without it, blocks can't be reordered/edited in the theme editor.pages.home.hero.title appears literally).{%- form 'contact' -%} for native Shopify forms. Submissions appear in Admin > Customers. Use name="contact[body]" for custom fields.opacity: 0 on load (reveal-on-scroll) won't show in the theme editor. Add: {% if request.design_mode %}style="opacity:1"{% endif %}Locale file (locales/en.default.json):
{
"sections": {
"hero": {
"eyebrow": "WELCOME",
"heading": "Your Brand Here"
}
}
}Usage: {{ 'sections.hero.eyebrow' | t }}
Locale-aware links:
{%- assign base = request.locale.root_url -%}
{%- if base == '/' -%}{%- assign base = '' -%}{%- endif -%}
<a href="{{ base }}/products/my-product">Shop</a>~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.