Open-Source Wikis

/

Bitwarden Server

/

How to contribute

/

Patterns and conventions

bitwarden/server

Patterns and conventions

This page captures the in-codebase patterns that recur across teams. They are not all called out in CONTRIBUTING.md, so reading existing code in the area you are touching is the best way to absorb them.

Project layout

Every deployable service follows the same shape:

src/<App>/
├── Program.cs            # WebApplication.CreateBuilder + Startup wiring
├── Startup.cs            # ConfigureServices + Configure pipeline
├── Controllers/          # MVC / API controllers (or Razor Pages for Admin)
├── Models/               # Request / Response models per app
├── Jobs/                 # Quartz.NET hosted-service jobs
├── Utilities/            # Local extension methods
├── appsettings.*.json    # Per-environment config
├── Dockerfile            # ASP.NET Core 8 multi-stage build
├── entrypoint.sh         # Container entrypoint (e.g. wait-for-DB, run migrator)
└── build.sh / build.ps1  # Local build helper

The shared library src/Core/ is partitioned by product domain, not by layer:

src/Core/
├── Auth/             # Master password, 2FA, IdentityServer integration
├── AdminConsole/     # Organizations, Members, Groups, Policies, Providers
├── Billing/          # Stripe, plans, invoices, subscriptions, providers
├── Vault/            # Ciphers, Folders, Collections
├── Tools/            # Sends, Imports
├── KeyManagement/    # User key, key rotation, KDF
├── SecretsManager/   # SM projects, secrets, service accounts
├── NotificationCenter/ # In-product banners / notifications
├── Dirt/             # Reports (password health, breach), event integrations
├── Platform/         # Mail, push, installations, push registrations
├── Services/         # Cross-cutting (UserService, AttachmentStorage, FeatureService, ApplicationCache)
├── Entities/         # Cross-cutting entities (User, Device, ...)
├── Settings/         # GlobalSettings + sub-objects
├── Tokens/           # Tokenable<T> base for signed payload tokens
├── Utilities/        # CoreHelpers, ServiceCollectionExtensions, StaticStore
└── Constants.cs

A typical domain folder looks like Entities/, Models/ (Request / Response / Data), Repositories/ (interfaces), Services/ (interfaces + implementations), Commands/, Queries/, Authorization/, plus a XxxServiceCollectionExtensions.cs to register everything.

Command / Query / Service split

The codebase is in mid-migration from "fat services" to a CQRS-ish pattern:

  • Old styleIUserService.SaveAsync(User user) does everything (validation, persistence, event logging, mail). Lives in src/Core/Services/Implementations/UserService.cs (~1,200 lines).
  • New style — narrow command / query interfaces. For example src/Core/Auth/UserFeatures/Registration/ contains commands like IRegisterUserCommand and RegisterUserCommand that injects only what they need. Tests are smaller and the dependency graph is explicit.

When in doubt, prefer the new pattern in new code. Don't refactor existing fat services in unrelated PRs.

Repository pattern

Every entity has an IXxxRepository interface and two implementations:

  • src/Infrastructure.Dapper/<Domain>/Repositories/XxxRepository.cs — calls T-SQL stored procedures via Dapper. The class extends Repository<TId, TEntity> (src/Infrastructure.Dapper/Repositories/Repository.cs).
  • src/Infrastructure.EntityFramework/<Domain>/Repositories/XxxRepository.cs — uses EF Core. Mapped via src/Infrastructure.EntityFramework/<Domain>/Models/XxxMapperProfile.cs.

Both are registered together in services.AddDatabaseRepositories(globalSettings). The implementation choice is determined by globalSettings.DatabaseProvider.

When you add a method, add it to both implementations and the interface, and write a test/Infrastructure.IntegrationTest/ test so all four database engines exercise it.

Authorization

Two patterns coexist:

  1. Policy-based — declared in src/Api/Startup.cs (Policies.Application, Policies.Web, Policies.Push, Policies.Organization, etc.), based on JWT scope claims. Apply with [Authorize(Policy = Policies.Web)].
  2. Resource-basedIAuthorizationRequirement + AuthorizationHandler<TReq, TResource>. The newer code uses this so authorization decisions can depend on the loaded resource (e.g. "the user must own this cipher" or "the requester must be an admin of this collection"). See src/Core/Vault/AuthorizationHandlers/ and src/Core/AdminConsole/Authorization/.

PR #6001 ("Move to authorization to attributes/handlers/requirements", 2025-07) is the migration from in-controller if-statements to the resource-based handler pattern.

Settings binding

Strongly-typed config is the rule:

  • The root object is GlobalSettings (src/Core/Settings/GlobalSettings.cs). Each sub-object (e.g. GlobalSettings.SqlSettings, GlobalSettings.StripeSettings) maps one section of appsettings.*.json.
  • Per-app overrides live next to the project (src/Api/appsettings.*.json). Bound with services.Configure<TOptions>(Configuration.GetSection(...)).
  • Sensitive values are read from environment variables, user-secrets, or Azure Key Vault, in that order, depending on the host. The Setup utility (util/Setup/) writes a global.override.env for self-host.

Never read from IConfiguration directly inside business logic; always inject IOptions<T> or GlobalSettings.

Errors and exceptions

  • BadRequestException, NotFoundException, UnauthorizedException, etc. live in src/Core/Exceptions/. Throw them from controllers / commands; the ApiExceptionFilterAttribute (src/Api/Utilities/ApiExceptionFilterAttribute.cs) maps them to HTTP responses with consistent shape { message, errorMessage, exceptionMessage, exceptionStackTrace, validationErrors }.
  • For billing-specific failures, use BillingException (src/Core/Billing/BillingException.cs).
  • The DI helper services.AddBaseServices registers the filter on every controller-based app.

Eventing

User-visible auditable actions go through IEventService.LogXxxEventAsync(...) (src/Core/Services/Implementations/EventService.cs). Internally, that either writes directly to storage (Cosmos DB / Azure Table) or publishes onto a queue that EventsProcessor drains. New event types are added to EventType (src/Core/Enums/EventType.cs).

For integration events (Slack/Teams/Webhook/HEC/Datadog), the EventIntegrationsCommandsQueriesServiceCollectionExtensions and src/Core/AdminConsole/Services/Implementations/EventIntegrations/ plug additional handlers onto the same event stream.

Naming

  • C# files use PascalCase matching the type they declare.
  • Tables and stored procedures use PascalCase (Cipher_ReadByUserId, not cipher_read_by_user_id).
  • Database columns are also PascalCase.
  • Constants/feature flags use camelCase string keys (e.g. "bulk-auto-confirm-on-login") but their C# constant is PascalCase (BulkAutoConfirmOnLogin).
  • Two-team file ownership uses **/Vault/AuthorizationHandlers @bitwarden/team-vault-dev @bitwarden/team-admin-console-dev style globs in .github/CODEOWNERS.

Security guardrails

  • Never log plaintext master passwords, master-password hashes, or user keys. The User.MasterPassword field has [MaxLength(300)] and is treated as sensitive.
  • Cipher data is encrypted client-side — server-side code routinely receives ciphertext blobs and must not attempt to decrypt them. The only legitimate decryption boundary on the server is in util/RustSdk/ for SDK-port code paths.
  • All file uploads run through IAttachmentStorageService and are size-checked against Constants.FileSize101mb / FileSize501mb.
  • SecurityHeadersMiddleware ensures CSP / HSTS / X-Frame-Options, etc. are emitted by every controller-based host.

Style

  • The repo enforces dotnet format defaults defined in .editorconfig (5,509 bytes, opinionated).
  • Tabs are 4 spaces; braces always on new lines.
  • using directives sit outside the namespace block (legacy style); newer projects increasingly use file-scoped namespaces.
  • Comments are encouraged for non-obvious business invariants; XML docs are required for public-API types.

For specifics on a domain, jump to that feature or system page.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Bitwarden Server wiki | Factory