Showing users friendly errors when using Liquid templating in Rails

Fri May 7, 2021
~800 Words

When allowing users to work with templates in your system, e.g. for a CRM where you might want email templates, Liquid is a very widely-used option. If it has worked for Shopify all this time then there’s a good chance it will work for our users.

In this post I’ll touch on how to use Liquid in your Rails application, but the main problem solved here is how to make the template syntax error messages friendlier for your users.

Here’s an example of a Liquid error message if you forget to close the double braces:

Variable '{{' was not properly terminated with regexp: /\}\}/

This is clear enough to a programmer, but it’s likely that your users are not programmers, so termination won’t be immediately clear as a concept and ‘regexp’ won’t be familiar at all.

This error message could instead read more like

Your template has a pair of open braces ('{{') with no corresponding closing braces ('}}'), these need to match up. For example, if you have “Dear {{ first_name,” then please alter it to be “Dear {{ first_name }},”.

This could be improved but you get the idea–you can present a message that will make more sense to your particular audience, perhaps using familiar variable names also.

Using Liquid in Rails

There is a liquid-rails gem but this appears to be more for using Liquid in your view templates on the server side, and less about simply using it for user content. At time of writing the last commit was three years ago (in March 2018), so I would suggest integrating Liquid directly in your project rather than using this gem.

Add gem 'liquid' to your Gemfile to pull in the latest version.

Now wherever you would like to render a Liquid template you can simply do the following:

template_content = "Dear {{ first_name }},\n\nYour message.\n\nKind regards,\n\n{{ sender }}."

template_data = { 'first_name' => 'John', 'sender' => 'Stefan' }

rendered = Liquid::Template.parse(template_content).render(template_data)

Which would result in:

"Dear John,\n\nYour message.\n\nKind regards,\n\nStefan."

Providing alternative error messages

I couldn’t find this in the documentation, and a search through the issues on the Liquid Github repository didn’t uncover people asking about this, which has me wondering how often people do this–I’ll admit that I have not checked Shopify to see what kind of errors they report when you fill in invalid syntax.

Without documentation I instead looked through the source itself (bundle open liquid), which pointed to it having an I18n class (added here, back in 2013) which fetches template strings from a locale YAML file in the repository. At time of writing there is only one local provided, en.

A first step to providing our own error messages is to pass in our own (Rails-provided) I18n module in place of the Liquid-owned one. Here we set up a validation for the template body of an EmailTemplate model:

class EmailTemplate < ApplicationRecord
  has_rich_text :body

  validates_presence_of :body

  validate :body_is_valid_liquid_markup

  private

  def body_is_valid_liquid_markup
    # The to_s.to_str is required to case the ActiveSuport::SafeBuffer type to
    # a plain string, as Liquid 5 requires this.
    #
    # https://github.com/Shopify/liquid/pull/1421
    template = Liquid::Template.parse(body.to_s.to_str, locale: I18n)
  rescue Liquid::SyntaxError => e
    errors.add(:body, e.to_s)
  end
end

On introducing a template syntax error (for forgetting to close the braces) we get:

Liquid::SyntaxError: Liquid syntax error: translation missing: en.errors.syntax.variable_termination

which shows us how we should structure our own locale file, e.g. in our Rails app in config/locales/en.yml:

en:
  errors:
    syntax:
      variable_termination: Your template has a pair of open braces ('%{token}') with no corresponding closing braces ('}}'), these need to match up. For example, if you have "Dear {{ first_name," then please alter it to be "Dear {{ first_name }},".

I have left one instance of %{token} in there just to confirm that such variable substitution still happens, which it does. As far as I know the only delimiter for variables is {{ name }} so I don’t see the need for interpolating in this case but it’s good to be able to do so as necessary.

If you now introduce another type of error, e.g. an unterminated for ({% for ) then you will get:

translation missing: en.errors.syntax.tag_termination

At this point it would make sense to copy over the contents of the liquid locale YAML file in the repository into your local locale file so that you have a value for all possible template syntax errors.

You can now tweak any of these error messages accordingly, and potentially provide translations if it would make sense to do so for your userbase.

When you upgrade the liquid gem in your project be sure to check the locale file for any new entries, though after ~8 years of production use I wouldn’t expect to see (m)any additions.