Skip to content

Collection Filtering and Sorting

Total CMS provides powerful filterCollection and sortCollection Twig filters for advanced data manipulation. These filters allow you to perform complex filtering operations on collections using a variety of operators, making it easy to create dynamic, filtered content displays.

The filterCollection filter allows you to filter arrays of objects based on complex criteria. It uses the CollectionRefiner service under the hood.

{% set filtered = collection | filterCollection([
{
property: "field_name",
operator: "operator_name",
value: "filter_value"
}
]) %}

Multiple filters can be combined - all filters must match (AND logic):

{% set filtered = collection | filterCollection([
{property: "status", operator: "equal", value: "published"},
{property: "date", operator: "past", value: ""}
]) %}

Exact match (case-sensitive for strings, loose comparison).

{% set published = cms.collection.objects('blog') | filterCollection([
{property: "status", operator: "equal", value: "published"}
]) %}

Check if string contains substring (case-sensitive).

{% set guides = cms.collection.objects('blog') | filterCollection([
{property: "title", operator: "contains", value: "Guide"}
]) %}

Check if string starts with prefix.

{% set tutorials = cms.collection.objects('blog') | filterCollection([
{property: "title", operator: "starts", value: "How to"}
]) %}

Check if string ends with suffix.

{% set questions = cms.collection.objects('blog') | filterCollection([
{property: "title", operator: "ends", value: "?"}
]) %}

Regular expression pattern matching.

{% set posts = cms.collection.objects('blog') | filterCollection([
{property: "title", operator: "like", value: "PHP.*Tutorial"}
]) %}

All string operators have case-insensitive versions:

  • equalCaseInsensitive
  • containsCaseInsensitive
  • startsCaseInsensitive
  • endsCaseInsensitive
{% set posts = cms.collection.objects('blog') | filterCollection([
{property: "title", operator: "containsCaseInsensitive", value: "guide"}
]) %}
{# Matches "Guide", "GUIDE", "guide", etc. #}

Less than comparison.

{% set cheapProducts = cms.collection.objects('products') | filterCollection([
{property: "price", operator: "less", value: "50"}
]) %}

Less than or equal to.

{% set affordableProducts = cms.collection.objects('products') | filterCollection([
{property: "price", operator: "lesseq", value: "100"}
]) %}

Greater than comparison.

{% set premiumProducts = cms.collection.objects('products') | filterCollection([
{property: "price", operator: "greater", value: "100"}
]) %}

Greater than or equal to.

{% set expensiveProducts = cms.collection.objects('products') | filterCollection([
{property: "price", operator: "greatereq", value: "500"}
]) %}

Check if value is true (true, ‘true’, ‘1’, or 1).

{% set featured = cms.collection.objects('products') | filterCollection([
{property: "featured", operator: "istrue"}
]) %}

Check if value is false (false, ‘false’, ‘0’, or 0).

{% set notFeatured = cms.collection.objects('products') | filterCollection([
{property: "featured", operator: "isfalse"}
]) %}

Check if value is empty.

{% set noDescription = cms.collection.objects('products') | filterCollection([
{property: "description", operator: "isempty"}
]) %}

Check if value is not empty.

{% set hasDescription = cms.collection.objects('products') | filterCollection([
{property: "description", operator: "isnotempty"}
]) %}

past - Date is in the past

{% set pastEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "past"}
]) %}

future - Date is in the future

{% set upcomingEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "future"}
]) %}

today - Date is today

{% set todaysEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "today"}
]) %}

pastToday - Date is today or in the past

{% set currentAndPast = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "pastToday"}
]) %}

futureToday - Date is today or in the future

{% set currentAndFuture = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "futureToday"}
]) %}

todayPlusDays - Today through N days in future

{# Events from today through next 7 days #}
{% set nextWeekEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "todayPlusDays", value: 7}
]) %}

todayMinusDays - Today and N days back

{# Blog posts from last 30 days including today #}
{% set recentPosts = cms.collection.objects('blog') | filterCollection([
{property: "date", operator: "todayMinusDays", value: 30}
]) %}

after - Date is after another date

{% set recent = cms.collection.objects('blog') | filterCollection([
{property: "date", operator: "after", value: "2024-01-01"}
]) %}

before - Date is before another date

{% set archived = cms.collection.objects('blog') | filterCollection([
{property: "date", operator: "before", value: "2023-01-01"}
]) %}

thisWeek - Date is in current week (Monday-Sunday)

{% set thisWeeksPosts = cms.collection.objects('blog') | filterCollection([
{property: "date", operator: "thisWeek"}
]) %}

thisMonth - Date is in current month

{% set thisMonthsPosts = cms.collection.objects('blog') | filterCollection([
{property: "date", operator: "thisMonth"}
]) %}

thisYear - Date is in current year

{% set thisYearsPosts = cms.collection.objects('blog') | filterCollection([
{property: "date", operator: "thisYear"}
]) %}

between - Value is between min and max (inclusive)

Usage: value: "min,max"

{# Products priced between $10 and $100 #}
{% set affordableProducts = cms.collection.objects('products') | filterCollection([
{property: "price", operator: "between", value: "10,100"}
]) %}
{# Cars with mileage between 10k-50k miles #}
{% set usedCars = cms.collection.objects('cars') | filterCollection([
{property: "mileage", operator: "between", value: "10000,50000"}
]) %}
{# Ratings between 3-5 stars #}
{% set topRated = cms.collection.objects('reviews') | filterCollection([
{property: "rating", operator: "between", value: "3,5"}
]) %}

longerThan - Text exceeds N characters

{# Blog posts with detailed content (over 500 chars) #}
{% set detailedPosts = cms.collection.objects('blog') | filterCollection([
{property: "content", operator: "longerThan", value: 500}
]) %}

shorterThan - Text is under N characters

{# Products with short descriptions for grid view #}
{% set compactProducts = cms.collection.objects('products') | filterCollection([
{property: "description", operator: "shorterThan", value: 200}
]) %}

hasMin - Array has at least N items

{# Posts with at least 3 tags #}
{% set wellTaggedPosts = cms.collection.objects('blog') | filterCollection([
{property: "tags", operator: "hasMin", value: 3}
]) %}
{# Products with multiple images #}
{% set multiImageProducts = cms.collection.objects('products') | filterCollection([
{property: "gallery", operator: "hasMin", value: 2}
]) %}

hasMax - Array has at most N items

{# Products with 5 or fewer images #}
{% set simpleProducts = cms.collection.objects('products') | filterCollection([
{property: "gallery", operator: "hasMax", value: 5}
]) %}

hasCount - Array has exactly N items

{# Events with exactly 2 speakers #}
{% set dualSpeakerEvents = cms.collection.objects('events') | filterCollection([
{property: "speakers", operator: "hasCount", value: 2}
]) %}

isWeekday - Date is Monday through Friday

{# Business hours events only #}
{% set businessEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "isWeekday"}
]) %}

isWeekend - Date is Saturday or Sunday

{# Weekend activities #}
{% set weekendEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "isWeekend"}
]) %}

dayOfWeek - Date is specific day of week

Value can be day name (Monday-Sunday) or number (1=Monday, 7=Sunday):

{# Tuesday specials #}
{% set tuesdaySpecials = cms.collection.objects('specials') | filterCollection([
{property: "date", operator: "dayOfWeek", value: "Tuesday"}
]) %}
{# Using day number (1=Mon, 7=Sun) #}
{% set mondayEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "dayOfWeek", value: "1"}
]) %}

When filtering with an array of values, you can control whether items must match ANY value (OR logic) or ALL values (AND logic) using the logic parameter.

Returns items that match ANY of the values in the array:

{# Posts tagged with 'php' OR 'javascript' OR 'web' #}
{% set posts = cms.collection.objects('blog') | filterCollection([
{
property: "tags",
operator: "contains",
value: ["php", "javascript", "web"],
logic: "or" {# Default - can be omitted #}
}
]) %}
{# Products in category 'electronics' OR 'computers' #}
{% set products = cms.collection.objects('products') | filterCollection([
{
property: "category",
operator: "equal",
value: ["electronics", "computers"]
{# logic: "or" is the default #}
}
]) %}

Returns items that match ALL of the values in the array:

{# Posts that contain ALL tags: 'php' AND 'framework' AND 'tutorial' #}
{% set advancedPosts = cms.collection.objects('blog') | filterCollection([
{
property: "tags",
operator: "contains",
value: ["php", "framework", "tutorial"],
logic: "and"
}
]) %}
{# Products with ALL specified features #}
{% set premiumProducts = cms.collection.objects('products') | filterCollection([
{
property: "features",
operator: "contains",
value: ["waterproof", "wireless", "fast-charging"],
logic: "and" {# Must have ALL three features #}
}
]) %}
LogicBehaviorUse Case
"or" (default)Match ANY valueBroad filtering, multiple categories
"and"Match ALL valuesStrict requirements, feature completeness

Any operator can be negated by prefixing with not- or prepending the value with !:

{# Using not- prefix #}
{% set notPublished = cms.collection.objects('blog') | filterCollection([
{property: "status", operator: "not-equal", value: "published"}
]) %}
{# Using ! prefix on value #}
{% set notPublished = cms.collection.objects('blog') | filterCollection([
{property: "status", operator: "equal", value: "!published"}
]) %}
{# Not in the past (future or today) #}
{% set notPastEvents = cms.collection.objects('events') | filterCollection([
{property: "date", operator: "not-past"}
]) %}

The sortCollection filter sorts arrays based on one or more properties.

{% set sorted = collection | sortCollection([
{
property: "field_name",
reverse: false, {# Optional: true for descending #}
natural: false, {# Optional: true for natural sort #}
shuffle: false {# Optional: true to randomize #}
}
]) %}
{# Sort by date ascending #}
{% set sorted = cms.collection.objects('blog') | sortCollection([
{property: "date"}
]) %}
{# Sort by price descending #}
{% set sorted = cms.collection.objects('products') | sortCollection([
{property: "price", reverse: true}
]) %}

Sorts are applied in order - first sort is primary, subsequent sorts break ties:

{# Sort by featured (desc), then date (desc), then title (natural) #}
{% set posts = cms.collection.objects('blog') | sortCollection([
{property: "featured", reverse: true},
{property: "date", reverse: true},
{property: "title", natural: true}
]) %}

Natural sorting treats numbers within strings intelligently:

{# Without natural: "Item 1", "Item 10", "Item 2" #}
{# With natural: "Item 1", "Item 2", "Item 10" #}
{% set sorted = cms.collection.objects('products') | sortCollection([
{property: "name", natural: true}
]) %}
{# Randomize order #}
{% set randomProducts = cms.collection.objects('products') | sortCollection([
{property: "name", shuffle: true}
]) %}

The manualSort filter allows you to sort collections by an explicit order of values, with control over how remaining items are handled. This is useful when you need a specific custom order that can’t be achieved with standard property sorting.

{% set sorted = collection | manualSort({
property: "field_name",
order: ["value1", "value2", "value3"],
remainder: {property: "name"},
excludeRemainder: false
}) %}
OptionTypeDescription
propertystringThe property to match against the order array
orderarrayExplicit list of values defining the sort order
collectionstringCollection ID to auto-lookup order from collection metadata
remainderobjectSort rule for items not in the order array (same format as sortCollection)
excludeRemainderbooleanIf true, items not in the order array are excluded from results

Note: When using collection, the filter looks up manualSort.{property} from the collection’s metadata. You can use either order or collection, but order takes precedence if both are provided.

{# Sort team members by role in specific order #}
{% set team = cms.collection.objects("team") | manualSort({
property: 'role',
order: ['ceo', 'cfo', 'cmo', 'vp', 'director']
}) %}
{# CEO first, CFO second, CMO third, etc. #}
{# Any roles not listed appear at the end in original order #}

Items not matching the explicit order can be sorted by a secondary property:

{# Executives in order, then remaining staff sorted by lastName #}
{% set team = cms.collection.objects("team") | manualSort({
property: 'position',
order: ['ceo', 'cfo', 'cmo', 'vp'],
remainder: {property: 'lastName'}
}) %}

Use excludeRemainder to only return items that match the order array:

{# Only show featured team members in specific order #}
{% set featured = cms.collection.objects("team") | manualSort({
property: 'id',
order: ['john-smith', 'jane-doe', 'bob-wilson'],
excludeRemainder: true
}) %}
{# Returns exactly 3 items (if they exist) in that order #}

Store sort orders in the collection’s metadata for easy admin editing. Use the collection option to automatically look up the order:

{# Automatic lookup from collection metadata #}
{% set team = cms.collection.objects("team") | manualSort({
property: 'position',
collection: 'team',
remainder: {property: 'lastName'}
}) %}

This is equivalent to manually fetching the metadata:

{# Manual lookup (same result) #}
{% set meta = cms.collection.get('team') %}
{% set team = cms.collection.objects("team") | manualSort({
property: 'position',
order: meta.manualSort.position | default([]),
remainder: {property: 'lastName'}
}) %}

To configure the order, edit the collection settings in the admin and add JSON to the “Manual Sort Orders” field:

{
"position": ["ceo", "cfo", "cmo", "vp", "director", "manager"],
"department": ["executive", "engineering", "sales", "marketing"]
}

When multiple items have the same ordered value, the remainder rule sorts them:

{# If there are multiple VPs, sort them by name #}
{% set team = cms.collection.objects("team") | manualSort({
property: 'role',
order: ['ceo', 'vp', 'manager'],
remainder: {property: 'name'}
}) %}
{# Result: CEO, then all VPs sorted by name, then managers sorted by name, then everyone else by name #}

Portfolio with curated order:

{% set projects = cms.collection.objects("projects") | manualSort({
property: 'id',
order: ['flagship-project', 'award-winner', 'client-favorite'],
remainder: {property: 'date', reverse: true}
}) %}
{# Featured projects first, then remaining by date descending #}

Navigation menu order:

{% set pages = cms.collection.objects("pages") | manualSort({
property: 'slug',
order: ['home', 'about', 'services', 'portfolio', 'contact'],
excludeRemainder: true
}) %}
{# Only these pages, in this exact order #}

Product categories with priority:

{% set meta = cms.collection.get('products') %}
{% set products = cms.collection.objects("products") | manualSort({
property: 'category',
order: meta.manualSort.category | default(['featured', 'new', 'sale']),
remainder: {property: 'name', natural: true}
}) %}

{# Published posts from this year, sorted by date #}
{% set posts = cms.collection.objects('blog')
| filterCollection([
{property: "status", operator: "equal", value: "published"},
{property: "date", operator: "thisYear"}
])
| sortCollection([
{property: "featured", reverse: true},
{property: "date", reverse: true}
]) %}
{# In-stock products, price $20-$100, with good ratings #}
{% set products = cms.collection.objects('products')
| filterCollection([
{property: "instock", operator: "istrue"},
{property: "price", operator: "between", value: "20,100"},
{property: "rating", operator: "greatereq", value: "4"},
{property: "description", operator: "longerThan", value: 50}
])
| sortCollection([
{property: "rating", reverse: true},
{property: "price"}
]) %}
{# This week's events on weekdays, sorted by date #}
{% set events = cms.collection.objects('events')
| filterCollection([
{property: "date", operator: "thisWeek"},
{property: "date", operator: "isWeekday"},
{property: "cancelled", operator: "isfalse"}
])
| sortCollection([
{property: "date"},
{property: "start_time"}
]) %}
{# Well-tagged posts from last 30 days with detailed content #}
{% set qualityPosts = cms.collection.objects('blog')
| filterCollection([
{property: "date", operator: "todayMinusDays", value: 30},
{property: "tags", operator: "hasMin", value: 3},
{property: "content", operator: "longerThan", value: 1000},
{property: "status", operator: "equal", value: "published"}
])
| sortCollection([
{property: "views", reverse: true},
{property: "date", reverse: true}
]) %}
{# Special offers valid this weekend #}
{% set weekendDeals = cms.collection.objects('specials')
| filterCollection([
{property: "active", operator: "istrue"},
{property: "start_date", operator: "isWeekend"},
{property: "discount", operator: "greatereq", value: "20"}
])
| sortCollection([
{property: "discount", reverse: true}
]) %}
{# Dynamic filtering based on URL parameters #}
{% set minPrice = get.min | default(0) %}
{% set maxPrice = get.max | default(1000) %}
{% set category = get.category | default('') %}
{% set products = cms.collection.objects('products')
| filterCollection([
{property: "price", operator: "between", value: minPrice ~ "," ~ maxPrice},
{property: "category", operator: "equal", value: category}
])
| sortCollection([
{property: get.sort | default('price'), reverse: get.order == 'desc'}
]) %}

  1. Filter before sorting - Reduce the dataset size before sorting
  2. Use specific operators - More specific operators are faster
  3. Limit results - Use | slice(0, 10) after filtering
  4. Cache filtered results - For expensive operations
{# Good: Filter first, then sort, then limit #}
{% set results = cms.collection.objects('blog')
| filterCollection([...])
| sortCollection([...])
| slice(0, 10) %}