Skip to content

Granit Bundles — one PackageReference, not thirty

Granit ships 420 packages. That level of granularity is great for control but overwhelming when you’re starting a fresh project — you shouldn’t have to know the full module graph to get a working API up. Bundles solve this: a bundle is a NuGet meta-package with no code of its own, just a curated set of PackageReference entries you’d otherwise type by hand. One package reference, one Add*() call, and the entire feature group is wired correctly.

PainBundle’s answer
Onboarding asks for “the right modules to start”One meta-package per job
Manually composing 8 references for a typical Web APIGranit.Bundle.Api
Forgetting Granit.Persistence.EntityFrameworkCore and crashing at first saveBundles include the persistence root
Mixing up which OpenIddict packages go togetherGranit.Bundle.OpenIddict ships the full server + EFC + endpoints + jobs
Maintaining your “starter template”Bump the bundle version; transitive deps follow
BundleShips fromUse case
Essentialsgranit-dotnetEvery project — the foundation
Apigranit-dotnetREST API projects — adds versioning + OpenAPI + Idempotency + Localization + Redis caching
OpenIddictgranit-dotnetAuthorization server (issuer of access tokens) — adds the full OpenIddict stack + local identity
Notificationsgranit-dotnetMulti-channel notifications — Email (SMTP) + SignalR with persistence + admin API
SaaSgranit-businessMulti-tenant operations — MultiTenancy + Features + RateLimiting + Bulkhead
Documentsgranit-businessDocument generation — Templating (Scriban) + DocumentGeneration (PDF, Excel) with persistence

The foundation. Every Granit project starts here.

PackageRole
GranitCore abstractions (ICurrentUserService, ICurrentTenant, base entity types)
Granit.TimingTimeProvider injection
Granit.GuidsSequential GUID generation
Granit.ValidationFluentValidation wiring
Granit.Persistence.EntityFrameworkCoreAddGranitDbContext<T>, UseGranitInterceptors, auto-discovered interceptors
Granit.ObservabilitySerilog + OpenTelemetry tracing/metrics, OTLP export
Granit.Http.CorsStandardized CORS with startup validation
Granit.Http.ExceptionHandlingRFC 7807 Problem Details, 4xx/5xx message masking
Granit.Http.ResponseCompressiongzip / brotli by default
Granit.Http.SecurityHeadersPer-endpoint CSP composition, HSTS, X-Content-Type-Options
Granit.Http.SecurityHeaders.AbstractionsContracts for the ICspContributor pattern — referenced by packages that declare their own CSP relaxations (transitive via Granit.Http.SecurityHeaders)
Granit.DiagnosticsActivity sources registry, health-check primitives

Granit.Http.SecurityHeaders.Endpoints ships separately (not bundled) and adds a /security-headers/csp audit endpoint gated by Diagnostics.Monitoring.Read for security auditors. Reference it explicitly when you need it.

Everything in Essentials + the REST API surface.

PackageRole
Granit.Bundle.Essentials (transitive)The foundation
Granit.Http.ApiVersioningURL / header / query versioning
Granit.Http.ApiDocumentationOpenAPI 3.1 + Scalar UI, versioned per major API
Granit.Http.IdempotencyStripe-style replay-safe Idempotency-Key middleware
Granit.Localization + .EntityFrameworkCoreAccept-Language resolution, runtime overrides
Granit.Caching.StackExchangeRedisDistributed cache backbone for output caching, idempotency, sessions

A complete authorization server (token issuer). Use this when your app owns the identity store; if you only need to consume tokens issued by Keycloak / EntraId / Cognito, use Granit.Authentication.JwtBearer.<provider> directly.

PackageRole
Granit.Authentication.OpenIddictJWT Bearer validation against the local OpenIddict issuer
Granit.OpenIddictOpenIddict core
Granit.OpenIddict.ServerToken + authorization + introspection endpoints
Granit.OpenIddict.EntityFrameworkCoreEF-backed application/authorization/scope stores
Granit.OpenIddict.EndpointsAdmin API for application/scope management
Granit.OpenIddict.BackgroundJobsToken cleanup + key rotation jobs
Granit.Identity.Local.AspNetIdentityASP.NET Identity-backed LocalIdentity user store
Granit.Identity.Local.EndpointsSelf-service /account/* endpoints (register, reset, change password)
PackageRole
Granit.NotificationsChannel-agnostic dispatch (INotificationPublisher, types, preferences)
Granit.Notifications.EntityFrameworkCoreDelivery log + user preferences persistence
Granit.Notifications.EndpointsAdmin / user preference API
Granit.Notifications.EmailChannel-neutral email contract
Granit.Notifications.Email.SmtpSMTP provider (MailKit)
Granit.Notifications.SignalRReal-time browser push via SignalR + Redis backplane

Add other providers à la carte: Granit.Notifications.Email.{SendGrid,Brevo,Scaleway,Mailgun,Postmark,AwsSes,AzureCommunication}, Granit.Notifications.Sms.*, Granit.Notifications.WebPush, Granit.Notifications.MobilePush.*, Granit.Notifications.WhatsApp, Granit.Notifications.Zulip.

Focused multi-tenant infrastructure. Layer it on top of Granit.Bundle.Api.

PackageRole
Granit.MultiTenancyTenant resolution (domain, header, JWT claim), ICurrentTenant, side-aware permissions (MultiTenancySides)
Granit.FeaturesMulti-level feature flags (Tenant > Plan > Default)
Granit.Features.EntityFrameworkCorePersistent flag overrides
Granit.RateLimitingPer-tenant rate limiting (sliding/fixed window, token bucket, concurrency)
Granit.Http.BulkheadPer-tenant concurrency isolation
PackageRole
Granit.TemplatingITemplateEngine abstraction + lifecycle (draft/review/publish)
Granit.Templating.ScribanScriban-based engine (Liquid-compatible, sandboxed)
Granit.Templating.EntityFrameworkCoreTemplate + version persistence
Granit.DocumentGenerationIDocumentGenerator facade — two-stage pipeline (text render → binary)
Granit.DocumentGeneration.PdfHTML → PDF via headless browser, PDF/A-3b
Granit.DocumentGeneration.ExcelClosedXML-based engine

Reference the bundle package and call the corresponding method on the Granit builder:

builder.AddGranit<AppModule>(granit => granit
.AddApi());

You can combine any number of bundles in a single call. Deduplication is automatic — the [DependsOn] graph tracks what’s already registered and skips duplicates.

// Api includes Essentials; layering SaaS on top is safe — no double registration.
builder.AddGranit<AppModule>(granit => granit
.AddApi()
.AddSaaS()
.AddNotifications()
.AddDocuments());

Bundles are convenience, not requirement. If your project only needs three specific packages, reference them directly:

<PackageReference Include="Granit" />
<PackageReference Include="Granit.Observability" />
<PackageReference Include="Granit.Http.ExceptionHandling" />

This keeps your dependency footprint minimal and avoids pulling in modules you’ll never configure.