Umbraco Forms Search provides record indexing and querying via Umbraco.Cms.Search abstractions.
Install
| Product | Versions |
|---|---|
| .NET |
README
Umbraco.Forms.Search
Provides record indexing and querying for Umbraco Forms via the Umbraco.Cms.Search abstractions.
Compatible with Umbraco CMS and Forms 17+.
Installation
Umbraco.Forms.Search builds on Umbraco.Cms.Search.Core and, for the physical index and field registration, the official Umbraco.Cms.Search.Provider.Examine provider. Register the search core, the Examine provider, and then this package:
builder.CreateUmbracoBuilder()
.AddBackOffice()
.AddWebsite()
.AddDeliveryApi()
.AddSearchCore() // Umbraco.Cms.Search.Core
.AddExamineSearchProvider() // Umbraco.Cms.Search.Provider.Examine
.AddComposers()
.AddUmbracoForms()
.AddUmbracoFormsSearch() // this package
.Build();
AddUmbracoFormsSearch() does everything needed to put the Forms records index (UmbracoFormsRecordsIndex) onto Umbraco Search β it registers the index, the physical Examine index, and the field options (see How it works). You do not register the Examine index or FieldOptions yourself.
Disable the built-in Examine record indexing (required)
Umbraco Forms ships its own Examine-backed record indexing, which registers an index under the same name (UmbracoFormsRecordsIndex). Left enabled alongside this package, you get a duplicate-index error at startup (The indexer with name UmbracoFormsRecordsIndex already exists) and records indexed twice. Turn the built-in indexing off via configuration:
{
"Umbraco": {
"Forms": {
"Options": {
"DisableRecordIndexing": true
}
}
}
}
With this set, Forms skips registering its Examine record index, indexer, and IRecordReaderService, leaving this package as the sole owner of the records index. The database-backed record listing in the backoffice (IFormRecordSearcher) is unaffected β it is registered independently of record indexing.
How it works
The package routes the Forms records index through the Umbraco.Cms.Search abstractions, reusing the existing records index alias (Umbraco.Forms.Core.Constants.ExamineIndex.RecordIndexName, i.e. UmbracoFormsRecordsIndex).
Registration
UmbracoBuilderExtensions.AddUmbracoFormsSearch wires up:
- Registers the records index alias with
Umbraco.Cms.SearchviaIndexOptions.RegisterIndex<IIndexer, ISearcher>. This is a custom (self-managed) index, not a content index β so it is intentionally not listed in the Settings β Search dashboard and is not driven by the content-change pipeline. - Registers a physical Examine index for the alias via
AddExamineLuceneIndex<LuceneIndex, ConfigurationEnabledDirectoryFactory>. The Examine provider does not create one automatically for a registered alias. - Declares the record
FieldOptions(Examine provider) for the non-textual fields used in filtering, sorting and faceting βstate,form,memberKey,umbracoPageId,created,updated. Without this, keyword/integer filters and date/state sorting misbehave (unregistered keyword fields are analysed rather than stored raw, and sortable keyword fields are not created). - Overrides
IRecordReaderServicewith theUmbraco.Cms.Search-backed implementation. - Adds
IFormRecordIndexingServiceplus the startup populator and the save/delete notification handlers that keep the index in sync.
Steps 2 and 3 use Examine-specific APIs, so the package depends on Umbraco.Cms.Search.Provider.Examine.
Note on
IRecordReaderService: this is a public read API. In Umbraco Forms 17 the backoffice "Entries" list does not use it β that goes through the database-backedIFormRecordSearcher. OverridingIRecordReaderServicechanges the read path for any code that calls it, but does not by itself change the backoffice entries screen.
Indexing (FormRecordIndexingService)
For every record save, each field is projected into an IndexField on a single document keyed by Record.UniqueId:
| Field | Value type | Source |
|---|---|---|
blob |
TextsR1 |
All record field values concatenated, for relevance-ranked full-text search |
state |
Keywords |
FormState enum name (Submitted, Approved, β¦) |
ip |
Keywords |
Record.IP |
uniqueId |
Keywords |
Record.UniqueId |
created |
DateTimeOffsets |
Record.Created (coerced to UTC) |
updated |
DateTimeOffsets |
Record.Updated (coerced to UTC) |
form |
Keywords |
Record.Form (form id) |
memberKey |
Keywords |
Record.MemberKey |
currentPage |
Keywords |
Record.CurrentPage |
umbracoPageId |
Integers |
Record.UmbracoPageId |
recordFields |
Texts |
Record.RecordFields serialized as JSON |
Documents are single-variation (Variation(null, null)) and carry no protection.
Querying (RecordReaderService)
Implements every IRecordReaderService overload β approved/all records scoped by page, form, or member, including the RecordSortOptions overloads β by composing KeywordFilter / IntegerExactFilter filters and a Sorter (mapping RecordSortField to a DateTimeOffsetSorter for Created/Updated or a KeywordSorter for State; default is Created descending), paging with skip / take.
Because the index stores only record IDs, the service rehydrates full Record instances:
- Form is known (e.g.
GetRecordsFromForm): resolves theFormonce and callsIRecordStorage.GetRecords(uniqueIds, form). - Form is unknown (e.g.
GetRecordsFromPage): a lightweightincludeFields: falseload discovers which forms the IDs belong to, then issues one fullGetRecordscall per form so each record is loaded against its own schema.
The loaded records are re-projected onto the order the searcher returned, so the requested sort is preserved.
Startup population (FormRecordIndexPopulationHandler)
On UmbracoApplicationStartedNotification the handler checks IIndexer.GetMetadataAsync. If the index is empty it indexes all existing records; otherwise it logs and returns (so it does not re-index on every boot). The work runs on a background thread so it does not block startup, with per-form error isolation.
Keeping the index in sync (RecordSearchNotificationHandler)
Handles RecordSavingNotification (upsert each saved record) and RecordDeletingNotification (delete by UniqueId). Delete targets the record id directly; no index-wide scan.
Known limitations
- Examine zero-downtime indexing: the package registers a single physical index under the plain alias name. If you enable
Umbraco:CMS:Search:Examine:ZeroDowntimeIndexing, the provider resolves_a/_bshadow index names and the records index will not be found. It is off by default. - No management UI: as a custom Umbraco Search index, the records index is not shown in Settings β Search and has no built-in "rebuild" action. The startup populator and the save/delete handlers are the sync mechanism.
Project layout
src/Umbraco.Forms.Search/
βββ Extensions/
β βββ UmbracoBuilderExtensions.cs // AddUmbracoFormsSearch()
βββ NotificationHandlers/
β βββ FormRecordIndexPopulationHandler.cs // Background backfill on startup
β βββ RecordSearchNotificationHandler.cs // Save/delete sync
βββ Services/
βββ IFormRecordIndexingService.cs // Public indexing contract
βββ FormRecordIndexingService.cs // Document projection β IIndexer
βββ RecordReaderService.cs // IRecordReaderService over ISearcher
Adoption Assessment
A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.