ZKRINET Docs
Building blocks

Search — full-text search with SQLite FTS5

On Zkrinet: add the block under Building blocks in the panel — one click, your own code is untouched and a snapshot is taken first. The installation steps below apply if you run PocketBase yourself, for example after an export.

Fast free-text search in your collections, with ranking (bm25) and snippets where the match is highlighted. The search respects the collection's own rules: nobody may see a match that they would not have been allowed to see through PocketBase's regular list API.

Tested against PocketBase 0.40.4 (SQLite 3.53.3, built with ENABLE_FTS5).

Installation

Copy pb_hooks/ and pb_migrations/ to your PocketBase and restart. The collection zk_sok_installningar appears in the admin panel. The index itself lives in two regular SQLite tables (zk_sok_poster, zk_sok_index) that are not visible in the admin panel.

Choose what should be searchable

Create one row in zk_sok_installningar per collection (superuser only):

FieldExample
samlingartiklarThe collection's name
falt["titel", "innehall"]Fields of type text or editor (HTML tags are removed before indexing)
aktivonOff = the collection is not searchable

The index is built as soon as the row is saved, and is then kept in sync automatically when records are created, changed and deleted. Hidden fields cannot be selected — the snippets would otherwise show them. If a field is made hidden afterwards, it stops being searched immediately.

Rebuild the whole index (e.g. after importing directly into the database):

POST /api/zk/sok/bygg-om              (superuser)  all collections
POST /api/zk/sok/bygg-om?samling=x    (superuser)  one collection

Searching from the app

const res = await pb.send("/api/zk/sok", { query: { q: "grafikkort", samling: "artiklar", antal: 20, sida: 1 } })
// res = { traffar: [...], mer: true|false, sida, antal }
for (const t of res.traffar) {
  t.samling   // "artiklar"
  t.id        // the record's id
  t.falt      // the field that matched best
  t.utdrag    // "…om <mark>grafikkort</mark> här…" — HTML-safe, see below
  t.poang     // higher = better match
  t.post      // the whole record, exactly as the list API would have shown it
}

The query language

Swedish

The tokenizer is unicode61 remove_diacritics 0. The default choice in many guides (remove_diacritics 1/2) folds å/ä/ö to a/o — then "får" also finds "far" and "hål" also finds "hal". Here it does not. (In Swedish, å, ä and ö are separate letters, not accented variants, so these are different words: "får" = sheep/gets, "far" = father; "hål" = hole, "hal" = slippery; "kör" = choir/drives, "kor" = cows.)

SearchFindsDoes not find
fårfårfar
hålhålhal
körkörkor
åsa, ÅSAÅsa

Case is folded (including Å/å). The price: someone who searches ole does not find "Olé" — accented characters must be typed as they are in the text. For Swedish text that is the right trade-off.

Security

If you want to limit how often someone may search, turn on PocketBase's built-in rate limit (Settings → Rate limiting) with a rule for /api/zk/sok.

Limitations

Testing

./bank.py sok

14 tests: Swedish characters (får/far, hål/hal, kör/kor, Åsa/åsa, prefix), ranking, snippets (HTML-safe, editor fields cleaned), owner rule (A/B/guest, compared against PocketBase's own list API), that the response does not leak others' matches, locked and public collections, hidden fields, injection into the query language, sync on change/deletion, failed batch, validation of settings, rebuild (including with orphaned index rows), deleted collection, pages. The security protections are mutant-verified: every protection has been deliberately broken and caught by at least one test.