Features / Edit

Full-text search

A built-in search index per collection, BM25 ranking, one parameter on every list endpoint. Combines with filters, locale-aware, no external search service.

Search in a CMS usually means: stand up Elasticsearch next to it, keep it in sync, pay for it. RustyCMS brings the index with it. Every collection gets its own, writes keep it current, it lives on disk and survives restarts. For the frontend and the editorial team it is one parameter.

One parameter on every list

_q works on every list endpoint and returns hits ranked by relevance (BM25). The syntax covers what people actually type:

GET /api/content/blog_post?_q=hamburg              # words, AND-combined
GET /api/content/blog_post?_q=%22exact+phrase%22    # phrase in quotes
GET /api/content/blog_post?_q=hamb*&_locale=en     # prefix
GET /api/content/blog_post?_q=%2Bwind+-solar        # required term, excluded term

Case does not matter. A query that fails to parse is sanitized and retried instead of returning an error.

All collections at once

GET /api/search?q=… searches every collection the user is allowed to see and groups the hits per collection (limit_per_collection, default 5). That is the endpoint behind Cmd+K in the admin UI — and just as good a base for a site search in the frontend.

Locale-aware

Localized fields are indexed per language: German text lands in the German branch, English text in the English one. A request with _locale=en hits English content plus everything that is not localized — tags, for example, should stay findable in every language view.

Filters stay filters

Full text is only the first stage. Status, field filters, pagination, _resolve and _fields run afterwards as usual; an explicit _sort overrides the relevance order. So “search for turbine” combines with “published only, category energy only, sorted by date” in a single request.

An index nobody has to look after

The first _q request on a cold collection builds the index from the store — about a second for ten thousand entries. Every write updates it in the background without blocking the write path. After a restart it is opened from disk; if it is empty, the next request rebuilds it. The auto-backup deliberately leaves it out — it regenerates from the content.

In Postgres mode the database takes over: full text as tsvector, written in the same transaction as the entry, stemming per language — turbine finds turbines there as well. Several instances then share one index that never lags behind.

Deliberately left out

No highlighting of the matched context, no field boosting (the title does not weigh more than the body), and in Postgres mode no typo tolerance and no phrase or operator syntax. External collections do not go through the index. If you need those things, you know it — for everyone else the search is simply there.

Want to see this on your own project?

Tell me briefly what you're planning. I'll show the admin UI on a real installation and say honestly whether it fits.

Request early access