// Universal Search wiki
Developer Hooks
The public API facade and integration events for building on Universal Search.
You are building a companion app, an embedded factbox, a document action, a connector, and you need to call into Universal Search or react to what it does, without depending on its internals and without those internals breaking your extension on the next update.
Call the public API facade
Everything you need to call directly is on one codeunit, codeunit "BAA Search API". This is the only supported integration surface. Every other object in the app is internal and can change shape between versions; the procedures below are a stable, documented contract.
Search(SearchText: Text; MaxResults: Integer; var Results: JsonArray): searches every indexed table and field, and fillsResultswith up toMaxResultsranked matches. Never throws.Search(SearchText: Text; MaxResults: Integer; FilterTableNo: Integer; var Results: JsonArray; var TableCounts: JsonArray): the same search, optionally isolated to one table (FilterTableNo, 0 for every table), plus a per-table match count summary inTableCounts, the same data that powers the built-in filter chips.OpenResult(TableNo: Integer; SystemIdText: Text): opens the record a search result points to, with the same permission and existence checks the built-in search page uses. Raises a friendly error if the record cannot be opened.EnsureTableIndexed(TableNo: Integer; FieldNo: Integer; Weight: Integer; EnablePhonetic: Boolean): registers or updates an Index Configuration row from code. Useful for an app that wants to seed its own searchable fields on install.RebuildTable(TableNo: Integer): immediately rebuilds the index for one table, synchronously in the caller's session.ProcessQueueNow(): drains the change capture queue and processes any pending rebuilds immediately, instead of waiting for the next scheduled background run.
Note:
SearchandOpenResultnever throw for a normal "no results" or "cannot open" case. They degrade gracefully, so a caller does not need to wrap every call in error handling.
Subscribe to what Universal Search does
Beyond the facade, Universal Search publishes ten integration events across its pipeline, so you can observe or extend behavior at each stage without modifying Universal Search itself.
Query and open:
OnBeforeSearch: raised before any search pass runs. Take over the search entirely and supply your own results.OnAfterSearch: raised with the final, ranked result set, on every search that was not already replaced byOnBeforeSearch.OnBeforeEmitResults: raised after retrieval has run but before ranking and capping are applied. Insert, re-score, or remove candidate rows.OnBeforeOpenResult: raised before the built-in open-record logic. Substitute your own behavior, for example inserting a reference into the current document instead of opening the record's card.
Indexing:
OnBeforeIndexRecord: raised before a record's configured fields are read. Take over indexing for that record entirely.OnAfterIndexRecord: raised once every configured field for a record has been indexed or cleared.OnAfterBuildIndexEntry: raised per field, after its normalized, compact, and phonetic variants are built but before they are saved. Adjust or augment what gets stored.OnAfterRebuildTable: raised after a table's index has been fully and successfully rebuilt.
Change capture and install:
OnAfterIsTableConfigured: raised on every table configuration check. Override whether a given table is treated as indexed, including dynamically, for example per company or user.OnAfterSeedDefaults: raised after Universal Search's own default Index Configuration rows are seeded on install. Add your own defaults alongside them without racing the built-in seeding.
A short subscriber example
This subscriber logs every search term and how many results it returned, using OnAfterSearch on codeunit "BAA Search Query Engine":
[EventSubscriber(ObjectType::Codeunit, Codeunit::"BAA Search Query Engine", 'OnAfterSearch', '', false, false)]
local procedure LogSearchTerm(SearchText: Text; MaxResults: Integer; var Results: JsonArray)
var
MySearchLog: Record "My Search Audit Log";
begin
MySearchLog.Init();
MySearchLog."Search Text" := CopyStr(SearchText, 1, MaxStrLen(MySearchLog."Search Text"));
MySearchLog."Result Count" := Results.Count();
MySearchLog.Insert();
end;
What you can build
- An embedded search factbox on a document or card page, calling
Searchdirectly instead of reimplementing lookup logic. - A custom result action, using
OnBeforeOpenResultto insert a reference into the current record instead of navigating away from it. - Extra indexed sources for your own tables, registered with
EnsureTableIndexedon install so they show up in search without asking the customer to configure them by hand.
If you are building a companion app and want to talk through an integration, contact Bitta Apps.