# How to Use Custom Transports in iroh with the unstable-custom-transports Feature

> Integrate custom transports into iroh's connection stack using the unstable-custom-transports feature. Implement the CustomTransport trait and prioritize paths with PathSelector.

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

---

**Enable the `unstable-custom-transports` Cargo feature to plug user-defined networking layers into iroh's connection stack, allowing you to implement the `CustomTransport` trait and prioritize custom paths via a `PathSelector` when building an `Endpoint`.**

The iroh networking library provides a pluggable transport architecture that extends its default IPv4/IPv6 and relay capabilities with alternative packet movers. By activating the `unstable-custom-transports` feature in the `n0-computer/iroh` repository, you gain access to the low-level APIs necessary to inject Bluetooth, Tor overlays, or in-memory test networks directly into the connection lifecycle.

## Enabling the Feature Flag

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

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

```

When enabled, the `transports` submodule becomes available through `iroh::endpoint`, exposing the traits required to define new transport layers.

## Core Architecture and Types

The custom transport system centers on types re-exported in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). According to the source code ([lines 41-46](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L41-L46)), the module publicly exposes:

- **`CustomTransport`**: The trait your implementation must satisfy
- **`CustomEndpoint`**: Represents the local endpoint of your custom transport
- **`CustomSender`**: Handles raw byte transmission for your protocol

Each transport is identified by a unique **`u16` transport ID**. The repository maintains a canonical registry in [[`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) where the internal test transport occupies ID `0x20` ([lines 5-9](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md#L5-L9)). Before deploying a production transport, allocate a new ID by submitting a PR to this registry.

## Implementing the CustomTransport Trait

A functional implementation requires defining address formatting and connection logic. The reference `TestTransport` in [`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) demonstrates the interface:

- **`addr()`**: Returns a `CustomAddr` containing your transport ID and opaque address data
- **`connect()`**: Establishes a stream and returns a `CustomSender` capable of sending and receiving raw packets

Your implementation handles the wire protocol—whether Bluetooth L2CAP frames, QUIC overlays, or encapsulated UDP—while iroh manages higher-level connection state.

## Configuring the Endpoint Builder

To activate your transport, use the `Endpoint::builder()` pattern. The implementation in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) exposes methods to inject transports and control path selection:

1. **Inject the transport** using `.preset()` as shown in the [custom-transport example](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) ([lines 71-87](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L71-L87))
2. **Optionally disable defaults** with `.clear_ip_transports()` and `.clear_relay_transports()` to force traffic through your layer
3. **Set a path selector** via `.path_selector()` to prioritize your transport over built-in options

The example configuration follows this pattern:

```rust
let mut builder = Endpoint::builder(presets::N0)
    .secret_key(secret_key)
    .preset(transport)               // Inject the custom transport
    .path_selector(Arc::new(PreferTestTransport));
if !self.keep_ip { 
    builder = builder.clear_ip_transports(); 
}
if !self.keep_relay { 
    builder = builder.clear_relay_transports(); 
}

```

## Writing a PathSelector

The `PathSelector` trait determines which network path iroh uses for a connection. To prefer your custom transport, implement the trait to check for `Addr::Custom` variants. The `PreferTestTransport` implementation in the example ([lines 37-69](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L37-L69)) shows the selection logic:

```rust
impl PathSelector for PreferTestTransport {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut selection = PathSelection::none();
        
        // Prioritize paths using the custom transport ID
        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;
        }
        
        // Fall back to lowest-RTT path otherwise
        // ...
        selection
    }
}

```

## Complete Usage Example

Below is a practical example binding an endpoint with a test transport and forcing it to win path selection:

```rust
use iroh::{Endpoint, SecretKey};
use iroh::endpoint::{Builder, PathSelector, PathSelectionContext, PathSelection, Addr};
use std::sync::Arc;

// From iroh/src/test_utils/test_transport.rs
use iroh::test_utils::test_transport::{TestTransport, TEST_TRANSPORT_ID};

struct PreferCustomTransport;

impl PathSelector for PreferCustomTransport {
    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
    }
}

fn build_endpoint(secret: SecretKey, transport: Arc<TestTransport>) -> Builder {
    Endpoint::builder(presets::N0)
        .secret_key(secret)
        .preset(transport)                    // Inject custom transport
        .path_selector(Arc::new(PreferCustomTransport))
        .clear_ip_transports()                // Optional: disable IPv4/IPv6
        .clear_relay_transports()             // Optional: disable relay
}

```

When executed, the connection resolves through the custom transport layer, verified by checking the selected path type ([lines 66-74](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L66-L74) in the example).

## Summary

- Enable **`unstable-custom-transports`** in [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml) to unlock the custom transport API
- Implement **`CustomTransport`** in [`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) to define packet movement and addressing
- Register your transport with a unique **16-bit ID** documented in [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md)
- Use **`Endpoint::builder()`** with `.preset()` to inject your implementation
- Implement **`PathSelector`** to prioritize `Addr::Custom` paths over standard IP or relay connections
- Reference the [custom-transport example](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) for working integration patterns

## Frequently Asked Questions

### What version of iroh supports unstable-custom-transports?

The feature is available in recent mainline builds and 0.x releases, though marked unstable because the API may evolve. Check the repository's [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml) for the exact feature flag definition and ensure you're tracking a compatible git revision.

### Can I use multiple custom transports simultaneously?

Yes. The `Builder` accumulates transports via multiple `.preset()` calls, and the `PathSelector` receives all available paths including multiple custom types. Your selector logic can prioritize between them based on transport ID or other path quality metrics.

### How do I allocate a new transport ID for my protocol?

Submit a pull request adding your transport ID and description to [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md). IDs are `u16` values where the range is split between reserved internal transports, experimental user transports, and stable allocations. Using an unregistered ID risks collisions with future iroh updates.

### Does enabling custom transports affect existing IPv4/IPv6 connections?

No, built-in transports remain active unless explicitly cleared. Call `.clear_ip_transports()` and `.clear_relay_transports()` on the builder to restrict the endpoint to only your custom implementation, or leave them enabled to allow fallback when your transport is unavailable.