Skip to content

Search matching

Every list page has a search box that does more than a simple “find rows whose name contains this string” — it expands abbreviations, treats numbers and their spelled-out forms as equivalent, supports phrase quoting, and recognises a handful of explicit operators for filtering by tag, note, field, or boolean. This page explains exactly what gets matched and how.

When you type a single word, the system:

  1. Tokenises it (splits on whitespace, lowercases).
  2. Expands it through the synonym table and the number-to-word table.
  3. Appends a wildcard so partial words match (Boland* matches Boland, Boland's, Bolands).
  4. Builds a SQL Server CONTAINS query that ORs the expansions together.

Multi-word queries get ANDed at the group level (every word must match somewhere), but within each word’s group the synonyms / number forms are ORed (any one of them is good enough).

So typing Acme Ltd matches “Acme Limited”, “Acme Ltd.”, and anything else where both Acme and Ltd-or-Limited appear in the indexed text.

The synonym table is bidirectional — every entry is a two-way mapping. Typing the short form matches the long form, and vice versa. A representative sample (the full set is around 60 pairs):

If you typeThe system also matches
ststreet
streetst
ave, avavenue
avenueave, av
blvdboulevard
rdroad
sqsquare
lnlane
plplace
pkpark
ctcourt
indindustrial
ieindustrial estate
tetrading estate
bldgbuilding
hsehouse
bpkbusiness park
ctrecentre
hqheadquarters
ltdlimited
grpgroup
offoffice
busbusiness
terrterrace

The mappings cover three broad categories — street types (st/street, ave/avenue, rd/road, etc.), building types (hse/house, ctre/centre, bldg/building), and company suffixes (ltd/limited, grp/group). They’re hand-maintained — additions go through engineering.

For the complete list at any point in time, see src/services/api/app/app.api/Services/Search/SearchTokenizer.cs.

Numbers and their spelled-out forms match interchangeably for digits 0-19, decades from twenty to ninety, plus hundred and thousand.

Examples:

TypingAlso matches
3three
three3
10ten
100hundred
1000thousand

This is useful for things like “3 Cromwell Place” matching “Three Cromwell Place” without forcing one form or the other on data entry.

The list covers digits 0-9, tens 10-19, the tens family (20, 30, 40… 90), 100, and 1000. Compound numbers (e.g. “thirty-two”) aren’t expanded — those are matched character-by-character.

Every word in your search gets a * suffix appended so partial words still match. Typing Boland matches Boland’s, Bolands, Boland Industrial Estate, etc. The wildcard runs against the end of words, not the start — searching land will not match Roland.

The suffix is applied after synonym expansion, so st* also gets the street* form added.

Wrap a phrase in double quotes to require the words appear together in that order:

"cromwell road" — matches records containing the phrase “Cromwell Road” but not records where “Cromwell” and “Road” appear in different places.

Phrases don’t get wildcard-expanded or synonym-expanded — they’re matched literally (apart from the system normalising case and stripping extra whitespace).

The search box supports five explicit operators in addition to free-text matching:

Prefix a word with # to filter to records carrying that tag:

#confidential — show only records tagged confidential.

Tag names are case-insensitive. Multiple # tokens AND together: #confidential #high-priority requires both tags.

@note — filter to records with notes containing a word

Section titled “@note — filter to records with notes containing a word”

Prefix a word with @ to filter to records whose notes contain that text:

@deadline — show only records that have at least one note mentioning deadline.

Note that @ matches against the notes, not the parent record’s free text. A record whose name is “Deadline Holdings” won’t match @deadline unless one of its notes also mentions it.

field:value — filter by a specific field

Section titled “field:value — filter by a specific field”

Use field:value to filter on a named field rather than the general index:

companynumber:12345 — match records whose CompanyNumber is exactly 12345 (or starts with it, given the wildcard suffix).

The available fields differ by entity. On Addresses you can use postalcode:, country:, locality:. On Companies, companynumber:, url:. Field names are matched case-insensitively. If you use a field name that doesn’t exist on the entity you’re searching, the filter is ignored (rather than blowing up the whole search).

+bool and -bool — boolean field filtering

Section titled “+bool and -bool — boolean field filtering”

Prefix a boolean field name with + to require it true, or - to require it false:

+verified — only verified records. -retired — exclude closed records.

The bool name maps to the entity’s flag field — +verified → IsVerified = true, -retired → IsRetired = false (which is the default). Combine freely with other operators.

You can mix any of the above in a single query. The general pattern:

general words "phrase" #tag1 #tag2 field:value +bool1 -bool2 @note

Order doesn’t matter — operators are recognised by their prefix character, not their position. All operators AND together; within a single field:value you can have OR semantics by repeating: country:gb country:us means “country is GB or US”.

A few things to know about the limits of the search:

  • The @note operator searches against notes for entities of the current list. Cross-entity note search (find any record whose notes mention X) isn’t a single-query feature.
  • Tag content lives in a separate table; the #tag operator hits that table directly, not the general text index. A tag named “confidential” will only be found via #confidential, not by typing confidential into a free-text search.
  • The synonym list is hand-maintained — only entries in the table count as synonyms. Typing incorp won’t match incorporated; engineering would need to add that pair.
  • Excerpts from a record’s full free text aren’t returned with results — the search yields full records, not snippets. To see why a record matched, examine the record.
  • Tokeniser + synonym table + number-to-word table: src/services/api/app/app.api/Services/Search/SearchTokenizer.cs
  • Query parser (extracts operators from your input): src/services/api/app/app.api/Services/Search/SearchQueryParser.cs
  • CONTAINS pattern builder: src/services/api/app/app.api/Services/Search/FullTextSearchHelper.cs
  • Per-entity search services: AddressSearchService.cs, CompanySearchService.cs, SchemeSearchService.cs, etc., under src/services/api/app/app.api/Services/Search/
  • Backing indexes: SQL Server full-text indexes on the query.*List views (see src/data/app/query/Tables/)