Code theme (Liquid)
Write your site in your own HTML, CSS and JavaScript; file structure, variables, filters, security rules and a complete example theme.
When to use it
Ready-made designs and the page builder cover most panels. If you want to design your storefront pixel by pixel, Admin → Code theme lets you write the home page and your own pages as Liquid templates.
A code theme only replaces storefront pages. Ordering, payments, sign-in/sign-up, the customer panel and the services page keep working as they are. Theme CSS variables, your custom CSS and custom code are still loaded on themed pages.
Files
| File | Address | Role |
|---|---|---|
layout.liquid | — | Wraps every themed page. The page goes in with {{ content_for_layout | raw }}. Required. |
home.liquid | /en | Home page. Required. |
about.liquid | /en/about | Every {name}.liquid file is published at its own address |
_card.liquid | — | Files starting with an underscore are partials; no address, used with {% render '_card' %} |
Rules:
- File names: lowercase letters, digits and dashes; a leading
_for partials; at most 42 characters +.liquid. - At most 40 files, at most 100 KB per file.
- Reserved addresses (
admin,dashboard,login,register,services,blog,api,api-docs,order,media…) can't be page files. - No separate CSS/JS/image files: write CSS in
<style>and JavaScript in<script>; upload images to the Media library and use their/media/...URL. - Every file is parsed on save; syntax errors show the file name and the first line of the error.
Variables
| Variable | Content |
|---|---|
site.name, site.url, site.logo, site.logo_dark | Panel name, address, logos |
site.locale, site.locales | Current language and the panel's languages |
site.support_email, site.telegram | Support email and Telegram link |
user.logged_in | Whether the visitor is signed in |
user.email, user.name, user.balance | When signed in; balance as a USD number |
nav.header | Menu items: list of { label, href } |
catalog.groups | Service groups: { name, slug, platform, services (count), categories: [{ name }] } |
catalog.service_count | Total services on sale |
blog.posts | Latest posts: { title, excerpt, cover, url, date } |
page.template, page.locale | Name of the rendered file and language |
content_for_layout | Only in layout.liquid: the rendered page |
An undefined variable renders empty instead of failing.
Filters
All built-in Liquid filters (date, upcase, truncate, where, sort…) and tags (if, for, assign, capture, render…) are available. SMMFor adds three filters:
| Filter | Example | Result |
|---|---|---|
t | {{ 'nav.services' | t }} | The panel's translated text in the visitor's language. Missing keys print the key itself. |
url | {{ '/services' | url }} | Language-prefixed address: /en/services. https:// URLs are left as they are. |
money | {{ user.balance | money }} | Formats a USD amount: $12.50 |
An unknown filter is an error (to catch typos).
Security rules
{{ }}output is HTML-escaped. Use| rawto print trusted HTML as-is.- Templates have no disk or network access; they can only
renderthe theme's own files. - Each page has a time (250 ms) and memory limit.
- Your
<script>tags automatically get the site's security nonce; external scripts can be loaded through them. Inline event attributes likeonclick="..."andjavascript:links don't run; attach events withaddEventListener. - Publishing and activating ask for your 2FA code (or admin password), because a theme can run scripts on your site.
- If a page fails, the site doesn't crash; an error message is shown in place of that page.
Publishing
- Save draft: doesn't touch the site.
- Preview: opens the draft in a new tab with
?theme_preview=1. Only you (admin) see the draft. - Publish: makes the draft the live version and adds it to the history (the last 20 versions are kept).
- Tick "Use the code theme on my site". The theme is shown only when it is published and active. Turn it off and the site immediately falls back to your block pages/design.
- Restore an earlier version: loads a past version into the draft; nothing changes on the site until you publish.
A published theme can stay cached for up to 60 seconds.
A complete example theme
These four files make a working theme with a menu, sign-in aware buttons, platform cards, blog posts, an FAQ and an About page. Colours come from the theme variables, so your Appearance colours apply here too.
layout.liquid
<div class="ct">
<header class="ct-header">
<a class="ct-brand" href="{{ '/' | url }}">
{% if site.logo != blank %}<img src="{{ site.logo }}" alt="{{ site.name }}" height="32">{% else %}{{ site.name }}{% endif %}
</a>
<nav class="ct-nav">
{% for item in nav.header %}<a href="{{ item.href }}">{{ item.label }}</a>{% endfor %}
<a href="{{ '/services' | url }}">{{ 'nav.services' | t }}</a>
<a href="{{ '/about' | url }}">About</a>
</nav>
<div class="ct-actions">
{% if user.logged_in %}
<span class="ct-balance">{{ user.balance | money }}</span>
<a class="ct-btn" href="{{ '/dashboard/new-order' | url }}">{{ 'nav.dashboard' | t }}</a>
{% else %}
<a href="{{ '/login' | url }}">{{ 'nav.login' | t }}</a>
<a class="ct-btn" href="{{ '/register' | url }}">{{ 'nav.register' | t }}</a>
{% endif %}
</div>
<button class="ct-burger" type="button" aria-label="Menu">☰</button>
</header>
<main>{{ content_for_layout | raw }}</main>
<footer class="ct-footer">
<span>© {{ 'now' | date: '%Y' }} {{ site.name }}</span>
{% if site.support_email != blank %}<a href="mailto:{{ site.support_email }}">{{ site.support_email }}</a>{% endif %}
{% if site.telegram != blank %}<a href="{{ site.telegram }}">Telegram</a>{% endif %}
</footer>
</div>
<style>
.ct { max-width: 1160px; margin: 0 auto; padding: 20px 16px; color: var(--on-surface); }
.ct-header { display: flex; align-items: center; gap: 20px; padding: 10px 0 28px; }
.ct-brand { font: 800 20px/1 var(--font-family-display); display: flex; align-items: center; }
.ct-nav { display: flex; gap: 18px; margin-left: 12px; }
.ct-nav a, .ct-actions a { color: var(--on-surface-variant); font-weight: 600; }
.ct-nav a:hover { color: var(--primary); }
.ct-actions { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.ct-balance { font-weight: 700; color: var(--primary); }
.ct-btn { background: var(--brand-solid); color: var(--on-brand) !important; padding: 10px 18px; border-radius: var(--radius-3xl); font-weight: 700; }
.ct-burger { display: none; margin-left: auto; font-size: 22px; background: none; border: 0; color: inherit; }
.ct-card { background: var(--surface-container-lowest); border: 1px solid var(--outline-variant); border-radius: var(--radius-2xl); padding: 20px; }
.ct-footer { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 72px; padding-top: 24px; border-top: 1px solid var(--outline-variant); color: var(--on-surface-variant); font-size: 14px; }
@media (max-width: 760px) {
.ct-nav, .ct-actions { display: none; }
.ct-burger { display: block; }
.ct.open .ct-nav, .ct.open .ct-actions { display: flex; flex-direction: column; align-items: flex-start; width: 100%; margin: 0; }
.ct-header { flex-wrap: wrap; }
}
</style>
<script>
// Inline onclick doesn't run; use addEventListener.
document.querySelector(".ct-burger")?.addEventListener("click", () => {
document.querySelector(".ct")?.classList.toggle("open");
});
</script>
home.liquid
<section class="hero">
<p class="eyebrow">{{ catalog.service_count }} services · 24/7 support</p>
<h1>Grow your accounts with {{ site.name }}</h1>
<p class="lead">{{ 'landing.lead' | t }}</p>
<div class="cta">
<a class="ct-btn" href="{{ '/dashboard/new-order' | url }}">{{ 'landing.ctaPrimary' | t }}</a>
<a href="{{ '/services' | url }}">Price list →</a>
</div>
</section>
{% render '_platforms', catalog: catalog %}
{% if blog.posts.size > 0 %}
<h2 class="title">From the blog</h2>
<div class="grid">
{% for p in blog.posts limit: 3 %}
<a class="ct-card" href="{{ p.url }}">
{% if p.cover != blank %}<img src="{{ p.cover }}" alt="" loading="lazy">{% endif %}
<strong>{{ p.title }}</strong>
<p>{{ p.excerpt | truncate: 120 }}</p>
</a>
{% endfor %}
</div>
{% endif %}
<h2 class="title">FAQ</h2>
<details class="ct-card"><summary>When does my order start?</summary><p>Most services start within minutes.</p></details>
<details class="ct-card"><summary>How can I pay?</summary><p>Add funds by card, crypto or bank transfer.</p></details>
<style>
.hero { text-align: center; padding: 56px 0 40px; }
.hero h1 { font: 900 clamp(32px, 6vw, 56px)/1.08 var(--font-family-display); letter-spacing: -0.02em; }
.eyebrow { color: var(--primary); font-weight: 700; text-transform: uppercase; letter-spacing: .12em; font-size: 12px; }
.lead { max-width: 560px; margin: 16px auto 28px; color: var(--on-surface-variant); font-size: 18px; }
.cta { display: flex; gap: 18px; justify-content: center; align-items: center; }
.title { font: 800 26px/1.2 var(--font-family-display); margin: 56px 0 18px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.grid img { width: 100%; border-radius: var(--radius-xl); margin-bottom: 10px; }
details.ct-card { margin-bottom: 10px; }
details summary { cursor: pointer; font-weight: 700; }
</style>
_platforms.liquid
<section class="platforms">
{% for group in catalog.groups limit: 8 %}
<a class="ct-card platform" href="{{ '/services' | url }}#{{ group.slug }}">
<strong>{{ group.name }}</strong>
<span>{{ group.services }} services</span>
<small>{% for c in group.categories limit: 3 %}{{ c.name }}{% unless forloop.last %} · {% endunless %}{% endfor %}</small>
</a>
{% endfor %}
</section>
<style>
.platforms { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.platform { display: flex; flex-direction: column; gap: 4px; transition: transform .15s; }
.platform:hover { transform: translateY(-2px); border-color: var(--primary); }
.platform span { color: var(--primary); font-weight: 700; }
.platform small { color: var(--on-surface-variant); }
</style>
about.liquid
<section class="ct-card" style="max-width:760px;margin:24px auto">
<h1 style="font:800 34px/1.15 var(--font-family-display)">About us</h1>
<p>{{ site.name }} sells social media services at wholesale prices.</p>
{% if user.logged_in %}
<p>Welcome back {{ user.name | default: user.email }}! Your balance: <strong>{{ user.balance | money }}</strong></p>
{% endif %}
</section>
This page is published at /en/about.
Common mistakes
| Symptom | Cause / fix |
|---|---|
| HTML tags show up as text | Output is escaped. Use | raw for trusted HTML. |
undefined filter: ... | Typo in a filter name; only Liquid's filters plus t, url, money exist. |
Template not found: _x | The partial doesn't exist or is named differently; don't write .liquid inside render. |
| A button does nothing | Inline onclick is blocked; use addEventListener. |
| Changes don't show on the site | Did you publish, and is "use the code theme" on? Wait a minute for the cache. |
X: this address is used by the panel | The file name clashes with a reserved address; pick another name. |