Reference

MenoFilter API

Meno gives you two complementary ways to narrow a collection of items. At build time, you query a collection with getCollectionList(...) (or filter an already-fetched array with queryList(...)) so the page ships with exactly the items you want. At runtime, MenoFilter — a small client-side library — adds live filtering, search, sort, and pagination on top of the rendered list, driven entirely by data-* attributes.

This page is the precise reference for both. For the task-oriented walkthrough, see the Filtering and Search guide. For collection-backed lists in general, see Lists; for the underlying field schema, see CMS.

Build-time query helpers

These run in a page's frontmatter (the --- block), import from meno-astro, and return a plain array you map over in markup. The result is static HTML — nothing ships to the browser unless you also wire MenoFilter.

getCollectionList

Resolve a CMS collection to an array of items at build time.

---
import { getCollectionList, style } from 'meno-astro';
import { BaseLayout } from 'meno-astro/components';

const posts = await getCollectionList("blog", {
  filter: { field: "published", operator: "eq", value: true },
  sort: { field: "publishedAt", order: "desc" },
  limit: 6
}, Astro);
---
<BaseLayout meta={meta}>
  <div class={style({ base: { display: "grid", gap: "16px" } })}>
    {posts.map((blog, blogIndex) => (
      <article class={style({ base: { padding: "16px" } })}>
        <h3 class={style({ base: { fontSize: "20px" } })}>{blog.title}</h3>
        <p>{blog.excerpt}</p>
      </article>
    ))}
  </div>
</BaseLayout>

The signature is getCollectionList(source, query?, Astro, getCollection?). The source is the collection name (matching the folder under src/content/). It runs at build time and synthesizes _url and _id on each returned item. The loop variable defaults to the singularized collection name (blog for a "blog" source); make your {blog.title} templates match it, or set itemAs in the query to choose a name.

Query options

The optional second argument shapes the result. The same option shape is accepted by getCollectionList and queryList.

Option

Type

Description

filter

object or object[]

One filter condition or an array of conditions (ANDed)

sort

object or object[]

One sort spec or an array applied in order

limit

number

Maximum items to return

offset

number

Skip the first N items

itemAs

string

Loop variable name (collection lists)

Filter config shape

A single filter condition is { field, operator, value }. Pass an array to require multiple conditions.

const featured = await getCollectionList("products", {
  filter: [
    { field: "category", operator: "eq", value: "electronics" },
    { field: "price", operator: "lte", value: 500 }
  ]
}, Astro);

Field

Description

field

The item field to test

operator

One of the operators below (default eq)

value

The value to compare against

Build-time operators: eq, neq, gt, gte, lt, lte, contains, in.

Sort config shape

A sort spec is { field, order }. order is "asc" (default) or "desc". Pass an array to break ties with successive fields.

sort: [
  { field: "featured", order: "desc" },
  { field: "publishedAt", order: "desc" }
]

queryList

queryList(items, query) applies the same filter / sort / limit / offset options to an array you already have in hand — no collection fetch. Use it for a nested list filtered by an outer loop variable, or to re-slice a list you fetched once.

---
import { getCollectionList, queryList } from 'meno-astro';

const categories = await getCollectionList("categories", {}, Astro);
const allProducts = await getCollectionList("products", {}, Astro);
---
{categories.map((category, categoryIndex) => (
  <section>
    <h2>{category.title}</h2>
    {queryList(allProducts, {
      filter: { field: "categoryId", operator: "eq", value: category._id },
      sort: { field: "price", order: "asc" },
      limit: 4
    }).map((product, productIndex) => (
      <article>{product.title}</article>
    ))}
  </section>
))}

Parsing query strings

When a filter or sort comes from a string (a URL parameter, a stored config), use the parsers from meno-astro to turn it into the config objects above.

Helper

Signature

Description

parseFilterExpression

(expr: string) => FilterConfig

Parse a filter string into a { field, operator, value } config

serializeFilterExpression

(config) => string

Inverse of parseFilterExpression

parseSortExpression

(expr: string) => SortConfig

Parse a sort string such as "publishedAt desc" into { field, order }

serializeSortExpression

(config) => string

Inverse of parseSortExpression

MenoFilter (client runtime)

MenoFilter is a client-side JavaScript library for filtering, sorting, searching, and paginating collection data in the browser. It works two ways that can be combined: declarative wiring through data-* attributes on your markup, and a programmatic JavaScript API. The data-attribute path needs no custom code — Meno auto-creates and wires an instance per data-meno-filter container when the page loads.

Wiring it up

Render the collection at build time, mark the container with data-meno-filter="<collection>", mark the items wrapper with data-meno-list, and give each item the data-* fields you want to filter, search, or sort on. MenoFilter reads those item attributes and re-renders the visible set as the user interacts.

---
import { getCollectionList, style } from 'meno-astro';
import { BaseLayout } from 'meno-astro/components';

const productsList = await getCollectionList("products", {}, Astro);
---
<BaseLayout meta={meta}>
  <div class={style({ base: { padding: "40px" } })} data-meno-filter="products" data-meno-per-page="6">
    <input
      class={style({ base: { width: "100%", padding: "8px 12px" } })}
      type="text"
      placeholder="Search..."
      data-meno-search
      data-meno-search-fields="title,description"
    />
    <button data-meno-filter-field="category" data-meno-filter-value="*">All</button>
    <button data-meno-filter-field="category" data-meno-filter-value="electronics">Electronics</button>
    <span data-meno-count="results">0</span> results
    <div class={style({ base: { display: "grid", gap: "16px" } })} data-meno-list>
      {productsList.map((item, itemIndex) => (
        <div
          class={style({ base: { border: "1px solid var(--border)", padding: "16px" } })}
          data-id={item._id}
          data-category={item.category}
          data-price={item.price}
          data-title={item.title}
          data-description={item.description}
        >
          <h3 class={style({ base: { fontSize: "20px" } })}>{item.title}</h3>
          <p>{item.description}</p>
          <span class={style({ base: { fontWeight: "bold" } })}>{`$${item.price}`}</span>
        </div>
      ))}
    </div>
    <div class={style({ base: { textAlign: "center", color: "var(--muted)" } })} data-meno-empty>No products found.</div>
    <button data-meno-page="prev">Prev</button>
    <span data-meno-page-current>1</span> / <span data-meno-page-total>1</span>
    <button data-meno-page="next">Next</button>
  </div>
</BaseLayout>

Remember rule 1 of the dialect: styles live in style({...}), never in a raw class="..." string. The data-meno-* attributes are plain attributes alongside the class={style(...)}.

Getting an instance

Instances are registered by collection name. Each data-meno-filter container gets one automatically on load; you can also create one in your own component script.

// Get the auto-created instance
const filter = MenoFilter.get('products');

// Or create one programmatically
const filter = new MenoFilter({
  collection: 'products',
  perPage: 10,
  filterMatch: 'and'
});
await filter.init(); // loads data from inline JSON or a static file

Constructor options

Option

Type

Default

Description

collection

string

(required)

Collection name

perPage

number

0 (no pagination)

Items per page

filterMatch

`"and" \

"or"`

"and"

How multiple filter fields combine

fieldTypes

Record<string, FieldType>

{}

Type hints for value coercion

urlSync

boolean

false

Sync filter state to URL query params

urlMode

`"push" \

"replace"`

"replace"

URL update mode (push adds history entries)

fuzzySearch

boolean

false

Enable fuzzy text matching

fuzzyThreshold

number

0.3

Fuzzy match threshold (0–1, lower is stricter)

Static methods

Method

Returns

Description

MenoFilter.get(collection)

`MenoFilter \

undefined`

Get instance by collection name

MenoFilter.getAll()

Map<string, MenoFilter>

Get all registered instances

MenoFilter.has(collection)

boolean

Check whether an instance exists

MenoFilter.destroy(collection)

void

Destroy and unregister an instance

MenoFilter.destroyAll()

void

Destroy all instances

Filtering methods

Method

Returns

Description

filter(criteria)

CMSItem[]

Apply filter criteria (AND logic between fields)

filterOr(criteriaList)

CMSItem[]

Apply OR logic between criteria objects

addFilter(field, value)

CMSItem[]

Add or update a single filter (merges with existing)

removeFilter(field)

CMSItem[]

Remove the filter for a specific field

clearFilters()

CMSItem[]

Clear all filters (keeps sort and search)

getFilters()

FilterCriteria

Get current filter criteria

filterRange(field, { min, max })

CMSItem[]

Apply a range filter (uses $gte / $lte)

clearRangeFilter(field)

CMSItem[]

Clear a range filter

Simple equality:

filter.filter({ category: 'tech' });

Multiple conditions (AND):

filter.filter({ category: 'tech', published: true });

With operators:

filter.filter({
  price: { $gt: 100, $lt: 500 },
  tags: { $contains: 'featured' }
});

OR filtering:

filter.filterOr([{ category: 'tech' }, { category: 'design' }]);

Range filtering:

filter.filterRange('price', { min: 100, max: 500 });
filter.filterRange('date', { min: '2024-01-01' });

Filter operators

These operators are the runtime (client) operator set, expressed as $-prefixed keys on a filter value object.

Operator

Description

Value type

$eq

Equal to

any

$neq

Not equal to

any

$gt

Greater than

`number \

string`

$gte

Greater than or equal

`number \

string`

$lt

Less than

`number \

string`

$lte

Less than or equal

`number \

string`

$contains

String contains (case-insensitive) or array includes

string

$notContains

String does not contain or array excludes

string

$startsWith

String starts with (case-insensitive)

string

$endsWith

String ends with (case-insensitive)

string

$in

Value is in the given array

any[]

$nin

Value is not in the given array

any[]

$empty

Is empty/null (true) or is not empty (false)

boolean

A filter value of "*", "", null, or undefined is treated as "show all" and skipped.

Search methods

Method

Returns

Description

search(query, fields?)

CMSItem[]

Search text across fields (case-insensitive)

clearSearch()

CMSItem[]

Clear the search query

getSearch()

{ query, fields }

Get current search state

setFuzzySearch(enabled, threshold?)

void

Enable or disable fuzzy matching

isFuzzySearchEnabled()

boolean

Check whether fuzzy search is on

// Search all string fields
filter.search('react tutorial');

// Search specific fields only
filter.search('react', ['title', 'tags']);

// Enable fuzzy matching
filter.setFuzzySearch(true, 0.3);
filter.search('reckt'); // matches "react" with typo tolerance

Sort methods

Method

Returns

Description

sort(field, order?)

CMSItem[]

Sort by field ("asc" or "desc", default "asc")

clearSort()

CMSItem[]

Clear sorting

getSort()

`SortConfig \

null`

Get the current sort configuration

filter.sort('publishedAt', 'desc');
filter.sort('title', 'asc');
filter.clearSort();

The runtime sort config is { field, order }, the same shape the build-time sort option uses.

Pagination methods

Method

Returns

Description

setPage(n)

CMSItem[]

Go to a page number

nextPage()

CMSItem[]

Go to the next page

prevPage()

CMSItem[]

Go to the previous page

setPerPage(n)

CMSItem[]

Set items per page

getPageInfo()

PageInfo

Get the pagination state

PageInfo fields: current (1-based page), total (page count), hasNext, hasPrev, totalItems (filtered count), perPage.

filter.setPerPage(12);
filter.setPage(2);

const info = filter.getPageInfo();
// { current: 2, total: 5, hasNext: true, hasPrev: true, totalItems: 58, perPage: 12 }

Load-more methods

An alternative to pagination where items are appended incrementally.

Method

Returns

Description

loadMore(count?)

CMSItem[]

Load more items (defaults to perPage)

getVisibleItems()

CMSItem[]

Get the currently visible items

getLoadMoreInfo()

LoadMoreInfo

Get the load-more state

initLoadMore(initialCount?)

void

Initialize load-more mode

LoadMoreInfo fields: visible, total, remaining, hasMore.

filter.initLoadMore(6);
filter.loadMore();  // shows 6 more
filter.loadMore(3); // shows 3 more

const info = filter.getLoadMoreInfo();
// { visible: 15, total: 50, remaining: 35, hasMore: true }

Data access methods

Method

Returns

Description

getAll()

CMSItem[]

All items (unfiltered, unsorted)

getFiltered()

CMSItem[]

Filtered items (before pagination)

getItems()

CMSItem[]

Current page items

getById(id)

`CMSItem \

undefined`

Find an item by _id or _filename

getUniqueValues(field)

unknown[]

All unique values for a field

getFacets(field)

FacetCounts

Value counts from the filtered items

getAllFacets(field)

FacetCounts

Value counts from all items

getState()

FilterState

Full state snapshot

const categories = filter.getUniqueValues('category');
// ['tech', 'design', 'marketing']

const facets = filter.getFacets('category');
// { tech: 12, design: 8, marketing: 3 }

Reset

Method

Returns

Description

reset()

CMSItem[]

Reset all filters, search, sort, and pagination

Events

Subscribe to lifecycle events with on(). It returns an unsubscribe function.

const unsubscribe = filter.on('afterFilter', (items) => {
  console.log('Filtered to', items.length, 'items');
});

unsubscribe(); // later

Event

Callback data

Description

init

CMSItem[]

Fired after data is loaded

beforeFilter

FilterCriteria

Before filter criteria are applied

afterFilter

CMSItem[]

After filtering completes

beforeSort

SortConfig

Before sort is applied

afterSort

CMSItem[]

After sorting completes

beforeSearch

{ query, fields }

Before search is applied

afterSearch

CMSItem[]

After search completes

beforeRender

varies

Before DOM render

afterRender

varies

After DOM render

pageChange

PageInfo

When the page changes

loadMore

LoadMoreInfo

When more items are loaded

reset

null

When reset() is called

You can also watch the full state or subscribe to the current items only:

const unwatch = filter.watch((state) => {
  console.log(state.pageItems.length, 'items on page', state.page.current);
});

const unsub = filter.subscribe((items) => {
  console.log(items.length, 'current items');
});

URL sync

Synchronize filter state with URL query parameters so users can share filtered views. Enable it with the urlSync constructor option, the data-meno-url-sync attribute, or at runtime:

filter.enableUrlSync({ mode: 'push' }); // adds browser history entries
filter.enableUrlSync();                 // replaces the URL without history
filter.disableUrlSync();

URL parameter format:

  • Filters: ?filter.category=tech&filter.price.$gt=100

  • Sort: ?sort=publishedAt:desc

  • Search: ?search=react&search.fields=title,tags

  • Page: ?page=2&perPage=12

Data attribute reference

Use these HTML attributes for declarative filtering. They sit alongside class={style(...)} on the relevant elements.

Container attributes

Attribute

Description

data-meno-filter="collection"

Root filter container; value is the collection name

data-meno-list

Marks the element containing list items

data-meno-per-page="n"

Items per page

`data-meno-filter-match="and\

or"`

How multiple filters combine

data-meno-debounce="ms"

Debounce delay for inputs

data-meno-active-class="className"

Class added to active filter buttons

data-meno-types='{"field":"number"}'

JSON object of field type hints

data-meno-url-sync

Enable URL query parameter sync

Filter controls

Attribute

Description

data-meno-filter-field="field"

Field this control filters on

data-meno-filter-value="value"

Value to filter by (on buttons/links)

`data-meno-filter-mode="single\

multi"`

Single or multi-select mode

data-meno-clear

Clears all filters when clicked

data-meno-reset

Resets all state (filters, sort, search, page)

Range controls

Attribute

Description

data-meno-range="field"

Marks a range filter container

`data-meno-range-bound="min\

max"`

Marks an input as the min or max bound

Search controls

Attribute

Description

data-meno-search

Marks a search input

data-meno-search-fields="field1,field2"

Comma-separated fields to search

data-meno-search-fuzzy

Enable fuzzy matching

data-meno-search-threshold="0.3"

Fuzzy match threshold

Sort controls

Attribute

Description

data-meno-sort="field"

Sort by this field when clicked

`data-meno-sort-order="asc\

desc"`

Sort direction

A <select> marked with data-meno-sort reads field:order from its option values (for example price:asc, publishedAt:desc).

Pagination controls

Attribute

Description

`data-meno-page="prev\

next\

n"`

Page navigation button

data-meno-page-current

Displays the current page number

data-meno-page-total

Displays the total page count

data-meno-page-buttons

Container for auto-generated page buttons

data-meno-per-page-select

Select/input to change items per page

Load-more controls

Attribute

Description

data-meno-load-more

"Load more" button

data-meno-remaining

Displays the remaining item count

Display elements

Attribute

Description

`data-meno-count="results\

total\

visible"`

Shows filtered, total, or visible item count

data-meno-facet="field"

Shows facet counts for a field

data-meno-empty

Shown when no items match the filters

A data-meno-facet value may target a single value with field:value (for example data-meno-facet="category:electronics") to show the count for just that value.


Build-time querying and the MenoFilter runtime compose cleanly: render the full set (or a pre-narrowed slice) with getCollectionList / queryList, then layer data-meno-* controls on top for live interaction. See the Filtering and Search guide for end-to-end patterns, Lists for collection rendering, and CMS for defining the fields you filter on.

Building the future of digital experiences, one website at a time

Product

Resources

Comparisons

© 2026 Meno. All rights reserved.