Open-source project
dmotz/trystero avatar
dmotz/trystero

Trystero: WebRTC matchmaking without your own signalling server

✨🤝✨ Build instant multiplayer webapps, no server required — Magic WebRTC matchmaking over BitTorrent, Nostr, MQTT, IPFS, Supabase, and Firebase

2,770 stars160 forksTypeScriptMIT

At a glance

What is it?
Trystero is an MIT-licensed TypeScript library that connects browsers directly over WebRTC and borrows public networks for signalling. It removes matchmaking infrastructure, but it also hands peer discovery to someone else's relay.
Who is it for?
Adopt Trystero for browser games, shared canvases, small group chat and other room-sized apps where you would otherwise deploy a signalling server and do not want to. Do not adopt it if you need a central authority to authenticate users, persist history, or guarantee that a room is reachable at a moment you choose.
Can I use it commercially?
Yes. MIT 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 last received commits 7 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The signalling server Trystero removes

WebRTC gives browsers a way to talk directly, but the two sides still have to find each other first. That exchange of session description data normally means a matchmaking server you deploy, pay for and keep alive. Trystero's README states the project abstracts that away and offers several strategies for connecting peers: BitTorrent, Nostr, MQTT, Supabase, Firebase, IPFS and a self-hosted WebSocket relay.

The audience is narrow and identifiable. It is front-end developers building multiplayer games, collaborative editors, shared whiteboards or small group chat, where the number of simultaneous peers per room is modest. The README explains why rooms exist at all: browsers can only handle a limited number of WebRTC connections at a time, so it recommends dividing users into groups. That is a design constraint, not a marketing line, and it should shape how you model your app before you write any code.

What actually flows through the relay

The architecture separates two things that are easy to conflate. Signalling traffic is small: peer information, SDP, goes through whichever strategy you chose. Application data does not. The README states that beyond peer discovery, your app's data never touches the strategy medium and is sent directly peer-to-peer and end-to-end encrypted between users.

On top of raw WebRTC, Trystero adds its own layer. Rooms and broadcasting, automatic serialization and deserialization of data, metadata attached to binary data and media streams, automatic chunking and throttling of large data, progress events and promises for transfers, session data encryption, and React hooks. There is also a server-side path: peers can run on Node, Bun and Deno.

The strategy is swappable through the import path rather than an option object. The default package runs on Nostr; @trystero-p2p/mqtt, @trystero-p2p/torrent, @trystero-p2p/supabase, @trystero-p2p/firebase, @trystero-p2p/ipfs and @trystero-p2p/ws-relay are alternatives. Same API, different discovery medium, which is the cleanest part of the design. It also means the failure characteristics of your app change with a one-line import edit, and those characteristics are not identical across strategies.

Install Trystero and join a room

Install with npm, then import joinRoom from the default package. The README gives this exact pair of steps, and a CDN alternative via esm.run for projects with no package manager.

bash
npm i trystero
js
import {joinRoom} from 'trystero'

Joining needs two arguments: a config object with a required appId, and a room ID. The README asks that appId be a completely unique identifier for your app. Two footnotes qualify that: with Firebase, appId should be your databaseURL, and with Supabase, your project URL.

js
const config = {appId: 'san_narciso_3d'}
const room = joinRoom(config, 'yoyodyne')

From there, peer events are plain property assignments. The README's examples log a peer ID on join and leave, and route incoming media streams into a video element. To stop, call room.leave(). The local user's own ID is available as an import named selfId.

js
room.onPeerJoin = peerId => console.log(`${peerId} joined`)
room.onPeerLeave = peerId => console.log(`${peerId} left`)

Custom messages go through makeAction. You create a named action once, then call send with optional targeting. Binary payloads such as blobs and TypedArrays are handled automatically and arrive as an ArrayBuffer, per the README.

js
const drink = room.makeAction('drink')
drink.send({drink: 'negroni', withIce: true}, {target: friendId})
drink.onMessage = (data, {peerId}) =>
  console.log(`got a ${data.drink} from ${peerId}`)

Actions can also be request/response. The README shows makeAction with kind: 'request' and an onRequest handler, then an await on request() with a target and timeoutMs. A requestMany() variant resolves with a peer-labeled result for every target and takes an onResult callback so you can react as each peer answers. In TypeScript, makeAction accepts a type parameter, so a CursorMove type can be attached to the action name.

Where the no-server promise breaks down

Removing your signalling server does not remove dependency on a server. It moves that dependency to a public network or a third-party service you do not operate. The README is direct about this: the default package runs on the Nostr network. If that network degrades, or a relay refuses your traffic, your matchmaking degrades with it. The same applies to MQTT brokers, IPFS, and the hosted Supabase and Firebase paths.

Supabase and Firebase are the sharpest trade-off. They are not decentralised, and they require setup, which the README covers in dedicated Supabase setup and Firebase setup sections. If you pick one of those, you have reintroduced an account, a project URL and a service that can rate-limit or bill you. You have avoided writing a signalling server, not avoided infrastructure.

Two more constraints are worth stating plainly. The room model exists because of a browser limit on concurrent WebRTC connections, so large single-room audiences are out of scope by design. And the README has a Troubleshooting connection issues section, which is itself an admission that connections fail in ways users will notice. NAT traversal, restrictive corporate networks and symmetric NAT are the usual suspects; the documentation does not promise a relay fallback that fixes every case. If your users sit behind hostile networks, test that before you ship, not after.

PeerJS, the self-hosted relay, and what changes

PeerJS is the obvious comparison, and the search data around Trystero includes it. The difference is in who runs the signalling path. PeerJS expects you to point at a PeerServer, either the public one or your own deployment. Trystero inverts that: it treats signalling as a commodity and lets you pick the medium, with a self-hosted WebSocket relay available as one of the choices via @trystero-p2p/ws-relay.

That relay option matters more than it looks. It is the escape hatch for teams that want Trystero's room and action abstractions but do not want to depend on a public network. You get the same API surface and keep control of the signalling endpoint. The cost is that you are back to running something.

Trystero also adds abstractions PeerJS does not ship in the same shape: chunking and throttling for large payloads, progress events and promises for transfers, metadata on binary data and streams, session data encryption, and React hooks. Whether those save you time depends on whether you are sending files and media or just small messages. For a cursor-position feed, they are overhead you will not notice.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-24. Recent releases are 0.25.4 on 2026-08-30, 0.25.3 on 2026-07-13 and 0.25.2 on 2026-06-11. That is a steady cadence on the 0.25 line, but it is still a 0.x version, which in practice means the maintainers have not promised API stability.

The repository is a pnpm monorepo: packages/, a workspace file, tsdown for builds, oxlint and oxfmt for lint and format, Playwright for browser tests, plus separate Node, Bun and Deno test scripts. There is a changeset workflow for versioning and publishing. If you vendor or patch Trystero, that layout is what you are working against, and the strategy packages are split, so a fix may land in one package rather than the core.

The licence is MIT. That permits commercial use, modification and redistribution with the licence and copyright notice preserved. It is a permissive licence, so the practical question is not legal exposure from the code but the terms of whatever signalling medium you select. Public relays and hosted services have their own acceptable-use rules, and MIT says nothing about them. This is not legal advice; check the terms of the specific network your app depends on.

Editorial conclusion

Adopt Trystero for browser games, shared canvases, small group chat and other room-sized apps where you would otherwise deploy a signalling server and do not want to. Do not adopt it if you need a central authority to authenticate users, persist history, or guarantee that a room is reachable at a moment you choose. Before committing, read the Which strategy should I choose section of the README, run your own test against the strategy you pick, and confirm the relay or network you depend on is acceptable for your users' networks.

Frequently asked questions

What is Trystero?

Trystero is a TypeScript library for building multiplayer web apps that connect browsers directly over WebRTC, with peer discovery handled by BitTorrent, Nostr, MQTT, Supabase, Firebase, IPFS or a self-hosted WebSocket relay instead of your own matchmaking server. It is published under the MIT licence and installs from npm as trystero.

Which signalling strategy should I choose for Trystero?

The README has a Which strategy should I choose section and a strategy comparison. The default package runs on Nostr, and other strategies are selected by changing the import to a package such as @trystero-p2p/mqtt or @trystero-p2p/ws-relay. Supabase and Firebase require their own setup, documented in separate README sections.

Can Trystero peers run on a server instead of a browser?

Yes. The README states peers can run server-side on Node, Bun and Deno, and the repository includes separate test scripts for each of those runtimes.

Official sources

  1. dmotz/trystero on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community

Where developers are discussing it

Posts on Hacker News, dev.to and Lobsters that link to this repository, found by our scan of those communities. 1 in the last 30 days; newest first.

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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dmotz-trystero.svg)](https://hysenlabs.com/projects/dmotz-trystero)