![]() |
VOOZH | about |
dotnet add package Elastic.Mapping --version 0.48.0
NuGet\Install-Package Elastic.Mapping -Version 0.48.0
<PackageReference Include="Elastic.Mapping" Version="0.48.0" />
<PackageVersion Include="Elastic.Mapping" Version="0.48.0" />Directory.Packages.props
<PackageReference Include="Elastic.Mapping" />Project file
paket add Elastic.Mapping --version 0.48.0
#r "nuget: Elastic.Mapping, 0.48.0"
#:package Elastic.Mapping@0.48.0
#addin nuget:?package=Elastic.Mapping&version=0.48.0Install as a Cake Addin
#tool nuget:?package=Elastic.Mapping&version=0.48.0Install as a Cake Tool
Compile-time Elasticsearch mappings for .NET. Native AOT ready. Define your index mappings, analysis chains, and field metadata with C# attributes and get reflection-free generated code that works with System.Text.Json source generation out of the box.
Elasticsearch field names are strings. Typos are silent. Refactors break queries. Manual JSON mappings drift from your code.
Elastic.Mapping fixes this with a source generator that turns your POCOs into type-safe, pre-computed mapping infrastructure at build time -- zero reflection, zero runtime overhead, fully AOT compatible.
public class Product
{
[Keyword]
public string Id { get; set; }
[Text(Analyzer = "standard")]
public string Name { get; set; }
public double Price { get; set; }
public bool InStock { get; set; }
[Nested]
public List<Category> Categories { get; set; }
}
[ElasticsearchMappingContext]
[Index<Product>(Name = "products")]
[DataStream<ApplicationLog>(Type = "logs", Dataset = "myapp", Namespace = "production")]
public static partial class MyContext;
// Type-safe field names -- rename the C# property, these update automatically
MyContext.Product.Fields.Name // "name"
MyContext.Product.Fields.Price // "price"
MyContext.Product.Fields.InStock // "inStock"
// Index targets
MyContext.Product.IndexStrategy.WriteTarget // "products"
// Data stream naming follows Elastic conventions
MyContext.ApplicationLog.IndexStrategy.DataStreamName // "logs-myapp-production"
// Pre-built JSON for index creation
var json = MyContext.Product.Context.GetIndexJson();
// Change detection -- only update when mappings actually change
if (clusterHash != MyContext.Product.Hash) UpdateMappings();
Elastic.Mapping is built around System.Text.Json. Link your STJ source-generated JsonSerializerContext and the mapping generator inherits your serialization configuration automatically -- one source of truth for both JSON serialization and Elasticsearch field names.
// Your STJ source-generated context
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
UseStringEnumConverter = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(Order))]
public partial class MyJsonContext : JsonSerializerContext;
// Link it to the mapping context
[ElasticsearchMappingContext(JsonContext = typeof(MyJsonContext))]
[Index<Product>(Name = "products")]
[Index<Order>(Name = "orders")]
public static partial class MyContext;
The generator reads [JsonSourceGenerationOptions] at compile time and applies:
| STJ Option | Effect on Mappings |
|---|---|
PropertyNamingPolicy |
Field names follow the same policy (camelCase, snake_case_lower, kebab-case-lower, etc.) |
UseStringEnumConverter |
Enum fields map to keyword instead of integer |
DefaultIgnoreCondition |
Ignored properties are excluded from mappings |
IgnoreReadOnlyProperties |
Read-only properties are excluded from mappings |
Per-property [JsonPropertyName("custom_name")] and [JsonIgnore] attributes are always respected, with or without a linked context.
This means your Elasticsearch field names, your JSON wire format, and your C# properties all stay in sync -- at compile time, with no reflection.
Every feature in Elastic.Mapping is AOT compatible:
JsonSerializerContext for a fully AOT serialization pipelinePublish with dotnet publish -p:PublishAot=true and everything works.
Control how properties map to Elasticsearch field types:
| Attribute | Elasticsearch Type | Use Case |
|---|---|---|
[Text] |
text |
Full-text search, analyzers |
[Keyword] |
keyword |
Exact match, aggregations, sorting |
[Date] |
date |
Timestamps, date math |
[Nested] |
nested |
Preserve array element relationships |
[GeoPoint] |
geo_point |
Latitude/longitude |
[DenseVector(Dims = 384)] |
dense_vector |
Embeddings, kNN search |
[SemanticText] |
semantic_text |
ELSER / semantic search |
[Ip] |
ip |
IPv4/IPv6 addresses |
[Completion] |
completion |
Autocomplete suggestions |
Properties without attributes are inferred from their CLR type (string → keyword, int → integer, DateTime → date, etc.).
IConfigureElasticsearch<T>Implement IConfigureElasticsearch<T> to configure analysis, mappings, and index settings for a document type. There are two ways to wire this up:
Create a separate class that implements the interface and reference it via Configuration = typeof(...):
public class ProductConfig : IConfigureElasticsearch<Product>
{
public AnalysisBuilder ConfigureAnalysis(AnalysisBuilder analysis) => analysis
.Analyzer("product_search", a => a.Custom()
.Tokenizer(BuiltIn.Tokenizers.Standard)
.Filters(BuiltIn.TokenFilters.Lowercase, "english_stemmer", "edge_ngram_3_8"))
.TokenFilter("english_stemmer", f => f.Stemmer()
.Language(BuiltIn.StemmerLanguages.English))
.TokenFilter("edge_ngram_3_8", f => f.EdgeNGram()
.MinGram(3).MaxGram(8));
public MappingsBuilder<Product> ConfigureMappings(MappingsBuilder<Product> mappings) => mappings
.Name(f => f.Analyzer("product_search")
.MultiField("keyword", mf => mf.Keyword().IgnoreAbove(256)));
public IReadOnlyDictionary<string, string> IndexSettings => new Dictionary<string, string>
{
["index.default_pipeline"] = "product-enrichment"
};
}
[ElasticsearchMappingContext]
[Index<Product>(Name = "products", Configuration = typeof(ProductConfig))]
public static partial class MyContext;
If you prefer to keep configuration on the entity itself, implement the interface directly:
public class Product : IConfigureElasticsearch<Product>
{
[Keyword]
public string Id { get; set; }
[Text(Analyzer = "product_search")]
public string Name { get; set; }
public double Price { get; set; }
public AnalysisBuilder ConfigureAnalysis(AnalysisBuilder analysis) => analysis
.Analyzer("product_search", a => a.Custom()
.Tokenizer(BuiltIn.Tokenizers.Standard)
.Filters(BuiltIn.TokenFilters.Lowercase));
public MappingsBuilder<Product> ConfigureMappings(MappingsBuilder<Product> mappings) => mappings
.Name(f => f.Analyzer("product_search")
.MultiField("keyword", mf => mf.Keyword().IgnoreAbove(256)));
}
// No Configuration needed -- the generator detects the interface on the entity itself
[ElasticsearchMappingContext]
[Index<Product>(Name = "products")]
public static partial class MyContext;
All three interface members use default implementations, so you only need to override what you customize:
| Member | Default | Purpose |
|---|---|---|
ConfigureAnalysis |
no-op | Custom analyzers, tokenizers, filters |
ConfigureMappings |
no-op | Field overrides, runtime fields, dynamic templates |
IndexSettings |
null |
Additional index settings (e.g. default_pipeline) |
The generated ProductAnalysis class gives you type-safe constants for your custom components:
MyContext.Product.Analysis.Analyzers.ProductSearch // "product_search"
MyContext.Product.Analysis.TokenFilters.EnglishStemmer // "english_stemmer"
If your configuration class needs services (e.g. to read settings from IConfiguration), call RegisterServiceProvider before first access to mapping JSON:
// In your startup / Program.cs
services.AddSingleton<IConfigureElasticsearch<Product>, ProductConfig>();
var sp = services.BuildServiceProvider();
MyContext.RegisterServiceProvider(sp);
When no IServiceProvider is registered (or the service isn't found), the generator falls back to new ProductConfig() (parameterless constructor).
[Index<Product>(
Name = "products",
WriteAlias = "products-write",
ReadAlias = "products-read",
Shards = 3,
RefreshInterval = "5s"
)]
[Index<Order>(Name = "orders", DatePattern = "yyyy.MM")]
// Write target: orders-2025.02
// Search pattern: orders-*
[DataStream<ApplicationLog>(Type = "logs", Dataset = "ecommerce.app", Namespace = "production")]
// Data stream: logs-ecommerce.app-production
// Search pattern: logs-ecommerce.app-*
The source generator creates extension methods on MappingsBuilder<T> for each property on your registered types. Customize mappings at the property level with full IntelliSense:
public MappingsBuilder<Product> ConfigureMappings(MappingsBuilder<Product> mappings) => mappings
.Name(f => f.Analyzer("product_search"))
.Price(f => f.DocValues(true))
.AddRuntimeField("discount_pct", r => r.Double()
.Script("emit((doc['price'].value - doc['sale_price'].value) / doc['price'].value * 100)"))
.AddDynamicTemplate("labels_as_keyword", dt => dt
.PathMatch("labels.*")
.Mapping(m => m.Keyword()));
For each registered type, the source generator produces:
MyContext.Product.Fields.Name (compile-time safe field names)PropertyToField / FieldToProperty dictionariesMappingsBuilder<T> for customizationAll generated at compile time. Zero reflection at runtime. AOT compatible. Aligned with your System.Text.Json configuration.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 2 NuGet packages that depend on Elastic.Mapping:
| Package | Downloads |
|---|---|
|
Elastic.Ingest.Elasticsearch
Offers an easy to use ChannelWriter implementation to push data concurrently to Elasticsearch using Elastic.Transport |
|
|
Elastic.Internal.Search.Contract
Package Description |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.48.0 | 423 | 6/3/2026 |
| 0.47.0 | 148 | 6/3/2026 |
| 0.46.0 | 3,056 | 6/2/2026 |
| 0.45.0 | 1,397 | 5/12/2026 |
| 0.44.1 | 1,831 | 5/12/2026 |
| 0.44.0 | 226 | 5/11/2026 |
| 0.43.0 | 11,899 | 4/20/2026 |
| 0.42.0 | 149 | 4/20/2026 |
| 0.41.2 | 4,430 | 4/15/2026 |
| 0.41.1 | 334 | 4/15/2026 |
| 0.41.0 | 2,482 | 3/31/2026 |
| 0.40.0 | 6,696 | 3/21/2026 |
| 0.39.0 | 175 | 3/20/2026 |
| 0.38.0 | 188 | 3/20/2026 |
| 0.37.0 | 2,796 | 3/5/2026 |
| 0.36.0 | 150 | 3/5/2026 |
| 0.35.0 | 206 | 3/5/2026 |
| 0.34.5 | 4,864 | 3/3/2026 |
| 0.34.4 | 187 | 3/3/2026 |
| 0.34.3 | 201 | 3/3/2026 |