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

# Customizing Transports

> In Ditto, synchronization happens over a variety of network transports — including local peer-to-peer connections (Bluetooth, LAN, AWDL, WI-FI Aware, and Wi-Fi), as well as cloud-based sync via the Ditto Server. While Ditto handles these connections automatically by default, you may want more control over which transports are used, how they’re configured, or when they’re enabled. This guide walks you through how to customize these transport settings to suit your app’s specific connectivity and sync requirements.

You can read more about the various transports that Ditto uses in [Mesh Networking](/key-concepts/mesh-networking#transports).

<Info>
  Although Ditto automatically attempts to connect to other instances on the Local Area Network (LAN), Bluetooth Low Energy (LE), and Apple Wireless Direct Link (AWDL), supplying a custom instance of the `DittoTransportConfig` does *not* enable this feature by default.  Rather, you manually enable peer-to-peer connections using `EnableAllPeerToPeer()`.

  This is only true if you provide a custom instance of the `DittoTransportConfig` object.  If you do not provide a custom instance, Ditto will automatically enable all peer-to-peer transports by default.

  The best way to ensure that you are using the correct transport configuration is to use the `updateTransportConfig` API to update the `DittoTransportConfig` object.  This will ensure that you are using the correct transport configuration and that you are not missing any transports.
</Info>

## Enabling and Disabling Transports

When a new Ditto instance is created, a default transport configuration is supplied that enables all peer-to-peer transports by default.  You can control which transports are enabled or disabled by updating the `DittoTransportConfig` object.

<Info>
  Changing the transport configuration via the `updateTransportConfig` API  `after` sync has been started will instantly apply the changes without the need to manually stop and start sync.
</Info>

<CodeGroup>
  ```swift Swift theme={null}
  ditto.updateTransportConfig { transportConfig in
    //enable/disable each transport separately

    //BluetoothLe
    transportConfig.peerToPeer.bluetoothLE.isEnabled = true
    //Local Area Network
    transportConfig.peerToPeer.lan.isEnabled = true
    //Awdl
    transportConfig.peerToPeer.awdl.isEnabled = true
  }

  do {
    try ditto.sync.start()
  } catch (let err) {
    print(err.localizedDescription)
  }
  ```

  ```kotlin Kotlin theme={null}
  ditto.updateTransportConfig { transportConfig ->

    //enable/disable each transport separately

    //BluetoothLe
    transportConfig.peerToPeer.bluetoothLe.enabled = true
    //Local Area Network
    transportConfig.peerToPeer.lan.enabled = true
    //Wifi Aware
    transportConfig.peerToPeer.wifiAware.enabled = true
  }

  ditto.sync.start()
  ```

  ```javascript JS theme={null}
  ditto.updateTransportConfig((transportConfig) => {
    //enable/disable each transport separately

    //BluetoothLe
    transportConfig.peerToPeer.bluetoothLE.isEnabled = true
    // Local Area Network
    transportConfig.peerToPeer.lan.isEnabled = true
    // AWDL
    transportConfig.peerToPeer.awdl.isEnabled = true
    //wifi aware
    transportConfig.peerToPeer.wifiAware.isEnabled = true
  });

  ditto.startSync()
  ```

  ```typescript TS theme={null}
  import { Ditto, TransportConfig } from '@dittolive/ditto'

  ditto.updateTransportConfig((transportConfig: TransportConfig): void => {
    // enable/disable each transport separately

    // BluetoothLe
    transportConfig.peerToPeer.bluetoothLE.isEnabled = true
    // Local Area Network
    transportConfig.peerToPeer.lan.isEnabled = true
    // AWDL
    transportConfig.peerToPeer.awdl.isEnabled = true
    // wifi aware
    transportConfig.peerToPeer.wifiAware.isEnabled = true
  })

  ditto.startSync()
  ```

  ```java Java theme={null}
  ditto.updateTransportConfig(transportConfig -> {
    //Or enable/disable each transport separately

    //BluetoothLe
    transportConfig.getPeerToPeer().getBluetoothLe().setEnabled(true);
    //Local Area Network
    transportConfig.getPeerToPeer().getLan().setEnabled(true);
    //Wifi Aware
    transportConfig.getPeerToPeer().getWifiAware().setEnabled(true);

  });
  try {
      ditto.getSync().start();
  } catch(DittoError error) {
      // handle error
  }
  ```

  ```csharp C# theme={null}
  ditto.UpdateTransportConfig(transportConfig =>
  {
    //enable/disable each transport separately

    //BluetoothLe
    transportConfig.PeerToPeer.BluetoothLE.Enabled = true;
    //Local Area Network
    transportConfig.PeerToPeer.Lan.Enabled = true;
    //Awdl
    transportConfig.PeerToPeer.Awdl.Enabled = true;
      //wifi aware
    transportConfig.peerToPeer.WifiAware.enabled = true
  });

  ditto.Sync.Start();
  ```

  ```cpp C++ theme={null}
  ditto->update_transport_config([&](ditto::TransportConfig &transportConfig) {
    //enable/disable each transport separately

    //BluetoothLe
    transportConfig.peer_to_peer.bluetooth_le.enabled = true;
    //Local Area Network
    transportConfig.peer_to_peer.lan.enabled = true;
    // Apple Wireless Direct Link
    transportConfig.peer_to_peer.awdl.enabled = true;
    //wifi aware
    transportConfig.peer_to_peer.wifi_aware.enabled = true;
  });

  ditto.get_sync().start();
  ```

  ```rust Rust theme={null}
  ditto.update_transport_config(|transport_config| {
      //BluetoothLe
    transport_config.peer_to_peer.bluetooth_le.enabled = true;
    //Local Area Network
    transport_config.peer_to_peer.lan.enabled = true;
  });

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

  ```dart Dart theme={null}
  ditto.updateTransportConfig((transportConfig) {
    //enable/disable each transport separately

    //BluetoothLe
    transportConfig.peerToPeer.bluetoothLE.isEnabled = true
    //Local Area Network
    transportConfig.peerToPeer.lan.isEnabled = true
    // Apple Wireless Direct Link
    transportConfig.peerToPeer.awdl.isEnabled = true;
    //wifi aware
    transportConfig.peerToPeer.wifiAware.isEnabled = true;
  });

  ditto.startSync();
  ```

  ```go Go theme={null}
  // The transport config API is not available in the current Go SDK Public Preview
  ```
</CodeGroup>

## Syncing with Ditto Server / WebSocket Connections

To sync with the Ditto Server or to initialize a WebSocket connection, you need to add a `websocketURL` to the `DittoTransportConfig` object.  You can do this by calling `updateTransportConfig` and adding the `websocketURL` to the `connect.websocketURLs` array.

<Info>
  Using the `updateTransportConfig` API to remove all `webSocketURLs` from the connect object will disable sync with the Ditto Server instantly without needing to call stop and start sync on the Ditto instance.
</Info>

<CodeGroup>
  ```swift Swift 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)
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val androidDependencies = DefaultAndroidDittoDependencies(applicationContext)
      val identity =
          DittoIdentity.OnlinePlayground(
              dependencies = androidDependencies,
              appId = "REPLACE_ME_WITH_YOUR_APP_ID",
              token = "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
              customAuthUrl = "REPLACE_ME_WITH_YOUR_AUTH_URL",
              enableDittoCloudSync = false  // This is required to be set to false to use the correct URLs
          )

      ditto = Ditto(androidDependencies, identity)

      ditto?.updateTransportConfig { config ->
          config.connect.websocketUrls.add("REPLACE_ME_WITH_YOUR_WEBSOCKET_URL")
      }

      // Disable DQL strict mode so that collection definitions are not required in DQL queries
      ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")
      
      ditto.startSync()
  } catch (e: DittoError) {
      Log.e("Ditto error", e.message!!)
  }
  ```

  ```dart Dart theme={null}

  await Ditto.init();

  final identity = OnlinePlaygroundIdentity(
      appId: "REPLACE_ME_WITH_YOUR_APP_ID",
      token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
      customAuthUrl: "REPLACE_ME_WITH_YOUR_AUTH_URL",
      enableDittoCloudSync: false // This is required to be set to false to use the correct URLs
  );
  final ditto = await Ditto.open(identity);

  ditto.updateTransportConfig((config) {
    // Set the Ditto Websocket URL
    config.connect.webSocketUrls.add("wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL");
  });

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

  ditto.startSync();
  ```

  <CodeGroup>
    ```javascript JavaScript theme={null}
    await init();

    const ditto = new Ditto({
      type: "onlinePlayground",
      appID: "REPLACE_ME_WITH_YOUR_APP_ID",
      token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
      customAuthURL: "REPLACE_ME_WITH_YOUR_AUTH_URL",
      enableDittoCloudSync: false, // This is required to be set to false to use the correct URLs
    });

    ditto.updateTransportConfig((config) => {
      config.connect.websocketURLs.push(
        'wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL'
      );
    });

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

    ditto.startSync();
    ```

    ```typescript TypeScript theme={null}
    import { init, Ditto, TransportConfig } from '@dittolive/ditto';

    await init();

    const ditto: Ditto = new Ditto({
      type: "onlinePlayground",
      appID: "REPLACE_ME_WITH_YOUR_APP_ID",
      token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
      customAuthURL: "REPLACE_ME_WITH_YOUR_AUTH_URL",
      enableDittoCloudSync: false, // This is required to be set to false to use the correct URLs
    });

    ditto.updateTransportConfig((config: TransportConfig) => {
      config.connect.websocketURLs.push(
        'wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL'
      );
    });

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

    ditto.startSync();
    ```
  </CodeGroup>

  ```typescript TS theme={null}
  import { init, Ditto, TransportConfig } from '@dittolive/ditto'

  await init()

  const ditto = new Ditto({
    type: "onlinePlayground",
    appID: "REPLACE_ME_WITH_YOUR_APP_ID",
    token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
    customAuthURL: "REPLACE_ME_WITH_YOUR_AUTH_URL",
    enableDittoCloudSync: false, // This is required to be set to false to use the correct URLs
  })

  ditto.updateTransportConfig((config: TransportConfig): void => {
    config.connect.websocketURLs.push(
      'wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL'
    )
  })

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

  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;

  try {
      // Configure Ditto with playground settings
      DittoConfig config = new DittoConfig.Builder("REPLACE_ME_WITH_YOUR_APP_ID")
          .connect(new DittoConfig.Connect.Server("wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL"))
          .build();

      Ditto ditto = DittoFactory.create(config);

      // Set up authentication with playground token
      ditto.getAuth().login(
          "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
          DittoAuthenticationProvider.playground()
      ).thenAccept(clientInfo -> {
          // Disable DQL strict mode so that collection definitions are not required in DQL queries
          ditto.getStore().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false");

          ditto.startSync();
      }).exceptionally(error -> {
          // Handle authentication error
          return null;
      });
  } catch (DittoError e) {
      // Handle error in Ditto initialization
  }
  ```

  ```csharp C# theme={null}
  var identity = DittoIdentity.OnlinePlayground(
      appId, 
      playgroundToken, 
      false, // This is required to be set to false to use the correct URLs
      authUrl);

  ditto = new Ditto(identity, tempDir);

  ditto.UpdateTransportConfig(config =>
  {
      // Set the Ditto Websocket URL
      config.Connect.WebSocketUrls.Add("wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL");
  });

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

  ditto.StartSync();
  ```

  ```cpp C++ theme={null}

  const auto identity = ditto::Identity::OnlinePlayground(
      std::move(app_id),
      std::move(online_playground_token),
      enable_cloud_sync,                  // This is required to be set to false to use the correct URLs
      std::move(custom_url)
  );

  ditto->update_transport_config([&](ditto::TransportConfig &config) {
      config.enable_all_peer_to_peer();
      config.connect.websocket_urls.insert(websocket_url);
  });
  // Required for compatibility with DQL.
  ditto->disable_sync_with_v3();

  // Disable DQL strict mode so that collection definitions are not required in DQL queries
  ditto->get_store().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false");

  ditto->start_sync();
  ```

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

  // Initialize with DittoConfig using database_id (was app_id in v4)
  let database_id = DatabaseId::from_str("REPLACE_ME_WITH_YOUR_DATABASE_ID")?;
  let config = DittoConfig::new(
      database_id.clone(),
      DittoConnect::Server {
          url: format!("https://{}.cloud.ditto.live", database_id)
      }
  );

  let ditto = Ditto::open(config)?;

  // Set up authentication expiration handler
  ditto.auth().set_expiration_handler(|ditto, seconds_remaining| {
      ditto.auth().login(
          "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
          Authenticator::development_provider(),
          |result| {
              if let Err(e) = result {
                  eprintln!("Auth error: {}", e);
              }
          }
      );
  });

  // Configure transport (optional)
  ditto.set_transport_config({
      let mut config = TransportConfig::new();
      config
          .connect
          .websocket_urls
          .insert(format!("wss://{}.cloud.ditto.live", database_id));
      config
  });

  // Start sync (was ditto.start_sync() in v4)
  ditto.sync().start()?;
  ```

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

## Configuring Additional Settings

If you need additional connection configurations for the current Ditto instance, configure it to listen for connections on a specific port and to connect to remote instances using a host and port:

## Connecting to Remote Small Peers

<Note>
  Supported in SDK version 4.12.0 and later. On previous SDK versions, use `transportConfig.connect.tcpServers` for equivalent behavior.
</Note>

The configuration for connecting to remote small peers uses DQL queries rather than the `updateTransportConfig` API. This system configuration can be updated at any time, though changes will only take effect while sync is active.

Suppose you have a device in your mesh that acts as a connection hub, and you want every other device to connect to the hub.

1. Define the TCP listening port (e.g. `12345`) as described in [Listening for Connections](#listening-for-connections).
2. Retrieve the IP address of the hub device, or assign it a static IP. In this example, the IP address is `10.0.0.143`.
3. Set `TRANSPORTS_DISCOVERED_PEERS` on all the other devices in the mesh to make them connect to the hub. See code examples below:

<CodeGroup>
  ```swift Swift theme={null}

  await ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]
  """);
  ```

  ```kotlin Kotlin theme={null}

  ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]
  """)
  ```

  ```javascript JS theme={null}

  await ditto.store.execute(`
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]
  `);
  ```

  ```typescript TS theme={null}
  import { Ditto, QueryResult } from '@dittolive/ditto'

  const result: QueryResult = await ditto.store.execute(`
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]
  `)
  ```

  ```java Java theme={null}

  ditto.getStore().execute(
    "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = "
    + "[{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]").toCompletableFuture().join();
  ```

  ```csharp C# theme={null}

  await ditto.Store.ExecuteAsync(
    "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = "
    + "[{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]"
  );
  ```

  ```cpp C++ theme={null}

  ditto.get_store().execute(
    "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = "
    + "[{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]"
  );
  ```

  ```rust Rust theme={null}

  ditto.store().execute(
      "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = \
      [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]",
  ).await?;
  ```

  ```dart Dart theme={null}

  await ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]
  """);
  ```

  ```go Go theme={null}
  result, err := dit.Store().Execute(
      `ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
       [{'address': 'tcp://10.0.0.143:12345', 'type': 'force'}]`
  )
  if err != nil {
      return err
  }
  defer result.Close()  // cleanup
  ```
</CodeGroup>

<Info>
  The `address` field supports domain names, IPv4 addresses, and IPv6 addresses. `tcp` is the only supported scheme. The following are all valid addresses:

  * `tcp://192.168.1.86:12345`
  * `tcp://[2001:db8::1:0]:12345`
  * `tcp://mydevice.local:12345`
</Info>

<Info>
  The `type` field supports two options:

  * `candidate` (default): Ditto will use this peer as a connection candidate when forming an optimal mesh. If this peer is removed from the list, the connection may persist.
  * `force`: Ditto will always attempt to connect to this peer. If this peer is removed from the list, the connection will immediately be dropped.
</Info>

## Listening for Connections

For some use cases, you can configure a Ditto instance to accept incoming TCP connections from remote peers at a specific IP address and port. This is different from the automatic peer-to-peer discovery that happens by default. Use this when you want your device to act as a connection hub that other devices can connect to directly, rather than relying solely on automatic discovery mechanisms like mDNS or Bluetooth.

<CodeGroup>
  ```swift Swift theme={null}
  ditto.updateTransportConfig { transportConfig in
    // Listen for incoming connections on port 4000

    // By default Tcp Enabled is false, be sure to set it to true.
    transportConfig.listen.tcp.isEnabled = true
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.listen.tcp.interfaceIP = "0.0.0.0"
    transportConfig.listen.tcp.port = 4000
  }

  do {
    try ditto.startSync()
  } catch (let err) {
    print(err.localizedDescription)
  }
  ```

  ```kotlin Kotlin theme={null}
  ditto.updateTransportConfig { transportConfig ->
    // Listen for incoming connections on port 4000

    // By default Tcp Enabled is false, be sure to set it to true.
    transportConfig.listen.tcp.isEnabled = true
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.listen.tcp.interfaceIP = "0.0.0.0"
    transportConfig.listen.tcp.port = 4000
  }

  ditto.startSync()
  ```

  ```javascript JS theme={null}

  ditto.updateTransportConfig((transportConfig) => {
    // Listen for incoming connections on port 4000

    // By default Tcp Enabled is false, be sure to set it to true.
    transportConfig.listen.tcp.isEnabled = true;
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.listen.tcp.interfaceIP = '0.0.0.0';
    transportConfig.listen.tcp.port = 4000;
  });

  ditto.startSync()
  ```

  ```typescript TS theme={null}
  import { Ditto, TransportConfig } from '@dittolive/ditto'

  ditto.updateTransportConfig((transportConfig: TransportConfig): void => {
    // Listen for incoming connections on port 4000

    // By default Tcp Enabled is false, be sure to set it to true.
    transportConfig.listen.tcp.isEnabled = true
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.listen.tcp.interfaceIP = '0.0.0.0'
    transportConfig.listen.tcp.port = 4000
  })

  ditto.startSync()
  ```

  ```java Java theme={null}

  ditto.updateTransportConfig(transportConfig -> {
    // Listen for incoming connections on port 4000
    DittoListen listen = new DittoListen();
    DittoTcpListenConfig tcpListenConfig = new DittoTcpListenConfig();
    // By default Tcp Enabled is false, be sure to set it to true.
    tcpListenConfig.setEnabled(true);
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    tcpListenConfig.setInterfaceIp("0.0.0.0");
    tcpListenConfig.setPort(4000);
    transportConfig.setListen(listen.setTcp(tcpListenConfig));
  });
  try {
      ditto.startSync();
  } catch(DittoError error) {
      // handle error
  }
  ```

  ```csharp C# theme={null}

  ditto.UpdateTransportConfig(transportConfig =>
  {
    // Listen for incoming connections on port 4000
    transportConfig.Listen.Tcp = new DittoTcpListenConfig();
    // By default Listen.Tcp.Enabled is false, be sure to set it to true.
    transportConfig.Listen.Tcp.Enabled = true;
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.Listen.Tcp.InterfaceIp = "0.0.0.0";
    // specify your port.
    transportConfig.Listen.Tcp.Port = 4000;
  });

  ditto.StartSync();
  ```

  ```cpp C++ theme={null}

  ditto->update_transport_config([&](ditto::TransportConfig &transportConfig) {
    // Listen for incoming connections on port 4000

    // By default listen.tcp.enabled is false, be sure to set it to true.
    transportConfig.listen.tcp.enabled = true;
    transportConfig.listen.http.enabled = false;
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.listen.tcp.interface_ip = "0.0.0.0";
    transportConfig.listen.tcp.port = 4000;
  });

  ditto.start_sync();
  ```

  ```rust Rust theme={null}

  ditto.update_transport_config(|transport_config| {
    // Listen for incoming connections on port 4000

    // By default listen.tcp.enabled is false, be sure to set it to true.
    transport_config.listen.tcp.enabled = true;
    transport_config.listen.http.enabled = false;
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transport_config.listen.tcp.interface_ip = "0.0.0.0".to_string();
    transport_config.listen.tcp.port = 4000;

  });

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

  ```dart Dart theme={null}

  ditto.updateTransportConfig((transportConfig) {
    // Listen for incoming connections on port 4000

    // By default listen.tcp.enabled is false, be sure to set it to true.
    transportConfig.listen.tcp.isEnabled = true
    // if you want to listen on localhost, most likely you will use 0.0.0.0
    // do not use "localhost" as a string
    transportConfig.listen.tcp.interfaceIP = '0.0.0.0'
    transportConfig.listen.tcp.port = 4000;
  });

  ditto.startSync();
  ```

  ```go Go theme={null}
  // The transport config API is not available in the current Go SDK Public Preview
  ```
</CodeGroup>

## Combining Multiple Transports

A reminder when a new Ditto instance is created, a default transport configuration is supplied that enables all peer-to-peer transports by default.  You can combine multiple transports to suit your needs. For example, you can listen for incoming connections on a specific port and connect to remote devices.

<CodeGroup>
  ```swift Swift theme={null}

  ditto.updateTransportConfig { transportConfig in
    // 1. Listen for incoming connections on port 4000
    transportConfig.listen.tcp.isEnabled = true
    transportConfig.listen.tcp.interfaceIP = "0.0.0.0"
    transportConfig.listen.tcp.port = 4000
  }

  // 2. Connect explicitly to remote devices
  await ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},
    {'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]
  """);

  do {
    try ditto.startSync()
  } catch (let err) {
    print(err.localizedDescription)
  }
  ```

  ```kotlin Kotlin theme={null}

  ditto.updateTransportConfig { transportConfig ->
    // 1. Listen for incoming connections on port 4000
    transportConfig.listen.tcp.isEnabled = true
    transportConfig.listen.tcp.interfaceIP = "0.0.0.0"
    transportConfig.listen.tcp.port = 4000
  }

  // 2. Connect explicitly to remote devices
  ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},
    {'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]
  """);

  ditto.startSync()
  ```

  ```javascript JS theme={null}
  ditto.updateTransportConfig((transportConfig) => {
    // 1. Listen for incoming connections on port 4000
    transportConfig.listen.tcp.isEnabled = true;
    transportConfig.listen.tcp.interfaceIP = '0.0.0.0';
    transportConfig.listen.tcp.port = 4000;
  });

  // 2. Connect explicitly to remote devices
  await ditto.store.execute(`
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},
    {'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]
  `);

  ditto.startSync()
  ```

  ```typescript TS theme={null}
  import { Ditto, TransportConfig, QueryResult } from '@dittolive/ditto'

  ditto.updateTransportConfig((transportConfig: TransportConfig): void => {
    // 1. Listen for incoming connections on port 4000
    transportConfig.listen.tcp.isEnabled = true
    transportConfig.listen.tcp.interfaceIP = '0.0.0.0'
    transportConfig.listen.tcp.port = 4000
  })

  // 2. Connect explicitly to remote devices
  const result: QueryResult = await ditto.store.execute(`
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},
    {'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]
  `)

  ditto.startSync()
  ```

  ```java Java theme={null}

  ditto.updateTransportConfig(transportConfig -> {
    // 1. Listen for incoming connections on port 4000
    DittoListen listen = new DittoListen();
    DittoTcpListenConfig tcpListenConfig = new DittoTcpListenConfig();
    tcpListenConfig.setEnabled(true);
    tcpListenConfig.setInterfaceIp("0.0.0.0");
    tcpListenConfig.setPort(4000);
    transportConfig.setListen(listen.setTcp(tcpListenConfig));

    // 2. Connect explicitly to remote devices
    transportConfig.getConnect().setTcpServers(Sets.newHashSet("135.1.5.5:12345", "185.1.5.5:12345"));
  });
  try {
      ditto.startSync();
  } catch(DittoError error) {
      // handle error
  }
  ```

  ```csharp C# theme={null}
  ditto.UpdateTransportConfig(transportConfig =>
  {
    // 1. Listen for incoming connections on port 4000
    transportConfig.Listen.Tcp = new DittoTcpListenConfig();
    transportConfig.Listen.Tcp.Enabled = true;
    transportConfig.Listen.Tcp.InterfaceIp = "0.0.0.0";
    transportConfig.Listen.Tcp.Port = 4000;
  });

  // 2. Connect explicitly to remote devices
  await ditto.Store.ExecuteAsync(
    "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = "
    + "[{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},"
    + "{'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]"
  );

  ditto.StartSync();
  ```

  ```cpp C++ theme={null}

  ditto->update_transport_config([&](ditto::TransportConfig &transportConfig) {
    // 1. Listen for incoming connections on port 4000
    transportConfig.listen.tcp.enabled = true;
    transportConfig.listen.http.enabled = false;
    transportConfig.listen.tcp.interface_ip = "0.0.0.0";
    transportConfig.listen.tcp.port = 4000;
  });

  // 2. Connect explicitly to remote devices
  ditto.get_store().execute(
    "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = "
    + "[{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},"
    + "{'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]"
  );

  ditto.start_sync();
  ```

  ```rust Rust theme={null}

  ditto.update_transport_config(|transport_config| {
    // 1. Listen for incoming connections on port 4000
    transport_config.listen.tcp.enabled = true;
    transport_config.listen.http.enabled = false;
    transport_config.listen.tcp.interface_ip = "0.0.0.0".to_string();
    transport_config.listen.tcp.port = 4000;
  });


  // 2. Connect explicitly to remote devices
  ditto.store().execute(
      "ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = \
      [{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},\
      {'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]",
  ).await?;

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

  ```dart Dart theme={null}
  ditto.updateTransportConfig((transportConfig) {
    // 1. Listen for incoming connections on port 4000
    transportConfig.listen.tcp.isEnabled = true
    transportConfig.listen.tcp.interfaceIP = '0.0.0.0'
    transportConfig.listen.tcp.port = 4000;
  });

  // 2. Connect explicitly to remote devices
  await ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://135.1.5.5:12345', 'type': 'force'},
      {'address': 'tcp://185.1.5.5:12345', 'type': 'force'}]
  """);

  ditto.startSync();
  ```

  ```go Go theme={null}
  // The transport config API is not available in the current Go SDK Public Preview
  ```
</CodeGroup>

## Replacing mDNS Discovery

<Note>
  Supported in SDK version 4.12.0 and later.
</Note>

In rare cases where mDNS discovery may not be suitable, Ditto gives you full control over LAN discovery.

On each device:

1. Disable peer-to-peer LAN in the [`TransportConfig`](#enabling-and-disabling-transports).
2. [Configure the TCP server](#listening-for-connections) to listen on a defined port. This port does not have to be the same for each peer, but you do have to define it for each peer.
3. Read the `discovery_hint` using the following DQL query:

* `SELECT * FROM system:transports_info WHERE _id = 'discovery_hint'`

4. Get the device's IP address on the local network.
5. Send the device's IP address, TCP listening port, and `discovery_hint` to every other peer in the mesh. (This information must be sent on channels outside Ditto because we assume that all peers start disconnected from each other.)
6. Update `TRANSPORTS_DISCOVERED_PEERS` with the full list of discovered peers as the device receives discovery information from other devices.

<CodeGroup>
  ```swift Swift theme={null}

  // Listen for connections on port 12345
  ditto.updateTransportConfig { transportConfig in
    transportConfig.listen.tcp.isEnabled = true
    transportConfig.listen.tcp.interfaceIP = "0.0.0.0"
    transportConfig.listen.tcp.port = 12345
  }

  // Get the device's IP address (platform-specific)
  // ...

  // Start sync
  do {
    try ditto.startSync()
  } catch (let err) {
    print(err.localizedDescription)
  }

  // Get the device's discovery hint (this may change every time startSync() is called)
  if let discoveryHint = await getDiscoveryHint(ditto) {
    // Discovery hint available here
  }

  // Broadcast local IP, port (12345), and discovery hint to all other devices
  // ...

  // As we receive broadcasts from other devices, update TRANSPORTS_DISCOVERED_PEERS with the full list of discovered peers
  await ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'discovery_hint': 'Q2CGGdmF-A', 'type': 'candidate'},
    {'address': 'tcp://10.0.0.154:12345', 'discovery_hint': 'Q2CG6y5vGw', 'type': 'candidate'}]
  """);

  // ...

  // Function definition for completeness
  func getDiscoveryHint(ditto: Ditto) async -> String? {
    let discoveryHint = try? await ditto.store
      .execute(query: "SELECT * FROM system:transports_info WHERE _id = 'discovery_hint'")
    return discoveryHint?.items.first?.value["value"] as? String
  }
  ```

  ```kotlin Kotlin theme={null}

  // Listen for connections on port 12345
  ditto.updateTransportConfig { transportConfig ->
    transportConfig.listen.tcp.isEnabled = true
    transportConfig.listen.tcp.interfaceIP = "0.0.0.0"
    transportConfig.listen.tcp.port = 12345
  }

  // Get the device's IP address (platform-specific)
  // ...

  // Start sync
  ditto.startSync()

  // Get the device's discovery hint (this may change every time startSync() is called)
  getDiscoveryHint(ditto)?.let { discoveryHint ->
    // Discovery hint available here
  }

  // Broadcast local IP, port (12345), and discovery hint to all other devices
  // ...

  // As we receive broadcasts from other devices, update TRANSPORTS_DISCOVERED_PEERS with the full list of discovered peers
  ditto.store.execute("""
    ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS =
    [{'address': 'tcp://10.0.0.143:12345', 'discovery_hint': 'Q2CGGdmF-A', 'type': 'candidate'},
    {'address': 'tcp://10.0.0.154:12345', 'discovery_hint': 'Q2CG6y5vGw', 'type': 'candidate'}]
  """)

  // ...

  // Function definition for completeness
  suspend fun getDiscoveryHint(ditto: Ditto): String? {
    val discoveryHint = ditto.store
      .execute("SELECT * FROM system:transports_info WHERE _id = 'discovery_hint'")
    return discoveryHint.items.firstOrNull()?.value?.get("value")?.toString()
  }
  ```

  ```rust Rust theme={null}

  // Listen for connections on port 12345
  ditto.update_transport_config(|transport_config| {
    transport_config.listen.tcp.enabled = true;
    transport_config.listen.tcp.interface_ip = "0.0.0.0".to_string();
    transport_config.listen.tcp.port = 12345;

  });

  // Get the device's IP address (platform-specific)
  // ...

  // Start sync
  ditto.sync().start()?;

  // Get the device's discovery hint (this may change every time start_sync() is called)
  if let Ok(discovery_hint) = get_discovery_hint(&ditto).await {
    // Discovery hint available here
  }

  // Broadcast local IP, port (12345), and discovery hint to all other devices
  // ...

  // As we receive broadcasts from other devices, update TRANSPORTS_DISCOVERED_PEERS with the full list of discovered peers
  let _ = ditto.store().execute("ALTER SYSTEM SET TRANSPORTS_DISCOVERED_PEERS = \
    [{'address': 'tcp://10.0.0.143:12345', 'discovery_hint': 'Q2CGGdmF-A', 'type': 'candidate'}, \
    {'address': 'tcp://10.0.0.154:12345', 'discovery_hint': 'Q2CG6y5vGw', 'type': 'candidate'}]").await;

  // ...

  // Function definition for completeness
  async fn get_discovery_hint(ditto: &Ditto) -> Option<String> {
      let discovery_hint = ditto
          .store()
          .execute("SELECT * FROM system:transports_info WHERE _id = 'discovery_hint'")
          .await
          .ok()?;
      let discovery_hint = discovery_hint.iter().next()?.value();
      if let CborValue::Text(discovery_hint) = discovery_hint.get("value")? {
          Some(discovery_hint.to_string())
      } else {
          None
      }
  }
  ```

  ```go Go theme={null}
  // The transport config API is not available in the current Go SDK Public Preview
  ```
</CodeGroup>

<Info>
  Notes about `discovery_hint`:

  * It is an optional field in `TRANSPORTS_DISCOVERED_PEERS`.
  * It is an opaque string that uniquely identifies each peer in the mesh. It is used to make more intelligent decisions about which peers to connect to.
  * It may change for a peer as often as `startSync()` is called.
  * It does not contain information that could be used to track a device long-term, and it is safe to advertise in the clear.
</Info>

## Monitoring Conditions

<CodeGroup>
  ```swift Swift theme={null}
  // Setting up inside a ViewController
  let ditto = Ditto(identity: DittoIdentity.onlinePlayground(appID: "REPLACE_ME_WITH_YOUR_APP_ID", token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN"))
  ditto.delegate = self
  try! ditto.startSync()

  // Now you can observe real time changes to the transport conditions:
  extension ViewController: DittoDelegate {
     func transportConditionDidChange(transportID: Int64, condition: TransportCondition) {
         if condition == .BleDisabled {
             print("BLE disabled")
         } else if condition == .NoBleCentralPermission {
             print("Permission missing for BLE")
         } else if condition == .NoBlePeripheralPermission {
             print("Permission missing for BLE")
         }
     }
  }
  ```

  ```kotlin Kotlin theme={null}
  // ... Setting up inside an Activity
  val androidDependencies = DefaultAndroidDittoDependencies(applicationContext)
  val ditto = Ditto(androidDependencies, DittoIdentity.OnlinePlayground
  (androidDependencies, appId = "REPLACE_WITH_APP_ID", token = "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN"))
  ditto.callback = this
  ditto.startSync()

  // Now you can observe real time changes to the transport conditions:

  class MainActivity : AppCompatActivity(), DittoCallback {

      override fun transportConditionDidChange(transportId: Long, condition: TransportCondition) {
          var toastText: String? = null
          if (condition == TransportCondition.TRANSPORT_CONDITION_BLE_DISABLED) {
              toastText = "BLE disabled"
          } else if (condition == TransportCondition.TRANSPORT_CONDITION_NO_BLE_CENTRAL_PERMISSION) {
              toastText = "Permission missing for BLE"
          } else if (condition == TransportCondition.TRANSPORT_CONDITION_NO_BLE_PERIPHERAL_PERMISSION) {
              toastText = "Permission missing for BLE"
          }
          toastText?.let {
              Handler(mainLooper).post {
                  Toast.makeText(this, it, Toast.LENGTH_LONG).show()
              }
          }
      }
  }
  ```

  ```javascript JS theme={null}
  const transportConditionsObserver = ditto.observeTransportConditions((condition, source) => {
    if (condition === 'BLEDisabled') {
      console.log('BLE disabled')
    } else if (condition === 'NoBLECentralPermission') {
      console.log('Permission missing for BLE')
    } else if (condition === 'NoBLEPeripheralPermission') {
      console.log('Permissions missing for BLE')
    }
  })
  ```

  ```typescript TS theme={null}
  import { Ditto, TransportCondition, ConditionSource } from '@dittolive/ditto'

  const transportConditionsObserver = ditto.observeTransportConditions(
    (condition: TransportCondition, source: ConditionSource): void => {
      if (condition === 'BLEDisabled') {
        console.log('BLE disabled')
      } else if (condition === 'NoBLECentralPermission') {
        console.log('Permission missing for BLE')
      } else if (condition === 'NoBLEPeripheralPermission') {
        console.log('Permissions missing for BLE')
      }
    }
  )
  ```

  ```java Java theme={null}
  // Setting up inside an Activity
  DefaultAndroidDittoDependencies androidDependencies = new DefaultAndroidDittoDependencies(getApplicationContext());
  Ditto ditto = new Ditto(androidDependencies, new DittoIdentity.OnlinePlayground(androidDependenciesOne, "REPLACE_WITH_APP_ID"));
  ditto.callback = this;
  ditto.startSync();

  // Now you can observe real time changes to the transport conditions:
  public class MainActivity extends AppCompatActivity implements DittoCallback {
      @Override
      public void transportConditionDidChange(@NotNull DittoTransportCondition condition, @NotNull DittoConditionSource transportId) {
          String toastText = null;
          if (condition == DittoTransportCondition.BleDisabled) {
              toastText = "BLE disabled";
          } else if (condition == DittoTransportCondition.NoBleCentralPermission) {
              toastText = "Permission missing for BLE";
          } else if (condition == DittoTransportCondition.NoBlePeripheralPermission) {
              toastText = "Permission missing for BLE";
          }

          if (toastText != null) {
              String finalToastText = toastText;
              runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                      Toast.makeText(MainActivity.this, finalToastText, Toast.LENGTH_LONG).show();
                  }
              });
          }
      }
  }
  ```

  ```csharp C# theme={null}
  // not supported in C#
  ```

  ```cpp C++ theme={null}
  // not supported in C++
  ```

  ```rust Rust theme={null}
  // not supported in Rust
  ```

  ```dart Dart theme={null}
  // Coming Soon
  ```

  ```go Go theme={null}
  ```
</CodeGroup>
