# Matchbox: WebRTC peer-to-peer networking for Rust, in the browser or native

> Matchbox is a Rust workspace that gives applications UDP-like, unordered and unreliable peer-to-peer connections over WebRTC, with a socket abstraction for WebAssembly and native builds, a signalling server library, a ready made full-mesh server, and a Bevy integration. The signalling service is only needed to set connections up, and the crates are dual-licensed under MIT and Apache-2.0.

**johanhelsing/matchbox** — Painless peer-to-peer WebRTC networking for rust wasm (and native!)

- Repository: https://github.com/johanhelsing/matchbox
- Stars: 1,153 · Forks: 113
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/johanhelsing-matchbox

## UDP-like networking over WebRTC

Matchbox is described as painless peer-to-peer WebRTC networking for Rust's native and wasm applications, and the goal is stated precisely: to enable UDP-like, unordered, unreliable p2p connections in web browsers or native, to facilitate low-latency multiplayer games.

That phrasing is the whole design. Multiplayer games do not want a reliable ordered stream, they want something closer to a game UDP protocol, where a dropped position update is replaced by the next one a few milliseconds later rather than retransmitted and delayed. WebRTC data channels have an unordered and unreliable mode, and that is the mode Matchbox puts at the centre of its API.

It is not a limitation to work around, it is the feature. The README states that Matchbox supports both unreliable and reliable data channels, with configurable ordering guarantees and variable packet retransmits. So the same abstraction gives you a fire-and-forget channel for position updates and an ordered channel for events that must arrive, and the decision is per channel rather than per library.

The target user is a Rust game developer targeting the browser, and the second audience is the same developer targeting a native build, because the socket abstraction works in both. The primary language is Rust, the default branch is `main`, and the crates are published on crates.io with docs on docs.rs. The last push to `main` was on 2026-06-02 and the recent releases are v0.14.0 from 2026-02-13, v0.13.0 from 2025-10-25 and v0.12.0 from 2025-05-22. The repository is not archived.

The project also credits Ernest Wong's Dango Tribute experiment, saying that `matchbox_socket` is heavily inspired by its wasm-bindgen server_socket and that Matchbox would probably not exist without it. Acknowledging the specific ancestor of a design is a good sign, because it tells you what to read if the API puzzles you.

## Signalling sets up the connection, then the server steps aside

The how it works section is short and the mechanism is worth following closely, because it explains both the cost and the benefit of the approach.

WebRTC allows direct connections between peers, but in order to establish those connections some kind of signaling service is needed. `matchbox_server` is such a service. Once the connections are established, however, data will flow directly between peers, and no traffic will go through the signalling server.

That last clause is the entire appeal. A traditional multiplayer backend relays every message, which means the backend is the bandwidth cost, the latency, the scaling limit and the single point of failure. Here the server only introduces peers to each other.

The flow is the standard WebRTC one. When a client wants to join a p2p mesh network, it connects to the signalling service, and the signalling server then notifies the peers that have already connected about the new peer, sending a `NewPeer` event. The peers then negotiate a connection through the signalling server, where the initiator sends an offer and the recipient responds with an answer. Once peers have enough information relayed, an `RTCPeerConnection` is established for each peer, which comes with one or more data channels.

The deployment requirement follows from this. The signalling service needs to run somewhere all clients can reach it over http or https connections, and in production this usually means the public internet.

There is an important consequence for the topology, and it is the one to think about before choosing this project. The description of `matchbox_server` is a ready to use full-mesh signalling server. In a full mesh, every peer holds a connection to every other peer, so the number of connections grows with the square of the player count, and every peer has to maintain and pay for all of them. The project ships live two-player and four-player demos, which is a fair indication of the scale the design assumes.

## Four crates with clear boundaries

The workspace is split into crates that mirror the layers of the problem, and the `Cargo.toml` at the root lists the members:

```toml
[workspace]
members = [
  "bevy_matchbox",
  "matchbox_protocol",
  "matchbox_socket",
  "matchbox_server",
  "matchbox_signaling",
  "examples/*",
]
```

`matchbox_socket` is the socket abstraction for Wasm or Native, and it carries a `ggrs` feature that provides a socket compatible with ggrs, the rollback netcode library. That feature is what makes the project usable with the usual Rust networking stack for peer-to-peer games rather than only with hand-rolled message handling.

`matchbox_signaling` is a signaling server library with ready to use examples, and `matchbox_server` is the ready to use full-mesh signalling server built from it. Separating the library from the ready made server is the useful division: if the default mesh does not fit your game, you can build your own signalling service on the protocol rather than forking the server.

`matchbox_protocol` is the shared protocol layer, which is what lets the socket, the server and the Bevy integration agree on the messages, including the `NewPeer` event and the offer and answer negotiation.

`bevy_matchbox` is the integration for the Bevy game engine, and it is a separate crate rather than a feature because Bevy has its own dependency tree and its own version cycle.

The resolver setting in the same manifest is a small but telling detail. The comment reads `resolver = "2"` with the note that it is important because Bevy and WGPU need it. That is the kind of comment that saves an afternoon for someone integrating a Bevy crate into a workspace that was on the older resolver.

For a game, the usual dependency is the socket crate plus, if you use Bevy, the Bevy integration. The server crates are for whoever operates the signalling service.

## Bevy support is version-paired, and the table shows the cost

The README carries a compatibility table between Bevy and `bevy_matchbox`, and it is the most informative thing in the documentation:

| bevy | bevy_matchbox |
| 0.18 | 0.14, main |
| 0.17 | 0.13 |
| 0.16 | 0.12 |
| 0.15 | 0.11 |
| 0.14 | 0.10 |
| 0.13 | 0.9 |
| 0.12 | 0.8 |
| 0.11 | 0.7 |
| 0.10 | 0.6 |
| < 0.9 | Unsupported |

Two things jump out. The current Bevy release, 0.18, is served by `bevy_matchbox` 0.14, and also by `main`, which tells you the integration is developed against the tip of the branch and released on a lag. And the versions run in lockstep, one Bevy minor for one `bevy_matchbox` minor, which means every Bevy upgrade is also a `bevy_matchbox` upgrade.

That is the cost of the integration, and it is worth pricing before you commit. A Bevy project on 0.15 that wants the current Matchbox has to move to 0.18, and a Bevy project on 0.18 that wants a stable `bevy_matchbox` has to take 0.14 rather than `main`. The row that says anything below 0.9 is unsupported means the integration is a young thing with a short history, which is normal and fine, as long as you know it.

The `examples/` directory shows what the integration buys you in practice. There is `bevy_ggrs`, an example browser game using `bevy` and `bevy_ggrs`, with a live two-player demo and a live four-player demo, and there is `simple`, a simple communication loop using `matchbox_socket`. There are also `custom_signaller`, `error_handling` and `async_example`, which between them cover the three situations you will hit: your own signalling service, what to do when the connection fails, and the asynchronous API without a game loop.

There is also a tutorial for usage with Bevy and GGRS, and a separate showcase of projects using Matchbox, from the NES Bundler and the Cargo Space co-op game to Lavagna, a collaborative blackboard for online meetings.

## What application code actually has to do

The README is emphatic that the WebRTC machinery is hidden from Rust application code, and then describes the entire client-side surface in three bullets.

You create a new socket and give it a signalling server url. You await the message loop future that processes new messages. And that is the integration work.

The second point has three documented ways to satisfy it. If you are using Bevy, it is done automatically by `bevy_matchbox`, as the `bevy_ggrs` example shows. Otherwise, if you are using WASM, `wasm-bindgen-futures` can help, as in the `simple` example. Alternatively the future can be polled manually, once per frame.

That last option is the one to understand for a non-Bevy game, because it defines what the main loop looks like. A socket whose message loop is a future that you poll once per frame is a socket that does not block your frame, and that is the property you want in a game loop where a dropped frame is a dropped frame.

The lifecycle is equally small. You can hook into the lifecycle of your socket through the socket's API, such as connection state changes, and you can send packets to peers using the socket through a simple, non-blocking method. Non-blocking send is the counterpart to the polled future, and together they are the whole contract: the library never blocks your game loop, and the game never blocks on the network.

The dependency for a project is short. In a Cargo manifest it is the socket crate, at the version matching your release, plus the Bevy integration if you are on Bevy, and the `ggrs` feature if you want the rollback netcode compatible socket.

## Licence, cadence, and where WebRTC is the wrong answer

The licence is the most permissive option available. All code in the repository is dual-licensed under either the MIT License or the Apache License, Version 2.0, with the licence files at the root, and the README adds the standard dual-licensing clause for contributions. The project metadata records the licence as Apache-2.0, which matches the more restrictive of the two and is the sensible default for a project that offers both.

The version cadence is regular enough to rely on: a release roughly every four to five months across the last three tags, v0.12.0 in May 2025, v0.13.0 in October 2025 and v0.14.0 in February 2026, with the last push on 2026-06-02. The crate is still pre-1.0, so the minor version can carry breaking changes, which is the norm in the Rust ecosystem and something to plan for rather than a reason to avoid it.

There are three cases where Matchbox is the wrong tool, and the first is the most important.

Matchbox is a mesh library. A full mesh means every peer connects to every other peer, so the connection count grows with the square of the player count and every client pays the bandwidth for all of them. A game with twenty or thirty players, or a game with a dedicated authoritative server, wants a star or a client-server topology, and the answer there is a relay rather than a mesh.

The second is delivery guarantees. The library does support reliable and ordered channels, but the whole design is built around the unreliable case, and a game that cannot proceed without every message arriving is a game that wants a different transport.

The third is operational reachability. The signalling service has to be somewhere all clients can reach over http or https, which in production usually means the public internet, which means it is an availability dependency for connection establishment even though it is not a data path. A game for a closed network, an offline LAN party or an air-gapped environment has to run the signalling service itself somewhere reachable, and the ready made server exists for exactly that.

The alternative approach, and the one to weigh against, is a WebSocket or UDP relay for the whole session. The difference in approach is that the relay keeps the server in the data path permanently, which costs bandwidth and adds latency to every message, but it gives you one topology, one authorisation point, and message ordering you control. Matchbox moves that cost onto the clients and onto the complexity of peer negotiation. For a small peer-to-peer shooter, that is the right trade. For a game with a persistent world and many players, it usually is not.

## Conclusion

Adopt Matchbox if you are building a multiplayer game in Rust for the browser and you want peer-to-peer traffic without running a relay for every byte, because the signalling server is only involved in setting up the connections and the crates cover the socket, the protocol and the Bevy integration. Do not adopt it if you need a star topology with a central authority, if you need guaranteed delivery, or if you are not willing to run a signalling service that your clients can reach. Verify three things first: that the `bevy_matchbox` version you pin matches your Bevy version, since the compatibility table pairs them one to one, that your deployment can serve the signalling service over http or https at an address every client can reach, and that the reliability mode you choose matches what your game can tolerate losing.

## FAQ

### What does the Matchbox project do?

It provides peer-to-peer WebRTC networking for Rust applications, native or WebAssembly, with the goal of UDP-like, unordered and unreliable connections for low-latency multiplayer games. It supports both unreliable and reliable data channels, with configurable ordering guarantees and variable packet retransmits.

### Which crates make up the Matchbox workspace?

The workspace contains `matchbox_socket`, a socket abstraction for Wasm or Native with a `ggrs` feature, `matchbox_signaling`, a signalling server library, `matchbox_server`, a ready to use full-mesh signalling server, `bevy_matchbox`, the Bevy integration, and `matchbox_protocol`, the shared protocol layer.

### Does data traffic go through the Matchbox signalling server?

No. The signalling service is needed to establish the connections, and the server notifies existing peers of a newcomer with a `NewPeer` event, but once the connections are established data flows directly between peers and no traffic goes through the signalling server.

### Which Bevy versions does bevy_matchbox support?

The README's compatibility table pairs them one to one: Bevy 0.18 with bevy_matchbox 0.14 or main, 0.17 with 0.13, 0.16 with 0.12, 0.15 with 0.11, down to Bevy 0.10 with 0.6. Anything below Bevy 0.9 is listed as unsupported.

### What licence is Matchbox released under?

All code in the repository is dual-licensed under either the MIT License or the Apache License, Version 2.0, with the licence files at the repository root, and contributions are dual-licensed the same way unless stated otherwise.

## Sources

- [Issues](https://github.com/johanhelsing/matchbox/issues)
- [johanhelsing/matchbox on GitHub](https://github.com/johanhelsing/matchbox)
- [License: Apache-2.0](https://github.com/johanhelsing/matchbox/blob/main/LICENSE)
- [README](https://github.com/johanhelsing/matchbox/blob/main/README.md)
- [Releases](https://github.com/johanhelsing/matchbox/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/johanhelsing-matchbox
