Custom Liquid
When a Mercer section doesn’t fit your needs, the Custom Liquid
section and the Custom Liquid block let you drop in arbitrary
Liquid, HTML, and inline style markup. This guide shows where each
lives, when to use which, and what’s safe (and unsafe) to put inside.
Before adding custom code, duplicate your theme in Shopify admin. Standard Mercer support covers documented theme behavior; developing or maintaining your own Custom Liquid is outside that scope. A modified theme still qualifies for theme support, though we may ask you to reproduce an issue in a clean, unpublished draft. For advanced customization, a Shopify Partner can help. See the support policy.
Mercer ships two Custom Liquid surfaces:
- Custom Liquid section — a free-form section you can add anywhere a section is allowed (every template except the gift-card page, which isn’t section-based). It can be added to the password page.
- Custom Liquid block — available inside every section that
supports
@appblocks. Use this when you want to inline custom markup inside an existing section instead of as its own row on the page.
If you’re new to Liquid, see Shopify’s Liquid reference .
Custom Liquid section
The section has one setting: a Liquid code textarea. Whatever
you put in it is rendered as-is — Liquid evaluates, HTML renders,
inline style and script tags work.
To add it:
- Open the Theme Editor.
- Pick the template you want to add it to.
- Click Add section → Custom Liquid.
- Click into the section, paste your Liquid / HTML.
- Save.
Example: announcement strip
<div style="background: #FFD7BA; color: #4A1F00; text-align: center; padding: 0.5rem;">
Free shipping on orders over $80 — code <strong>SUMMER</strong>.
</div>Example: time-gated banner (Liquid if + now)
{%- assign launch = '2026-06-01' | date: '%s' -%}
{%- assign now = 'now' | date: '%s' -%}
{%- if now < launch -%}
<div style="background: #1A1A1A; color: #fff; text-align: center; padding: 1rem;">
The Spring 2026 collection drops June 1.
</div>
{%- endif -%}Example: pulling a metafield value
{%- assign tagline = shop.metafields.brand.tagline -%}
{%- if tagline -%}
<p class="brand-tagline">{{ tagline }}</p>
{%- endif -%}Example: third-party embed
<!-- Press testimonial widget -->
<div id="press-widget" data-widget="press"></div>
<script src="https://example.com/widget.js" defer></script>Custom Liquid block
Available inside any section that has a “Blocks” panel and supports
@app blocks. To add:
- Open a section in the Editor (e.g. Image with text).
- Open the section’s block list in the editor sidebar.
- Click Add block → Custom Liquid.
- Drag to reorder among the section’s other blocks.
- Click into the block, paste your Liquid / HTML.
The block lets you compose custom content inside an existing section without breaking the section’s layout. Common pattern: appending a note below an image-with-text block, or inserting a custom “Why we built this” paragraph between two columns of a multi-column section.
When to use which
| If you want… | Use the… |
|---|---|
| A whole-row, full-width custom slice | Custom Liquid section |
| Custom markup inside an existing section’s flow | Custom Liquid block |
| To embed a third-party app without code | The third-party app’s app block |
| To show different content per market / locale | A Custom Liquid section with if request.locale.iso_code == 'en' |
| To override Mercer’s PDP layout | Open the product template and edit its Product section; don’t fight it with Custom Liquid |
Available Liquid drops and filters
Inside Custom Liquid, you have access to all of Shopify’s standard global drops:
shop— store name, currency, etc.request— the current request (request.locale,request.path).customer— the signed-in customer (ornil).cart— the current cart.linklists.<handle>— your menu lists.routes— Shopify route helpers (routes.cart_url, etc.).localization— the active country / language (localization.country,localization.language).template— the current template name.page_title,page_description— current page meta.
Note:
Mercer.routesis not a Liquid drop — it’s a client-side JavaScript global (window.Mercer, defined in the theme’s JS). It isn’t accessible in Liquid;{{ Mercer.routes }}renders nothing. Use it from an inline<script>for locale-aware AJAX, e.g.Mercer.routes.url('cart/add.js').
Localization in Custom Liquid
If you hard-code a path like /products/wool-trench, that path will
not be auto-localized for visitors in other markets. To localize
correctly, use routes or the localized-href snippet:
<!-- Bad: hard-coded path doesn't localize -->
<a href="/products/wool-trench">Shop the trench</a>
<!-- Good: routes-prefixed -->
<a href="{{ routes.collections_url }}/outerwear">Shop outerwear</a>
<!-- Best: explicit collection drop -->
{%- assign coll = collections.outerwear -%}
{%- if coll -%}
<a href="{{ coll.url }}">{{ coll.title }}</a>
{%- endif -%}For internal links to products / collections / pages, prefer the
Shopify drops (product.url, collection.url, page.url) — Shopify
auto-localizes these drop URLs for the active market. For raw paths
that don’t come from a drop, the theme prefixes them with
routes.root_url (see the localized-href snippet).
Custom CSS
Inline style tags work inside Custom Liquid. For one-off styling,
this is fine. For anything cross-page or theme-wide, you’re better off
asking the Mercer team to add a setting that exposes the value you need
(via support) — Custom Liquid CSS
isn’t easily reusable.
<style>
.my-custom-banner {
background: var(--surface-alt);
color: var(--ink);
padding: var(--space-3);
}
</style>
<div class="my-custom-banner">
Custom announcement using Mercer's design tokens.
</div>Mercer’s design tokens (the var(--ink), var(--surface),
var(--space-3) etc. above) are available globally. Using them keeps
your custom block visually consistent with the rest of the storefront.
Custom JavaScript
An inline classic script runs when the browser parses it, so later HTML and
Mercer’s deferred scripts may not be ready. defer only delays external classic
scripts with a src attribute; it has no effect on an inline classic script.
See the script element reference .
For a small initializer, check readiness before attaching the listener:
<script>
(() => {
function initialize() {
// Find this widget's element and initialize it once.
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize, { once: true });
} else {
initialize();
}
})();
</script>The theme editor can replace section HTML without a full page load.
Scripts inserted through innerHTML do not execute automatically, and
DOMContentLoaded does not fire again. For reusable widgets, put initialization
in a theme asset loaded once, make it safe to run more than once, and handle
shopify:section:load and cleanup events.
Do not depend on private theme globals being available immediately.
Security and what NOT to put in Custom Liquid
Custom Liquid runs as the merchant’s code. It’s not sandboxed. Some things to avoid:
- Don’t paste customer-submitted content without filtering.
Shopify form submissions (cart notes, customer names, etc.) can
contain HTML; rendering them raw is an XSS vector. If you absolutely
must render them, append
| escape:{{ form.name | escape }}not{{ form.name }}. - Don’t paste credentials. Custom Liquid renders to public HTML. API keys, tokens, passwords are leaked to anyone who views the page source.
- Don’t disable Mercer’s
scriptdefers. Mercer’s perf budget depends on all main-bundle scripts being deferred. If your custom script blocks render, your LCP and CLS metrics suffer.
If a third-party widget you want to embed asks you to inject a
script tag without defer, ask the vendor for a deferred build. If
they don’t have one, that’s a perf cost you’re choosing to pay; it
won’t fall on Mercer.
Editor preview vs production rendering
The theme editor can re-render sections without reloading the preview.
Use localization.country
for the selected storefront country; it is not a promise of the visitor’s physical
location. The request object has no country property.
Customer and cart state depend on the preview session. Do not assume that the customer is always absent or the cart is always empty. Test customer-aware and cart-aware code on a storefront preview with representative session states.
Common Custom Liquid pitfalls
”My Custom Liquid breaks the page”
A syntax error in Liquid can break the entire page render. Check the error reported in the editor or storefront preview and review the Liquid around the reported location. Error presentation can vary.
If the page won’t render at all, save an empty Custom Liquid first to restore the section, then debug your snippet in a smaller scope.
”My script runs twice”
Most likely you’ve added a script tag to a Custom Liquid block inside
a section, and that section appears more than once on the page. Each
section instance renders its own copy of the block. Solutions:
- Wrap the script in a “has it run yet” guard:
if (!window.__myWidget) { window.__myWidget = true; /* initialize */ } - Move the script to a Custom Liquid section instead of a block, and add it to the page once.
”My styles don’t apply”
Inline style tags work, but specificity issues can mean Mercer’s
own CSS overrides yours. Use the Mercer design tokens (var(--ink),
var(--space-3)) to compose with the theme; or scope your selectors
with a unique class (e.g. .my-banner-XYZ) and avoid using !important.
”My CTA button doesn’t look like Mercer’s other buttons”
Use Mercer’s button class:
<a class="btn btn--primary" href="...">Solid CTA</a>
<a class="btn btn--secondary" href="...">Outlined / secondary CTA</a>
<a class="btn btn--ghost" href="...">Lighter / ghost CTA</a>The button styles inherit your preset’s radius / border / letter-spacing settings. Hand-coded buttons won’t.
What’s next
- Read Section reference to see if a built-in section can replace your Custom Liquid use case.
- Read the FAQ for general theme gotchas.
- For widgets / sections you’d like to see built into Mercer natively, use the support form and choose Feature request. Suggestions do not guarantee a feature or delivery date.