CtrlK
BlogDocsLog inGet started
Tessl Logo

orchardcore-query-indexing

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

Quality

82%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

OrchardCore Query & Indexing

This skill guides you through OrchardCore's query and indexing systems following project conventions.

Two distinct systems, often confused:

  • Queries (OrchardCore.Queries) — named, stored queries executed on demand (SQL, Lucene, Elasticsearch sources). Return raw rows or content items.
  • Indexing (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.

Decide what you need

GoalSystem
Run a stored SQL/Lucene query, expose via GraphQL/APIQueries
Make content searchable (full-text)Indexing (index profile + provider)
Index a custom part/field's data into searchIContentPartIndexHandler / IContentFieldIndexHandler
Fast relational lookup by a columnYesSql MapIndex → see orchardcore-data-migration
Run a search box over an indexSearch (ISearchService)

Workflow A: define and run a query

Step 1: Pick a source

Query has Name, Source (Sql/Lucene/Elasticsearch), ReturnContentItems, and source-specific metadata (e.g. a SQL Template).

Step 2: Create via recipe or manager

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);

Step 3: Execute

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.

Workflow B: index content for search

Step 1: Create an index profile

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.

Step 2: Index custom part/field data

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:

FlagEffect
Storekeep the value retrievable from the index
Keywordindex as a non-analyzed keyword (exact match)
Sanitizestrip HTML before indexing
Noneanalyzed full-text (default text)

Step 3: Register the handler

services.AddScoped<IContentPartIndexHandler, AliasPartIndexHandler>();
// field:
services.AddScoped<IContentFieldIndexHandler, MyFieldIndexHandler>();

Step 4: Reset / rebuild

Recipe steps ResetIndex (re-sync from last position) and RebuildIndex (drop + rebuild) manage existing indexes. The admin Search/Indexing UI exposes the same.

Quick Reference

Query APIs

TypeMember
IQueryManagerNewAsync, SaveAsync, GetQueryAsync, ExecuteQueryAsync, ListQueriesAsync
IQuerySourceName, ExecuteQueryAsync(query, parameters) — implement to add a source
IQueryResultsIEnumerable<object> Items

Indexing APIs

TypeMember
IIndexProfileManagerNewAsync, CreateAsync, UpdateAsync, FindByNameAsync, SynchronizeAsync, ResetAsync
IDocumentIndexHandlerBuildIndexAsync, DocumentsAddedOrUpdatedAsync, DocumentsDeletedAsync
IContentPartIndexHandler / IContentFieldIndexHandlerBuildIndexAsync(...) to populate DocumentIndex
IIndexManager (per provider)CreateAsync, RebuildAsync, DeleteAsync, ExistsAsync
IDocumentIndexManagerAddOrUpdateDocumentsAsync, DeleteDocumentsAsync
DocumentIndexSet(name, value, options) overloads; Types enum

Register an indexing source

services.AddIndexingSource<TIndexManager, TDocumentIndexManager, TIndexNameProvider>(
    providerName, implementationType);
// e.g. the Lucene module: services.AddLuceneIndexingSource(implementationType);

Search

ISearchService.SearchAsync(IndexProfile index, string term, int start, int size)SearchResult. Providers: OrchardCore.Search.Lucene, .Elasticsearch, .AzureAI.

Gotchas

  • "Index" is overloaded: full-text index profiles (this skill) vs YesSql MapIndex tables (orchardcore-data-migration). Don't mix them.
  • Query recipe step is 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.
  • SQL query Template is Liquid first, then SQL — parameters are Liquid variables, not ADO placeholders.

References

  • references/indexing.md — index profiles, handlers, DocumentIndex, providers, recipe steps
  • src/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 commands
Repository
OrchardCMS/OrchardCore
Last updated
First committed

Is this your skill?

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.