Kontent.Ai.Delivery.SourceGeneration
20.0.1
dotnet add package Kontent.Ai.Delivery.SourceGeneration --version 20.0.1
NuGet\Install-Package Kontent.Ai.Delivery.SourceGeneration -Version 20.0.1
<PackageReference Include="Kontent.Ai.Delivery.SourceGeneration" Version="20.0.1" />
<PackageVersion Include="Kontent.Ai.Delivery.SourceGeneration" Version="20.0.1" />
<PackageReference Include="Kontent.Ai.Delivery.SourceGeneration" />
paket add Kontent.Ai.Delivery.SourceGeneration --version 20.0.1
#r "nuget: Kontent.Ai.Delivery.SourceGeneration, 20.0.1"
#:package Kontent.Ai.Delivery.SourceGeneration@20.0.1
#addin nuget:?package=Kontent.Ai.Delivery.SourceGeneration&version=20.0.1
#tool nuget:?package=Kontent.Ai.Delivery.SourceGeneration&version=20.0.1
Kontent.ai Delivery SDK for .NET
The official .NET SDK for the Kontent.ai Delivery API, enabling you to retrieve content from your Kontent.ai projects with a modern, type-safe, and highly extensible client library.
Building an ASP.NET Core app? Check out Kontent.ai ASP.NET Core Extensions — a companion package that adds a <rich-text> tag helper for rendering Kontent.ai rich text in Razor views (with full IHtmlResolver integration), an <img-asset> tag helper for responsive images with automatic srcset/sizes, webhook signature validation middleware, and cache invalidation straight from a webhook notification.
Table of Contents
- Installation
- Upgrade Guide
- Quick Start
- Documentation - every task guide
- Querying - items, filtering, ordering, paging, languages
- Content Models - generated records, linked items, dynamic access
- Caching - memory, hybrid, invalidation
- Rich Text - resolvers, inline images, blocks
- Assets and Images - renditions and transformations
- Setting Up the Delivery Client
- Caching
- Rich Text
- Configuration Options
- Error Handling
- Source Tracking (for Tool Authors)
- Contributing
- License
Installation
Install the SDK via NuGet Package Manager:
dotnet add package Kontent.Ai.Delivery
Or via the Package Manager Console:
Install-Package Kontent.Ai.Delivery
Optional packages:
| Package | Purpose |
|---|---|
Kontent.Ai.Delivery.Caching |
FusionCache-backed memory and hybrid caching |
Kontent.Ai.Delivery.SourceGeneration |
Compile-time type provider via source generation |
dotnet add package Kontent.Ai.Delivery.Caching
dotnet add package Kontent.Ai.Delivery.SourceGeneration
The SDK targets net10.0, and all of these packages ship on one version. See the changelog for what each release changed.
Upgrade Guide
Upgrade guides are kept one per major under docs/upgrade/; skipping a major means reading them in sequence.
- Coming from 19.x — read 19 → 20. The move to .NET 10 and the builder registration are the work; two behaviour changes compile unchanged and are listed first.
- Coming from 18.x — read 18 → 19 first, then 19 → 20.
Quick Start
You need the package installed, .NET 10, and your environment ID — in Kontent.ai,
under Environment settings → Environment ID. Swap homepage for a codename that exists in that
environment.
using Kontent.Ai.Delivery;
await using var client = DeliveryClient.Create(delivery =>
delivery.Options.Configure(options => options.EnvironmentId = "<your-environment-id>"));
var result = await client.GetItem("homepage").ExecuteAsync();
if (result.IsSuccess)
{
Console.WriteLine(result.Value.Item.System.Name);
}
else
{
Console.WriteLine($"{(int)result.StatusCode}: {result.Error?.Message}");
}
A call returns a result rather than throwing, so IsSuccess is the check every time — see
Error Handling.
This is the standalone form, which suits a console app, a script or a test. In an application, register the client in your container instead: Setting Up the Delivery Client.
The quick start reads only system metadata. Element values are reachable without a model through
IDynamicElements, but generated records are the recommended path: they give compile-time names,
typed element values and automatic type filtering. Generate them with the
model generator,
then query them with GetItem<Article>(...) - see Content Models
and Querying. Typed queries are the
baseline throughout the guides below.
Documentation
This README covers installation, registration and configuration. Everything else lives beside it:
| Guide | What it answers |
|---|---|
| Querying | Items, types, taxonomies, used-in lookups, filtering, ordering, projection, paging, languages, and what a result carries |
| Content Models | Generated records, source-generated type resolution, linked items, dynamic access when the type is unknown until runtime |
| Caching | Memory and hybrid caches, keys, expiration, dependency keys, webhook invalidation, purging, multi-tenancy |
| Rich Text | Link and embedded-content resolvers, inline images, custom HTML nodes, reading blocks |
| Assets and Images | Renditions, a custom asset domain, and ImageUrlBuilder transformations |
| Multiple Clients | Named clients, preview vs production, multi-tenant and multi-brand setups |
| Performance | Query shaping, rate limits, parallelism, monitoring |
| Extensibility | Custom type providers, and the conventions that map elements to properties |
| Architecture | How the SDK is put together, one invariant per boundary - start here to contribute |
| Upgrade Guides | One per major: 18 → 19, 19 → 20 |
Setting Up the Delivery Client
The SDK is designed to work with .NET's dependency injection container. Register the IDeliveryClient in your Program.cs or Startup.cs:
Basic Registration
services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
options.EnvironmentId = "your-environment-id";
}));
Registration from Configuration
// appsettings.json
{
"DeliveryOptions": {
"EnvironmentId": "your-environment-id",
"UsePreviewApi": false
}
}
// Program.cs
services.AddDeliveryClient(delivery => delivery.Options.BindConfiguration("DeliveryOptions")); // in a host, from the container's IConfiguration
services.AddDeliveryClient(delivery => delivery.Options.Bind(configuration.GetSection("MyDeliverySection"))); // or a section you hold
The default section name is available as DeliveryOptions.DefaultConfigurationSectionName, so tooling
that resolves the SDK's configuration from the same sources does not have to hard-code it.
Options is the client's OptionsBuilder<DeliveryOptions>, so everything the options system offers is
there: Configure, Configure<TDependency>, Bind, BindConfiguration, PostConfigure, Validate.
Binding is change-token backed: edits to the underlying source are picked up through
IOptionsMonitor<DeliveryOptions> without rebuilding the container. Binding from configuration and
customizing the pipeline are two steps on the same builder:
services.AddDeliveryClient(delivery =>
{
delivery.Options.BindConfiguration("DeliveryOptions");
delivery.TuneRetry(retry => retry.MaxRetryAttempts = 5);
});
Registration from Other DI Services
Use Options.Configure<IServiceProvider> when Delivery options need values from other registered services:
services.Configure<SiteOptions>(configuration.GetSection("Site"));
services.AddDeliveryClient(delivery => delivery.Options.Configure<IServiceProvider>((options, sp) =>
{
var site = sp.GetRequiredService<IOptions<SiteOptions>>().Value;
options.EnvironmentId = site.EnvironmentId;
}));
The callback must not resolve IDeliveryClient, IOptions<DeliveryOptions> or anything that depends on
them: doing so re-enters the options factory, and the container recurses without bound.
API Mode Helpers
The members that set more than one property come as extension methods on DeliveryOptions, usable inside
Configure and on any instance:
services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
options.EnvironmentId = "your-environment-id";
options.UsePreviewApi("your-preview-api-key"); // or UseProductionApi(), UseProductionApi(secureAccessApiKey), UseCustomEndpoint(url)
}));
Source-Generated Type Provider (Recommended)
Nothing to register. When your models carry the [ContentTypeCodename] attribute and the models project
references Kontent.Ai.Delivery.SourceGeneration, the generator emits a GeneratedTypeProvider at compile
time and the SDK discovers it at runtime, searching the entry assembly and its references:
services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
options.EnvironmentId = "your-environment-id";
}));
Keep the attributed models in a single project for auto-discovery to be predictable. If they are split
across compilations on purpose, register an ITypeProvider explicitly instead. The Content Models
guide
covers what the generator produces and the diagnostics it reports.
A model must be a class or a record class. The SDK hydrates elements on the instance it deserialized, so a struct would be copied and its values lost — the generator reports KDSG003, and the client throws NotSupportedException if a struct reaches it another way.
Registering a Custom Type Provider
To override the auto-discovered provider, register your own — before the client, or on the builder's
Services. The SDK registers its default with TryAddSingleton, so yours wins either way:
services.AddDeliveryClient(delivery =>
{
delivery.Services.AddSingleton<ITypeProvider, MyCustomTypeProvider>();
delivery.Options.Configure(options => options.EnvironmentId = "your-environment-id");
});
Without Dependency Injection
For console applications, scripts, or anywhere a container is not available, DeliveryClient.Create takes
the same builder as AddDeliveryClient and runs the same registration inside a private container the
client owns:
await using var client = DeliveryClient.Create(delivery => delivery.Options.Configure(o => o.EnvironmentId = "your-environment-id"));
// Anything the container path can do, this can do - caching, resilience, an explicit type provider.
await using var cachedClient = DeliveryClient.Create(delivery =>
{
delivery.Options.Configure(o => o.EnvironmentId = "your-environment-id");
delivery.UseMemoryCache(o => o.DefaultExpiration = TimeSpan.FromMinutes(30));
});
// Or from a pre-built options instance.
await using var fromOptions = DeliveryClient.Create(new DeliveryOptions { EnvironmentId = "your-environment-id" });
The builder is one type in both hosting modes:
.Options— the client'sOptionsBuilder<DeliveryOptions>(Configure,Bind,BindConfiguration, …).HttpClient— the namedIHttpClientBuilderthe transport is built on (ConfigurePrimaryHttpMessageHandler,AddHttpMessageHandler, …).TuneRetry(...)— adjusts the default retry options.ConfigureResilience(...)— replaces the default resilience pipeline.Servicesand.Name— what everything else attaches to: a customITypeProvider, anILoggerFactory, the caching package'sUseMemoryCache/UseHybridCache/UseCacheManager
Create returns the concrete DeliveryClient, which owns the container it was built from — disposing it
tears that down, which is why the examples use await using. Keep the result as var (or
DeliveryClient); widening it to IDeliveryClient drops the disposal, because the interface deliberately
does not carry it. A client resolved from a container is owned by that container, so there is nothing for
you to dispose there. IDeliveryClientFactory is the other half of that split: it resolves named clients
from a container you own, while Create builds a standalone one over a container it owns. Invalid options
surface as OptionsValidationException from Create.
UseCustomEndpoint(...) sets the same endpoint for both Production and Preview. Real deployments usually differ, so if you need both modes on custom domains, register separate named clients and give each its own endpoint.
Caching
Caching is the first thing to add to a production application - it is also the SDK's answer to rate limits. It ships separately:
dotnet add package Kontent.Ai.Delivery.Caching
Attach it to a client in the same callback that configures it:
services.AddDeliveryClient(delivery =>
{
delivery.Options.Configure(options => options.EnvironmentId = "your-environment-id");
delivery.UseMemoryCache(o => o.DefaultExpiration = TimeSpan.FromHours(1));
});
UseHybridCache is the L1+L2 form for multi-instance deployments, backed by any IDistributedCache.
Running more than one instance? Register an IFusionCacheBackplane as well. Part of the invalidation state lives in each instance rather than in the shared cache, so without one an instance can go on serving content another already evicted. The SDK picks the backplane up from the container automatically.
Caching is transparent once attached: cacheable queries are cached, keyed by their parameters. Dynamic
queries, preview clients and WaitForLoadingNewContent(true) are deliberately never cached.
The Caching Guide has the rest - hybrid setup, cache keys, expiration, dependency keys for output caching, webhook invalidation, purging, and multi-tenant scenarios.
Rich Text
A rich text element is structured content, not an HTML string. Render it with ToHtmlAsync():
var result = await client.GetItem<Article>("my-article").ExecuteAsync();
var html = await result.Value.Elements.BodyCopy.ToHtmlAsync();
The default resolver handles text, images and standard HTML. To control how content item links and
embedded content render, build a resolver and pass it in. A resolver's return value is inserted as
HTML unescaped, so encode every element value you interpolate (HtmlEncoder is in
System.Text.Encodings.Web); the rendered output of resolveChildren is already HTML and must not
be encoded:
var resolver = new HtmlResolverBuilder()
.WithContentItemLinkResolver("article", async (link, resolveChildren) =>
$"<a href=\"/articles/{HtmlEncoder.Default.Encode(link.Metadata?.UrlSlug ?? "")}\">{await resolveChildren(link.Children)}</a>")
.WithContentResolver<Tweet>(t => $"<blockquote>{HtmlEncoder.Default.Encode(t.Elements.TweetText ?? "")}</blockquote>")
.Build();
var html = await result.Value.Elements.BodyCopy.ToHtmlAsync(resolver);
Rich text is also enumerable, when you want the blocks rather than the HTML.
The Rich Text Customization Guide covers the rest: registering the resolver in DI, async and nested resolution, inline images, custom HTML nodes, and the extension methods for reading blocks out of an element.
Rendering in Razor? Kontent.Ai.AspNetCore has a <rich-text> tag helper that picks the registered IHtmlResolver up for you.
Configuration Options
The DeliveryOptions class provides comprehensive configuration:
services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
// Required: Your Kontent.ai environment ID
options.EnvironmentId = "your-environment-id";
// Preview API settings
options.UsePreviewApi = false;
options.PreviewApiKey = "your-preview-api-key";
// Secured production API (if enabled in Kontent.ai)
options.UseSecureAccess = false;
options.SecureAccessApiKey = "your-secure-api-key";
// Retry and resilience settings
options.EnableResilience = true;
// Default image rendition preset
options.DefaultRenditionPreset = "default";
// Custom asset domain (rewrites all asset URLs to use your CDN)
options.CustomAssetDomain = "https://assets.example.com";
// Custom endpoints (for proxy scenarios, set independently)
options.ProductionEndpoint = "https://deliver.kontent.ai";
options.PreviewEndpoint = "https://preview-deliver.kontent.ai";
}));
options.UseCustomEndpoint(...) is a convenience method that sets both endpoints to the same URL. For distinct preview/production custom domains, configure separate clients and set endpoints per client.
The HTTP client and the resilience pipeline are configured on the same builder. HttpClient is the
named IHttpClientBuilder the transport is built on, so every Microsoft.Extensions.Http extension
applies, and whatever you configure there runs after the SDK's own setup. Leave HttpClient.Timeout
alone: the SDK sets it as the ceiling on the whole call, and DeliveryOptions.Timeout is the way to
change that ceiling, so an override there silently caps the retry sequence:
services.AddDeliveryClient(delivery =>
{
delivery.Options.Configure(options => options.EnvironmentId = "your-environment-id");
delivery.HttpClient.ConfigureHttpClient(client => client.DefaultRequestHeaders.Add("X-App", "my-app"));
delivery.TuneRetry(retry =>
{
retry.MaxRetryAttempts = 5;
retry.Delay = TimeSpan.FromSeconds(2);
});
});
Tuning retries
TuneRetry receives the SDK's initialized HttpRetryStrategyOptions before the default pipeline is
assembled. Change only the settings you need; the remaining defaults, including Retry-After handling
and the 30-second per-attempt timeout, stay in place. Replacing ShouldHandle or DelayGenerator
replaces that part of the retry behavior.
Callbacks run in registration order with fresh options for each pipeline construction, in both
AddDeliveryClient and DeliveryClient.Create. They do not run when EnableResilience is false or
ConfigureResilience replaces the pipeline. Tuning retains the default pipeline's timeout behavior.
Timeouts
Two clocks bound a request, and they are not the same one:
- Per attempt — the default resilience pipeline cancels any single HTTP attempt after 30 seconds and retries it on a fresh connection. By default, up to four attempts, each with its own budget.
- The whole call —
DeliveryOptions.Timeoutcovers every attempt and the waits between them.
Timeout is unset by default, which keeps the SDK's own rule: the default pipeline bounds each attempt,
so the call runs as long as its retries need; with EnableResilience = false or a pipeline of your own,
HttpClient's 100-second default applies, because nothing else is known to bound the request.
Set it and it always wins, whatever the pipeline:
services.AddDeliveryClient(delivery => delivery.Options.Configure(o =>
{
o.EnvironmentId = "your-environment-id";
o.Timeout = TimeSpan.FromMinutes(5);
}));
Timeout.InfiniteTimeSpan removes the ceiling outright. Note that it outranks Retry-After: when the API
rate-limits you, the pipeline waits exactly as long as the server asked, but the call is still cut short if
your ceiling runs out first.
Error Handling
The SDK uses a result pattern instead of throwing exceptions for API errors. This makes error handling explicit and predictable.
Checking for Errors
var result = await client.GetItem<Article>("my-article").ExecuteAsync();
if (result.IsSuccess)
{
var article = result.Value;
// Process article
}
else
{
// Handle error
var error = result.Error;
Console.WriteLine($"Error: {error.Message}");
Console.WriteLine($"Status: {result.StatusCode}");
// Error details for debugging/logging
if (error.RequestId != null)
Console.WriteLine($"Request ID: {error.RequestId}");
if (error.ErrorCode.HasValue)
Console.WriteLine($"Error Code: {error.ErrorCode}");
}
IError Properties
| Property | Description |
|---|---|
Message |
Human-readable error description |
RequestId |
Unique request ID for Kontent.ai support |
ErrorCode |
Kontent.ai-specific error code |
SpecificCode |
More specific error code |
Exception |
Underlying exception (for network errors, etc.) |
Source Tracking (for Tool Authors)
Every request the SDK sends carries two analytics headers:
X-KC-SDKID— identifies this SDK. Alwaysnuget.org;Kontent.Ai.Delivery;<version>. Not configurable.X-KC-SOURCE— identifies a library built on top of the SDK. Set only when a caller assembly opts in. Omitted otherwise.
End-user applications need do nothing here. This matters only if you publish a library that wraps the Delivery SDK. If you do, add one of these at assembly level (AssemblyInfo.cs, or a top-level file); at request time the SDK walks the call stack, finds your assembly and reads the attribute:
// Name and version from the assembly — the usual case.
[assembly: DeliverySourceTrackingHeader]
// Override the name (your package id differs from your assembly name), version still from the assembly.
[assembly: DeliverySourceTrackingHeader("Acme.Kontent.Ai.AwesomeTool")]
// Pin both, independent of assembly metadata.
[assembly: DeliverySourceTrackingHeader("Acme.Kontent.Ai.AwesomeTool", 1, 2, 3, "beta")]
Contributing
Contributions are welcome. Use GitHub Issues for bug reports and feature requests, and open pull requests in this repository for code contributions.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Questions or feedback? Visit our GitHub Issues or check the Kontent.ai Developer Hub.
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 20.0.1 | 267 | 9/18/2026 |
| 20.0.0 | 163 | 9/16/2026 |
| 20.0.0-rc.3 | 688 | 9/8/2026 |
| 20.0.0-rc.2 | 340 | 8/12/2026 |
| 20.0.0-rc.1 | 194 | 8/7/2026 |
| 19.4.0 | 1,340 | 8/3/2026 |
| 19.3.1 | 129 | 7/30/2026 |
| 19.3.0 | 194 | 6/24/2026 |
| 19.2.0 | 3,025 | 5/4/2026 |
| 19.1.0 | 140 | 4/27/2026 |
| 19.0.0 | 326 | 4/19/2026 |
| 19.0.0-rc5 | 169 | 4/13/2026 |
| 19.0.0-rc4 | 174 | 3/18/2026 |
| 19.0.0-rc3 | 122 | 3/6/2026 |
| 19.0.0-rc2 | 130 | 3/4/2026 |
| 19.0.0-rc1 | 379 | 2/20/2026 |