Tag template
Reference for the tag object — free-form post labels
The tags template renders pages that display all posts matching one or more tags. Tag pages are served by a dedicated route rather than the standard template lookup.
Location
└── theme
└── templates
└── tags.liquidURL Structure
Tags are scoped to a blog, so tag pages are accessible at:
/blogs/:blogSlug/tags/:tagged- Single tag/blogs/:blogSlug/tags/:tag+tag2- Multiple tags (AND logic)
For example:
/blogs/news/tags/featured- Posts tagged "featured"/blogs/news/tags/news+featured- Posts tagged BOTH "news" AND "featured"
Each tag record exposes a ready-built url property in exactly this shape.
Prefer linking with the url property rather than assembling the path by hand,
so links stay correct if the URL structure ever changes.
Template Variables
The tags template receives these variables:
The tags Array
The tag slugs taken from the URL — an array of strings, one per tag being filtered:
| Property | Type | Description |
|---|---|---|
tags | array | Tag slugs being filtered (strings) |
On the tag archive page the tags variable holds the raw slugs from the URL
(so a single-tag page gives ["featured"]). This is different from a post's
own tags, which are full objects — see the warning below.
The posts Array
An array of posts matching ALL specified tags:
| Property | Type | Description |
|---|---|---|
posts | array | Posts matching the tag filter |
Each post in the array has access to all standard post properties.
A post's own tags and categories are arrays of objects, not plain
strings. Each entry has a title and a slug — loop over them and read
tag.title and tag.slug. You do not need handleize; use tag.slug
directly when building links.
Basic Template Example
{% layout 'layouts/default.liquid' %}
{% capture content_for_layout %}
<div class="tags-page">
<header class="tags-header">
<h1>
Posts tagged:
{% for tag in tags %}
<span class="tag-badge">{{ tag }}</span>
{% unless forloop.last %} + {% endunless %}
{% endfor %}
</h1>
<p class="post-count">{{ posts.size }} {% if posts.size == 1 %}post{% else %}posts{% endif %}</p>
</header>
<div class="posts-list">
{% if posts.size > 0 %}
{% for post in posts %}
<article class="post-card">
{% if post.image %}
<img
src="{{ post.image | image_url: width: 400, height: 250 }}"
alt="{{ post.title }}"
loading="lazy"
>
{% endif %}
<div class="post-card__content">
<h2>
<a href="/posts/{{ post.slug }}">{{ post.title }}</a>
</h2>
{% if post.publishDate %}
<time datetime="{{ post.publishDate }}">
{{ post.publishDate | date: '%B %d, %Y' }}
</time>
{% endif %}
{% if post.description %}
<p>{{ post.description | truncate: 150 }}</p>
{% endif %}
</div>
</article>
{% endfor %}
{% else %}
<p class="no-posts">No posts found with these tags.</p>
{% endif %}
</div>
</div>
{% endcapture %}Working with Tags
Display Current Tags
<div class="active-tags">
<span>Filtering by:</span>
{% for tag in tags %}
<span class="tag tag--active">{{ tag }}</span>
{% endfor %}
</div>Tag Pills with Remove Option
The page-level tags are slugs, so you can rebuild the +-joined path
directly. Take the blog slug from any post so the links stay scoped to the
current blog:
{% assign blog_slug = posts.first.blog.slug %}
<div class="tag-filters">
{% for tag in tags %}
{% assign other_tags = '' %}
{% for t in tags %}
{% if t != tag %}
{% if other_tags != '' %}
{% assign other_tags = other_tags | append: '+' %}
{% endif %}
{% assign other_tags = other_tags | append: t %}
{% endif %}
{% endfor %}
<span class="tag-pill">
{{ tag }}
{% if other_tags != '' %}
<a href="/blogs/{{ blog_slug }}/tags/{{ other_tags }}" class="remove-tag" aria-label="Remove {{ tag }} filter">×</a>
{% else %}
<a href="/blogs/{{ blog_slug }}" class="remove-tag" aria-label="Remove {{ tag }} filter">×</a>
{% endif %}
</span>
{% endfor %}
{% if tags.size > 1 %}
<a href="/blogs/{{ blog_slug }}" class="clear-all">Clear all</a>
{% endif %}
</div>Show All Tags on Each Post
A post's tags are objects with title and slug. Build the archive URL from
the post's blog slug and the tag slug, and compare tag.slug against the
page-level tags slugs to highlight the active ones:
{% for post in posts %}
<article class="post-card">
<h3><a href="/posts/{{ post.slug }}">{{ post.title }}</a></h3>
{% if post.tags.size > 0 %}
<div class="post-tags">
{% for tag in post.tags %}
<a href="/blogs/{{ post.blog.slug }}/tags/{{ tag.slug }}"
class="tag {% if tags contains tag.slug %}tag--active{% endif %}">
{{ tag.title }}
</a>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}Complete Template Example
{% layout 'layouts/default.liquid' %}
{% capture content_for_layout %}
{% assign blog_slug = posts.first.blog.slug %}
<div class="tags-page">
{# Breadcrumb navigation #}
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
<li aria-current="page">
Tagged:
{% for tag in tags %}
{{ tag }}{% unless forloop.last %}, {% endunless %}
{% endfor %}
</li>
</ol>
</nav>
{# Page header #}
<header class="tags-header">
<h1>
{% if tags.size == 1 %}
Posts tagged "{{ tags.first }}"
{% else %}
Posts tagged:
{% endif %}
</h1>
{# Active tag filters #}
{% if tags.size > 1 %}
<div class="active-filters">
{% for tag in tags %}
{% comment %} Build URL without this tag {% endcomment %}
{% assign remaining_tags = '' %}
{% for t in tags %}
{% if t != tag %}
{% if remaining_tags != '' %}
{% assign remaining_tags = remaining_tags | append: '+' %}
{% endif %}
{% assign remaining_tags = remaining_tags | append: t %}
{% endif %}
{% endfor %}
<span class="filter-tag">
{{ tag }}
{% if remaining_tags != '' %}
<a href="/blogs/{{ blog_slug }}/tags/{{ remaining_tags }}" class="remove" title="Remove this filter">×</a>
{% else %}
<a href="/blogs/{{ blog_slug }}" class="remove" title="Remove this filter">×</a>
{% endif %}
</span>
{% endfor %}
<a href="/blogs/{{ blog_slug }}" class="clear-filters">Clear all filters</a>
</div>
{% endif %}
<p class="results-count">
{{ posts.size }} {% if posts.size == 1 %}result{% else %}results{% endif %}
</p>
</header>
{# Posts listing #}
{% if posts.size > 0 %}
<section class="tags-posts">
<div class="post-grid">
{% for post in posts %}
<article class="post-card">
<a href="/posts/{{ post.slug }}" class="post-card__link">
{% if post.image %}
<img
src="{{ post.image | image_url: width: 400, height: 250 }}"
alt="{{ post.title }}"
class="post-card__image"
loading="lazy"
>
{% endif %}
<div class="post-card__content">
{% if post.categories.size > 0 %}
<span class="post-card__category">{{ post.categories.first.title }}</span>
{% endif %}
<h2 class="post-card__title">{{ post.title }}</h2>
<div class="post-card__meta">
{% if post.publishDate %}
<time datetime="{{ post.publishDate }}">
{{ post.publishDate | date: '%b %d, %Y' }}
</time>
{% endif %}
{% if post.authors.size > 0 %}
<span class="author">by {{ post.authors.first.name }}</span>
{% endif %}
</div>
{% if post.description %}
<p class="post-card__excerpt">
{{ post.description | truncate: 120 }}
</p>
{% endif %}
{# Show post tags, highlighting active ones #}
{% if post.tags.size > 0 %}
<div class="post-card__tags">
{% for tag in post.tags %}
<span class="mini-tag {% if tags contains tag.slug %}mini-tag--active{% endif %}">
{{ tag.title }}
</span>
{% endfor %}
</div>
{% endif %}
</div>
</a>
</article>
{% endfor %}
</div>
</section>
{% else %}
<div class="tags-empty">
<h2>No Posts Found</h2>
<p>
No posts match
{% if tags.size == 1 %}
the tag "{{ tags.first }}".
{% else %}
all of these tags: {{ tags | join: ', ' }}.
{% endif %}
</p>
<p>Try removing some filters or browse all posts.</p>
<a href="/blog" class="btn btn--secondary">Browse All Posts</a>
</div>
{% endif %}
{# Navigation #}
<nav class="tags-nav">
<a href="/blog" class="back-link">
← Back to Blog
</a>
</nav>
</div>
{% endcapture %}
{% schema %}
{
"name": "tags",
"settings": [
{
"type": "select",
"name": "layout",
"label": "Posts Layout",
"options": [
{ "value": "grid", "label": "Grid" },
{ "value": "list", "label": "List" }
],
"defaultValue": "grid"
},
{
"type": "checkbox",
"name": "show_post_tags",
"label": "Show Tags on Each Post",
"defaultValue": true
}
]
}
{% endschema %}Linking to Tags
From Post Templates
A post's tags are objects with title and slug. Build the archive URL from
the post's blog slug and the tag slug — the same shape core generates:
{# In post.liquid template #}
{% if post.tags.size > 0 %}
<div class="post-tags">
<span class="tags-label">Tags:</span>
{% for tag in post.tags %}
<a href="/blogs/{{ post.blog.slug }}/tags/{{ tag.slug }}" class="tag">{{ tag.title }}</a>
{% endfor %}
</div>
{% endif %}Combining Tags
Join multiple tag slugs with + for an AND filter. Tag pages are blog-scoped,
so include the blog slug:
{# Link to posts with multiple tags #}
<a href="/blogs/news/tags/featured+world-premiere">Featured premieres</a>
<a href="/blogs/news/tags/interview+behind-the-scenes">Behind-the-scenes interviews</a>Tag Cloud Component
When you have the full tag records (for example via the blog's tags list),
each one carries a ready-built url, so you can link to it directly:
{# Display all available tags #}
{% if blog.tags.size > 0 %}
<aside class="tag-cloud">
<h3>Browse by Tag</h3>
<div class="tags">
{% for tag in blog.tags %}
<a href="{{ tag.url }}" class="tag">
{{ tag.title }}
</a>
{% endfor %}
</div>
</aside>
{% endif %}Combining Tags vs Categories
| Feature | Tags | Categories |
|---|---|---|
| URL | /blogs/:blogSlug/tags/:tagged | /blogs/:blogSlug/categories/:slug |
| Multiple filter | Yes (+ syntax) | No |
| Hierarchy | Flat | Flat |
| User-defined | Yes | Admin-defined |
| Best for | Flexible labeling | Primary classification |
Alternate Tags Templates
Create variations for different tag presentations:
templates/
├── tags.liquid # Default tags template
├── tags.minimal.liquid # Simple list view
└── tags.magazine.liquid # Magazine-style layout