# Configuring Custom Transport Layers in iroh with unstable-custom-transports

> Learn to configure custom transport layers in iroh using the unstable-custom-transports feature. Implement pluggable networking for Bluetooth, Tor, and more with our guide.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-21

---

**The `unstable-custom-transports` feature flag in iroh enables a pluggable networking API that lets developers implement custom packet transport layers—such as Bluetooth, Tor overlays, or proprietary protocols—by implementing the `CustomTransport` trait, registering a unique transport ID, and injecting the transport into the `Endpoint` builder with a custom `PathSelector` to prioritize it over standard IPv4/IPv6 or relay paths.**

The `n0-computer/iroh` repository provides a modular networking stack designed for distributed systems. By enabling the `unstable-custom-transports` Cargo feature, you can extend the default transport implementations with user-defined networking layers that integrate seamlessly with iroh's connection logic and path selection algorithms.

## Enabling the unstable-custom-transports Feature

To access the custom transport APIs, enable the feature in your [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml):

```toml
[dependencies]
iroh = { version = "0.x", features = ["unstable-custom-transports"] }

```

When this feature is active, the `iroh::endpoint` module re-exports the low-level transport types required for implementation. Specifically, [[`src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/src/endpoint.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L29-L48) exposes the `transports` submodule, which includes `CustomTransport`, `CustomEndpoint`, and `CustomSender` at lines 41-46.

## Architecture of Custom Transports

Custom transports in iroh are identified by a **transport ID**, which is a `u16` value that distinguishes the networking protocol. The repository maintains a canonical registry of these IDs in [[`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md#L5-L9). For example, the built-in test transport reserves ID `0x20`.

The architecture separates concerns across three layers:

- **Transport Implementation**: Defines how packets are sent and received
- **Endpoint Builder**: Configures which transports are available to a local endpoint
- **Path Selector**: Determines which transport to use for a given connection

## Implementing a Custom Transport

### The CustomTransport Trait

A custom transport must implement the `CustomTransport` trait, which defines two essential methods:

- **`addr()`**: Returns a `CustomAddr` containing the transport ID and opaque address data
- **`connect()`**: Creates a `CustomSender` capable of sending and receiving raw bytes

These types are defined in the socket layer and re-exported through the endpoint module.

### Reference Implementation

The repository provides a reference implementation in [[`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs). The `TestTransport` struct demonstrates how to implement the trait for an in-memory network, using transport ID `0x20` and providing the required `CustomEndpoint` and `CustomSender` implementations.

## Configuring Endpoints with Custom Transports

### Adding Transports to the Builder

The [`Endpoint::builder()`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) creates a `Builder` that accumulates transport configurations. To add a custom transport, use the `.preset()` method, which accepts any type implementing `CustomTransport`:

```rust
let transport = Arc::new(TestTransport::new());
let builder = Endpoint::builder(presets::N0)
    .secret_key(secret_key)
    .preset(transport);

```

To disable default transports, chain the builder methods `.clear_ip_transports()` and `.clear_relay_transports()`. This forces the endpoint to use only your custom implementation.

### Implementing Path Selection

The [`PathSelector`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) trait determines which available path to use for a connection. The example in [[`examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/examples/custom-transport.rs)](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) (lines 37-69) demonstrates a `PreferTestTransport` selector that prioritizes paths using the custom transport ID:

```rust
impl PathSelector for PreferTestTransport {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut selection = PathSelection::none();
        
        // Prefer the custom transport if available
        if let Some(p) = ctx.paths().find(|p| {
            matches!(p.network_path().remote(), 
                Addr::Custom(c) if c.id() == TEST_TRANSPORT_ID)
        }) {
            selection.set(&p);
            return selection;
        }
        
        // Fallback to lowest RTT path
        ctx.select_lowest_rtt(&mut selection);
        selection
    }
}

```

Attach the selector to the builder using `.path_selector(Arc::new(PreferTestTransport))`.

## Complete Configuration Example

The following example demonstrates building an endpoint that prefers the test custom transport while disabling standard IP transports:

```rust
use iroh::{
    Endpoint, SecretKey, 
    endpoint::{Builder, PathSelector, PathSelectionContext},
    endpoint::transports::Addr,
    test_utils::test_transport::{TEST_TRANSPORT_ID, TestTransport},
};
use std::sync::Arc;

struct PreferTestTransport;

impl PathSelector for PreferTestTransport {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut sel = PathSelection::none();
        if let Some(p) = ctx.paths().find(|p| {
            matches!(p.network_path().remote(), 
                Addr::Custom(c) if c.id() == TEST_TRANSPORT_ID)
        }) {
            sel.set(&p);
        }
        sel
    }
}

async fn create_custom_endpoint(
    secret_key: SecretKey,
    transport: Arc<TestTransport>,
) -> anyhow::Result<Endpoint> {
    Endpoint::builder(presets::N0)
        .secret_key(secret_key)
        .preset(transport)
        .path_selector(Arc::new(PreferTestTransport))
        .clear_ip_transports()
        .clear_relay_transports()
        .bind()
        .await
}

```

Running this configuration ensures that all connections attempt to use your custom transport implementation before falling back to alternative paths.

## Summary

- **Enable the feature** by adding `unstable-custom-transports` to your Cargo dependencies to access the `CustomTransport` trait and related types.
- **Implement the trait** by defining `addr()` and `connect()` methods, using a unique transport ID registered in [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md).
- **Configure the builder** using `.preset()` to inject your transport, and optionally call `.clear_ip_transports()` to disable default networking.
- **Control selection** by implementing `PathSelector` to prioritize `Addr::Custom` variants with your transport ID.
- **Reference the test implementation** in [`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) and the working example in [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) for complete integration patterns.

## Frequently Asked Questions

### What is the unstable-custom-transports feature flag in iroh?

The `unstable-custom-transports` feature flag is a Cargo configuration that unlocks iroh's experimental transport plugin API. When enabled, it exposes the `CustomTransport`, `CustomEndpoint`, and `CustomSender` types from the `iroh::endpoint::transports` module, allowing developers to implement alternative networking layers that can coexist with or replace the built-in IPv4, IPv6, and relay transports.

### How do I register a new transport ID for my custom implementation?

Transport IDs are 16-bit unsigned integers (`u16`) tracked in the [[`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) file at the repository root. To register a new transport, submit a pull request to the `n0-computer/iroh` repository that adds your transport ID to the registry table, along with a description of the protocol and its status (reserved, experimental, or stable). The test transport uses ID `0x20` as a reference implementation.

### Can custom transports coexist with standard IP and relay transports?

Yes, custom transports can coexist with the default IPv4, IPv6, and relay transports. By default, adding a custom transport via `.preset()` makes it available alongside standard transports. You can selectively disable IP or relay transports using `.clear_ip_transports()` and `.clear_relay_transports()` on the `Builder` if you want to force connections through your custom implementation or create a specialized network configuration.

### What methods must a custom transport implement?

A custom transport must implement the `CustomTransport` trait, which requires two primary methods: `addr()`, which returns a `CustomAddr` containing the transport ID and address data, and `connect()`, which establishes a connection and returns a `CustomSender` for transmitting and receiving raw packet data. Additionally, the transport must provide a way to create a `CustomEndpoint` that can accept incoming connections, typically through a constructor method that returns an `Arc<dyn CustomTransport>`.