Channels
Channels are Nimbu's custom content types. Use them to manage any structured content: team members, testimonials, case studies, events, locations, FAQs—anything beyond the built-in blogs and products.
What Are Channels?
Channels are:
- Custom content types you define in the Nimbu admin
- Flexible with custom fields (text, rich text, files, references, dates, etc.)
- Queryable with powerful filtering using
{% scope %}and{% sort %} - Enumerable with helper methods like
first,last,random,count
Creating Channels
In the Nimbu admin:
- Go to Channels
- Create a new channel (e.g., "team", "testimonials", "events")
- Define custom fields (name, bio, photo, role, etc.)
- Add entries through the admin interface
Accessing Channel Data
Access channels through the channels drop:
{{ channels.team }} {# Access 'team' channel #}
{{ channels.testimonials }} {# Access 'testimonials' channel #}Channel Properties
| Property | Description |
|---|---|
slug | Channel slug/identifier |
name | Channel display name |
description | Channel description |
attributes | Array of field definitions |
select_options | Options for select fields |
new_entry | Create new entry form |
updated_at | Last update timestamp |
| All custom fields | Access by field name |
Enumeration Helpers
| Method | Description |
|---|---|
all | All entries |
first | First entry |
last | Last entry |
latest | Most recently created |
random | Random entries |
count, size | Number of entries |
empty? | Boolean: no entries |
any? | Boolean: has entries |
Basic Channel Usage
Listing All Entries
<div class="team-members">
{% for member in channels.team.all %}
<div class="team-member">
<img src="{{ member.photo.url | filter, width: '300px' }}" alt="{{ member.name }}">
<h3>{{ member.name }}</h3>
<p class="role">{{ member.role }}</p>
<p class="bio">{{ member.bio }}</p>
</div>
{% endfor %}
</div>With Limit
<!-- Show first 3 entries -->
{% for testimonial in channels.testimonials.all limit: 3 %}
<blockquote>
<p>{{ testimonial.quote }}</p>
<cite>{{ testimonial.customer_name }}</cite>
</blockquote>
{% endfor %}
<!-- Show random 4 entries -->
{% for item in channels.portfolio.random limit: 4 %}
{% include 'portfolio-card', item: item %}
{% endfor %}Filtering with {% scope %}
The {% scope %} tag filters channel entries using powerful query expressions:
Basic Filtering
<!-- Filter by field value -->
{% scope role == 'Developer' %}
{% for member in channels.team.all %}
<p>{{ member.name }} - {{ member.role }}</p>
{% endfor %}
{% endscope %}
<!-- Filter by boolean -->
{% scope featured == true %}
{% for project in channels.portfolio.all %}
{% include 'project-card', project: project %}
{% endfor %}
{% endscope %}Comparison Operators
| Operator | Comparison Type | Usage Example |
|---|---|---|
== | Equals | status == 'published' |
!= | Not equals | status != 'draft' |
< | Less than | price < 100 |
> | Greater than | views > 1000 |
<= | Less than or equal | stock <= 10 |
>= | Greater than or equal | rating >= 4 |
{% scope price > 5000 %}
<!-- Products over €50 -->
{% endscope %}
{% scope publication_date >= '2024-01-01' %}
<!-- Published in 2024 or later -->
{% endscope %}String Operators
| Operator | String Operation | Usage Example |
|---|---|---|
contains | Contains substring | title contains 'Guide' |
start | Starts with | name start 'Dr.' |
end | Ends with | email end '@example.com' |
regex | Matches regex | sku regex '^PROD-' |
{% scope title contains 'Tutorial' %}
<!-- Articles with 'Tutorial' in title -->
{% endscope %}
{% scope email end '@company.com' %}
<!-- Company email addresses -->
{% endscope %}Array Operators
| Operator | Array Operation | Usage Example |
|---|---|---|
in | Value in array | category_slug in params.categories |
nin | Value not in array | status nin ['draft', 'archived'] |
{% scope status in ['published', 'featured'] %}
<!-- Published or featured items -->
{% endscope %}
{% scope category_slug in params.selected_categories %}
<!-- User-selected categories -->
{% endscope %}Existence Operators
| Operator | Existence Check | Usage Example |
|---|---|---|
exists | Field exists and is not null | featured_image exists |
{% scope featured_image exists %}
<!-- Only items with images -->
{% endscope %}
{% scope end_date exists %}
<!-- Events with end dates -->
{% endscope %}Logical Operators
Combine conditions with and / or:
{% scope status == 'published' and featured == true %}
<!-- Published AND featured -->
{% endscope %}
{% scope category == 'news' or category == 'blog' %}
<!-- News OR blog category -->
{% endscope %}
{% scope price > 1000 and price < 5000 and in_stock == true %}
<!-- Mid-range products in stock -->
{% endscope %}Nested Scopes
{% scope category == 'events' %}
<h2>Upcoming Events</h2>
{% scope start_date >= 'now' %}
{% for event in channels.calendar.all %}
<p>{{ event.title }} - {{ event.start_date | localized_date }}</p>
{% endfor %}
{% endscope %}
{% endscope %}Reference Filtering
Filter by referenced content:
<!-- Filter by referenced item ID -->
{% scope author_id == current_author.id %}
<!-- Articles by this author -->
{% endscope %}
<!-- Filter by multiple references -->
{% scope category_ids contains selected_category.id %}
<!-- Items in this category -->
{% endscope %}Real-World Filtering Examples
Event Calendar
<section class="upcoming-events">
<h2>Upcoming Events</h2>
{% scope start_date >= 'now' %}
{% sort start_date asc %}
{% for event in channels.events.all limit: 5 %}
<div class="event">
<h3>{{ event.title }}</h3>
<p class="date">{{ event.start_date | localized_date: 'long' }}</p>
<p class="location">{{ event.location }}</p>
</div>
{% endfor %}
{% endsort %}
{% endscope %}
</section>Team Directory with Filters
<div class="team-directory">
<!-- Department filter from URL params -->
{% if params.department %}
{% scope department == params.department %}
{% for member in channels.team.all %}
{% include 'team-card', member: member %}
{% endfor %}
{% endscope %}
{% else %}
{% for member in channels.team.all %}
{% include 'team-card', member: member %}
{% endfor %}
{% endif %}
</div>Portfolio with Category Filter
{% if params.category %}
{% scope category_slug == params.category %}
{% for project in channels.portfolio.all %}
{% include 'project-card', project: project %}
{% endfor %}
{% endscope %}
{% else %}
{% scope featured == true %}
{% for project in channels.portfolio.random limit: 6 %}
{% include 'project-card', project: project %}
{% endfor %}
{% endscope %}
{% endif %}Sorting with {% sort %}
The {% sort %} tag orders channel entries:
Basic Sorting
{% sort name asc %}
{% for member in channels.team.all %}
<p>{{ member.name }}</p>
{% endfor %}
{% endsort %}
{% sort created_at desc %}
{% for article in channels.blog.all %}
<p>{{ article.title }}</p>
{% endfor %}
{% endsort %}Multi-Field Sorting
{% sort priority desc, name asc %}
{% for task in channels.tasks.all %}
<p>{{ task.priority }} - {{ task.name }}</p>
{% endfor %}
{% endsort %}Combining Scope and Sort
{% scope status == 'published' %}
{% sort published_date desc %}
{% for article in channels.blog.all %}
<article>
<h2>{{ article.title }}</h2>
<p class="date">{{ article.published_date | localized_date }}</p>
<p>{{ article.excerpt }}</p>
</article>
{% endfor %}
{% endsort %}
{% endscope %}Channel Entry Properties
Every channel entry has these standard properties:
| Property | Description |
|---|---|
id | Unique entry ID |
created_at | Creation timestamp |
updated_at | Last update timestamp |
title | Entry title |
url | Entry detail page URL (if routable) |
_permalink | Custom permalink |
_slug | URL slug |
_seo_title | SEO title override |
_seo_description | SEO description |
| All custom fields | By field name |
Plus all your custom fields are accessible directly:
{{ entry.name }}
{{ entry.bio }}
{{ entry.photo.url }}
{{ entry.start_date }}
{{ entry.featured }}Working with Reference Fields
Reference fields link to other content:
<!-- Author reference -->
{% if article.author %}
<p class="byline">
By {{ article.author.name }}
{% if article.author.photo %}
<img src="{{ article.author.photo.url | filter, width: '50px' }}" alt="{{ article.author.name }}">
{% endif %}
</p>
{% endif %}
<!-- Multiple references -->
{% if project.team_members %}
<div class="project-team">
{% for member in project.team_members %}
<div class="team-member">
<img src="{{ member.photo.url | filter, width: '100px' }}" alt="{{ member.name }}">
<p>{{ member.name }}</p>
</div>
{% endfor %}
</div>
{% endif %}Pagination
Use {% paginate %} for large channel listings:
{% paginate channels.blog.all by 10 %}
{% for article in channels.blog.all %}
<article>
<h2><a href="{{ article.url }}">{{ article.title }}</a></h2>
<p>{{ article.excerpt }}</p>
</article>
{% endfor %}
{{ paginate | default_pagination }}
{% endpaginate %}Channel Detail Pages
Create detail pages by routing to templates:
Channel Entry Template
templates/team_member.liquid:
<div class="team-member-page">
<div class="container">
<article class="member-details">
<header>
{% if entry.photo %}
<img src="{{ entry.photo.url | filter, width: '400px' }}" alt="{{ entry.name }}">
{% endif %}
<h1>{{ entry.name }}</h1>
<p class="role">{{ entry.role }}</p>
</header>
<div class="member-bio">
{{ entry.bio }}
</div>
{% if entry.email %}
<p><a href="mailto:{{ entry.email }}">{{ entry.email }}</a></p>
{% endif %}
{% if entry.social_links %}
<ul class="social-links">
{% for link in entry.social_links %}
<li><a href="{{ link.url }}">{{ link.platform }}</a></li>
{% endfor %}
</ul>
{% endif %}
</article>
<!-- Related team members -->
<aside class="related-members">
<h2>More Team Members</h2>
{% scope role == entry.role %}
{% for member in channels.team.random limit: 3 %}
{% unless member.id == entry.id %}
{% include 'team-card', member: member %}
{% endunless %}
{% endfor %}
{% endscope %}
</aside>
</div>
</div>Forms for Channels
Create submission forms for channels:
{% form channels.contact, class: 'contact-form' %}
{% error_messages_for form_model %}
{% input 'name', label: 'Name', required: true %}
{% input 'email', as: 'email', label: 'Email', required: true %}
{% input 'phone', label: 'Phone' %}
{% text_area 'message', label: 'Message', rows: 5, required: true %}
{% submit_tag 'Send Message', class: 'btn btn-primary' %}
{% endform %}See Forms for complete form documentation.
Advanced Patterns
Dynamic Filtering
<form action="{{ url.current_path }}" method="get" class="filter-form">
<select name="category">
<option value="">All Categories</option>
{% for cat in channels.projects.select_options.category %}
<option value="{{ cat }}" {% if params.category == cat %}selected{% endif %}>
{{ cat }}
</option>
{% endfor %}
</select>
<select name="status">
<option value="">All Statuses</option>
<option value="active" {% if params.status == 'active' %}selected{% endif %}>Active</option>
<option value="completed" {% if params.status == 'completed' %}selected{% endif %}>Completed</option>
</select>
<button type="submit">Filter</button>
</form>
<!-- Apply filters -->
{% assign has_filters = false %}
{% if params.category or params.status %}
{% assign has_filters = true %}
{% endif %}
{% if has_filters %}
{% capture scope_expr %}
{% if params.category %}category == '{{ params.category }}'{% endif %}
{% if params.category and params.status %} and {% endif %}
{% if params.status %}status == '{{ params.status }}'{% endif %}
{% endcapture %}
{% scope {{ scope_expr }} %}
{% for project in channels.projects.all %}
{% include 'project-card', project: project %}
{% endfor %}
{% endscope %}
{% else %}
{% for project in channels.projects.all %}
{% include 'project-card', project: project %}
{% endfor %}
{% endif %}Counting & Grouping
<!-- Count by category -->
{% assign categories = channels.blog.all | group_by: 'category' %}
{% for category_group in categories %}
<p>{{ category_group.name }}: {{ category_group.items.size }} articles</p>
{% endfor %}
<!-- Group by year -->
{% assign by_year = channels.events.all | group_by_exp: 'event', 'event.start_date | date: "%Y"' %}
{% for year_group in by_year %}
<h3>{{ year_group.name }}</h3>
{% for event in year_group.items %}
<p>{{ event.title }}</p>
{% endfor %}
{% endfor %}Best Practices
1. Use Scope for Filtering
Always use {% scope %} instead of {% if %} inside loops for better performance:
<!-- ❌ Bad: Filter in loop -->
{% for item in channels.news.all %}
{% if item.featured %}
{{ item.title }}
{% endif %}
{% endfor %}
<!-- ✅ Good: Filter with scope -->
{% scope featured == true %}
{% for item in channels.news.all %}
{{ item.title }}
{% endfor %}
{% endscope %}2. Cache Channel Queries
{% cache 'featured-team', expires_in: 3600 %}
{% scope featured == true %}
{% for member in channels.team.all %}
{% include 'team-card', member: member %}
{% endfor %}
{% endscope %}
{% endcache %}3. Use Pagination for Large Sets
{% paginate channels.blog.all by 20 %}
<!-- Content -->
{{ paginate | default_pagination }}
{% endpaginate %}4. Check for Existence
{% if channels.team.any? %}
<!-- Show team -->
{% else %}
<p>No team members yet.</p>
{% endif %}5. Use References
Instead of storing names/IDs manually, use reference fields to link content and maintain data integrity.
Troubleshooting
Scope Not Working
- Check field names match exactly (case-sensitive)
- Ensure operators are correct (
==not=) - Verify data types (strings need quotes, booleans don't)
Empty Results
- Check if entries exist:
{{ channels.channel_name.count }} - Verify scope conditions are correct
- Test without scope to see all entries
Performance Issues
- Use
limitto restrict results - Add caching with
{% cache %} - Use pagination for large sets
Next Steps
- Pages - Use channels in editable references
- Forms - Create channel submission forms
- Filters - Arrays - Advanced filtering with
where,group_by - Global Variables - Complete channels drop reference
Build powerful custom content experiences with Channels!