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

> Learn to use custom transports in iroh with the unstable-custom-transport feature. Route packets over Bluetooth or Tor while retaining QUIC and NAT traversal logic.

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

---

**Enable the `unstable-custom-transports` feature flag to inject user-defined networking layers into iroh's `Endpoint`, allowing you to route packets over bespoke channels like Bluetooth or Tor while retaining higher-level QUIC and NAT traversal logic.**

Iroh is a peer-to-peer networking stack that ships with built-in IPv4, IPv6, and relay transports, but also exposes a pluggable transport API for custom implementations. By activating the `unstable-custom-transports` feature, developers can register their own transport layers—identified by unique transport IDs—and prioritize them using custom path selectors.

## Architecture of Custom Transports in iroh

### The Transport Module and Feature Gating

In [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), the `transports` submodule is conditionally compiled behind the `unstable-custom-transports` flag ([lines 29-48](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L29-L48)). When enabled, it re-exports the low-level transport primitives from the socket layer, including `CustomTransport`, `CustomEndpoint`, and `CustomSender` ([lines 41-46](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs#L41-L46)).

### Transport Identification

Each custom transport must register a unique **transport ID** (a `u16`) in the repository's [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) file. The test transport used in examples reserves ID `0x20` (see [lines 5-9](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md#L5-L9)).

## Implementing the CustomTransport Trait

### Core Interface

A custom transport implements the `CustomTransport` trait defined in the socket layer. The reference implementation, `TestTransport`, lives in [`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) and demonstrates the required interface.

The transport must return a `CustomAddr` containing the transport ID and opaque address data via the `addr()` method. Connections are established through `connect()`, which returns a `CustomSender` capable of sending and receiving raw bytes.

## Configuring the Endpoint Builder

### Enabling the Feature

Add the feature to your [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml):

```toml
[dependencies]
iroh = { git = "https://github.com/n0-computer/iroh", features = ["unstable-custom-transports"] }

```

### Builder Pattern

The `Builder` struct in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) provides methods to inject custom transports and clear defaults. The example in [`iroh/examples/custom-transport.rs`](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)) shows the configuration 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 !keep_ip { builder = builder.clear_ip_transports(); }
if !keep_relay { builder = builder.clear_relay_transports(); }

```

## Selecting Custom Transport Paths

### Implementing PathSelector

To prioritize your custom transport, implement the `PathSelector` trait. The example's `PreferTestTransport` ([lines 37-69](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L37-L69)) demonstrates selecting paths where the remote address matches your transport ID:

```rust
impl PathSelector for PreferTestTransport {
    fn select(&self, ctx: &PathSelectionContext<'_>) -> PathSelection {
        let mut selection = PathSelection::none();
        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 if custom path unavailable
        ctx.select_lowest_rtt(selection)
    }
}

```

## Complete Integration Example

### End-to-End Setup

The following excerpt combines the builder configuration with path selection, mirroring the test harness in [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs):

```rust
use iroh::{
    Endpoint, SecretKey, endpoint::{Builder, transports::Addr, PathSelector, PathSelectionContext},
    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: SecretKey, 
    transport: Arc<TestTransport>
) -> anyhow::Result<Endpoint> {
    Endpoint::builder(presets::N0)
        .secret_key(secret)
        .preset(transport)
        .path_selector(Arc::new(PreferTestTransport))
        .clear_ip_transports()
        .clear_relay_transports()
        .bind()
        .await
}

```

## Summary

- Enable the `unstable-custom-transports` feature flag to access the transport API in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs).
- Implement `CustomTransport` and register a unique transport ID in [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md).
- Use `Builder::preset()` to inject your transport and `clear_ip_transports()`/`clear_relay_transports()` to exclude defaults.
- Implement `PathSelector` to prioritize your custom transport over built-in paths.
- Reference the full example in [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) for a working implementation.

## Frequently Asked Questions

### What is the unstable-custom-transport feature in iroh?

The `unstable-custom-transports` feature flag gates access to iroh's pluggable transport API, allowing developers to inject custom networking implementations alongside or in place of the built-in IPv4, IPv6, and relay transports. When enabled, it exposes the `CustomTransport` trait and related types through the `endpoint::transports` module.

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

Transport IDs are reserved in the [`TRANSPORTS.md`](https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md) file at the repository root. Submit a pull request to n0-computer/iroh to register a new `u16` ID for your transport to avoid collisions with other implementations. The test transport uses ID `0x20` as a reference.

### Can I use custom transports alongside standard IP networking?

Yes. The `Builder` API allows you to add custom transports via `.preset()` while retaining IP transports, or explicitly remove them using `.clear_ip_transports()` and `.clear_relay_transports()` if you want to force traffic through your custom layer only. The example demonstrates verification that the custom path was selected ([lines 66-74](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs#L66-L74)).

### Where can I find a reference implementation of a custom transport?

The `TestTransport` struct in [`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) provides a complete reference implementation, and [`iroh/examples/custom-transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/custom-transport.rs) demonstrates how to configure and select it within an application. Additionally, the low-level transport abstractions are defined in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs).