Internal SDK
The Internal SDK is designed for internal use within Bitwarden and supports key functionality for managing encrypted data, vault access, and user authentication. Written in Rust, the SDK is versatile and provides bindings for a variety of platforms, including mobile clients (Kotlin and Swift) and web clients (JavaScript/TypeScript).
This section will provide guidance on developing with the SDK in a way that ensures compatibility across both mobile and web platforms. It will cover best practices for structuring code, addressing platform-specific challenges, and ensuring that your implementation works seamlessly across Bitwarden’s mobile and web applications.
Crate structure
The internal SDK is structured as a single
Git repository with multiple internal crates. This
document describes the general structure of the project. Please review the README in the
repository for information about the specific crates or implementation details.
Crates in the project fall into one of these categories.
- Bindings
- Application Interfaces
- Features
- Core and Utility
We generally strive towards extracting features into separate crates to keep the bitwarden-core
crate as lean as possible. This has multiple benefits such as faster compile-time and clear
ownership of features.
This hierarchy winds up producing a structure that looks like:
Prior to bitwarden/sdk-internal#468, the application interfaces had not been explicitly created.
Bindings
Bindings are those crates whose purpose is to provide bindings for other projects by targeting
wasm, iOS, and Android. The two mobile targets are built using UniFFI. See
below for more information.
Application Interfaces
An application interface collects the various features relevant for a given Bitwarden product, e.g. Password Manager, or Secrets Manager, into a single easy-to-use client for that particular product.
These clients, exposed through an external binding layer, are how consumers of the SDK will interact with it.
Core and Utility
The bitwarden-core crate contains the core runtime of the SDK. See the
crate documentation for
more details.
Features and Domains
Feature and domain crates constitute the application business logic. Feature crates depend on
bitwarden-core for their runtime and provide extensions to the Client struct to implement
specific domains.
The each feature or domain crate exposes its extended Client struct(s), which can be further
grouped into application interfaces for consumption. See the
VaultClient
as as example.
Client structure
One of the core concepts of our SDK is the "client". The client groups the SDK API surface into domain-specific bundles for easier instantiation and use by the consuming application.
There are two recommended approaches for structuring a client, depending on the size of the domain.
Single file
Define the client struct, its initialization, and all method impl blocks in one file. This
minimizes indirection and keeps related code easy to discover. Prefer this structure when the file
is manageable in size (~500 lines, including tests).
domain_client.rs
├── DomainClient struct definition and initialization
└── impl DomainClient with full method implementations and tests
Per-method files or subdirectories
When the single file would otherwise become unwieldy (~500 lines, including tests), the client definition should be split from individual method implementations.
Define the client struct in one file and each method in either its own file or its own subdirectory, depending on the implementation complexity.
When each method is self-contained and does not require supporting types alongside it, individual methods can be split into separate files.
domain/
├── domain_client.rs # DomainClient struct definition and initialization
├── mod.rs
├── method_name.rs # impl DomainClient { fn method_name() } and tests
└── other_method.rs # impl DomainClient { fn other_method() } and tests
For more complex clients, subdirectories can be used to contain the impl DomainClient block for
that method, its tests, and any supporting types.
domain/
├── domain_client.rs # DomainClient struct definition and initialization
├── mod.rs
└── method_name/
├── mod.rs
├── method_name.rs # impl DomainClient { fn method_name() } and tests
└── request.rs # supporting types (errors, etc.)
Avoid the thin passthrough pattern, where the client delegates to free functions defined elsewhere. This creates unnecessary indirection and splits documentation away from the API surface.
impl LoginClient {
// Avoid delegating the entire implementation to another function like this.
pub async fn login_with_password(&self, data: LoginData) -> Result<()> {
login_with_password(self.client, data).await
}
}
Language bindings
The internal SDK supports mobile and web platforms and uses UniFFI and wasm-bindgen to generate
bindings for those targets.
Mobile bindings
We use UniFFI to generate bindings for the mobile platforms, more specifically we publish Android and iOS libraries with Kotlin and Swift bindings, respectively. While UniFFI supports additional languages they typically lag a few releases behind the UniFFI core library.
The Android bindings are currently published on
GitHub Packages in the sdk_internal repository. The
Swift package is published in the sdk-swift repository.
Web bindings
For the web bindings we use wasm-bindgen to generate a
WebAssembly module that can be used in JavaScript / TypeScript. To ensure compatibility with
browsers that do not support WebAssembly, we also generate a JavaScript module from the WebAssembly
that can be used as a fallback.
The WebAssembly module is published on npm.