---
title: "Liquid Variables"
description: "Custom Liquid variables available in FirearmCart themes"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.firearmcart.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Liquid Variables

FirearmCart themes use Liquid templating. In addition to standard Liquid objects, FirearmCart provides custom variables tailored for firearms and ammunition retailers.

---

## Overview

### What is Liquid?

Liquid is a template language that allows you to:

- Display dynamic content
- Access product, collection, and store data
- Create logic and loops
- Use filters to transform data

### Liquid Syntax

**Objects** - Output data:
```liquid
{{ product.title }}
{{ shop.name }}
```

**Tags** - Logic and control:
```liquid
{% if product.available %}
  In Stock
{% endif %}
```

**Filters** - Transform output:
```liquid
{{ product.price | money }}
{{ product.title | upcase }}
```

### Where Theme Files Live

Themes follow the conventional folder layout:

| Path | Contents |
|------|----------|
| `layout/theme.liquid` | The base layout wrapping every page |
| `templates/` | Page templates, usually JSON |
| `sections/` | Sections, each with its own settings schema |
| `snippets/` | Partials pulled in with `{% render %}` |
| `blocks/` | Theme blocks |
| `assets/` | CSS, JavaScript and images |
| `config/` | Theme settings schema and saved values |
| `locales/` | Translation files used by the `t` filter |

---

## FirearmCart Product Variables

### product.specifications

Outputs an HTML table of product specifications. This is useful for displaying firearm details like caliber, barrel length, capacity, and other technical specifications.

**Usage:**

```liquid
{{ product.specifications }}
```

**Output Example:**

For a product with specifications:

| Name | Value |
|------|-------|
| Category | Handguns |
| Model | 1911 Commander |
| Caliber | .45 ACP |
| Barrel Length | 4.25" |
| State Restrictions | CA |

The variable outputs:

```html
<table class="product-specifications">
  <tbody>
<tr><th>Category</th><td>Handguns</td></tr>
<tr><th>Model</th><td>1911 Commander</td></tr>
<tr><th>Caliber</th><td>.45 ACP</td></tr>
<tr><th>Barrel Length</th><td>4.25"</td></tr>
<tr><th>State Restrictions</th><td>CA</td></tr>
  </tbody>
</table>
```

**Styling:**

Style the specifications table using the `.product-specifications` CSS class:

```css
.product-specifications {
  width: 100%;
  border-collapse: collapse;
}

.product-specifications th,
.product-specifications td {
  padding: 8px 12px;
  border-bottom: 1px solid #e5e5e5;
  text-align: left;
}

.product-specifications th {
  font-weight: 600;
  width: 40%;
}
```

**Notes:**

- Returns an empty string if no specifications are set
- All values are HTML-escaped for security
- Can be used in any template where the `product` object is available

---

### product.sku

The product's SKU (Stock Keeping Unit) at the product level.

```liquid
{{ product.sku }}
```

**Example Output:**
```
GLK-G19-GEN5-BLK
```

**Note:** For products with variants, each variant also has its own SKU accessible via `product.selected_or_first_available_variant.sku`.

---

### product.barcode

The product's barcode (UPC, EAN, ISBN, etc.) at the product level.

```liquid
{{ product.barcode }}
```

**Example Output:**
```
764503913150
```

**Note:** For products with variants, each variant also has its own barcode accessible via the variant object.

---

### product.rating

Outputs an inline SVG star rating based on product reviews — five stars, filled to the average, with half-stars supported.

```liquid
{{ product.rating }}
```

**Output Example:**

For a product with a 4.2 rating:

```html
<span class="product-rating__stars" style="display:inline-flex;gap:1px;align-items:center">
  <svg ...></svg>  <!-- one per star -->
</span>
```

**Important Notes:**

- Returns an empty string if the product has no reviews
- This is **HTML, not a number** — echo it, never compare it to a value
- The stars carry inline styles, including their colour, so a CSS rule on the wrapper will not restyle them
- Output is inline SVG, not text characters
- Can be used in custom liquid blocks

---

### product.rating_full

Outputs stars plus a text summary showing the average rating and review count.

```liquid
{{ product.rating_full }}
```

**Output Example:**

For a product with 37 reviews and a 4.2 average rating:

```html
<span class="product-rating" style="display:inline-flex;align-items:center;gap:0.4em">
  <span class="product-rating__stars">…</span>
  <span class="product-rating__text" style="font-size:0.9em;color:#6b7280">4.2 (37 reviews)</span>
</span>
```

**Important Notes:**

- Returns an empty string unless the product has at least one review
- Includes both visual stars and text summary
- The review count is pluralized for you ("1 review" / "37 reviews")
- Can be used in custom liquid blocks

---

### Using Product Ratings in Custom Liquid Blocks

Product rating variables are commonly used in custom liquid blocks within theme sections or custom code:

**Basic Usage:**

```liquid
{% comment %} Just display stars {% endcomment %}
{{ product.rating }}

{% comment %} Display stars with text summary {% endcomment %}
{{ product.rating_full }}
```

**Conditional Display:**

Only show ratings if reviews exist:

```liquid
{% if product.rating != blank %}
  <div class="product-reviews">
{{ product.rating_full }}
  </div>
{% endif %}
```

Always guard with `!= blank`. An unreviewed product returns an empty string, and an empty string is truthy in Liquid — so a bare `{% if product.rating %}` is always true.

**In Product Cards:**

```liquid
<div class="product-card">
  <h3>{{ product.title }}</h3>
  <p>{{ product.price | money }}</p>

  {% if product.rating != blank %}
{{ product.rating }}
  {% endif %}

  <a href="{{ product.url }}">View Product</a>
</div>
```

**Styling:**

The wrappers are yours to style, but the stars themselves are drawn with inline styles and will ignore your colours:

```css
.product-rating {
  vertical-align: middle;
}

.product-rating__text {
  font-size: 14px;
  color: #666;
}
```

**Review counts:** the numeric aggregates behind these variables are also available, when you want the values rather than the rendered stars:

```liquid
{{ product.metafields.reviews.rating.value.rating }}   {% comment %} e.g. 4.2 {% endcomment %}
{{ product.metafields.reviews.rating_count }}          {% comment %} e.g. 37  {% endcomment %}
```

---

## Global Objects

### shop

Store information available on every page:

```liquid
{{ shop.name }}                      # Store name
{{ shop.currency }}                  # Currency code (USD)
{{ shop.products_count }}            # Number of storefront-visible products
{{ shop.logo }}                      # Store logo image object
{{ shop.brand.logo }}                # Same logo, under the brand object
{{ shop.policies }}                  # Published store policies
{{ shop.enabled_payment_types }}     # Payment badges to display
{{ shop.customer_accounts_enabled }} # Whether customer accounts are on
```

Loop `shop.policies` for the footer rather than hardcoding policy links — each entry has `title`, `url`, `handle` and `body`, and they are served at `/policies/{handle}`. An unpublished policy disappears from this list and its URL returns 404.

`shop.email`, `shop.phone` and `shop.domain` exist for theme compatibility but are always empty — a theme that prints them shows nothing. Put contact details in a page or a section setting instead.

---

### settings

Theme settings defined in the theme's settings schema:

```liquid
{{ settings.logo }}
{{ settings.logo_height }}
{{ settings.color_schemes }}
```

The exact setting names come from your theme. Three are supplied by the platform regardless of theme: `settings.logo` (taken from your store settings and overriding whatever the theme stored), plus `settings.logo_height` and `settings.logo_height_mobile`.

**Theme-Level vs Section-Level:**

```liquid
{% comment %} Theme settings (global) {% endcomment %}
{{ settings.logo }}

{% comment %} Section settings (local to section) {% endcomment %}
{{ section.settings.heading }}
```

---

### cart

Shopping cart information:

```liquid
{{ cart.item_count }}            # Number of items
{{ cart.total_price | money }}   # Cart total (in cents — pipe through money)
{{ cart.original_total_price | money }}
{{ cart.total_discount | money }}
{{ cart.items }}                 # Array of cart items
{{ cart.currency.symbol }}       # $

{% for item in cart.items %}
  {{ item.product.title }} - {{ item.quantity }}
  {{ item.final_line_price | money }}
{% endfor %}
```

---

### template

Current template being rendered:

```liquid
{{ template }}               # Template name (e.g., "product", "collection")
{{ template.name }}          # Same value
```

`template.suffix` and `template.directory` exist for compatibility but are always empty.

---

### routes

Route helpers for generating URLs:

```liquid
{{ routes.root_url }}                    # Home page
{{ routes.search_url }}                  # Search page
{{ routes.predictive_search_url }}       # Search suggestions endpoint
{{ routes.all_products_collection_url }} # All products
{{ routes.cart_url }}                    # Cart page
{{ routes.cart_add_url }}                # Cart endpoints, for theme JavaScript
{{ routes.cart_change_url }}
{{ routes.cart_update_url }}
{{ routes.cart_clear_url }}
{{ routes.account_url }}                 # Account page
{{ routes.account_login_url }}           # Login page
{{ routes.account_logout_url }}
{{ routes.account_register_url }}
{{ routes.account_orders_url }}
{{ routes.account_addresses_url }}
{{ routes.product_recommendations_url }}
```

Always use these rather than hardcoding paths — in preview and editor contexts they carry a prefix that a literal `/cart` would miss.

---

### request

```liquid
{{ request.design_mode }}    # true while rendering inside the editor
{{ request.locale }}         # Current locale
```

---

### powered_by_link

Outputs the complete "Powered by FirearmCart" link as HTML. A custom footer must emit it:

```liquid
{{ powered_by_link }}
```

---

### social

Your store's social profile URLs, for footer and header icons:

```liquid
{{ social.facebook }}
{{ social.instagram }}
{{ social.youtube }}
{{ social.twitter }}
{{ social.tiktok }}
{{ social.pinterest }}
```

---

### linklists

Your storefront navigation menus, keyed by handle. `main-menu` always resolves to the active menu:

```liquid
{% for link in linklists['main-menu'].links %}
  <a href="{{ link.url }}" {% if link.current %}aria-current="page"{% endif %}>{{ link.title }}</a>
{% endfor %}
```

Each link has `title`, `url`, `handle`, `current`, `active`, and nested `links` for submenus. `menus` is an alias for the same data.

A `link_list` section setting is resolved to the menu object for you, so use `section.settings.menu.links` directly — do not look the value up in `linklists`.

---

## Page-Specific Objects

### product

Available on product pages:

```liquid
{{ product.title }}          # Product name
{{ product.handle }}         # URL slug
{{ product.url }}            # Product URL
{{ product.description }}    # Full description
{{ product.price | money }}  # Current price (stored in cents)
{{ product.compare_at_price | money }} # Original price, only when above the selling price
{{ product.on_sale }}        # true when compare-at price is above the selling price
{{ product.price_min | money }}
{{ product.price_max | money }}
{{ product.price_varies }}   # true when variants differ in price
{{ product.available }}      # In stock?
{{ product.product_type }}   # Product type
{{ product.vendor }}         # Manufacturer, falling back to brand
{{ product.featured_image }} # First image
{{ product.images }}         # Product images
{{ product.media }}          # Product media
{{ product.variants }}       # Product variants
{{ product.options_with_values }} # Options with their values
{{ product.has_only_default_variant }}
{{ product.selected_or_first_available_variant }} # Current variant

{% comment %} FirearmCart specific {% endcomment %}
{{ product.specifications }} # Specifications table
{{ product.sku }}            # Product SKU
{{ product.barcode }}        # Product barcode
{{ product.manufacturer }}   # Manufacturer
{{ product.brand }}          # Brand
{{ product.rating }}         # Star rating (SVG)
{{ product.rating_full }}    # Stars + text summary
```

**Notes:**

- Prices are integers in cents. Always pipe them through `money` — a bare `{{ product.price }}` prints `59999`.
- `manufacturer`, `brand` and `vendor` are empty strings when unset, so test them with `!= blank` rather than for truthiness.
- There is no `product.tags` — use `product.product_type`, `product.manufacturer` or a collection to group products.

---

### collection

Available on collection pages:

```liquid
{{ collection.title }}       # Collection name
{{ collection.handle }}      # URL slug
{{ collection.description }} # Collection description
{{ collection.products }}    # Products on the current page of the collection
{{ collection.products_count }} # Number of products
{{ collection.image }}       # Collection image
{{ collection.url }}         # Collection URL
{{ collection.filters }}     # Available filter groups
{{ collection.sort_options }} # Available sort orders

{% for product in collection.products %}
  {{ product.title }}
{% endfor %}
```

`collection.products` holds one page of results, not the whole collection.

`collection.filters` lists the filter groups for the current page in Shopify's shape. Each group has a `label`, `param_name`, `type` and `values`, and each value carries `label`, `count`, `active`, `url_to_add` and `url_to_remove`. The groups are Availability (`filter.v.availability`), Price (`filter.v.price`, a `price_range` with `min_value` and `max_value`), Sale (`filter.p.on_sale`), In Store (`filter.p.in_store`) when the catalog uses it, Product type (`filter.p.product_type`) when the page spans more than one type or one is selected, and specification groups (`filter.p.m.custom.*`) limited by the theme's [Collection Filters](/themes/visual-editor#collection-filters) setting. Specification groups are sorted alphabetically.

---

### page

Available on pages:

```liquid
{{ page.title }}             # Page title
{{ page.content }}           # Page content (HTML)
{{ page.handle }}            # Page handle/slug
{{ page.description }}       # Page description
{{ page.seo_title }}         # SEO title
{{ page.seo_description }}   # SEO description
```

Policy pages (`/policies/{handle}`) render through the page object too, with the policy's name as `page.title` and its text as `page.content`.

---

### article

Available on blog article pages:

```liquid
{{ article.title }}          # Article title
{{ article.handle }}         # URL slug
{{ article.url }}            # Article URL
{{ article.content }}        # Article content (HTML)
{{ article.excerpt }}        # Excerpt, or the first 200 characters of the content
{{ article.author }}         # Author name
{{ article.published_at }}   # Publish date
{{ article.published_at | date: '%B %d, %Y' }}
{{ article.image }}          # Featured image, or nil when there is none
{{ article.tags }}           # Article tag names
```

`article.published_at` is already a readable date string, so printing it bare works; pipe it through `date` when you want a different format. `article.published_at_timestamp` holds the same moment as a Unix timestamp.

`article.tags` holds tag **names**, while tag URLs use the slugified handle. To link a tag to its filtered page, use `link_to_type`, which builds `/blogs/{blog}/tagged/{tag}` and slugifies the name for you:

```liquid
{% for tag in article.tags %}
  {{ tag | link_to_type: blog.handle }}
{% endfor %}
```

---

### blog

Available on blog pages:

```liquid
{{ blog.title }}             # Blog title
{{ blog.handle }}            # URL slug
{{ blog.url }}               # Blog URL
{{ blog.articles }}          # Published articles, newest first
{{ blog.articles_count }}    # Number of published articles
{{ blog.all_tags }}          # Every tag used in this blog

{% for article in blog.articles %}
  {{ article.title }}
{% endfor %}
```

Every blog is also reachable from any page through the global `blogs` object, keyed by handle:

```liquid
{% assign news = blogs['news'] %}
{% for article in news.articles limit: 3 %}
  <a href="{{ article.url }}">{{ article.title }}</a>
{% endfor %}
```

There is no global `articles` object — articles are reached through a blog, or as the `article` object on an article page. Note that a `blog` **section setting** is already resolved to the blog object, so use `section.settings.blog` directly rather than looking it up in `blogs`.

---

### search

Available on search results pages:

```liquid
{{ search.terms }}           # Search query
{{ search.performed }}       # true once a query has been submitted
{{ search.results }}         # Search results
{{ search.results_count }}   # Number of results

{% for result in search.results %}
  {{ result.title }}
{% endfor %}
```

---

## Section Objects

### section

Available within sections:

```liquid
{{ section.id }}             # Unique section ID
{{ section.type }}           # Section type (its file name)
{{ section.settings.heading }} # Section setting value
{{ section.blocks }}         # Section blocks
{{ section.block_order }}    # Block IDs in display order

{% for block in section.blocks %}
  {{ block.id }}             # Block ID
  {{ block.type }}           # Block type
  {{ block.settings.text }}  # Block setting
{% endfor %}
```

**Important:** Use `section.settings.*` for anything the section declares in its own schema. Bare `settings.*` is the theme-global scope — correct for values you did not declare, such as `settings.logo`.

---

## Common Filters

### Money Formatting

Prices are stored in cents. Every money filter divides by 100 for you:

```liquid
{{ product.price | money }}
# Output: $599.99

{{ product.price | money_without_currency }}
# Output: 599.99

{{ product.price | money_without_trailing_zeros }}
# Output: $599  (or $599.50 when there are cents)

{{ product.price | money_with_currency }}
# Output: $599.99
```

---

### Date Formatting

```liquid
{{ article.published_at | date: '%B %d, %Y' }}
# Output: January 15, 2025

{{ article.published_at | date: '%m/%d/%Y' }}
# Output: 01/15/2025
```

---

### String Filters

```liquid
{{ product.title | upcase }}
# Output: GLOCK 19 GEN 5

{{ product.title | downcase }}
# Output: glock 19 gen 5

{{ product.description | truncate: 100 }}
# Truncates to 100 characters

{{ product.description | strip_html }}
# Removes HTML tags

{{ product.handle | replace: '-', ' ' }}
# Replaces hyphens with spaces

{{ product.title | escape }}
# HTML-escapes the value — output is never escaped for you

{{ section.settings.heading | default: 'Featured Products' }}
# Falls back when the value is empty
```

---

### Image Filters

```liquid
{{ product.featured_image | image_url: width: 400 }}
# Image URL at 400px width

{{ product.featured_image | image_url: width: 400, height: 400 }}
# Image URL at specific dimensions

{{ section.settings.image | image_url: width: 1200, format: 'webp' }}
# Requesting a format

{{ section.settings.image | image_url: width: 1200 | image_tag: alt: shop.name }}
# Complete <img> tag — always pipe through image_url first

{{ 'hero-1' | placeholder_svg_tag: 'my-section__placeholder' }}
# Designed placeholder for an empty image setting
```

`image_url` accepts `width`, `height` and `format`. Always pass a width — with no arguments it returns the original image, and the browser downloads the full-size file. A `crop` argument is accepted for compatibility but ignored, so do your cropping in CSS with `object-fit`.

Never pipe an SVG through `image_url` — use `asset_url` on its own.

---

### Asset Filters

```liquid
{{ 'base.css' | asset_url }}
# Theme asset URL, with a cache-busting version

{{ 'base.css' | asset_url | stylesheet_tag }}
# Complete <link> tag

{{ 'theme.js' | asset_url | script_tag }}
# Complete <script> tag

{{ 'icon-sprite.svg' | inline_asset_content }}
# Inlines a text asset, such as an SVG sprite
```

Only pass filenames that actually exist in the theme. An unknown filename resolves to a same-domain URL that 404s silently, with nothing reported anywhere.

---

### Link Filters

```liquid
{{ shop.name | link_to: routes.root_url }}
# <a href="/">Store name</a>

{{ tag | link_to_type: blog.handle }}
# <a href="/blogs/news/tagged/announcements">Announcements</a>
```

For ordinary links, write the anchor yourself and use the `routes` object for the URL.

---

### Translation Filter

```liquid
{{ 'products.add_to_cart' | t }}
# Translated string from the theme's locale files

{{ 'products.items_count' | t: count: cart.item_count }}
# Translated with variable
```

When a key has no translation, the key itself is output — that string appearing on your storefront means the locale file is missing the entry.

---

## Control Flow Tags

### Conditionals

```liquid
{% if product.available %}
  <button>Add to Cart</button>
{% else %}
  <button disabled>Out of Stock</button>
{% endif %}

{% if product.product_type == 'Firearm' %}
  <p>FFL Transfer Required</p>
{% elsif product.product_type == 'Ammunition' %}
  <p>Ground Shipping Only</p>
{% endif %}

{% unless product.available %}
  <span>Sold Out</span>
{% endunless %}
```

---

### Loops

```liquid
{% for product in collection.products %}
  <div class="product-card">
{{ product.title }}
  </div>
{% endfor %}

{% for product in collection.products limit: 4 %}
  {% comment %} Show only first 4 {% endcomment %}
{% endfor %}

{% for product in collection.products offset: 4 %}
  {% comment %} Skip first 4 {% endcomment %}
{% endfor %}
```

**Loop Variables:**

```liquid
{% for product in collection.products %}
  {{ forloop.index }}      # 1, 2, 3...
  {{ forloop.index0 }}     # 0, 1, 2...
  {{ forloop.first }}      # true for first item
  {{ forloop.last }}       # true for last item
  {{ forloop.length }}     # total count
{% endfor %}
```

---

### Case/When

```liquid
{% case product.product_type %}
  {% when 'Firearm' %}
<p>Requires FFL Transfer</p>
  {% when 'Ammunition' %}
<p>Ships Ground Only</p>
  {% else %}
<p>Standard Shipping</p>
{% endcase %}
```

---

## Variable Tags

### assign

```liquid
{% assign featured_product = collection.products.first %}
{{ featured_product.title }}
```

---

### capture

```liquid
{% capture product_info %}
  {{ product.title }} - {{ product.price | money }}
{% endcapture %}

{{ product_info }}
```

---

## Including Templates

### render

Include a snippet:

```liquid
{% render 'product-card', product: product %}
```

With parameters:

```liquid
{% render 'icon', icon: 'cart', size: 24 %}
```

---

### section

Render a section file directly:

```liquid
{% section 'featured-products' %}
```

Pages are normally composed from JSON page templates and section groups instead, which is what makes sections reorderable in the Visual Editor. A theme whose base layout pulls in individual sections with this tag will fail import — see [Theme Sections](/themes/sections).

---

### comment

```liquid
{% comment %} Notes for other developers — never rendered {% endcomment %}
```

Liquid has no `{# ... #}` comment syntax; anything written that way is printed to the page as text.

---

## Best Practices

### Check for Empty Values

```liquid
{% if product.description != blank %}
  {{ product.description }}
{% endif %}
```

---

### Use Default Values

```liquid
{{ section.settings.heading | default: 'Featured Products' }}
```

---

### Optimize Loops

```liquid
{% comment %} Limit loops for performance {% endcomment %}
{% for product in collection.products limit: 12 %}
  ...
{% endfor %}
```

---

### HTML Escape

Output is **not** escaped for you. Escape any value that a customer or merchant typed and that is meant to be plain text:

```liquid
{% comment %} Escaped — safe in an attribute or as text {% endcomment %}
<img alt="{{ product.title | escape }}">

{% comment %} Raw HTML — correct for rich text fields, which are meant to contain markup {% endcomment %}
{{ product.description }}
{{ product.specifications }}
```

---

### Unknown Filters Fail Silently

A misspelled or non-existent filter is not an error — the value passes through unchanged. `{{ product.title | uppcase }}` renders the plain title, and nothing reports a problem, so check spelling when a filter appears to have no effect.

---

## Related Documentation

- [Visual Editor](/themes/visual-editor) - Theme customization
- [Theme Sections](/themes/sections) - Section management
- [Creating Products](/products/creating-products) - Product specifications

Source: https://docs.firearmcart.com/themes/liquid-variables/index.mdx
