Skip to content

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:

  1. Go to Channels
  2. Create a new channel (e.g., "team", "testimonials", "events")
  3. Define custom fields (name, bio, photo, role, etc.)
  4. Add entries through the admin interface

Accessing Channel Data

Access channels through the channels drop:

liquid
{{ channels.team }}           {# Access 'team' channel #}
{{ channels.testimonials }}   {# Access 'testimonials' channel #}

Channel Properties

PropertyDescription
slugChannel slug/identifier
nameChannel display name
descriptionChannel description
attributesArray of field definitions
select_optionsOptions for select fields
new_entryCreate new entry form
updated_atLast update timestamp
All custom fieldsAccess by field name

Enumeration Helpers

MethodDescription
allAll entries
firstFirst entry
lastLast entry
latestMost recently created
randomRandom entries
count, sizeNumber of entries
empty?Boolean: no entries
any?Boolean: has entries

Basic Channel Usage

Listing All Entries

liquid
<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

liquid
<!-- 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

liquid
<!-- 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

OperatorComparison TypeUsage Example
==Equalsstatus == 'published'
!=Not equalsstatus != 'draft'
<Less thanprice < 100
>Greater thanviews > 1000
<=Less than or equalstock <= 10
>=Greater than or equalrating >= 4
liquid
{% scope price > 5000 %}
  <!-- Products over €50 -->
{% endscope %}

{% scope publication_date >= '2024-01-01' %}
  <!-- Published in 2024 or later -->
{% endscope %}

String Operators

OperatorString OperationUsage Example
containsContains substringtitle contains 'Guide'
startStarts withname start 'Dr.'
endEnds withemail end '@example.com'
regexMatches regexsku regex '^PROD-'
liquid
{% scope title contains 'Tutorial' %}
  <!-- Articles with 'Tutorial' in title -->
{% endscope %}

{% scope email end '@company.com' %}
  <!-- Company email addresses -->
{% endscope %}

Array Operators

OperatorArray OperationUsage Example
inValue in arraycategory_slug in params.categories
ninValue not in arraystatus nin ['draft', 'archived']
liquid
{% scope status in ['published', 'featured'] %}
  <!-- Published or featured items -->
{% endscope %}

{% scope category_slug in params.selected_categories %}
  <!-- User-selected categories -->
{% endscope %}

Existence Operators

OperatorExistence CheckUsage Example
existsField exists and is not nullfeatured_image exists
liquid
{% 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:

liquid
{% 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

liquid
{% 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:

liquid
<!-- 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

liquid
<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

liquid
<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

liquid
{% 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

liquid
{% 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

liquid
{% sort priority desc, name asc %}
  {% for task in channels.tasks.all %}
    <p>{{ task.priority }} - {{ task.name }}</p>
  {% endfor %}
{% endsort %}

Combining Scope and Sort

liquid
{% 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:

PropertyDescription
idUnique entry ID
created_atCreation timestamp
updated_atLast update timestamp
titleEntry title
urlEntry detail page URL (if routable)
_permalinkCustom permalink
_slugURL slug
_seo_titleSEO title override
_seo_descriptionSEO description
All custom fieldsBy field name

Plus all your custom fields are accessible directly:

liquid
{{ entry.name }}
{{ entry.bio }}
{{ entry.photo.url }}
{{ entry.start_date }}
{{ entry.featured }}

Working with Reference Fields

Reference fields link to other content:

liquid
<!-- 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:

liquid
{% 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:

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:

liquid
{% 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

liquid
<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

liquid
<!-- 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:

liquid
<!-- ❌ 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

liquid
{% 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

liquid
{% paginate channels.blog.all by 20 %}
  <!-- Content -->
  {{ paginate | default_pagination }}
{% endpaginate %}

4. Check for Existence

liquid
{% 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 limit to restrict results
  • Add caching with {% cache %}
  • Use pagination for large sets

Next Steps

Build powerful custom content experiences with Channels!