Functions and filters
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
| Function | Arguments | Returns | On failure |
|---|---|---|---|
get_content_items | Named: ContentType, Filter, OrderBy, PageNumber, PageSize | object with Items and TotalCount | nil for an unknown content type; the page fails on a bad filter |
get_content_item_by_id | Positional: the item id | one content item | nil if the id is unknown or malformed |
get_content_type_by_developer_name | Positional: developer name | content type with its fields | nil |
get_main_menu | none | the menu marked as main | throws if no main menu exists |
get_menu | Positional: menu developer name | a menu | nil |
raytha_function | Positional: function name, method name; then named arguments | whatever the function returns | nil, silently |
render_section | Positional: section name; named layout options | HTML string | empty string |
get_section | Positional: section name | array of widgets | empty 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>
| Argument | Notes |
|---|---|
ContentType | Developer name of the content type, for example posts. Required. |
Filter | A filter expression. See Filtering and sorting. Omit for no filter. |
OrderBy | For example PrimaryField asc or rank desc, CreationTime desc. Default: CreationTime desc. |
PageNumber | 1-based. Missing, 0 and 1 all return the first page. |
PageSize | Missing 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 positionalget_content_items("posts"); either way the content type is empty and the result is nil. - Only published items are returned, whatever
Filtersays. TotalCountcounts all matches, not only this page.- The function has no
Searchargument. To offer a search box, link to a list view with?search=(see Template recipes). - An unknown content type returns nil, so
posts.Itemsis 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:
| Member | Notes |
|---|---|
Id, PrimaryField, RoutePath | As on Target. |
CreationTime, LastModificationTime | UTC. |
CreatorUser, LastModifierUser | Id, FirstName, LastName, FullName, EmailAddress, or nothing. |
PublishedContent | The published fields, as field value objects. |
DraftContent | The saved draft fields. Never print it on a public page. |
IsPublished, IsDraft | Booleans. |
ContentTypeId, WebTemplateId | Ids. |
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_menureturns nil for an unknown menu, butget_main_menuthrows 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
CreationTimeandLastModificationTime. 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 withdate. - 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.
| Filter | Returns | Use it when |
|---|---|---|
attachment_url (same as attachment_redirect_url) | {PathBase}/raytha/media-items/objectkey/{key}, an anonymous endpoint that redirects to the current file URL | You want a link that never expires. This is the right choice for content fields. |
attachment_public_url | The 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 exampleRoutePathorIsPublished. - 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.
| Group | Filters |
|---|---|
| Strings | append, 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 encoding | escape, escape_once, url_encode, url_decode, base64_encode, base64_decode, base64_url_safe_encode, md5, sha1, sha256 |
| Numbers | abs, plus, minus, times, divided_by, modulo, ceil, floor, round, at_least, at_most |
| Arrays | join, first, last, size, reverse, sort, sort_natural, uniq, compact, concat, map, sum, where, find, find_index, has, reject |
| Dates | date, time_zone, format_date (.NET format strings, for example "yyyy-MM-dd") |
| Other | default, format_number, format_string, raw |
Behaviours worth knowing:
- An unknown filter name is ignored, not an error:
{{ x | nonexistent }}printsx. Shopify-only filters such asmoney,where_exp,camelcaseandpluralizedo not exist here. If a filter seems to do nothing, check the spelling. divided_bydivides integers as integers:10 | divided_by: 4is2. Use4.0for a decimal result.truncate: 5counts the ellipsis, so"abcdefgh" | truncate: 5isab....defaultreplaces nothing,falseand an empty string. It does not apply to a field object, only to its.Value.wherecompares a direct property of each item, such asRoutePath,PrimaryFieldorIsPublished. It does not reach into custom fields. Filter custom fields with theFilterargument or an{% if %}inside the loop.dateaccepts"now"as input.%Zprints a numeric offset such as+00:00, not a name.escapeturns&,<,>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. includeandrenderdo not work; see Introduction to templates.- Names that are not defined in a context, for example
render_sectioninside a widget, evaluate to nothing and do not raise an error.