naia: server-authoritative replication for Rust multiplayer games, in Rust and in the browser
a cross-platform (including Wasm!) networking library built in Rust. Intended to make multiplayer game development dead-simple & lightning-fast
At a glance
- What is it?
- naia is a Rust networking library for multiplayer games that replicates entity state automatically rather than making you write message plumbing, with a shared Protocol type that both ends agree on, a room and channel model for scoping traffic, and a client that can run in a browser over WebRTC. It is ECS-agnostic, with Bevy and macroquad adapters, and it is explicit that prediction and interpolation are your job.
- Who is it for?
- naia fits a team building a multiplayer game in Rust that wants state replication rather than a message bus, and that either uses Bevy or wants to stay ECS-agnostic, and that wants a browser client without writing a WebRTC layer.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The Protocol value, which is the contract both ends agree on
naia is a cross-platform networking library built in Rust, intended to make multiplayer game development dead-simple and lightning-fast, and the primary language is Rust with the licence dual MIT and Apache.
The central idea is that you define a shared Protocol, described as a compile-time list of replicated component types, message types and channel configurations, that both the server and the client agree on. Given that protocol, the division of labour is clean.
The server spawns entities, attaches replicated components, assigns users to rooms, and calls send_all_packets every tick. naia diffs changed fields and delivers them to every in-scope client automatically. The client receives entity spawn, update and despawn events and the current server-side field values with no extra bookkeeping. Either side can send typed messages over ordered-reliable, unordered-reliable or unreliable channels. And the server can delegate authority over a specific entity to a client, allowing client mutations to flow back while the server retains final ownership.
The Protocol value is therefore not a convenience, it is the handshake. Both sides build from the same Protocol value, and a hash mismatch during the handshake causes rejection. That is the mechanism that stops a client built against a different protocol from connecting and desynchronising, and it means the protocol definition is a single source of truth that has to be deployed in step with the binaries.
The internal networking model follows the Tribes 2 Networking Model, which is an interesting choice of precedent. Tribes 2 is a well-known precedent, and its model is about scoping and relevance rather than reliability, which is the same problem a modern authoritative server has when it has hundreds of entities and a few hundred players.
The crate map makes the dependency shape obvious. naia-shared holds the protocol definition, the component derives and the channel types, and it is the crate you write your shared code against. naia-server and naia-client are the core implementations, and naia-bevy-server and naia-bevy-client are the adapters.
Rooms, channels, ticks and the authority delegation model
The quick concepts list in the README is the mental model in six items, and each one earns its place.
A Protocol is the shared type registry, and the hash mismatch on the handshake is what enforces it.
An Entity is any Copy plus Eq plus Hash value your world allocates. naia tracks which entities are replicated and to whom, but never allocates them itself. That is the ECS-agnostic claim in practice: your engine owns entity allocation, naia only observes.
A Room is a coarse membership group, and the rule is that a user and an entity must share a room before replication is possible. The suggested mental image is a match, a zone, a lobby. So rooms are the scoping primitive, and they are what makes a hundred players in a hundred lobbies possible without every player receiving every entity.
A Channel is a named transport lane with configurable ordering and reliability, and both messages and entity actions travel through channels. The reference table is the part to memorise, because the right channel per kind of traffic is most of the performance story:
UnorderedUnreliable has no ordering and no reliability and is for high-frequency telemetry. SequencedUnreliable is newest-wins with no reliability, and is for position updates where a stale packet is acceptable. UnorderedReliable has no ordering but guaranteed delivery, for one-off notifications. OrderedReliable is FIFO with guaranteed delivery, for chat and game events. TickBuffered is per-tick and guaranteed, and is the one for client input. And there is a bidirectional reliable mode for request and response pairs.
A Tick is the server's heartbeat. take_tick_events advances the tick counter, and TickBuffered channels deliver client input at the correct server tick for prediction and rollback. That is the piece that makes rollback networking possible, and it is why the channel type is a per-tick guarantee rather than just a delivery guarantee.
Authority delegation is the last concept and the most interesting. A server entity can be marked Delegated, allowing a client to request write authority; the server grants or denies and can revoke at any time. For a game where the player owns their character, that means the movement authority can sit with the client while the server keeps final say, and the server can pull it back if the client starts sending nonsense.
The crate split, and how a project wires it up
The getting started section shows the dependency setup for the three ways you can use the library, and the shape is the same in all of them: a shared crate, a server crate, a client crate.
The core version, with no ECS:
# shared/Cargo.toml
[dependencies]
naia-shared = "0.24"
# server/Cargo.toml
[dependencies]
naia-server = "0.24"
naia-shared = { path = "../shared" }
# client/Cargo.toml
[dependencies]
naia-client = "0.24"
naia-shared = { path = "../shared" }The Bevy adapter version is the same shape with naia-bevy-server and naia-bevy-client in place of the core crates, and the macroquad version needs only naia-macroquad-client on the client side.
Two details are worth pausing on. The shared crate is referenced by path rather than by version, which is the correct choice, because the protocol definition has to be the same source in both binaries and a version number would let them drift. And the versions shown are 0.24, while the latest release is v0.25.0 from 2026-05-12, with v0.24.0 from 2024-12-13 before it. The snippets in the documentation are one release behind the newest tag, which is a small thing but the kind of thing that produces a confusing version mismatch error on first try.
The demos directory shows the full range, with demos/basic/ described as a minimal working example, demos/bevy/ as a complete Bevy demo and demos/macroquad/ as a macroquad demo. The Cargo.toml workspace listing shows how much is in there, with separate crates for the basic demo's server, shared and three different client flavours, an app client, a wasm_bindgen client and a miniquad client, the Bevy demo's server, shared and client, the macroquad demo's three, and a socket demo with the same three client variants.
That is a lot of demo permutations, and it tells you the design intent. A client that runs in a browser, in a native window, in a Bevy app and in a miniquad app all against the same shared protocol is the point, and it is why the socket layer is separated into socket/client, socket/server and socket/shared rather than being buried in the core.
Transports: UDP natively, WebRTC in the browser, and nothing on mobile yet
The platform support table is short and honest about the gaps.
On Linux, macOS and Windows the transport is UDP through the naia-socket-native crate. In the browser, on wasm32-unknown-unknown, the transport is a WebRTC data channel, which requires enabling the wbindgen feature and building with wasm-pack or trunk. The server always runs natively, and only the client needs WebRTC support for browser targets.
The mobile story is where the honesty matters. iOS and Android natively are marked as not yet supported, blocked on transport_quic, with a pointer to FEATURES.md. The workaround is listed immediately below: iOS and Android via WebView, running the WASM client inside WKWebView on iOS or an Android WebView, using the same build as the browser target.
So a mobile native client in naia does not exist today, and the path to one is a WebView shell around the browser build. That is a real constraint for a game studio whose target is a native mobile client, and it is worth knowing before the architecture is committed to.
The WebRTC choice is the interesting design decision. WebRTC is what lets a browser client work without a plugin, and the data channel over it gives the browser a reliable or unreliable transport as the channel type requires. The cost is that WebRTC is a different transport from raw UDP, so the browser client and the native client are not the same code path at the socket level, even though they share everything above it.
One more platform note. Steam relay through ISteamNetworkingSockets is not built in, but the Socket trait is pluggable, so a community crate can add Steam networking without modifying naia core. That is a good pattern for a library, keeping the vendor specific code out of the core while making room for it.
The repository listing backs all of this up. There is a socket/ directory with the three sub-crates, an adapters/ directory with the Bevy adapters, and separate client/, server/ and shared/ directories at the top level.
What naia does not do, and how it compares to what you already know
The comparison table in the README is unusually direct, and it is the most useful part of the documentation for anyone choosing a library.
Against renet, the difference is that naia replicates ECS state automatically by diffing and sending, where renet is message-only. naia wraps channels as typed Rust generics where renet has send_message and receive_message, and it calls the client handle UserKey where renet calls it ClientId.
Against lightyear, naia is ECS-agnostic where lightyear is Bevy only, and it uses a builder pattern for the component registry where lightyear has a ComponentRegistry. The significant difference is in prediction, where lightyear has a Predicted component and naia does not have a built-in equivalent, and the README is explicit about that: naia supplies the data and you write the prediction and interpolation logic, with a pointer to docs/PREDICTION.md.
Against bevy_replicon, naia has fine-grained per-user scope control through rooms and UserScopeMut where bevy_replicon has a visibility filter, and naia uses server.spawn_entity with a Replicate derive where bevy_replicon uses a Replication marker.
The prediction point deserves emphasis, because it is where a lot of the work in a multiplayer game actually lives. naia gives you the TickBuffered channel that delivers client input at the matching server tick, and it gives you the server state, but the client side prediction and interpolation, the smoothing of remote entities between updates, is code you write. That is a defensible division, since prediction policies differ per game, but it means naia is a replication library rather than a full netcode solution, and the work is on your side of the line.
The repository also shows the testing depth. There are separate crates for loom, for npa and bevy_npa, a compile_fail crate for testing that invalid code does not compile, a spec_tool crate, a bench crate, and a fuzz/ directory. A loom test suite in a networking library is a strong signal, because the races that matter in a replication system are exactly the ones that do not reproduce reliably.
There is a metrics/ crate and a benches/ directory with an iai/ directory, and the release profile sets opt-level to s with lto enabled. The iai crate is commented out of the workspace members with a note that it is disabled until the iai-callgrind-runner is installed in CI or on the PATH, with a pointer to an internal migration plan. That is a temporary local removal rather than a removal from the project.
Editorial conclusion
naia fits a team building a multiplayer game in Rust that wants state replication rather than a message bus, and that either uses Bevy or wants to stay ECS-agnostic, and that wants a browser client without writing a WebRTC layer. It is the wrong choice if you need client side prediction and interpolation out of the box, because the project states those are not built in, and it is the wrong choice for mobile native clients today, since iOS and Android are marked as not yet supported and blocked on transport_quic. Before adopting, read docs/CONCEPTS.md for the mental model and docs/PREDICTION.md for the work you will still have to do, and check the version you pin, since the getting started snippets in the documentation still reference 0.24 while the latest release is v0.25.0.
Frequently asked questions
What is naia and what does it automate?
It is a Rust networking library for multiplayer games that does server-authoritative entity replication. You define a shared Protocol of replicated component types, message types and channel configurations, and then naia diffs changed fields server side and delivers them to in-scope clients automatically, so the client receives spawn, update and despawn events with no extra bookkeeping.
How do I choose the right channel in naia?
The channel mode determines ordering and reliability. UnorderedUnreliable is for high-frequency telemetry, SequencedUnreliable is newest-wins for position updates where stale data is fine, UnorderedReliable is for one-off notifications, OrderedReliable is for chat and game events, and TickBuffered delivers client input at the correct server tick for prediction and rollback.
Does naia support mobile clients?
Not natively. iOS and Android native are listed as not yet supported, blocked on transport_quic, and the workaround is running the WebAssembly client inside WKWebView on iOS or an Android WebView. Native mobile support is blocked on transport_quic.
Does naia include client side prediction and interpolation?
No. The comparison table in the documentation states that naia supplies the data and you write the prediction and interpolation logic, pointing at docs/PREDICTION.md. What naia does provide is the TickBuffered channel, which delivers client input at the matching server tick, and the server state needed to write it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/naia-lib-naia)Was this useful?