How search behaves

What a reader's query actually matches, the two-character floor and fifty-result cap, why only some searches are recorded, why no identity is stored, and how to read the zero-result list that is this addon's most useful output.

6 min readUpdated 3 August 2026search, analytics, knowledge-gaps

Search is the reason a knowledge base exists, and it is also the only part of this addon that reports back. Understanding exactly what it matches saves you from writing articles nobody can find, and understanding what it records tells you what to write next.

What a query matches

A case-insensitive substring match against the article's question text and answer text, over published articles only.

That is the whole algorithm. There is no full-text index, no stemming, no synonym list, no relevance ranking and no fuzzy matching. Results come back in order sequence — the position you gave them on their page — not by how well they match.

The consequences are worth being blunt about:

  • withdrawals does not match an article that only says withdrawal. There is no stemming. Plurals, tenses and hyphenation are all separate strings.
  • how do I cash out matches nothing unless that exact phrase appears in an article. The query is one string, not a set of words.
  • A typo returns nothing. There is no tolerance.
  • The best answer is not first. An article that mentions "fee" once in passing outranks the fee article if it has a lower order value.

The practical mitigation is in the writing, not the configuration. Put the words a reader would actually type into the question itself, and put the alternatives into the answer body — the answer is searched too, so a line reading "Also called cashing out or payout" makes three more queries work.

Tags are not searched on the reader side. They are an admin filter only.

The limits

Rule Value
Minimum query length 2 characters — shorter is refused with a 400
Maximum results returned 50
Category filter Optional; all means no filter
Result order By the article's order field, ascending

The fifty-result cap is not paginated. A two-character query on a large library hits it immediately and the reader sees an arbitrary fifty, which is another reason the minimum exists.

Wildcard characters are escaped before the query is built. A reader searching for % gets articles containing a literal percent sign, not every article in the table — which is what happened before those characters were escaped.

Two different searches

There are two search paths and they behave differently. This trips people up when testing.

The help-centre search box posts to the search endpoint. It applies everything above, and it records the query.

The search parameter on the article list filters the list in place. It matches the same two columns, it respects your category and page filters, and it is paginated — but it records nothing. The admin list's search box behaves the same way, additionally matching the category column.

If you are checking that search recording works, use the reader-facing box on /faq. Filtering the list does not produce a recorded search.

As a reader types, a short list of matching questions appears under the box. Those suggestions are computed in the browser, from the page of articles already loaded — not from a request to the server.

So the suggestions can only ever propose questions from the current page of results, and the absence of a suggestion means nothing. Pressing Enter runs the real search against the whole library and routinely returns things the suggestion list never offered. Do not report "the suggestion did not appear" as a search fault.

What is recorded, and what is not

Every search through the help-centre box writes a row to faq_searches with the query, the number of results it returned, and the category filter in force — provided the query is longer than three characters.

Nothing else is stored. In particular:

The userId column exists and is always null. The search endpoint requires no authentication and does not resolve an optional session, so the handler genuinely does not know who is asking.

That is the right shape for the question this data answers. "What are people asking?" needs no identity, and a knowledge-gap report is not a surveillance log. If you need per-user behaviour, this is not the table for it and cannot be made into one by configuration.

The four-character floor means one- to three-character queries — ticker symbols like BTC, or 2FA — leave no trace. If your library is full of short terms, expect the search report to under-count.

Recording is fire-and-forget: it does not block the response, and a failure is written to the backend log rather than swallowed. If searches stop being recorded, there will be a FAQ warning line saying so.

Recording used to be conditional on a user id that this endpoint can never have, because it is public and public routes skip authentication entirely. The condition was never true, so nothing was recorded for seven months.

That emptied the trending-searches block, the admin's top-queries list and — the real loss — the zero-result report. Everything below starts accumulating from the day you upgrade; there is no historical data to recover.

Reading the search report

Two views onto the same table.

On the reader side, the help centre shows a What others are searching block: the most frequent queries of the last seven days, with a count each. Clicking one runs it. Queries that returned nothing are excluded — there is no point sending a reader down a path that already failed.

On the admin side, the dashboard shows the top queries with, for each, the average number of results it returned and a No results flag on any query averaging under one.

That flag is the output worth acting on. A query people run repeatedly and which returns nothing is a missing article, named by the people who wanted it, in their own words. It is the only place in this product that tells you what your library does not cover.

A workable weekly routine:

  1. Open the dashboard at /admin/faq and read the search block.

  2. Take the zero-result queries in frequency order. Frequency matters more than the wording — five people asking the same thing badly is still five people.

  3. Decide, for each, whether it is a gap or a vocabulary problem. If you have an article that answers it under different words, the fix is to add those words to the article, not to write a second one. Substring matching means the reader's phrasing has to appear literally somewhere in the question or answer.

  4. Write the genuinely missing ones, using the query itself as the starting point for the question text.

  5. Check the same list next week. A query that keeps appearing after you wrote for it is a phrasing mismatch, not a content gap.

The Troubleshooter

/faq/troubleshooter is a separate reader path that does not use search at all. It asks three questions — category, kind of problem, how often it happens — and then narrows the library down to matching articles.

One thing to know about it: it loads up to 100 articles to work from, and its first step builds its category options out of what it loaded. On a library larger than that, the tool answers from a subset. There is no configuration for the limit.

It also records nothing. A reader who solves their problem through the Troubleshooter leaves no trace in the search report, so a busy Troubleshooter and a quiet search report are consistent with each other rather than contradictory.