Templated index names
Use NameTemplate on [Index<T>] when the index name contains segments that are only known at runtime -- environment, search variant, tenant, region, or any other dimension. The source generator parses your template and produces a CreateContext(...) factory method with typed parameters.
Statically defined index names work for simple cases, but real deployments often need the same document type written to different indices depending on runtime context:
docs-semantic-productionvsdocs-lexical-stagingorders-us-east-1vsorders-eu-west-1articles-team-a-ingestvsarticles-team-b-search
Without NameTemplate, you'd need to declare one [Index<T>] per combination or build index names manually. NameTemplate lets you declare the pattern once and resolve it at runtime with full type safety.
[ElasticsearchMappingContext]
[Index<KnowledgeArticle>(
NameTemplate = "docs-{searchType}-{env}",
DatePattern = "yyyy.MM.dd.HHmmss"
)]
public static partial class ArticleContext;
The generator produces a CreateContext method instead of a static Context property:
// CreateContext(string searchType, string? env = null)
var ctx = ArticleContext.KnowledgeArticle.CreateContext("semantic", env: "production");
ctx.IndexStrategy.WriteTarget // "docs-semantic-production"
ctx.SearchStrategy.Pattern // "docs-semantic-production-*" (auto-derived from DatePattern)
ctx.IndexStrategy.DatePattern // "yyyy.MM.dd.HHmmss"
Placeholders are {name} tokens in the template string. The generator classifies them into two categories:
Any placeholder that is not a well-known name becomes a required string parameter:
[Index<T>(NameTemplate = "articles-{team}-{component}")]
// Generates: CreateContext(string component, string team)
The names {env}, {environment}, and {namespace} are treated specially:
- They become optional
string?parameters with a default ofnull - When
null, they resolve from environment variables:DOTNET_ENVIRONMENT>ASPNETCORE_ENVIRONMENT>ENVIRONMENT>"development" - They are always placed last in the method signature, after all custom placeholders
[Index<T>(NameTemplate = "geo-{namespace}")]
// Generates: CreateContext(string? @namespace = null)
var ctx = MyContext.LocationRecord.CreateContext();
// WriteTarget: "geo-development" (from env variable fallback)
var ctx = MyContext.LocationRecord.CreateContext(@namespace: "us-east-1");
// WriteTarget: "geo-us-east-1"
Name and NameTemplate are mutually exclusive on [Index<T>]:
| Property | Resolver exposes | When to use |
|---|---|---|
Name = "products" |
MyContext.Product.Context (static property) |
Fixed index name known at compile time |
NameTemplate = "docs-{type}-{env}" |
MyContext.Product.CreateContext(...) (factory method) |
Index name depends on runtime parameters |
When neither is set, the index name defaults to the type name lowercased (same as Name).
When DatePattern is set alongside NameTemplate, the generated CreateContext method:
- Sets
IndexStrategy.WriteTargetto the interpolated name (e.g.,"docs-semantic-production") - Carries
IndexStrategy.DatePatternthrough to the returned context - Auto-derives
SearchStrategy.Patternas"{writeTarget}-*"(e.g.,"docs-semantic-production-*")
Use ResolveIndexName on the returned context to get the full time-stamped index name:
var ctx = ArticleContext.KnowledgeArticle.CreateContext("semantic", env: "prod");
var indexName = ctx.ResolveIndexName(DateTimeOffset.UtcNow);
// "docs-semantic-prod-2026.02.24.143045"
Without DatePattern, the write target is the final index name and SearchStrategy.Pattern is null.
Combine Variant with NameTemplate to register the same document type with different template patterns:
[ElasticsearchMappingContext]
[Index<KnowledgeArticle>(
NameTemplate = "docs-{searchType}-{env}",
DatePattern = "yyyy.MM.dd.HHmmss"
)]
[Index<KnowledgeArticle>(
NameTemplate = "articles-{team}-{component}",
Variant = "Multi"
)]
public static partial class ArticleContext;
This generates two resolvers:
ArticleContext.KnowledgeArticle.CreateContext(string searchType, string? env = null)ArticleContext.KnowledgeArticleMulti.CreateContext(string component, string team)
Each returns an independent ElasticsearchTypeContext with its own write target and strategy configuration.
The ElasticsearchTypeContext returned by CreateContext works identically to a static Context:
var ctx = ArticleContext.KnowledgeArticle.CreateContext("semantic", env: "production");
// Pass to channel options
var options = new IngestChannelOptions<KnowledgeArticle>(transport, ctx);
using var channel = new IngestChannel<KnowledgeArticle>(options);
// Pass to strategies
var strategy = IngestStrategies.Index<KnowledgeArticle>(ctx);
// Resolve helpers
ctx.ResolveIndexName(timestamp)
ctx.ResolveWriteAlias()
ctx.ResolveReadTarget()
ctx.ResolveSearchPattern()
- time-stamped index name
- write alias
- read alias or write target
- search wildcard
Each CreateContext call returns an independent context. You can create multiple contexts from the same resolver for different runtime configurations:
var semantic = ArticleContext.KnowledgeArticle.CreateContext("semantic", env: "production");
var lexical = ArticleContext.KnowledgeArticle.CreateContext("lexical", env: "production");
// Use with IncrementalSyncOrchestrator
using var orchestrator = new IncrementalSyncOrchestrator<KnowledgeArticle>(
transport,
primary: lexical,
secondary: semantic
);
Data streams don't use NameTemplate. Instead, the Namespace property on [DataStream<T>] provides equivalent environment-awareness:
// Namespace resolved from environment variables when omitted
[DataStream<LogEntry>(Type = "logs", Dataset = "myapp")]
// Explicit namespace
[DataStream<LogEntry>(Type = "logs", Dataset = "myapp", Namespace = "production")]
See mapping context for the full attribute reference.
- Mapping context: complete attribute reference for
[Index<T>],[DataStream<T>], and[WiredStream<T>] - Single index: fixed index naming
- Incremental sync: coordinating multiple indices from templated contexts