Works with OrchardCore queries and indexing — SQL/Lucene/Elasticsearch queries, full-text index profiles, content part/field index handlers, and search. Use when the user needs to define a query, index content for search, write an index handler, register an indexing source, or add query/index recipe steps.
68
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
This skill guides you through OrchardCore's query and indexing systems following project conventions.
Two distinct systems, often confused:
OrchardCore.Queries) — named, stored queries executed on demand (SQL, Lucene, Elasticsearch sources). Return raw rows or content items.OrchardCore.Indexing + providers) — build full-text index profiles that mirror content into Lucene/Elasticsearch/Azure AI Search for searching. Driven by index handlers.Separate again from YesSql SQL index tables (MapIndex + SchemaBuilder) — those are relational indexes for fast DB queries, covered by the orchardcore-data-migration skill, not here.
| Goal | System |
|---|---|
| Run a stored SQL/Lucene query, expose via GraphQL/API | Queries |
| Make content searchable (full-text) | Indexing (index profile + provider) |
| Index a custom part/field's data into search | IContentPartIndexHandler / IContentFieldIndexHandler |
| Fast relational lookup by a column | YesSql MapIndex → see orchardcore-data-migration |
| Run a search box over an index | Search (ISearchService) |
Query has Name, Source (Sql/Lucene/Elasticsearch), ReturnContentItems, and source-specific metadata (e.g. a SQL Template).
Recipe step:
{
"name": "queries",
"Queries": [
{
"Source": "Sql",
"Name": "RecentPosts",
"Template": "select DocumentId from ContentItemIndex where ContentType = 'BlogPost'",
"ReturnContentItems": true
}
]
}Programmatically:
var query = await _queryManager.NewAsync("Sql", data);
await _queryManager.SaveAsync(query);var query = await _queryManager.GetQueryAsync("RecentPosts");
var results = await _queryManager.ExecuteQueryAsync(query, new Dictionary<string, object>
{
["maxResults"] = 10,
});
foreach (var item in results.Items) { /* JsonObject or ContentItem */ }SQL templates are Liquid-rendered (parameters become Liquid vars), parsed by SqlParser, then run via Dapper. ReturnContentItems = true loads ContentItems by DocumentId; otherwise rows are returned as JsonObject.
A profile binds a provider (Lucene/Elasticsearch/AzureAISearch) + a type (Content) + which content types to index.
{
"name": "CreateOrUpdateIndexProfile",
"indexes": [
{
"Name": "BlogPostsLucene",
"IndexName": "blogposts",
"ProviderName": "Lucene",
"Type": "Content",
"Properties": {
"ContentIndexMetadata": {
"IndexLatest": false,
"IndexedContentTypes": [ "BlogPost" ],
"Culture": "any"
},
"LuceneIndexMetadata": { "AnalyzerName": "standard", "StoreSourceData": true }
}
}
]
}Content changes raise indexing tasks; ContentIndexingService resolves the content item and calls every part/field index handler to build a DocumentIndex, which the provider stores.
For a custom part, implement ContentPartIndexHandler<TPart>:
public class AliasPartIndexHandler : ContentPartIndexHandler<AliasPart>
{
public override Task BuildIndexAsync(AliasPart part, BuildPartIndexContext context)
{
var options = DocumentIndexOptions.Keyword | DocumentIndexOptions.Store;
foreach (var key in context.Keys)
{
context.DocumentIndex.Set(key, part.Alias, options);
}
return Task.CompletedTask;
}
}Fields use ContentFieldIndexHandler<TField> with BuildFieldIndexContext. context.Keys are the index field names to populate; DocumentIndexOptions flags control storage:
| Flag | Effect |
|---|---|
Store | keep the value retrievable from the index |
Keyword | index as a non-analyzed keyword (exact match) |
Sanitize | strip HTML before indexing |
None | analyzed full-text (default text) |
services.AddScoped<IContentPartIndexHandler, AliasPartIndexHandler>();
// field:
services.AddScoped<IContentFieldIndexHandler, MyFieldIndexHandler>();Recipe steps ResetIndex (re-sync from last position) and RebuildIndex (drop + rebuild) manage existing indexes. The admin Search/Indexing UI exposes the same.
| Type | Member |
|---|---|
IQueryManager | NewAsync, SaveAsync, GetQueryAsync, ExecuteQueryAsync, ListQueriesAsync |
IQuerySource | Name, ExecuteQueryAsync(query, parameters) — implement to add a source |
IQueryResults | IEnumerable<object> Items |
| Type | Member |
|---|---|
IIndexProfileManager | NewAsync, CreateAsync, UpdateAsync, FindByNameAsync, SynchronizeAsync, ResetAsync |
IDocumentIndexHandler | BuildIndexAsync, DocumentsAddedOrUpdatedAsync, DocumentsDeletedAsync |
IContentPartIndexHandler / IContentFieldIndexHandler | BuildIndexAsync(...) to populate DocumentIndex |
IIndexManager (per provider) | CreateAsync, RebuildAsync, DeleteAsync, ExistsAsync |
IDocumentIndexManager | AddOrUpdateDocumentsAsync, DeleteDocumentsAsync |
DocumentIndex | Set(name, value, options) overloads; Types enum |
services.AddIndexingSource<TIndexManager, TDocumentIndexManager, TIndexNameProvider>(
providerName, implementationType);
// e.g. the Lucene module: services.AddLuceneIndexingSource(implementationType);ISearchService.SearchAsync(IndexProfile index, string term, int start, int size) → SearchResult. Providers: OrchardCore.Search.Lucene, .Elasticsearch, .AzureAI.
orchardcore-data-migration). Don't mix them.queries; index profile step is CreateOrUpdateIndexProfile. Older lucene-index / ElasticIndexSettings steps are deprecated — prefer CreateOrUpdateIndexProfile.IndexLatest: true indexes draft (latest) versions; false indexes published. Match to your search UI.Template is Liquid first, then SQL — parameters are Liquid variables, not ADO placeholders.references/indexing.md — index profiles, handlers, DocumentIndex, providers, recipe stepssrc/docs/reference/modules/Queries/README.md (repo)src/docs/reference/modules/Indexing/README.md (repo)src/docs/reference/modules/{Lucene,Elasticsearch,SQLIndexing}/README.md (repo)AGENTS.md (repo root) — build commandsb0e1e44
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.