The Unity transport that you drop into NetworkManager in place of UnityTransport supports two connection modes. One field selects the mode:
Everything else about your game code stays the same. The mode decides who peers dial, which fields the transport reads, and the datagram payload ceiling it advertises.

Choosing Direct or Relay

Pick Direct when you control the host’s network path: LAN play, a port-forwarded host, or a dedicated server. Pick Relay when the host is an ordinary player’s machine behind NAT, or when you do not want players to learn each other’s addresses.

Transport fields by mode

The high-altitude integration (L-HI, Netcode for GameObjects) configures a ClutchCallTransport component. The low-altitude integration (L-LO, raw NetworkDriver / Netcode for Entities) configures a ClutchCallNetworkInterface struct. In L-HI, after assigning the transport to nm.NetworkConfig.NetworkTransport, you still call nm.StartHost() or nm.StartClient() as usual. In L-LO, you pass the configured interface to NetworkDriver.Create(iface).

Reachability requirements for Direct

Direct mode has no allocation step and no service in the middle. The host binds a QUIC/UDP listener and peers dial it:
For that to work, every client must be able to reach Address:Port over UDP — QUIC is UDP, not TCP, so a TCP-only port forward or load-balancer rule will not carry it. In practice that means one of:
  • the host and clients are on the same LAN;
  • the host’s router forwards the UDP port to the host machine;
  • the host is a dedicated server with a routable address and the UDP port open inbound.
If none of those hold, use Relay instead. There is no fallback inside Direct mode — a client that cannot reach the host simply fails to connect.

Creating an allocation and sharing a join code

Relay mode starts with an allocation, created from the console’s Allocations screen. The allocation represents the session on the relay fleet and yields a short join code. The host shares that join code with its players through whatever channel your game already uses — a lobby, an invite link, matchmaking metadata. The transport does not distribute join codes for you. You can also create an allocation from code. The host mints one, and each player redeems the join code. Do this before you start the NetworkManager, because the endpoint and the token must be in place first:
The API key carries your organisation’s identity, so it is a server credential. Ship it in a host or dedicated-server build only. For a player build, redeem the code through your own matchmaker and hand the result to ApplyAllocation(...), which needs no key:

Redeeming a join code: endpoint and token

A join code is not an address. JoinAsync redeems the code and gets back three things:
  1. A nearest-relay endpoint. Endpoint selection is geo-DNS based, so two players in different regions redeeming the same code can land on different relay addresses for the same session. You do not hardcode a relay hostname.
  2. A JWKS-verified token. The relay checks this token on the connection. Your game code never constructs it; redemption is what produces it.
  3. The session namespace, assigned by the platform. See Session namespace below.
Redemption is a network call, so it is awaitable. Complete it before you start the NetworkManager. This is why Relay mode needs no Address or Port — supplying them has no effect once ConnectionType is Relay. Conversely, a RelayJoinCode is meaningless in Direct mode. For background on how relay tokens are scoped and verified, see Authentication.

Session namespace and max players

SessionNamespace is the session’s identity on the platform. It is path-shaped:
In Relay mode the platform assigns it. Do not choose it. An allocation scopes the namespace to the organisation that owns the API key, so the value you ask for is a suffix, not the whole string. Ask for my-session and you get arena/<your-org-id>/my-session back. This is what keeps two customers apart. Both can ask for the same session name and still get two different namespaces, so neither can reach the other’s tracks. AllocateAsync, JoinAsync and ApplyAllocation all write the assigned value onto the transport for you. Read it back from the allocation if you need it:
Do not overwrite SessionNamespace after an allocation. The relay confines a session to the namespace in its token, so a value you set by hand is refused.
In Direct mode there is no allocation and no token, so you set SessionNamespace yourself. Use the same value on every peer. It applies at both altitudes — the L-LO ClutchCallNetworkInterface takes the same field alongside Role. MaxPlayers is the session’s player ceiling, set on the transport in the L-HI integration. The quickstart’s example value is 16; use the number your game design calls for.

Path MTU per mode

The transport advertises net_max_datagram as the path MTU for the mode in use: Relay is lower because the relay path carries additional framing around your payload. Size any single unreliable message to fit the smaller of the two if you intend to support both modes from one build — otherwise a snapshot that fits in a LAN Direct session will not fit once the same code runs through the relay. Deliveries that map onto QUIC reliable streams are not bounded by this number in the same way; QUIC segments them for you. The ceiling matters for the datagram-carried QoS levels. See the QoS / channel mapping table on the Netcode quickstart screen for which NetworkDelivery value lands on which QUIC carrier.