> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# DittoConfig

> New configuration-based initialization for Ditto instances

<Callout icon="circle-info" color="#3B82F6" iconType="regular">
  This content is for SDK V4. For the latest version, see the [V5 documentation](/sdk/latest).
</Callout>

<Warning>
  This API is in **preview** and will become the standard way to initialize Ditto instances in v5, replacing the legacy `DittoIdentity`-based initialization.
</Warning>

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

<Info>
  **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 ID** → **Database ID**
  * **App** (in portal) → **Database**
  * This change will be rolled out platform-wide Q4 2025.
</Info>

## 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

<CodeGroup>
  ```swift Swift theme={null}
  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
  }
  ```

  ```typescript JavaScript theme={null}
  interface DittoConfig {
      // The unique identifier for the Ditto database (previously "App ID")
      databaseID: string;

      // The connectivity configuration for this Ditto instance
      connect: DittoConfigConnect;

      // The persistence directory used by Ditto to persist data
      persistenceDirectory?: string;

      // Configuration for experimental features
      experimental?: DittoConfigExperimental;
  }
  ```

  ```kotlin Kotlin theme={null}
  data class DittoAndroidConfig(
      val context: Context,
      // The unique identifier for the Ditto database (previously "App ID")
      val databaseId: String,

      // The connectivity configuration for this Ditto instance
      val connect: DittoConfigConnect,

      // The persistence directory used by Ditto to persist data
      val persistenceDirectory: String? = null,

      // Configuration for experimental features
      val experimental: DittoConfigExperimental = DittoConfigExperimental()
  )
  ```

  ```java Java theme={null}
  public class DittoAndroidConfig {
      private final Context context;
      // The unique identifier for the Ditto database (previously "App ID")
      private final String databaseId;

      // The connectivity configuration for this Ditto instance
      private final DittoConfigConnect connect;

      // The persistence directory used by Ditto to persist data
      private final String persistenceDirectory;

      // Configuration for experimental features
      private final DittoConfigExperimental experimental;
  }
  ```

  ```csharp C# theme={null}
  public class DittoConfig
  {
      /// <summary>
      /// The unique identifier for the Ditto database (previously "App ID")
      /// </summary>
      public string DatabaseId { get; set; }

      /// <summary>
      /// The connectivity configuration for this Ditto instance
      /// </summary>
      public DittoConfigConnect Connect { get; set; }

      /// <summary>
      /// The persistence directory used by Ditto to persist data
      /// </summary>
      public string PersistenceDirectory { get; set; }

      /// <summary>
      /// Configuration for experimental features
      /// </summary>
      public DittoConfigExperimental Experimental { get; set; }
  }
  ```

  ```cpp C++ theme={null}
  class DittoConfig {
  public:
      // The unique identifier for the Ditto database (previously "App ID")
      std::string database_id;

      // The connectivity configuration for this Ditto instance
      Connect connect;

      // The persistence directory used by Ditto to persist data
      std::string persistence_directory;

      // Configuration for experimental features
      Experimental experimental;

      // Builder methods
      DittoConfig& set_database_id(const std::string& id);
      DittoConfig& set_connect(const Connect& conn);
      DittoConfig& set_persistence_directory(const std::string& dir);
      DittoConfig& set_experimental(const Experimental& exp);
  };
  ```

  ```rust Rust theme={null}
  pub struct DittoConfig {
      // The unique identifier for the Ditto database (previously "App ID")
      pub database_id: String,

      // The connectivity configuration for this Ditto instance
      pub connect: DittoConfigConnect,

      // The persistence directory used by Ditto to persist data
      pub persistence_directory: Option<PathBuf>,

      // Configuration for experimental features
      pub experimental: DittoConfigExperimental,
  }

  impl DittoConfig {
      // Constructor
      pub fn new(database_id: impl Into<String>, connect: DittoConfigConnect) -> Self;

      // Builder method for persistence directory
      pub fn with_persistence_directory(self, path: impl Into<PathBuf>) -> Self;
  }
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```

  ```go Go theme={null}
  type DittoConfig struct {
  	// DatabaseID is the unique identifier for the Ditto database.
  	DatabaseID string `cbor:"database_id" json:"databaseId"`

  	// Connect specifies how this instance discovers and connects to peers.
  	Connect DittoConfigConnectUnion `cbor:"connect" json:"connect"`

  	// PersistenceDirectory specifies where Ditto should persist data.
  	PersistenceDirectory string `cbor:"persistence_directory,omitempty" json:"persistenceDirectory,omitempty"`

  	// Experimental allows setting configuration for experimental features.
  	Experimental map[string]any `cbor:"experimental" json:"experimental"`
  }
  ```
</CodeGroup>

### 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 Cloud 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.

<CodeGroup>
  ```swift Swift theme={null}
  // 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()
  ```

  ```typescript JavaScript theme={null}
  // Your HTTP Cloud URL Endpoint
  const authURL = "REPLACE_ME_WITH_YOUR_URL";
  const databaseId = "REPLACE_ME_WITH_YOUR_DATABASE_ID";

  const config = new DittoConfig(
      databaseId, // This was "appID" in v4
      {
          mode: 'server',
          url: authURL
      }
  );

  const ditto = await Ditto.open(config);

  // Set up authentication expiration handler (required for server connections)
  await ditto.auth.setExpirationHandler(async (ditto, secondsRemaining) => {
      // Authenticate when token is expiring. Any errors will be logged in the Ditto logger.
      await ditto.auth.login(
          // Your development token, replace with your actual token
          "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN",
          // Use Authenticator.DEVELOPMENT_PROVIDER for playground, or your actual provider name
          Authenticator.DEVELOPMENT_PROVIDER
      );
      console.log("Authentication successful");
  });

  ditto.sync.start();
  ```

  ```kotlin Kotlin theme={null}
  // Your HTTP Cloud URL Endpoint
  val endpoint = "REPLACE_ME_WITH_YOUR_URL"
  val id = "REPLACE_ME_WITH_YOUR_DATABASE_ID"

  val config = DittoAndroidConfig(
      context,
      databaseId = id, // This was "appID" in v4
      connect = DittoConfigConnect.Server(url = URL(endpoint))
  )

  val ditto = Ditto.open(config)

  // Set up authentication expiration handler (required for server connections)
  ditto.auth.expirationHandler = { ditto, secondsRemaining ->
      // Authenticate when token is expiring
      ditto.auth.login(
          // Your development token, replace with your actual token
          token = "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN",
          // Use DittoAuthenticationProvider.development() for playground
          provider = DittoAuthenticationProvider.development()
      ) { clientInfo, error ->
          if (error != null) {
              println("Authentication failed: $error")
          } else {
              println("Authentication successful")
          }
      }
  }

  ditto.startSync()
  ```

  ```java Java theme={null}
  import com.ditto.java.Ditto;
  import com.ditto.java.DittoFactory;
  import com.ditto.java.DittoConfig;
  import com.ditto.java.DittoAuthenticationProvider;

  // Your HTTP Cloud URL Endpoint
  String endpoint = "REPLACE_ME_WITH_YOUR_URL";
  String databaseId = "REPLACE_ME_WITH_YOUR_DATABASE_ID";

  DittoConfig config = new DittoConfig.Builder(databaseId)
      .connect(new DittoConfig.Connect.Server(endpoint))
      .build();

  Ditto ditto = DittoFactory.create(config);

  // Set up authentication expiration handler (required for server connections)
  ditto.getAuth().setExpirationHandler((expiringDitto, timeUntilExpiration) -> {
      // Authenticate when token is expiring
      expiringDitto.getAuth().login(
          "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN",
          DittoAuthenticationProvider.development()
      ).thenAccept(clientInfo -> {
          System.out.println("Authentication successful");
      }).exceptionally(error -> {
          System.out.println("Authentication failed: " + error);
          return null;
      });
  });

  ditto.startSync();
  ```

  ```csharp C# theme={null}
  // Your HTTP Cloud URL Endpoint
  var endpoint = "REPLACE_ME_WITH_YOUR_URL";
  var id = "REPLACE_ME_WITH_YOUR_DATABASE_ID";

  var config = new DittoConfig(
      databaseId: id, // This was "appID" in v4
      connect: new DittoConfigConnect.Server(new Uri(endpoint))
  );

  var ditto = await Ditto.OpenAsync(config);

  // Set up authentication expiration handler (required for server connections)
  ditto.Auth.ExpirationHandler = async (ditto, secondsRemaining) =>
  {
      // Authenticate when token is expiring
      try
      {
          await ditto.Auth.LoginAsync(
              // Your development token, replace with your actual token
              "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN",
              // Use DittoAuthenticationProvider.Development for playground, or your actual provider
              DittoAuthenticationProvider.Development
          );
          Console.WriteLine("Authentication successful");
      }
      catch (Exception error)
      {
          Console.WriteLine($"Authentication failed: {error}");
      }
  };

  ditto.StartSync();
  ```

  ```cpp C++ theme={null}
  // Your HTTP Cloud URL Endpoint
  std::string endpoint = "REPLACE_ME_WITH_YOUR_URL";
  std::string id = "REPLACE_ME_WITH_YOUR_DATABASE_ID";

  auto config = DittoConfig::default_config()
      .set_database_id(id) // This was "appID" in v4
      .set_connect(DittoConfig::Connect::server(endpoint));

  auto ditto = Ditto::open(config);

  // Set up authentication expiration handler (required for server connections)
  ditto->auth().set_expiration_handler([](auto& ditto, int64_t seconds_remaining) {
      // Authenticate when token is expiring
      ditto.auth().login(
          // Your development token, replace with your actual token
          "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN",
          // Use Authenticator::get_development_provider() for playground
          Authenticator::get_development_provider(),
          [](auto client_info, auto error) {
              if (error) {
                  std::cout << "Authentication failed: " << error->message() << std::endl;
              } else {
                  std::cout << "Authentication successful" << std::endl;
              }
          }
      );
  });

  ditto->start_sync();
  ```

  ```rust Rust theme={null}
  use dittolive_ditto::prelude::*;
  use dittolive_ditto::identity::get_development_provider;
  use std::time::Duration;

  // Your HTTP Cloud URL Endpoint and Database ID
  let config = DittoConfig::new(
      "REPLACE_ME_WITH_YOUR_DATABASE_ID", // This was "app_id" in v4
      DittoConfigConnect::Server {
          url: "REPLACE_ME_WITH_YOUR_URL".parse().unwrap(),
      },
  );

  // Initialize Ditto
  let ditto = Ditto::open_sync(config)?;

  // Set up authentication expiration handler (required for Server connections)
  let auth = ditto.auth().expect("Auth is available in Server mode");
  auth.set_expiration_handler(async |ditto: &Ditto, _duration: Duration| {
      if let Some(auth) = ditto.auth() {
          // Fetch your token from your auth server
          let token = "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN";
          let provider = get_development_provider();
          match auth.login(token, &provider) {
              Ok(_) => println!("Authentication successful"),
              Err(e) => println!("Authentication failed: {}", e),
          }
      } else {
          eprintln!("Ditto authentication not configured");
      }
  });

  ditto.sync().start()?;
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy `DittoIdentity`-based API for Flutter applications.
  ```

  ```go Go theme={null}
  const endpoint = "REPLACE_ME_WITH_YOUR_URL"
  const id       = "REPLACE_ME_WITH_YOUR_DATABASE_ID"
  const token    = "REPLACE_ME_WITH_YOUR_DEVELOPMENT_TOKEN"

  config := ditto.DefaultDittoConfig().
      WithDatabaseID(id).
      WithConnect(&ditto.DittoConfigConnectServer{URL: endpoint})

  dit, err := ditto.Open(config)
  if err != nil {
      log.Fatalf("error: failed to open Ditto: %v", err)
  }
  defer dit.Close()

  // Set up authentication expiration handler (required for server connections)
  dit.Auth().SetExpirationHandler(
      func(d *ditto.Ditto, timeUntilExpiration time.Duration) {
          _, err := d.Auth().Login(token, ditto.DevelopmentAuthenticationProvider())
          if err != nil {
              log.Errorf("Authentication failed: %v", err)
          } else {
              log.Printf("Authentication succeeded")
          }
      })

  if err := dit.Sync().Start(); err != nil {
      log.Errorf("error: failed to start sync: %v", err)
  }
  ```
</CodeGroup>

### Small Peers Only

Restrict connectivity to small peers only. Formerly known as [SharedKey Authentication](/key-concepts/authentication-and-authorization#offline-shared-key). The mechanism is no different, but the terminology has changed to
better reflect its purpose.

<CodeGroup>
  ```swift Swift theme={null}
  // Without encryption
  let config = DittoConfig(
      databaseID: "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
      connect: .smallPeersOnly(privateKey: nil) // add your OpenSSL key in production
  )
  ```

  ```typescript JavaScript theme={null}
  // Without encryption
  const config = new DittoConfig(
      "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
      {
          type: 'smallPeersOnly',
          privateKey: undefined // add your OpenSSL key in production
      }
  );
  ```

  ```kotlin Kotlin theme={null}
  // Without encryption
  val config = DittoAndroidConfig(
      context,
      databaseId = "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
      connect = DittoConfigConnect.SmallPeersOnly(
          privateKey = null // add your OpenSSL key for e2e encryption
      )
  )
  ```

  ```java Java theme={null}
  // Without encryption
  DittoAndroidConfig config = new DittoAndroidConfig(
      context,
      "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
      new DittoConfigConnect.SmallPeersOnly(
          null // add your OpenSSL key in production
      )
  );
  ```

  ```csharp C# theme={null}
  // Without encryption
  var config = new DittoConfig(
      databaseId: "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
      connect: new DittoConfigConnect.SmallPeersOnly(
          privateKey: null // add your OpenSSL key in production
      )
  );
  ```

  ```cpp C++ theme={null}
  // Without encryption
  auto config = DittoConfig::default_config()
      .set_database_id("38d4b612-e6ea-42f2-ae3e-f4cba92c918")
      .set_connect(DittoConfig::Connect::small_peers_only(
          std::nullopt // add your OpenSSL key in production
      ));
  ```

  ```rust Rust theme={null}
  // Without encryption
  let config = DittoConfig::new(
      "38d4b612-e6ea-42f2-ae3e-f4cba92c918",
      DittoConfigConnect::SmallPeersOnly {
          private_key: None, // add your OpenSSL key in production
      },
  );
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```
</CodeGroup>

## 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**

<Warning>
  **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.
</Warning>

**Legacy `Ditto` initialization Behavior**

```swift theme={null}
// 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**

```swift theme={null}
//  Directory name includes the database ID
let config = DittoConfig(
    databaseID: "f232511c-256b-4b69-8d28-90283bf715d2",
    connect: .server(url: URL(string: "REPLACE_ME_WITH_YOUR_URL")!)
)
// Creates directory: /app/sandbox/ditto-f232511c-256b-4b69-8d28-90283bf715d2/
```

**Maintaining Compatibility**

To maintain compatibility with directory structure, explicitly set the persistence directory:

```swift theme={null}
// 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: "REPLACE_ME_WITH_YOUR_URL")!),
    persistenceDirectory: URL(string: "/app/sandbox/ditto")!  // old path
)
```

**Examples**

<CodeGroup>
  ```swift Swift theme={null}
  // 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
  )
  ```

  ```typescript JavaScript theme={null}
  // Absolute path
  const config1 = new DittoConfig(
      "f232511c-256b-4b69-8d28-90283bf715d2",
      { type: 'smallPeersOnly' },
      "/Users/me/DittoData"
  );

  // Relative path
  const config2 = new DittoConfig(
      "f232511c-256b-4b69-8d28-90283bf715d2",
      { type: 'smallPeersOnly' },
      "MyApp/Data"
  );

  // Default (undefined) - NEW: includes database ID
  const config3 = new DittoConfig(
      "f232511c-256b-4b69-8d28-90283bf715d2",
      { type: 'smallPeersOnly' }
      // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
  );
  ```

  ```kotlin Kotlin theme={null}
  // Absolute path
  val config1 = DittoAndroidConfig(
      context,
      databaseId = "f232511c-256b-4b69-8d28-90283bf715d2",
      connect = DittoConfigConnect.SmallPeersOnly(),
      persistenceDirectory = "/data/data/com.myapp/DittoData"
  )

  // Relative path
  val config2 = DittoAndroidConfig(
      context,
      databaseId = "f232511c-256b-4b69-8d28-90283bf715d2",
      connect = DittoConfigConnect.SmallPeersOnly(),
      persistenceDirectory = "MyApp/Data"
  )

  // Default (null) - NEW: includes database ID
  val config3 = DittoAndroidConfig(
      context,
      databaseId = "f232511c-256b-4b69-8d28-90283bf715d2",
      connect = DittoConfigConnect.SmallPeersOnly()
      // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
  )
  ```

  ```java Java theme={null}
  // Absolute path
  DittoAndroidConfig config1 = new DittoAndroidConfig(
      context,
      "f232511c-256b-4b69-8d28-90283bf715d2",
      new DittoConfigConnect.SmallPeersOnly(),
      "/data/data/com.myapp/DittoData"
  );

  // Relative path
  DittoAndroidConfig config2 = new DittoAndroidConfig(
      context,
      "f232511c-256b-4b69-8d28-90283bf715d2",
      new DittoConfigConnect.SmallPeersOnly(),
      "MyApp/Data"
  );

  // Default (null) - NEW: includes database ID
  DittoAndroidConfig config3 = new DittoAndroidConfig(
      context,
      "f232511c-256b-4b69-8d28-90283bf715d2",
      new DittoConfigConnect.SmallPeersOnly()
      // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
  );
  ```

  ```csharp C# theme={null}
  // Absolute path
  var config1 = new DittoConfig(
      databaseId: "f232511c-256b-4b69-8d28-90283bf715d2",
      connect: new DittoConfigConnect.SmallPeersOnly(),
      persistenceDirectory: "/Users/me/DittoData"
  );

  // Relative path
  var config2 = new DittoConfig(
      databaseId: "f232511c-256b-4b69-8d28-90283bf715d2",
      connect: new DittoConfigConnect.SmallPeersOnly(),
      persistenceDirectory: "MyApp/Data"
  );

  // Default (null) - NEW: includes database ID
  var config3 = new DittoConfig(
      databaseId: "f232511c-256b-4b69-8d28-90283bf715d2",
      connect: new DittoConfigConnect.SmallPeersOnly()
      // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
  );
  ```

  ```cpp C++ theme={null}
  // Absolute path
  auto config1 = DittoConfig::default_config()
      .set_database_id("f232511c-256b-4b69-8d28-90283bf715d2")
      .set_connect(DittoConfig::Connect::small_peers_only())
      .set_persistence_directory("/Users/me/DittoData");

  // Relative path
  auto config2 = DittoConfig::default_config()
      .set_database_id("f232511c-256b-4b69-8d28-90283bf715d2")
      .set_connect(DittoConfig::Connect::small_peers_only())
      .set_persistence_directory("MyApp/Data");

  // Default (empty string) - NEW: includes database ID
  auto config3 = DittoConfig::default_config()
      .set_database_id("f232511c-256b-4b69-8d28-90283bf715d2")
      .set_connect(DittoConfig::Connect::small_peers_only());
      // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
  ```

  ```rust Rust theme={null}
  // Absolute path
  let config1 = DittoConfig::new(
      "f232511c-256b-4b69-8d28-90283bf715d2",
      DittoConfigConnect::SmallPeersOnly { private_key: None },
  )
  .with_persistence_directory("/Users/me/DittoData");

  // Relative path
  let config2 = DittoConfig::new(
      "f232511c-256b-4b69-8d28-90283bf715d2",
      DittoConfigConnect::SmallPeersOnly { private_key: None },
  )
  .with_persistence_directory("MyApp/Data");

  // Default (None) - NEW: includes database ID
  let config3 = DittoConfig::new(
      "f232511c-256b-4b69-8d28-90283bf715d2",
      DittoConfigConnect::SmallPeersOnly { private_key: None },
  );
  // Will use "ditto-f232511c-256b-4b69-8d28-90283bf715d2" as default directory
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```
</CodeGroup>

<Tip>
  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.
</Tip>

## API Usage

### Async

Use the async initialization methods for non-blocking operations:

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let ditto = try await Ditto.open(config: config)
      // Ditto instance is ready to use
  } catch {
      print("Failed to initialize Ditto: \(error)")
  }
  ```

  ```typescript JavaScript theme={null}
  try {
      const ditto = await Ditto.open(config);
      // Ditto instance is ready to use
  } catch (error) {
      console.error("Failed to initialize Ditto:", error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val ditto = Ditto.open(config)
      // Ditto instance is ready to use
  } catch (error: DittoError) {
      println("Failed to initialize Ditto: $error")
  }
  ```

  ```java Java theme={null}
  try {
     Ditto ditto = Ditto.openSync(config);
     // Ditto instance is ready to use
  } catch (DittoError error) {
      System.err.println("Failed to initialize Ditto: " + error);
  }
  ```

  ```csharp C# theme={null}
  try
  {
       // Note: In .NET, OpenAsync() is the async variant, Open() is synchronous
      var ditto = await Ditto.OpenAsync(config);
      // Ditto instance is ready to use
  }
  catch (Exception error)
  {
      Console.WriteLine($"Failed to initialize Ditto: {error}");
  }
  ```

  ```cpp C++ theme={null}
  auto future = Ditto::open_async(config);
  future.then([](auto ditto) {
      // Ditto instance is ready to use
  }).on_error([](auto error) {
      std::cerr << "Failed to initialize Ditto: " << error.message() << std::endl;
  });
  ```

  ```rust Rust theme={null}
  match Ditto::open(config).await {
      Ok(ditto) => {
          // Ditto instance is ready to use
      }
      Err(error) => {
          eprintln!("Failed to initialize Ditto: {}", error);
      }
  }
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```

  ```go Go theme={null}
  // The Go SDK does not provide a non-blocking Open(). For asynchronous
  // initialization, run the blocking Open() in a goroutine and get the
  // result via a channel or other mechanism.
  type openResult struct {
      ditto *ditto.Ditto
      err   error
  }
  resultCh := make(chan, openResult, 1)
  go func() {
      dit, err := ditto.Open(config)
      resultCh <- openResult{ditto: dit, err: err}
  }()

  result := <-resultCh
  if result.err != nil {
      log.Errorf("error: Failed to initialize Ditto: %v", result.err)
      return result.err
  }
  dit := result.ditto
  ```
</CodeGroup>

### Sync

Use the synchronous variant when async is not available or practical:

<Warning>
  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.
</Warning>

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let ditto = try Ditto.openSync(config: config)
      // Ditto instance is ready to use
  } catch {
      print("Failed to initialize Ditto: \(error)")
  }
  ```

  ```typescript JavaScript theme={null}
  // JavaScript doesn't have a sync version - use await in an async function
  ```

  ```kotlin Kotlin theme={null}
  try {
      val ditto = Ditto.openSync(config)
      // Ditto instance is ready to use
  } catch (error: DittoError) {
      println("Failed to initialize Ditto: $e")
  }
  ```

  ```java Java theme={null}
  try {
      Ditto ditto = Ditto.openSync(config);
      // Ditto instance is ready to use
  } catch (DittoError error) {
      System.err.println("Failed to initialize Ditto: " + error);
  }
  ```

  ```csharp C# theme={null}
  try
  {
      // Note: In .NET, Open() is the synchronous variant, OpenAsync() is async
      var ditto = Ditto.Open(config);
      // Ditto instance is ready to use
  }
  catch (Exception error)
  {
      Console.WriteLine($"Failed to initialize Ditto: {error}");
  }
  ```

  ```cpp C++ theme={null}
  try {
      auto ditto = Ditto::open(config);
      // Ditto instance is ready to use
  } catch (const std::exception& error) {
      std::cerr << "Failed to initialize Ditto: " << error.what() << std::endl;
  }
  ```

  ```rust Rust theme={null}
  match Ditto::open_sync(config) {
      Ok(ditto) => {
          // Ditto instance is ready to use
      }
      Err(error) => {
          eprintln!("Failed to initialize Ditto: {}", error);
      }
  }
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```

  ```go Go theme={null}
  dit, err := ditto.Open(config)
  if err != nil {
      log.Errorf("error: failed to open Ditto: %v", err)
  }
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```swift Swift theme={null}
  // 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: "REPLACE_ME_WITH_YOUR_URL")!)

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

  ```typescript JavaScript theme={null}
  // Use the default configuration as a starting point
  const defaultConfig = DittoConfig.default;

  // Create a new config with modifications
  const customConfig = new DittoConfig(
      "38d4b612-e6ea-42f2-ae3e-f4cba92c918d",  // This was "appID" in v4
      {
          type: 'server',
          url: 'REPLACE_ME_WITH_YOUR_URL'
      },
      defaultConfig.persistenceDirectory
  );

  const ditto = await Ditto.open(customConfig);
  ```

  ```kotlin Kotlin theme={null}
  // Android doesn't have a default config - must provide context
  val config = DittoAndroidConfig(
      context,
      databaseId = "38d4b612-e6ea-42f2-ae3e-f4cba92c918d",  // This was "appID" in v4
      connect = DittoConfigConnect.Server(
          url = URI.create("REPLACE_ME_WITH_YOUR_URL")
      )
  )

  val ditto = Ditto.open(config)
  ```

  ```java Java theme={null}
  // Android doesn't have a default config - must provide context
  DittoAndroidConfig config = new DittoAndroidConfig(
      context,
      "38d4b612-e6ea-42f2-ae3e-f4cba92c918d",  // This was "appID" in v4
      new DittoConfigConnect.Server(
          URI.create("REPLACE_ME_WITH_YOUR_URL")
      )
  );

  Ditto ditto = Ditto.open(config);
  ```

  ```csharp C# theme={null}
  // Use the default configuration as a starting point
  var defaultConfig = DittoConfig.Default;

  // Create a new config with modifications
  var customConfig = new DittoConfig(
      databaseId: "38d4b612-e6ea-42f2-ae3e-f4cba92c918d",  // This was "appID" in v4
      connect: new DittoConfigConnect.Server(
          new Uri("REPLACE_ME_WITH_YOUR_URL")
      ),
      persistenceDirectory: defaultConfig.PersistenceDirectory
  );

  var ditto = await Ditto.OpenAsync(customConfig);
  ```

  ```cpp C++ theme={null}
  // Use the default configuration as a starting point
  auto customConfig = DittoConfig::default_config()
      .set_database_id("38d4b612-e6ea-42f2-ae3e-f4cba92c918d")  // This was "appID" in v4
      .set_connect(DittoConfig::Connect::server(
          "REPLACE_ME_WITH_YOUR_URL"
      ));

  auto ditto = Ditto::open(customConfig);
  ```

  ```rust Rust theme={null}
  // Create a new configuration
  let custom_config = DittoConfig::new(
      "38d4b612-e6ea-42f2-ae3e-f4cba92c918d",  // This was "appID" in v4
      DittoConfigConnect::Server {
          url: "REPLACE_ME_WITH_YOUR_URL".parse().unwrap(),
      },
  );

  let ditto = Ditto::open_sync(custom_config)?;
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```

  ```go Go theme={null}
  config := ditto.DefaultDittoConfig().
      WithDatabaseID("38d4b612-e6ea-42f2-ae3e-f4cba92c918d").
      WithConnect(&ditto.DittoConfigConnectServer{URL: "REPLACE_ME_WITH_YOUR_URL"})
  ```
</CodeGroup>

### 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:

<CodeGroup>
  ```swift Swift theme={null}
  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
  }
  ```

  ```typescript JavaScript theme={null}
  try {
      const ditto = await Ditto.open(config);
  } catch (error) {
      if (error.code === 'store/persistence-directory-locked') {
          // Another Ditto instance is using this directory
      } else if (error.code === 'validation/invalid-ditto-config') {
          // Configuration validation failed
      } else {
          // Other initialization failures
      }
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val ditto = Ditto.open(config)
  } catch (e: DittoError.StoreError) {
      when (e.reason) {
          is DittoError.StoreErrorReason.PersistenceDirectoryLocked -> {
              // Another Ditto instance is using this directory
          }
          else -> {
              // Other store errors
          }
      }
  } catch (e: DittoError.ValidationError) {
      when (e.reason) {
          is DittoError.ValidationErrorReason.InvalidDittoConfig -> {
              // Configuration validation failed
          }
          else -> {
              // Other validation errors
          }
      }
  } catch (e: Exception) {
      // Other initialization failures
  }
  ```

  ```java Java theme={null}
  try (Ditto ditto = Ditto.openSync(config)) {
      // Ditto is auto-closed by try-with-resources block
  } catch (DittoError.StoreError storeError) {
      if (storeError.getReason() instanceof DittoError.StoreErrorReason.PersistenceDirectoryLocked) {
          // Another Ditto instance is using this directory
      }
  } catch (DittoError.ValidationError validationError) {
      if (validationError.getReason() instanceof DittoError.ValidationErrorReason.InvalidDittoConfig) {
          // Configuration validation failed
      }
  } catch (DittoError error) {
      // Other initialization failures
  }
  ```

  ```csharp C# theme={null}
  try
  {
      var ditto = await Ditto.OpenAsync(config);
  }
  catch (DittoStoreException ex) when (ex.Reason is DittoStoreException.PersistenceDirectoryLocked)
  {
      // Another Ditto instance is using this directory
  }
  catch (DittoValidationException ex) when (ex.Reason is DittoValidationException.InvalidDittoConfig)
  {
      // Configuration validation failed
  }
  catch (Exception ex)
  {
      // Other initialization failures
  }
  ```

  ```cpp C++ theme={null}
  try {
      auto ditto = Ditto::open(config);
  } catch (const StoreError& e) {
      if (e.reason() == StoreError::Reason::persistence_directory_locked) {
          // Another Ditto instance is using this directory
      }
  } catch (const ValidationError& e) {
      if (e.reason() == ValidationError::Reason::invalid_ditto_config) {
          // Configuration validation failed
      }
  } catch (const std::exception& e) {
      // Other initialization failures
  }
  ```

  ```rust Rust theme={null}
  match Ditto::open_sync(config) {
      Ok(ditto) => {
          // Success - Ditto instance is ready to use
      }
      Err(error) => {
          // Handle error based on error message or type
          eprintln!("Failed to initialize Ditto: {}", error);

          // You can check specific error conditions:
          // - Persistence directory locked
          // - Invalid configuration
          // - Other initialization failures
      }
  }
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```

  ```go Go theme={null}
  dit, err := ditto.Open(config)
  if err != nil {
      log.Errorf("error: failed to open Ditto: %v", err)
  } else {
      // Success - Ditto instance is ready to use
  }
  ```
</CodeGroup>

## 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

<CodeGroup>
  ```swift v5 theme={null}
  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")!)
  )

  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)
  }
  ```

  ```swift v4 theme={null}
  ditto = Ditto(
    identity: DittoIdentity.onlinePlayground(
      appID: "REPLACE_ME_WITH_YOUR_APP_ID",
      token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
      enableDittoCloudSync: false, // This is required to be set to false to use the correct URLs
      customAuthURL: URL(string: "REPLACE_ME_WITH_YOUR_AUTH_URL")
    )
  )

  ditto.updateTransportConfig { transportConfig in
    // Set the Ditto Websocket URL
    transportConfig.connect.webSocketURLs.insert("wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL")
  }

  // Disable DQL strict mode so that collection definitions are not required in DQL queries
  try await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")

  do {
    try ditto.startSync()
  } catch {
    print(error.localizedDescription)
  }
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```swift Swift theme={null}
  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")
          }
      }
  }
  ```

  ```typescript JavaScript theme={null}
  ditto.auth.setExpirationHandler(async (ditto, secondsRemaining) => {
      try {
          await ditto.auth.login(myJWT, "my-auth-provider");
          console.log("Authentication successful");
      } catch (error) {
          console.error("Authentication failed:", error);
      }
  });
  ```

  ```kotlin Kotlin theme={null}
  ditto.auth?.expirationHandler = { ditto, secondsRemaining ->
      ditto.auth?.login(
          token = myJWT,
          provider = "my-auth-provider"
      ) { clientInfo, error ->
          if (error != null) {
              println("Authentication failed: $error")
          } else {
              println("Authentication successful")
          }
      }
  }
  ```

  ```java Java theme={null}
  ditto.auth.setExpirationHandler((dit, secondsRemaining) -> {
      dit.auth.login(
          myJWT,
          "my-auth-provider",
          new DittoLoginCallback() {
              public void callback(String clientInfo, DittoError error) {
                  if (error != null) {
                      System.out.println("Authentication failed: " + error);
                  } else {
                      System.out.println("Authentication successful");
                  }
              }
          }
      );
  });
  ```

  ```csharp C# theme={null}
  ditto.Auth.ExpirationHandler = async (ditto, secondsRemaining) =>
  {
      try
      {
          await ditto.Auth.LoginAsync(myJWT, "my-auth-provider");
          Console.WriteLine("Authentication successful");
      }
      catch (Exception error)
      {
          Console.WriteLine($"Authentication failed: {error}");
      }
  };
  ```

  ```cpp C++ theme={null}
  ditto->auth().set_expiration_handler([](auto& ditto, int64_t seconds_remaining) {
      ditto.auth().login(
          myJWT,
          "my-auth-provider",
          [](auto client_info, auto error) {
              if (error) {
                  std::cout << "Authentication failed: " << error->message() << std::endl;
              } else {
                  std::cout << "Authentication successful" << std::endl;
              }
          }
      );
  });
  ```

  ```rust Rust theme={null}
  ditto.auth().set_expiration_handler(|ditto, seconds_remaining| {
      ditto.auth().login(
          my_jwt,
          "my-auth-provider",
          |client_info, error| {
              match error {
                  Some(e) => println!("Authentication failed: {}", e),
                  None => println!("Authentication successful"),
              }
          },
      );
  });
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```
</CodeGroup>

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.

<CodeGroup>
  ```swift Swift theme={null}
  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`
      }
  }
  ```

  ```typescript JavaScript theme={null}
  ditto.auth.setExpirationHandler(async (ditto, secondsRemaining) => {
      if (secondsRemaining === 0) {
          // Do what you'd do in `authenticationRequired`
      } else {
          // Do what you'd do in `authenticationExpiringSoon`
      }
  });
  ```

  ```kotlin Kotlin theme={null}
  ditto.auth?.expirationHandler = { ditto, secondsRemaining ->
      if (secondsRemaining <= 0) {
          // Do what you'd do in `authenticationRequired`
      } else {
          // Do what you'd do in `authenticationExpiringSoon`
      }
  }
  ```

  ```java Java theme={null}
  ditto.auth.setExpirationHandler((dit, secondsRemaining) -> {
      if (secondsRemaining <= 0) {
          // Do what you'd do in `authenticationRequired`
      } else {
          // Do what you'd do in `authenticationExpiringSoon`
      }
  });
  ```

  ```csharp C# theme={null}
  ditto.Auth.ExpirationHandler = async (ditto, secondsRemaining) =>
  {
      if (secondsRemaining == 0)
      {
          // Do what you'd do in `authenticationRequired`
      }
      else
      {
          // Do what you'd do in `authenticationExpiringSoon`
      }
  };
  ```

  ```cpp C++ theme={null}
  ditto->auth().set_expiration_handler([](auto& ditto, int64_t seconds_remaining) {
      if (seconds_remaining == 0) {
          // Do what you'd do in `authenticationRequired`
      } else {
          // Do what you'd do in `authenticationExpiringSoon`
      }
  });
  ```

  ```rust Rust theme={null}
  ditto.auth().set_expiration_handler(|ditto, seconds_remaining| {
      if seconds_remaining == 0 {
          // Do what you'd do in `authenticationRequired`
      } else {
          // Do what you'd do in `authenticationExpiringSoon`
      }
  });
  ```

  ```dart Flutter theme={null}
  // ⚠️ Flutter SDK does not yet support the new DittoConfig-based initialization.
  // Continue using the legacy DittoIdentity-based API for Flutter applications.
  ```
</CodeGroup>

### 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.
