GitHub - osteele/liquid: A Liquid template engine in Go (original) (raw)
Liquid Template Parser
liquid is a pure Go implementation of Shopify Liquid templates. It was developed for use in theGojekyll port of the Jekyll static site generator.
Installation
go get github.com/osteele/liquid # latest version
go get -u github.com/osteele/liquid # development version
Usage
engine := liquid.NewEngine()
template := <h1>{{ page.title }}</h1>
bindings := map[string]any{
"page": map[string]string{
"title": "Introduction",
},
}
out, err := engine.ParseAndRenderString(template, bindings)
if err != nil { log.Fatalln(err) }
fmt.Println(out)
// Output:
Introduction
See the API documentation for additional examples.
Jekyll Compatibility
This library was originally developed for Gojekyll, a Go port of Jekyll. As such, it includes optional Jekyll-specific extensions that are not part of the Shopify Liquid specification.
To enable Jekyll compatibility mode:
engine := liquid.NewEngine() engine.EnableJekyllExtensions()
Jekyll extensions include:
- Dot notation in assign tags:
{% assign page.canonical_url = "/about/" %}- In standard Liquid, this would be a syntax error
- With Jekyll extensions enabled, this creates or updates nested object properties
- Intermediate objects are created automatically if they don't exist
Example:
engine := liquid.NewEngine() engine.EnableJekyllExtensions() // Enable Jekyll-specific features
template := {% assign page.meta.author = "John Doe" %}{{ page.meta.author }}
bindings := map[string]any{
"page": map[string]any{
"title": "Home",
},
}
out, _ := engine.ParseAndRenderString(template, bindings)
// Output: John Doe
Note: Jekyll extensions are disabled by default to maintain compatibility with standard Shopify Liquid.
Command-Line tool
go install github.com/osteele/liquid/cmd/liquid@latest installs a command-lineliquid executable. This is intended to make it easier to create test cases for bug reports.
$ liquid --help usage: liquid [FILE] $ echo '{{ "Hello World" | downcase | split: " " | first | append: "!"}}' | liquid hello!
Security
Important: If you plan to process untrusted templates (templates authored by users you don't fully trust), please review the Security Policy documentation.
Key security considerations:
- Sandboxed Execution: Templates cannot execute arbitrary code or access filesystem/network resources (by default)
- DoS Vulnerabilities: The engine is vulnerable to denial-of-service attacks via infinite loops and memory exhaustion when processing untrusted templates
- Resource Limiting via FRender: Use the
FRendermethod with custom writers to implement timeouts and output size limits for untrusted templates - Third-Party Extensions: Custom filters and tags execute arbitrary Go code and should be carefully audited
For detailed information about security guarantees, limitations, and production deployment recommendations, see SECURITY.md. For implementing resource limits, see the FRender documentation.
Documentation
This section provides a comprehensive guide to using and extending the Liquid template engine. Documentation is organized by topic:
Getting Started
- Installation - Install the library and command-line tool
- Usage - Quick start guide with examples
- Command-Line Tool - Testing templates from the command line
- API Documentation - Complete API reference on pkg.go.dev
Core Concepts
- Value Types - How Go values map to Liquid types
- Drops - Custom types in templates
- Status - Feature compatibility with Shopify Liquid
Advanced Usage
- Template Store - Custom template storage (filesystem, database, etc.)
- See also: Template Store Example
- Advanced Rendering - FRender for streaming, timeouts, and size limits
- See also: FRender Documentation
Security & Performance
- Security - Resource limits and security considerations
- See also: SECURITY.md
- FRender Documentation - Implementing resource limits in production
Internals
- Loop Semantics - Comparison with Ruby Liquid implementation
- References - Shopify Liquid documentation and resources
Contributing
- CONTRIBUTING.md - How to contribute to the project
- Contributors - List of project contributors
Status
These features of Shopify Liquid aren't implemented:
- Filter keyword parameters, for example
{{ image | img_url: '580x', scale: 2 }}. [Issue #42] - Warn and lax error modes.
- Non-strict filters. An undefined filter is currently an error.
Drops
Drops have a different design from the Shopify (Ruby) implementation. A Ruby drop sets liquid_attributes to a list of attributes that are exposed to Liquid. A Go drop implements ToLiquid() any, that returns a proxy object. Conventionally, the proxy is a map or struct that defines the exposed properties. See http://godoc.org/github.com/osteele/liquid#Drop for additional information.
Value Types
Render and friends take a Bindings parameter. This is a map of string toany, that associates template variable names with Go values.
Any Go value can be used as a variable value. These values have special meaning:
falseandnil- These, and no other values, are recognized as false by
and,or,{% if %},{% elsif %}, and{% case %}.
- These, and no other values, are recognized as false by
- Integers
- (Only) integers can be used as array indices:
array[1];array[n], wherearrayhas an array value andnhas an integer value. - (Only) integers can be used as the endpoints of a range:
{% for item in (1..5) %},{% for item in (start..end) %}wherestartandendhave integer values.
- (Only) integers can be used as array indices:
- Integers and floats
- Integers and floats are converted to their join type for comparison:
1 == 1.0evaluates totrue. Similarly,int8(1),int16(1),uint8(1)etc. are all==. - [There is currently no special treatment of complex numbers.]
- Integers and floats are converted to their join type for comparison:
- Integers, floats, and strings
- Integers, floats, and strings can be used in comparisons
<,>,<=,>=. Integers and floats can be usefully compared with each other. Strings can be usefully compared with each other, but not with other values. Any other comparison, e.g.1 < "one",1 > "one", is always false.
- Integers, floats, and strings can be used in comparisons
- Arrays (and slices)
- An array can be indexed by integer value:
array[1];array[n]wherenhas an integer value. - Arrays have
first,last, andsizeproperties:array.first == array[0],array[array.size-1] == array.last(wherearray.size > 0)
- An array can be indexed by integer value:
- Maps
- A map can be indexed by a string:
hash["key"];hash[s]whereshas a string value - A map can be accessed using property syntax
hash.key - Maps have a special
sizeproperty, that returns the size of the map.
- A map can be indexed by a string:
- Drops
- A value
valueof a type that implements theDropinterface acts as the valuevalue.ToLiquid(). There is no guarantee about how many timesToLiquidwill be called. [This is in contrast to Shopify Liquid, which both uses a different interface for drops, and makes stronger guarantees.]
- A value
- Structs
- A public field of a struct can be accessed by its name:
value.FieldName,value["fieldName"].
* A field tagged e.g.liquid:βnameβis accessed asvalue.nameinstead.
* If the value of the field is a function that takes no arguments and returns either one or two arguments, accessing it invokes the function, and the value of the property is its first return value.
* If the second return value is non-nil, accessing the field panics instead. - A function defined on a struct can be accessed by function name e.g.
value.Func,value["Func"].
* The same rules apply as to accessing a func-valued public field. - Note that despite being array- and map-like, structs do not have a special
value.sizeproperty.
- A public field of a struct can be accessed by its name:
[]byte- A value of type
[]byteis rendered as the corresponding string, and presented as a string to filters that expect one. A[]byteis not (currently) equivalent to astringfor all uses; for example,a < b,a contains b,hash[b]will not behave as expected whereaorbis a[]byte.
- A value of type
MapSlice- An instance of
yaml.MapSliceacts as a map. It implementsm.key,m[key], andm.size.
- An instance of
Template Store
The template store allows for usage of varying template storage implementations (embedded file system, database, service, etc). In order to use:
- Create a struct that implements TemplateStore
type TemplateStore interface {
ReadTemplate(templatename string) ([]byte, error)
} - Register with the engine
engine.RegisterTemplateStore(myTemplateStore)
FileTemplateStore is the default mechanism for backwards compatibility.
Refer to example for an example implementation.
Advanced Rendering
Custom Writers (FRender)
For advanced use cases like streaming to files, implementing timeouts, or limiting output size, use the FRender method to render directly to any io.Writer:
var buf bytes.Buffer err := template.FRender(&buf, bindings)
This is particularly useful for:
- Rendering large templates without buffering in memory
- Implementing cancellation via context
- Limiting output size from untrusted templates
- Custom output transformation
See the FRender documentation for detailed examples and security best practices.
References
Contributing
Bug reports, test cases, and code contributions are more than welcome. Please refer to the contribution guidelines.
Contributors
Thanks goes to these wonderful people (emoji key):
This project follows theall-contributorsspecification. Contributions of any kind welcome!
Attribution
| Package | Author | Description | License |
|---|---|---|---|
| Ragel | Adrian Thurston | scanning expressions | MIT |
| gopkg.in/yaml.v2 | Canonical | MapSlice | Apache License 2.0 |
Michael Hamrah's Lexing with Ragel and Parsing with Yacc using Gowas essential to understanding go yacc.
The original Liquid engine, of course, for the design and documentation of the Liquid template language. Many of the tag and filter test cases are taken directly from the Liquid documentation.
Other Implementations
Go
- karlseguin/liquid is a dormant implementation that inspired a lot of forks.
- acstech/liquid is a more active fork of Karl Seguin's implementation.
- hownowstephen/go-liquid
Other Languages
See Shopify's ports of Liquid to other environments.
License
MIT License