This API is in preview and will become the standard way to initialize Ditto instances in v5, replacing the legacy DittoIdentity-based initialization.
The Initialization Revamp introduces a new, more flexible way to configure and initialize Ditto instances using the DittoConfig structure and the Ditto.open() method.

Overview

The new initialization approach provides:
  • Cleaner, more intuitive API design
  • Better separation of concerns between identity and connectivity
  • Improved type safety and error handling
  • Support for both async and sync initialization patterns
  • Renamed terminology: “App ID” is now “Database ID” to better reflect its purpose

Terminology Changes in v5

App → Database: As part of v5, we’re renaming “App” to “Database” throughout the SDK to better reflect what these identifiers represent. Your Ditto instance connects to a specific database, not an application.
  • App IDDatabase ID
  • App (in portal) → Database
  • This change will be rolled out platform-wide Q4 2025.

Creating a new Ditto instance

Config and Open

DittoConfig encapsulates all the parameters required to configure a Ditto instance. Once you have a configuration, you can then pass that configuration to the Ditto.open() method to initialize a Ditto instance. The new Ditto.open(config) method is both asynchronous and fallible, meaning:
  • Asynchronous: The method returns a Promise/Future that must be awaited, allowing Ditto to perform initialization tasks without blocking your application
  • Fallible: Initialization can fail for various reasons, requiring proper error handling
public struct DittoConfig {
    /// The unique identifier for the Ditto database (previously "App ID")
    public var databaseID: String // Should be a UUID

    /// The connectivity configuration for this Ditto instance
    public var connect: DittoConfigConnect

    /// The persistence directory used by Ditto to persist data
    public var persistenceDirectory: URL?

    /// Configuration for experimental features
    public var experimental: DittoConfigExperimental
}

Connect

The DittoConfigConnect enum specifies how your Ditto instance discovers and connects to peers. There are two connection modes:
  • Server: Formerly known as OnlinePlayground or OnlineWithAuthentication
  • SmallPeersOnly: Formerly known as SharedKey or OfflinePlayground Authentication

Server Connection

Server Connection enables you to sync with all peers, both servers and other SDKs, using your HTTP URL Endpoint. This connection mode was formerly known as OnlinePlayground or OnlineWithAuthentication. These two authentication types have now been unified under one Server Connection, where you supply a token and a provider. Your database can be in development or authenticated mode. When in development mode, use the development provider. Otherwise, you can use your own customer authentication webhook provider.
// Your HTTP Cloud URL Endpoint
let config = DittoConfig(
    databaseID: "REPLACE_ME_WITH_YOUR_DATABASE_ID", // This was "appID" in v4
    connect: .server(url: URL(string: "REPLACE_ME_WITH_YOUR_URL")), // This was "Custom Auth URL" in v4
)

let ditto = try await Ditto.open(config: config)

// Set up authentication expiration handler (required for server connections)
ditto.auth?.expirationHandler = { [weak self] ditto, secondsRemaining in

    // Authenticate when token is expiring

    ditto.auth?.login(
        // Your development token, replace with your actual token
        token: "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN", 
        // Use .development or your actual provider name
        provider: .development 
    ) { clientInfo, error in
        if let error = error {
            print("Authentication failed: \(error)")
        } else {
            print("Authentication successful")
        }
    }
}

try ditto.sync.start()

Small Peers Only

Restrict connectivity to small peers only. Formerly known as SharedKey Authentication. The mechanism is no different, but the terminology has changed to better reflect its purpose.
// Without encryption
let config = DittoConfig(
    databaseID: "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
    connect: .smallPeersOnly(privateKey: nil) // add your OpenSSL key in production
)

Persistence Directory

The persistenceDirectory property accepts three types of values:
  • Absolute path: A URL with an absolute file path
  • Relative path: A URL with a relative file path, resolved relative to Ditto.defaultRootDirectory
  • nil: Uses a default directory named ditto-{database-id}, placed in Ditto.defaultRootDirectory
Important Change in v5: Directory Names Now Include Database ID
Breaking Change: In v5, the default persistence directory name includes the database ID. This is different from v4, which used a generic ditto directory for all apps.
Legacy Ditto initialization Behavior
// All apps share the same default directory name
let ditto = Ditto(identity: .onlinePlayground(
    appID: "f232511c-256b-4b69-8d28-90283bf715d2",
    token: "your-token"
))
// Creates directory: /app/sandbox/ditto/
Newly introduced Ditto.open() Behavior
//  Directory name includes the database ID
let config = DittoConfig(
    databaseID: "f232511c-256b-4b69-8d28-90283bf715d2",
    connect: .server(url: URL(string: "https://your-server.ditto.live")!)
)
// Creates directory: /app/sandbox/ditto-f232511c-256b-4b69-8d28-90283bf715d2/
Maintaining Compatibility To maintain compatibility with directory structure, explicitly set the persistence directory:
// For compatibility between the two methods, specify the old directory path
let config = DittoConfig(
    databaseID: "f232511c-256b-4b69-8d28-90283bf715d2",
    connect: .server(url: URL(string: "https://your-server.ditto.live")!),
    persistenceDirectory: URL(string: "/app/sandbox/ditto")!  // old path
)
Examples
// Absolute path
let config1 = DittoConfig(
    databaseID: "f232511c-256b-4b69-8d28-90283bf715d2",
    connect: .smallPeersOnly(),
    persistenceDirectory: URL(string: "/Users/me/DittoData")!
)

// Relative path
let config2 = DittoConfig(
    databaseID: "f232511c-256b-4b69-8d28-90283bf715d2",
    connect: .smallPeersOnly(),
    persistenceDirectory: URL(string: "MyApp/Data")!
)

// Default (nil) - NEW: includes database ID
let config3 = DittoConfig(
    databaseID: "f232511c-256b-4b69-8d28-90283bf715d2",
    connect: .smallPeersOnly(),
    persistenceDirectory: nil  // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
)
The new directory naming scheme allows multiple Ditto instances with different database IDs to coexist without conflicts, which wasn’t possible with the previous shared ditto directory approach.

API Usage

Async

Use the async initialization methods for non-blocking operations:
do {
    let ditto = try await Ditto.open(config: config)
    // Ditto instance is ready to use
} catch {
    print("Failed to initialize Ditto: \(error)")
}

Sync

Use the synchronous variant when async is not available or practical:
The synchronous variant blocks for a potentially long time during initialization and should be avoided when possible. Only use it in cases where longer blocking times are tolerable, such as command-line tools or initialization routines where blocking is acceptable.
do {
    let ditto = try Ditto.openSync(config: config)
    // Ditto instance is ready to use
} catch {
    print("Failed to initialize Ditto: \(error)")
}

Default Configuration

The default config by itself will not be able to synchronize data with other peers, but it is useful for testing. You can always use it as a starting place as a base configuration and modify it based on incoming parameters. The default configuration is the same as calling Ditto() in v4, but with the new DittoConfig structure.
// Use the default configuration as a starting point
let defaultConfig = DittoConfig.default

// Modify as needed
defaultConfig.databaseID = "38d4b612-e6ea-42f2-ae3e-f4cba92c918d",  // This was "appID" in v4
defaultConfig.connect = .server(url: URL(string: "https://my-server.ditto.live")!)

let ditto = try await Ditto.open(config: defaultConfig)

Error Handling

Common initialization failures include:
  • Persistence directory locked: Another Ditto instance is already using the specified directory
  • Invalid configuration: The provided DittoConfig contains invalid values
  • Missing required fields: Required configuration properties are not provided
The new initialization can throw several types of errors:
do {
    let ditto = try await Ditto.open(config: config)
} catch DittoSwiftError.storeError(.persistenceDirectoryLocked) {
    // Another Ditto instance is using this directory
} catch DittoSwiftError.validationError(.invalidDittoConfig) {
    // Configuration validation failed
} catch {
    // Other initialization failures
}

Best Practices

  1. Use async initialization when possible for better performance
  2. Create the Ditto instance once and keep it for the app’s lifetime rather than recreating it repeatedly
  3. Set authentication handlers immediately after initialization for server connections
  4. Handle errors gracefully and provide user feedback when initialization fails
  5. Use unique database IDs for different apps to avoid conflicts

Migration from Ditto Identity

When migrating from Ditto Identity to the new Ditto Config API, consider the following changes:
  • Initialization: Update your initialization code to use the new DittoConfig class.
  • Error Handling: Review and update error handling logic to accommodate new error types and structures.
  • Directory Structure: Ensure you set the persistenceDirectory to maintain compatibility.
  • Authentication Method: Uses a closure-based pattern instead of delegate pattern.

Online Playground

Instead of OnlinePlayground, now use DittoConfig in server connection mode.
  • Use DittoConfig with .server(url:) for server connections
  • Set the databaseID to your app’s unique identifier (UUID) (previously “App ID”)
  • Create an authentication handler closure to handle authentication events
  • Playground token is now called Development token
  • Websocket URL is no longer required
  • Remove updateTransportConfig method calls
let config = DittoConfig(
    databaseID: "REPLACE_ME_WITH_YOUR_DATABASE_ID", // This was "appID" in v4
    connect: .server(url: "REPLACE_ME_WITH_YOUR_URL")  
)

let ditto = try await Ditto.open(config: config)

// Set up authentication expiration handler (required for server connections)
ditto.auth?.expirationHandler = { [weak self] ditto, secondsRemaining in
    // Authenticate when token is expiring
    ditto.auth?.login(
        // Your development token, replace with your actual token
        token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN", 
        // Use .development or your actual provider name
        provider: .development 
    ) { clientInfo, error in
        if let error = error {
            print("Authentication failed: \(error)")
        } else {
            print("Authentication successful")
        }
    }
}

do {
  try ditto.sync.start()
} catch {
  print(error.localizedDescription)
}

Authentication Lifecycle

All authentication methods are backwards compatible with the new Ditto Config API. To simplify code, you no longer need to implement an extra authentication object. For authentication lifecycle hooks, you can now collapse authenticationRequired and authenticationExpiringSoon into a single closure.
ditto.auth?.expirationHandler = { [weak self] ditto, secondsRemaining in
    ditto.auth?.login(token: myJWT, provider: "my-auth-provider") { clientInfo, error in
        if let error = error {
            print("Authentication failed: \(error)")
        } else {
            print("Authentication successful")
        }
    }
}
If you need different behavior based on timing of the request, you can condition on secondsRemaining == 0 in the closure-based handler. This is optional.
ditto.auth?.expirationHandler = { [weak self] ditto, secondsRemaining in
    if secondsRemaining == 0 {
        // Do what you'd do in `authenticationRequired`
    } else {
        // Do what you'd do in `authenticationExpiringSoon` 
    }
}

SharedKey & Offline Playground

The migration is straightforward - these are renamed:
  • SharedKey becomes SmallPeersOnly(privateKey: "MY_PRIVATE_KEY")
  • OfflinePlayground becomes SmallPeersOnly(privateKey: nil)
  • Ditto() becomes DittoConfig.default

Manual Identity

Manual Identity is deprecated.