Platform CLI
Learn
Developer docs User guide Quickstart CLI and AI agents Blog
Company
Services About Contact Links Get started

Functions and filters

Updated

Raytha adds eight functions and six filters to standard Liquid. The functions fetch data and render sections. The filters format dates, build file URLs, group lists and serialise values. This page gives the exact signature, return shape and failure behaviour of each.

Functions at a glance

FunctionArgumentsReturnsOn failure
get_content_itemsNamed: ContentType, Filter, OrderBy, PageNumber, PageSizeobject with Items and TotalCountnil for an unknown content type; the page fails on a bad filter
get_content_item_by_idPositional: the item idone content itemnil if the id is unknown or malformed
get_content_type_by_developer_namePositional: developer namecontent type with its fieldsnil
get_main_menunonethe menu marked as mainthrows if no main menu exists
get_menuPositional: menu developer namea menunil
raytha_functionPositional: function name, method name; then named argumentswhatever the function returnsnil, silently
render_sectionPositional: section name; named layout optionsHTML stringempty string
get_sectionPositional: section namearray of widgetsempty array

render_section and get_section work only in site page templates; see Site pages and sections. They are not defined inside widget templates. The other six work in every template kind, widgets included.

get_content_items

{% assign posts = get_content_items(ContentType="posts", Filter="category eq 'news'", OrderBy="CreationTime desc", PageNumber=1, PageSize=5) %}
{% for post in posts.Items %}
  <a href="{{ PathBase }}/{{ post.RoutePath }}">{{ post.PrimaryField | escape }}</a>
{% endfor %}
<p>{{ posts.Items.size }} of {{ posts.TotalCount }}</p>
ArgumentNotes
ContentTypeDeveloper name of the content type, for example posts. Required.
FilterA filter expression. See Filtering and sorting. Omit for no filter.
OrderByFor example PrimaryField asc or rank desc, CreationTime desc. Default: CreationTime desc.
PageNumber1-based. Missing, 0 and 1 all return the first page.
PageSizeMissing or 0 gives 25. The maximum is 1000.
  • Argument names are case-sensitive and must be exactly as above. contenttype="posts" is silently ignored, and so is a positional get_content_items("posts"); either way the content type is empty and the result is nil.
  • Only published items are returned, whatever Filter says.
  • TotalCount counts all matches, not only this page.
  • The function has no Search argument. To offer a search box, link to a list view with ?search= (see Template recipes).
  • An unknown content type returns nil, so posts.Items is empty and loops render nothing. A filter that does not parse, or that names a field the content type does not have, fails the whole page with HTTP 400.
  • Each call runs a query and a count. Avoid calling it inside a loop over another list.

The shape of an item

Items from get_content_items and get_content_item_by_id are content item records, slightly different from Target on a detail page:

MemberNotes
Id, PrimaryField, RoutePathAs on Target.
CreationTime, LastModificationTimeUTC.
CreatorUser, LastModifierUserId, FirstName, LastName, FullName, EmailAddress, or nothing.
PublishedContentThe published fields, as field value objects.
DraftContentThe saved draft fields. Never print it on a public page.
IsPublished, IsDraftBooleans.
ContentTypeId, WebTemplateIdIds.

There is no Template member.

get_content_item_by_id

{% assign featured = get_content_item_by_id("EREREREREREREREREREREQ") %}
{% if featured and featured.IsPublished %}
  <a href="{{ PathBase }}/{{ featured.RoutePath }}">{{ featured.PrimaryField | escape }}</a>
{% endif %}

It returns one item, or nil when the id belongs to nothing or is not an id. Unlike get_content_items, it does not filter on published state: a draft comes back with its DraftContent. Always test IsPublished before you link to or print an item you looked up by id.

The id is the one Raytha shows in the admin and in Target.Id. Both its short form and the long GUID form work.

get_content_type_by_developer_name

{% assign type = get_content_type_by_developer_name("posts") %}
{% if type %}
  <h2>{{ type.LabelPlural | escape }}</h2>
  {% for field in type.ContentTypeFields %}
    {{ field.Label | escape }} ({{ field.FieldType.DeveloperName }})
  {% endfor %}
{% endif %}

It returns the content type with Id, IsActive, LabelPlural, LabelSingular, DeveloperName, DefaultRouteTemplate, Description, PrimaryFieldId, PrimaryField and ContentTypeFields. Each field has Id, DeveloperName, Label, Description, IsRequired, FieldOrder, FieldType (with DeveloperName and Label), Choices (each with Label, DeveloperName, Disabled), RelatedContentTypeId and SubFields. Use it to turn a dropdown's stored developer name into its label:

{% assign type = get_content_type_by_developer_name("posts") %}
{% for field in type.ContentTypeFields %}
  {% if field.DeveloperName == "category" %}
    {% for choice in field.Choices %}
      {% if choice.DeveloperName == Target.PublishedContent.category.Value %}{{ choice.Label | escape }}{% endif %}
    {% endfor %}
  {% endif %}
{% endfor %}

get_main_menu and get_menu

{% assign menu = get_main_menu() %}
<ul>
  {% for item in menu.MenuItems %}
    <li class="{{ item.CssClassName }}">
      <a href="{{ item.Url }}"{% if item.OpenInNewTab %} target="_blank" rel="noopener"{% endif %}>{{ item.Label | escape }}</a>
      {% if item.MenuItems.size > 0 %}
        <ul>{% for child in item.MenuItems %}<li><a href="{{ child.Url }}">{{ child.Label | escape }}</a></li>{% endfor %}</ul>
      {% endif %}
    </li>
  {% endfor %}
</ul>

A menu has Id, Label, DeveloperName, IsMainMenu and MenuItems. A menu item has Id, Label, Url, IsDisabled, OpenInNewTab, CssClassName, Ordinal, IsFirstItem, IsLastItem and its own MenuItems for nesting. Url is stored as typed in the menu editor. If you entered a relative URL, prefix it with {{ PathBase }} yourself. get_menu("footer") takes the menu's developer name and returns nil when there is no such menu. See Manage navigation menus for the editor.

Warning get_menu returns nil for an unknown menu, but get_main_menu throws a not-found error when no menu is marked as the main menu. A layout that calls it then fails for every page. Keep one menu marked as main.

raytha_function

{% assign quote = raytha_function("daily-quote", "get", topic="design") %}
{% if quote %}<blockquote>{{ quote | escape }}</blockquote>{% endif %}

Calls a method of a Raytha Function from a template. The first two arguments are the function's developer name and the method name. Named arguments after that become a JSON object that is passed to the function. It returns whatever the function returns, or nil when:

  • Raytha Functions are switched off in the configuration,
  • the function does not exist, is inactive, or its trigger type is not liquid_template,
  • the method name is empty, or the function throws or times out.

All failures are swallowed, so test the result for nil. Positional arguments after the method name are delivered as arg1, arg2 and so on. Do not mix named and positional arguments: they get mislabelled. Writing the functions themselves is covered in Liquid template trigger.

Filters

organization_time

Converts a UTC date to the organization's time zone, then formats it with a strftime format string, like Liquid's date.

{{ Target.CreationTime | organization_time: "%B %e, %Y at %H:%M" }}
  • Always pass a format. Without one the value is not converted and prints as the raw UTC timestamp.
  • A missing or unparsable input prints nothing.
  • It treats every input as UTC. Use it for CreationTime and LastModificationTime. Do not use it for a Date field: those are plain dates, and the filter would move them back a day in time zones west of UTC. Format those with date.
  • The time zone is CurrentOrganization.TimeZone.

attachment_url, attachment_redirect_url, attachment_public_url

Each takes the object key of an uploaded file, which is what an Attachment field stores in .Value, and returns a URL. An empty or missing key gives an empty string.

FilterReturnsUse it when
attachment_url (same as attachment_redirect_url){PathBase}/raytha/media-items/objectkey/{key}, an anonymous endpoint that redirects to the current file URLYou want a link that never expires. This is the right choice for content fields.
attachment_public_urlThe storage provider's direct URL for the file. For local storage, a root-relative /_static-files/... path. For cloud storage, the provider's download URL, which can be a presigned URL that expires (the default is one day).The file is theme media referenced from the layout, or you want to avoid the redirect hop.
{% if Target.PublishedContent.hero_image.HasValue %}
  <img src="{{ Target.PublishedContent.hero_image.Value | attachment_url }}" alt="{{ Target.PrimaryField | escape }}">
{% endif %}
<link rel="stylesheet" href="{{ "bootstrap.min.css" | attachment_public_url }}">

If your storage provider returns presigned URLs, do not cache a page that contains attachment_public_url output for longer than the expiry.

groupby

Groups an array by a property and returns an array of { key, items } objects, in the order each key first appears.

{% assign posts = get_content_items(ContentType="posts", OrderBy="PrimaryField asc", PageSize=100) %}
{% assign groups = posts.Items | groupby: "PublishedContent.category" %}
{% for group in groups %}
  <h2>{{ group.key | escape }}</h2>
  {% for post in group.items %}<p>{{ post.PrimaryField | escape }}</p>{% endfor %}
{% endfor %}
  • A property name that starts with PublishedContent. groups by that field's text (.Text). Any other name is read directly from the item, for example RoutePath or IsPublished.
  • Only one level after PublishedContent. is read. Do not add .Value.
  • Items with no value share the key of an empty string. A multiple select groups by its comma-joined text, not by each choice.
  • An empty property name returns an empty array.

json

Serialises any value to indented JSON text. Characters such as <, > and & are escaped as \u003C and so on, so the result is safe inside a <script> tag.

<script type="application/json" id="site-data">{{ CurrentOrganization.OrganizationName | json }}</script>

Do not pass whole items from get_content_items to json on a public page. They carry DraftContent, the creator's email address and ids. Build a small object from the fields you want instead.

Standard Liquid filters that work

Fluid 2.31 ships these. Raytha adds no restrictions.

GroupFilters
Stringsappend, prepend, capitalize, downcase, upcase, strip, lstrip, rstrip, strip_html, strip_newlines, newline_to_br, replace, replace_first, remove, remove_first, slice, truncate, truncatewords, split, handleize (alias handle)
Escaping and encodingescape, escape_once, url_encode, url_decode, base64_encode, base64_decode, base64_url_safe_encode, md5, sha1, sha256
Numbersabs, plus, minus, times, divided_by, modulo, ceil, floor, round, at_least, at_most
Arraysjoin, first, last, size, reverse, sort, sort_natural, uniq, compact, concat, map, sum, where, find, find_index, has, reject
Datesdate, time_zone, format_date (.NET format strings, for example "yyyy-MM-dd")
Otherdefault, format_number, format_string, raw

Behaviours worth knowing:

  • An unknown filter name is ignored, not an error: {{ x | nonexistent }} prints x. Shopify-only filters such as money, where_exp, camelcase and pluralize do not exist here. If a filter seems to do nothing, check the spelling.
  • divided_by divides integers as integers: 10 | divided_by: 4 is 2. Use 4.0 for a decimal result.
  • truncate: 5 counts the ellipsis, so "abcdefgh" | truncate: 5 is ab....
  • default replaces nothing, false and an empty string. It does not apply to a field object, only to its .Value.
  • where compares a direct property of each item, such as RoutePath, PrimaryField or IsPublished. It does not reach into custom fields. Filter custom fields with the Filter argument or an {% if %} inside the loop.
  • date accepts "now" as input. %Z prints a numeric offset such as +00:00, not a name.
  • escape turns &, <, > and quotes into entities. Use it on every value an editor or visitor can type.

Gotchas

  • Call functions with parentheses, even with no arguments, as the default layout does: {% assign menu = get_main_menu() %}. Without them you get a reference to the function, which prints nothing.
  • include and render do not work; see Introduction to templates.
  • Names that are not defined in a context, for example render_section inside a widget, evaluate to nothing and do not raise an error.

Next steps