Licensing
Capabilities, license tiers, and the license service
This page is the runtime reference for how a license shapes Doconut's behavior: what each license state allows, how capabilities are granted, and how to inspect all of it from code. (For loading a license, see Getting Started → License Setup.)
License states and what they do
| State | Opens documents? | Watermark | Capabilities |
|---|---|---|---|
| Full (paid) license (this build was released before its subscription date) | Yes | None | The ones purchased (Has* flags) |
| Temporary license (Developer/NFR — the evaluation license, before its expiry date) | Yes | None on the base viewer; plugin output is watermarked | All — blanket grant (the eval funnel) |
Expired temporary license (past its LicenseExpiry date) | Yes | Evaluation watermark | None granted |
| No license file at all | Yes | Evaluation watermark | None granted |
| Found but rejected (invalid signature, tampered, blacklisted, or a build released after the license's subscription date) | No — LicenseException on every open | — | — |
A paid license whose subscription date has passed keeps working — clean, with its capabilities — on every build released before that date. Only a build released after it is rejected.
Two consequences worth internalizing:
- A missing license is not a temporary license. With no license file, pages render watermarked and no capability (search, annotation, plugins) is active. The full-featured, unwatermarked experience requires an actual Temporary license (get one).
- Only a rejected license blocks opening. An expired Temporary license keeps the app running with the evaluation watermark, and a paid license past its subscription date keeps rendering clean on the builds it covers; but deploying a build released after the license's subscription date is rejected and fails fast with a
LicenseException, so you catch it at deploy — not by silently shipping watermarked output. That exception (fromOpenDocumentAsync) carries the reason fromLicense.RejectionMessage.
Capabilities
Doconut gates optional functionality by capability. It covers the built-in Search and Annotation features and the opt-in Converter, Dicom and Signer plugins, each granted by your license. The base viewer is not a capability — it is the prerequisite, surfaced as IsViewerLicensed:
var lic = app.Services.GetRequiredService<IDoconutLicenseService>();
bool viewerLicensed = lic.IsViewerLicensed; // non-TRIAL license present AND covering this build's versionThe single gate the whole pipeline uses is IsCapabilityGranted:
var lic = app.Services.GetRequiredService<IDoconutLicenseService>();
bool searchGranted = lic.IsCapabilityGranted(LicenseCapability.Search);Its logic, verbatim from the implementation: an active (non-expired) Temporary license grants every capability — including capabilities that don't have a dedicated purchase flag yet; a real purchased license grants exactly its Has* flags and is default-deny for anything unmapped. (A legacy TRIAL-type file is not an entitlement — it is treated as unlicensed: watermarked, nothing granted.)
Temporary licenses
Temporary (Developer/NFR) licenses are marked by an ExpiryNote tag and carry a hard expiry date:
| Member | Meaning |
|---|---|
IsTemporary | An ExpiryNote tag is present (detection is by tag presence — never match the note's text, it's dynamic) |
ExpiryNote | Informational text embedded in the license |
LicenseExpiry | The hard expiry date (DateTime?) |
Expiry semantics differ by tier: a temporary license expires on its LicenseExpiry date only (its subscription window is ignored; a temporary license missing the date is treated as expired, fail-safe). Every other license expires on its subscription/update-window date; for a paid license, IsExpired only reports that this date has passed — builds released before it stay clean and keep their capabilities.
IDoconutLicenseService reference
Inject IDoconutLicenseService (namespace Doconut.Core.Interfaces; registered as a singleton — the license is parsed once at startup):
| Member | Meaning |
|---|---|
License | Full DoconutLicenseInfo snapshot (below) |
IsTrial | A legacy TRIAL-type file (treated as unlicensed — not an entitlement) |
IsTemporary / ExpiryNote / LicenseExpiry | Temporary-license markers |
IsExpired | Expired per the tier rules above |
IsVersionValid | This build falls within the license's support window |
IsViewerLicensed | Non-TRIAL file found and version covered — base prerequisite |
IsCapabilityGranted(cap) | The single capability gate |
HasSearch HasAnnotation HasEditor HasConverter HasDicom HasDocumentTools HasSigner HasRedactor HasComparer HasForms HasProtector HasCompressor | One flag per capability: purchased by a license that covers this build, or granted by an active Temporary license. IsCapabilityGranted answers the same question by capability |
IsValidForDomain(host) | Host coverage; V4 licenses use the wildcard *.* |
LicenseRejectionReason | Non-empty ⇒ opening is blocked with this message |
DoconutLicenseInfo exposes the display-friendly snapshot for admin panels: Owner, LicenseType, Domain, IsLicenseFileFound, IsVersionValid, IsTrial, IsExpired, the Has* flags, the temporary-license fields, and RejectionMessage.
// Feature-flag your UI from the same source of truth the SDK uses
app.MapGet("/api/features", (IDoconutLicenseService lic) => Results.Ok(new
{
viewer = lic.IsViewerLicensed || lic.IsTemporary,
search = lic.IsCapabilityGranted(LicenseCapability.Search),
annotation = lic.IsCapabilityGranted(LicenseCapability.Annotation),
conversion = lic.HasConverter,
dicom = lic.HasDicom,
}));Health check
AddDoconut() registers a "doconut" health check that reports license/expiry state through the standard ASP.NET Core mechanism:
app.MapHealthChecks("/health");Wire it into your monitoring and an expiring subscription becomes an alert instead of a support ticket. The check reports Unhealthy when the license is rejected or IsExpired is true; for a paid license, that means the subscription date has passed and a package released after it will be rejected — the current build keeps rendering clean.
Version upgrades
A license covers a version window. Updating the NuGet package to a build newer than that window makes the license invalid for this build: IsVersionValid turns false and opening is blocked with a LicenseException (the reason is in License.RejectionMessage) — deliberately fail-fast, so a stale or unlicensed build is caught at deploy rather than silently watermarking production output. Renew the subscription, replace the .lic, and restart the application so AddDoconut() loads the new license.
Was this page helpful?