Shopify/liquid
Liquid markup language. Safe, customer facing template language for flexible web apps.
Healthy across the board
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 2d ago
- ✓11 active contributors
- ✓Distributed ownership (top contributor 28% of recent commits)
Show 3 more →Show less
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/shopify/liquid)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/shopify/liquid on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: Shopify/liquid
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/Shopify/liquid shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 2d ago
- 11 active contributors
- Distributed ownership (top contributor 28% of recent commits)
- MIT licensed
- CI configured
- Tests present
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live Shopify/liquid
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/Shopify/liquid.
What it runs against: a local clone of Shopify/liquid — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in Shopify/liquid | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of Shopify/liquid. If you don't
# have one yet, run these first:
#
# git clone https://github.com/Shopify/liquid.git
# cd liquid
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of Shopify/liquid and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "Shopify/liquid(\\.git)?\\b" \\
&& ok "origin remote is Shopify/liquid" \\
|| miss "origin remote is not Shopify/liquid (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~2d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/Shopify/liquid"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Shopify/liquid is a secure, stateless template engine written in Ruby that renders Liquid markup (a Smarty-inspired syntax with {{ variables }} and {% tags %}) without executing arbitrary code. It's designed for customer-facing templates in web applications, supporting filters, loops, conditionals, and custom blocks while maintaining strict sandboxing—templates compile once and render many times with different data contexts. Monolithic lib/ structure: lib/liquid.rb is the entry point, with core parsing in lib/liquid/lexer.rb and lib/liquid/parser.rb, tag/filter definitions spread across lib/liquid/*.rb files (e.g., lib/liquid/block.rb for block tags, lib/liquid/standardfilters.rb for built-in filters), and environment/context management in lib/liquid/environment.rb and lib/liquid/context.rb. Example server demo in example/server/ shows typical usage patterns.
👥Who it's for
SaaS platform builders and e-commerce companies (especially Shopify theme developers) who need to let non-technical users or merchants edit template content safely. Also appeals to Ruby developers building applications that require templating without eval-based security risks.
🌱Maturity & risk
Highly mature and production-ready. The repo shows 600K+ lines of Ruby with comprehensive Liquid support, active CI/CD via GitHub Actions (.github/workflows/liquid.yml, .github/workflows/cla.yml), and is a core Shopify-maintained project used at scale. Last evidenced by dependabot configuration and contribution guidelines, indicating ongoing maintenance.
Low risk for core functionality—this is Shopify's widely-used standard. Primary risk is the single-owner model (Shopify) for a critical dependency; breaking changes in major versions could affect many downstream theme developers. The reliance on Ruby itself means it's tied to Ruby ecosystem stability. No obvious abandoned signal, but custom extension APIs should be carefully versioned.
Active areas of work
Active maintenance indicated by dependabot.yaml and structured CI workflows; no specific PR/milestone data in file list, but the presence of CONTRIBUTING.md and codebase organization suggests ongoing feature development and security updates. Version history and Ruby version pinning (.ruby-version) indicate deliberate version management.
🚀Get running
git clone https://github.com/Shopify/liquid.git
cd liquid
bundle install
bundle exec rake test
(Gemfile present; bin/render executable suggests a CLI interface for testing templates)
Daily commands:
bundle exec rake test # Run full test suite
bundle exec bin/render FILE # Parse and render a .liquid file
cd example/server && ruby server.rb # Start demo web server
🗺️Map of the codebase
- lib/liquid.rb: Main entry point; loads all core modules and exports public API (Template, Environment, Context)
- lib/liquid/environment.rb: Central configuration object; where custom tags, filters, and security policies are registered per-context
- lib/liquid/lexer.rb: Tokenizes raw Liquid markup into tokens; critical for secure parsing without eval
- lib/liquid/parser.rb: Builds Abstract Syntax Tree from lexer tokens; validates syntax and creates executable nodes
- lib/liquid/context.rb: Manages variable scopes, filters, and data binding during template rendering
- lib/liquid/document.rb: Root node of the render tree; compiled template AST that gets executed during render()
- lib/liquid/block.rb: Base class for block-level tags ({% for %}, {% if %}, etc.); defines parse and render contracts
- lib/liquid/standardfilters.rb: Built-in filters library (capitalize, downcase, size, etc.); shows extension pattern for custom filters
- lib/liquid/resource_limits.rb: Enforces security boundaries: memory, render calls, assign depth—prevents DoS from malicious templates
- lib/liquid/partial_cache.rb: Caches compiled include/render templates to avoid re-parsing; performance optimization
🛠️How to make changes
Adding filters: lib/liquid/standardfilters.rb (or custom via Environment#add_filter). Adding tags: Create subclass of Liquid::Block or Liquid::Tag in lib/liquid/tags/ directory, register in environment. Parsing logic: lib/liquid/lexer.rb (tokenization) → lib/liquid/parser.rb (syntax tree). Context/data flow: lib/liquid/context.rb (variable scope). Tests: test/ directory (inferred from CI, not listed but standard Ruby test location).
🪤Traps & gotchas
- Environment isolation: Custom tags/filters added to a global Liquid::Template class will leak across all templates—use Liquid::Environment per-context instead (mentioned in README). 2. File system sandboxing: Liquid::FileSystem controls template inclusion paths; misconfiguration can expose unintended files. 3. Forloop drop behavior: lib/liquid/forloop_drop.rb adds special 'forloop' variable in loops; accessing outside loop scope silently returns nil (not an error). 4. Ruby version dependency: .ruby-version is pinned; certain filter behaviors may differ between Ruby versions (String handling, encoding). 5. I18n config: lib/liquid/i18n.rb loads from lib/liquid/locales/en.yml; custom locales must follow YAML structure or silently fall back to English.
💡Concepts to learn
- Stateless Compilation Pattern — Liquid separates parsing (expensive, done once) from rendering (cheap, done many times with different data). Understanding this parse-render split is fundamental to the API design and why Template.parse() is cached but .render() is stateless.
- Safe Template Sandboxing (No Eval) — Liquid's core value is that user-provided templates cannot execute arbitrary Ruby code. This is enforced by the lexer/parser whitelist of allowed tags and filters, making it safe for untrusted input—critical concept for security audits.
- Drop Protocol (Ruby Object Wrapping) — lib/liquid/drop.rb and lib/liquid/self_drop.rb define how Ruby objects expose data to templates via
to_liquid()method. Understanding this is essential for integrating domain objects (e.g., Product, User) into templates without exposing internal state. - Context Variable Scoping (Stack-based) — lib/liquid/context.rb manages nested variable scopes (global → for-loop → include). Template variables are resolved via scope stack lookup, not flat hash—subtle for debugging variable binding issues in nested loops and partials.
- Block Tag Parsing (Body Accumulation) — lib/liquid/block.rb and lib/liquid/block_body.rb show how block tags ({% for %}, {% if %}) parse and store their inner content as a closure before evaluation. Critical for understanding custom tag logic flow.
- Resource Limits (DoS Prevention) — lib/liquid/resource_limits.rb enforces quotas (memory, render call depth, assigned variable count) to prevent runaway template execution. Essential when running untrusted templates in multi-tenant systems.
- Filter Chaining (Left-to-Right Pipe Operator) — Liquid's
{{ value | filter1 | filter2 }}syntax chains transformations left-to-right. lib/liquid/standardfilters.rb shows how filters are registered as callable Ruby procs. Important for understanding both template behavior and extension points.
🔗Related repos
Shopify/liquid-c— C extension for Liquid parser; used for performance-critical paths in production Shopify infrastructureShopify/theme-liquid— Shopify-specific extensions and theme schema definitions on top of core Liquid; template developers' primary resourcerails/rails— Ruby on Rails uses Liquid-inspired ERB templating; ecosystem context for Ruby template engine patternsjinja/jinja— Python equivalent with nearly identical syntax; useful for understanding cross-language Liquid standardizationShopify/quilt— Shopify's JavaScript/React build tools; consumes compiled Liquid templates for theme rendering
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test coverage for lib/liquid/tag/disableable.rb and lib/liquid/tag/disabler.rb
These tag disabling/enabling modules exist in the codebase but appear to lack dedicated test files. Given that Liquid prioritizes security and safe template execution, testing the tag disabling mechanism is critical to ensure malicious or unwanted tags can be properly disabled. This would improve security posture and prevent regressions.
- [ ] Review lib/liquid/tag/disableable.rb and lib/liquid/tag/disabler.rb to understand the tag disabling API
- [ ] Check existing test structure in test/ directory to match testing patterns
- [ ] Create test/liquid/tag/test_disableable.rb with tests for enabling/disabling individual tags
- [ ] Create test/liquid/tag/test_disabler.rb with tests for the Disabler class and its integration with Environment
- [ ] Add edge case tests: disabling core tags, re-enabling tags, interaction with custom tags
Add tests and documentation for lib/liquid/profiler/ hooks integration
The profiler module exists (lib/liquid/profiler/hooks.rb) but appears to lack documented usage examples and comprehensive test coverage. Profiling is valuable for production debugging, and contributors would benefit from clear examples of how to use profiling hooks with the Environment class.
- [ ] Review lib/liquid/profiler/hooks.rb to understand available profiler hooks
- [ ] Check if test/liquid/test_profiler.rb exists and what it covers
- [ ] Add integration tests demonstrating profile hook registration and callback invocation in test/liquid/profiler/
- [ ] Add a profiler usage section to CONTRIBUTING.md with concrete example code showing hook setup
- [ ] Document profiler output format and available metrics in code comments or a dedicated doc
Implement missing unit tests for lib/liquid/range_lookup.rb
Range lookup is a specific utility that handles range-based variable access in Liquid templates (e.g., array[1..3]). This is a core feature but likely lacks dedicated test coverage. Testing edge cases like negative indices, out-of-bounds ranges, and type coercion would strengthen template safety and correctness.
- [ ] Review lib/liquid/range_lookup.rb to understand the range lookup implementation
- [ ] Create test/liquid/test_range_lookup.rb with tests for: basic ranges, negative indices, reverse ranges, invalid ranges
- [ ] Add tests for type coercion (string indices, float indices, nil ranges)
- [ ] Add tests for interaction with Liquid's resource_limits to ensure range lookups respect configured limits
- [ ] Verify edge cases like empty ranges and single-element ranges are handled correctly
🌿Good first issues
- Add Liquid syntax validation/linting: Create a lib/liquid/linter.rb module that detects common template mistakes (unmatched tags, unused variables, deprecated filters). Start with checking for orphaned
{% endif %}without{% if %}. - Expand standardfilters.rb test coverage: The file has 603K+ lines of Ruby but no visible test list; write parameterized tests for edge cases in filters like
splitwith empty strings,datewith timezone offsets, andarrayfilters with nil values. - Document custom tag creation: lib/liquid/block.rb exists but no concrete example in CONTRIBUTING.md; add a step-by-step 'Hello World' custom tag tutorial with a working example in example/server/custom_tags/hello_world.rb and corresponding test.
⭐Top contributors
Click to expand
Top contributors
- @tobi — 28 commits
- @dejmedus — 19 commits
- @karreiro — 18 commits
- @aswamy — 11 commits
- @charlespwd — 8 commits
📝Recent commits
Click to expand
Recent commits
1954a26— Update liquid-spec adapters (ianks)6d81b1b— Merge pull request #2077 from Shopify/remove-strict2-from-error-message (aswamy)dfddd8f— Remove "strict2" from bare bracket error message (aswamy)95ce7e7— Merge pull request #2067 from Shopify/strict2-increment-decrement (aswamy)197d755— Merge pull request #2065 from Shopify/strict2-assign-capture (aswamy)d0c5444— Merge pull request #2060 from Shopify/bare-bracket-self-keyword (aswamy)c990360— Add strict2_parse to increment and decrement tags (aswamy)a9c8562— Merge pull request #2066 from eregon/skip-slow-test (nirvdrum)9f4d7e7— Skip slow test raising many exceptions on non-CRuby (eregon)0d5c15a— Add strict2_parse to assign and capture tags (aswamy)
🔒Security observations
The Shopify Liquid template engine demonstrates a security-conscious design with built-in protections against code execution and template injection. Key strengths include the non-evaling architecture, resource limits framework, and sandboxed filter execution. However, potential vulnerabilities exist in file system access controls, resource exhaustion prevention, error message handling, and expression parsing validation. The project would benefit from regular security audits, comprehensive fuzzing of the parser and lexer components, and continuous dependency monitoring. The presence of CI/CD workflows and clear documentation suggests good security practices are already in place.
- Medium · Potential Server-Side Template Injection (SSTI) Risk —
lib/liquid/standardfilters.rb, lib/liquid/strainer_template.rb, lib/liquid/tag/. Liquid is a template engine designed to safely handle user-provided templates. However, the presence of 'standardfilters.rb' and dynamic tag evaluation suggests potential for SSTI if filters or custom tags are not properly sandboxed. The 'strainer_template.rb' indicates a strainer mechanism for filter execution which could be exploited if custom filters aren't validated. Fix: Ensure all custom filters and tags are whitelisted and validated. Review the strainer implementation to guarantee it prevents execution of arbitrary code. Implement strict resource limits and comprehensive input validation for all dynamic template constructs. - Medium · File Inclusion via File System Access —
lib/liquid/file_system.rb, lib/liquid/tags/include.rb, lib/liquid/tags/render.rb. The 'file_system.rb' module handles file inclusion through tags like 'include.rb' and 'render.rb'. If not properly configured, this could allow path traversal attacks to read arbitrary files from the server filesystem. Fix: Implement strict path validation and ensure file access is restricted to a whitelist of allowed directories. Use realpath() to prevent directory traversal. Add comprehensive tests for path traversal scenarios. - Medium · Resource Exhaustion via Unbounded Loops/Recursion —
lib/liquid/resource_limits.rb, lib/liquid/tags/for.rb, lib/liquid/tags/table_row.rb. The presence of 'resource_limits.rb' suggests awareness of resource exhaustion risks, but the implementation details are not visible. Tags like 'for.rb' and 'table_row.rb' could be exploited to create infinite loops or excessive memory consumption if resource limits are not properly enforced. Fix: Ensure resource_limits are properly enforced with reasonable defaults (max iterations, max depth, memory limits). Implement timeout mechanisms for template rendering. Add monitoring for resource usage and implement early termination for runaway templates. - Low · Deprecated Dependencies Not Tracked —
Gemfile, gemspec. The dependency file content was not provided in the analysis. Unable to verify if the project uses outdated or vulnerable gem versions. The Gemfile should be scanned for known vulnerabilities. Fix: Run 'bundle audit' regularly to check for known vulnerabilities in dependencies. Keep dependencies updated to latest secure versions. Integrate dependency scanning into CI/CD pipeline as shown in '.github/workflows/liquid.yml'. - Low · Potential Information Disclosure via Error Messages —
lib/liquid/errors.rb. The 'errors.rb' module handles error reporting. Detailed error messages could leak sensitive information about the template structure or system internals if not properly sanitized before user exposure. Fix: Implement error message sanitization to prevent leaking sensitive information. Use generic error messages for end users while logging detailed errors server-side. Ensure stack traces are not exposed in production environments. - Low · Lack of Input Validation in Expression Parsing —
lib/liquid/expression.rb, lib/liquid/lexer.rb, lib/liquid/parser.rb. The 'expression.rb' and 'lexer.rb' handle parsing of user-supplied expressions. If not properly validated, malicious expressions could potentially be crafted to bypass security controls. Fix: Implement comprehensive unit tests for expression parsing with malicious inputs. Use fuzzing to identify parsing edge cases. Ensure all expressions are parsed within a restricted grammar with no arbitrary code execution paths.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.