{"id":17928994,"url":"https://github.com/edgurgel/solid","last_synced_at":"2026-04-01T23:03:44.071Z","repository":{"id":10874109,"uuid":"66522657","full_name":"edgurgel/solid","owner":"edgurgel","description":"Liquid template engine in Elixir","archived":false,"fork":false,"pushed_at":"2026-03-22T07:19:33.000Z","size":522,"stargazers_count":248,"open_issues_count":11,"forks_count":54,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-03-22T22:25:38.558Z","etag":null,"topics":["elixir","liquid","parser","template-engine","template-engine-liquid"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/solid","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edgurgel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2016-08-25T03:51:17.000Z","updated_at":"2026-03-22T07:18:54.000Z","dependencies_parsed_at":"2024-06-03T08:01:57.108Z","dependency_job_id":"9f95b42f-a69e-410d-9bc0-53e254284411","html_url":"https://github.com/edgurgel/solid","commit_stats":{"total_commits":239,"total_committers":29,"mean_commits":8.241379310344827,"dds":"0.34309623430962344","last_synced_commit":"525696ea77fc9fe427e0636ac015c011311a8818"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/edgurgel/solid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fsolid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fsolid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fsolid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fsolid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edgurgel","download_url":"https://codeload.github.com/edgurgel/solid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fsolid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","liquid","parser","template-engine","template-engine-liquid"],"created_at":"2024-10-28T21:07:05.728Z","updated_at":"2026-04-01T23:03:44.021Z","avatar_url":"https://github.com/edgurgel.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Solid\n[![Module Version](https://img.shields.io/hexpm/v/solid.svg)](https://hex.pm/packages/solid)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/solid/)\n[![Total Download](https://img.shields.io/hexpm/dt/solid.svg)](https://hex.pm/packages/solid)\n[![License](https://img.shields.io/hexpm/l/solid.svg)](https://github.com/edgurgel/solid/blob/master/LICENSE.md)\n[![Last Updated](https://img.shields.io/github/last-commit/edgurgel/solid.svg)](https://github.com/edgurgel/solid/commits/master)\n\nSolid is an implementation in Elixir of the [Liquid](https://shopify.github.io/liquid/) template language with strict parsing.\n\n## Basic Usage\n\n```elixir\niex\u003e template = \"My name is {{ user.name }}\"\niex\u003e {:ok, template} = Solid.parse(template)\niex\u003e Solid.render!(template, %{ \"user\" =\u003e %{ \"name\" =\u003e \"José\" } }) |\u003e to_string\n\"My name is José\"\n```\n\n## Installation\n\nThe package can be installed with:\n\n```elixir\ndef deps do\n  [{:solid, \"~\u003e 1.0\"}]\nend\n```\n\n## Custom tags\n\nTo implement a new tag you need to create a new module that implements the `Tag` behaviour. It must implement a `parse/3` function that returns a struct that implements `Solid.Renderable`. Here is a simple example:\n\n```elixir\ndefmodule CurrentYear do\n  @enforce_keys [:loc]\n  defstruct [:loc]\n\n  @behaviour Solid.Tag\n\n  @impl true\n  def parse(\"get_current_year\", loc, context) do\n    with {:ok, [{:end, _}], context} \u003c- Solid.Lexer.tokenize_tag_end(context) do\n      {:ok, %__MODULE__{loc: loc}, context}\n    end\n  end\n\n  defimpl Solid.Renderable do\n    def render(_tag, context, _options) do\n      {[to_string(Date.utc_today().year)], context}\n    end\n  end\nend\n```\n\nNow to use it simply pass a `:tags` option to `Solid.parse/2` including your custom tag:\n\n```elixir\ntags = Map.put(Solid.Tag.default_tags(), \"get_current_year\", CurrentYear)\nSolid.parse!(\"{{ get_current_year }}\", tags: tags)\n```\n\nOne can also pass a subset of the default tags if a more restricted environment is necessary:\n\n```elixir\n# No comment tags allowed\ntags = Map.delete(Solid.Tag.default_tags(), \"comment\")\nSolid.parse!(\"{% comment %} {% endcomment %}\", tags: tags)\n```\n\nAn error will be presented as `comment` is not part of the allowed tags:\n\n```elixir\n** (Solid.TemplateError) Unexpected tag 'comment'\n1: {% comment %} {% endcomment %}\n   ^\nUnexpected tag 'endcomment'\n1: {% comment %} {% endcomment %}\n                 ^\n    (solid 1.0.0-rc1) lib/solid.ex:51: Solid.parse!/2\n    iex:2: (file)\n```\n\n## Custom filters\n\nWhile calling `Solid.render` one can pass a module with custom filters:\n\n```elixir\ndefmodule MyCustomFilters do\n  def add_one(x), do: x + 1\nend\n\n\"{{ number | add_one }}\"\n|\u003e Solid.parse!()\n|\u003e Solid.render!(%{ \"number\" =\u003e 41}, custom_filters: MyCustomFilters)\n|\u003e IO.puts()\n# 42\n```\n\nAlternatively, you can pass a function to the `custom_filters` option. This allows\nyour filters to access predefined state. An example use-case could be to allow the\nfilters to render strings in the user's default locale (or to override it by\nargument).\n\n``` elixir\nuser_locale = \"en\"\n\n\"{{ number | format_number }}\"\n|\u003e Solid.parse!()\n|\u003e Solid.render!(%{ \"number\" =\u003e 41}, custom_filters: fn\n  \"format_number\", [num] -\u003e\n    {:ok, Cldr.Number.to_string(num, locale: user_locale)}\n\n  \"format_number\", [num, locale] -\u003e\n    {:ok, Cldr.Number.to_string(num, locale: locale)}\n\n  _, _ -\u003e\n    :error\nend)\n|\u003e IO.puts()\n```\n\nThe callback must return either `{:ok, value}` or `:error`.\n\n## Strict rendering\n\nIf there are any missing variables/filters and `strict_variables: true` or `strict_filters: true` are passed as options `Solid.render/3` returns `{:error, errors, result}` where errors is the list of collected errors and `result` is the rendered template.\n\n`Solid.render!/3` raises if `strict_variables: true` is passed and there are missing variables.\n`Solid.render!/3` raises if `strict_filters: true` is passed and there are missing filters.\n\n## Caching\n\nIn order to cache `render`-ed templates, you can write your own cache adapter. It should implement behaviour `Solid.Caching`. By default it uses `Solid.Caching.NoCache` trivial adapter.\n\nIf you want to use for example [Cachex](https://github.com/whitfin/cachex) for that such implemention would look like:\n\n```elixir\ndefmodule CachexCache do\n  @behaviour Solid.Caching\n\n  @impl true\n  def get(key) do\n    case Cachex.get(:your_cache_name, key) do\n      {_, nil} -\u003e {:error, :not_found}\n      {:ok, value} -\u003e {:ok, value}\n      {:error, error_msg} -\u003e {:error, error_msg}\n    end\n  end\n\n  @impl true\n  def put(key, value) do\n    case Cachex.put(:my_cache, key, value) do\n      {:ok, true} -\u003e :ok\n      {:error, error_msg} -\u003e {:error, error_msg}\n    end\n  end\nend\n\n```\n\nAnd then pass it as an option to render `cache_module: CachexCache`. Now while using `{% render 'etc' %}` this custom cache will be used\n\n## Using structs in context\n\nIn order to pass structs to context you need to implement protocol `Solid.Matcher` for that. That protocol consist of one function `def match(data, keys)`. First argument is struct being provided and second is list of string, which are keys passed after `.` to the struct.\n\nFor example:\n\n```elixir\ndefmodule UserProfile do\n  defstruct [:full_name]\n\n  defimpl Solid.Matcher do\n    def match(user_profile, [\"full_name\"]), do: {:ok, user_profile.full_name}\n  end\nend\n\ndefmodule User do\n  defstruct [:email]\n\n  def load_profile(%User{} = _user) do\n    # implementation omitted\n    %UserProfile{full_name: \"John Doe\"}\n  end\n\n  defimpl Solid.Matcher do\n    def match(user, [\"email\"]), do: {:ok, user.email}\n    def match(user, [\"profile\" | keys]), do: user |\u003e User.load_profile() |\u003e @protocol.match(keys)\n  end\nend\n\ntemplate = ~s({{ user.email}}: {{ user.profile.full_name }})\ncontext = %{\n  \"user\" =\u003e %User{email: \"test@example.com\"}\n}\n\ntemplate |\u003e Solid.parse!() |\u003e Solid.render!(context) |\u003e to_string()\n# =\u003e test@example.com: John Doe\n```\n\nIf the `Solid.Matcher` protocol is not enough one can provide a module like this:\n\n```elixir\ndefmodule MyMatcher do\n  def match(_data, _keys), do: {:ok, 42}\nend\n\n# ...\nSolid.render!(template, %{\"number\" =\u003e 4}, matcher_module: MyMatcher)\n```\n\n## Sigil Support\n\nSolid provides a `~LIQUID` sigil for validating and compiling templates at compile time:\n\n```elixir\nimport Solid.Sigil\n\n# Validates syntax at compile time\ntemplate = ~LIQUID\"\"\"\nHello, {{ name }}!\n\"\"\"\n\n# Use the compiled template\nSolid.render!(template, %{\"name\" =\u003e \"World\"})\n```\n\nThe sigil will raise helpful CompileError messages with line numbers and context when templates contain syntax errors.\nExperimental VSCode syntax highlighting is available with the [Liquid Sigil](https://marketplace.visualstudio.com/items?itemName=JakubSkalecki.liquid-sigil) extension.\n\n## Contributing\n\nWhen adding new functionality or fixing bugs consider adding a new test case here inside `test/solid/integration/scenarios`. These scenarios are tested against the Ruby gem so we can try to stay as close as possible to the original implementation.\n\n## Copyright and License\n\nCopyright (c) 2016-2025 Eduardo Gurgel Pinho\n\nThis work is free. You can redistribute it and/or modify it under the\nterms of the MIT License. See the [LICENSE.md](./LICENSE.md) file for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fsolid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgurgel%2Fsolid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fsolid/lists"}