meshtastic/web: the SDK underneath the Meshtastic browser client
Meshtastic Web Client/JS Monorepo
At a glance
- What is it?
- The official Meshtastic web client is only one app in this pnpm monorepo. The more consequential artifact is a framework-agnostic TypeScript SDK with slice-based state, a strict protobuf boundary, and seven transport packages. Here is what that buys you, and where it stops.
- Who is it for?
- Adopt meshtastic/web if you need to read or command a Meshtastic device from a browser, Node, or Deno and you want the wire format handled for you. Do not adopt it if you need an offline-first client, a stable public API surface, or a permissive licence.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 3 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 September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What the monorepo actually contains, and who it is aimed at
The README describes this repository as consolidating the official Meshtastic web interface, a domain-driven JavaScript SDK, and a set of runtime-specific transport packages. The packages table lists eleven entries under packages/, plus apps/web. That split matters more than the headline. If you came looking for a hosted client, apps/web is the reference React application deployed at client.meshtastic.org. If you came to build something that talks to a radio, the artifact you want is packages/sdk, with packages/protobufs supplying the generated TypeScript stubs that the README calls the source of truth for every wire-level type.
The audience is therefore narrower than a web app launch. It is developers who need to read state from or send commands to a Meshtastic device from JavaScript, and who would rather not reimplement a frame parser, a protobuf mapping layer, and an ack pipeline themselves. The transport table makes the intended runtimes explicit: browsers get Web Bluetooth and Web Serial, servers get TCP over Node and Deno plus serial over Node, and anything already exposing a network interface is reachable over HTTP. There is no React Native package and no mobile-native transport in the list, so a mobile app is not a supported target out of the box.
The slice layout and the MeshClient orchestrator
The SDK does not organise itself by technical layer. Each feature lives in packages/sdk/src/features/<slice>/ with domain/ for pure TypeScript entities and value objects, application/ for use-cases such as SendTextUseCase and FavoriteNodeUseCase, infrastructure/ for the protobuf-to-domain mappers and admin-message adapters, and state/ for signals-backed reactive stores. A <Slice>Client.ts facade sits on top and exposes readable signals plus command methods. The README names the slices directly: device, chat, nodes, channels, config, telemetry, position, traceroute and files.
Above the slices sits MeshClient in packages/sdk/src/core/client/, described as a thin orchestrator that owns the transport, the queue, the event bus, and one instance of every slice client. The shared kernel also holds the Transport interface, typed pub/sub channels, the packet codec, the queue and xmodem modules, signal helpers, a tslog factory used by every class, and small identifier and error primitives. The stated boundary rule is strict: wire messages enter through the packet codec, get mapped into domain entities inside features/*/infrastructure/*Mapper.ts, and signals only ever expose the domain shape. That is a real constraint on contributors, not a slogan. If you add a slice, you are expected to keep protobuf types out of the state layer, which is more discipline than most SDKs of this size enforce.
Data flow in both directions
The README includes a two-way diagram, and it is worth reading literally. Inbound, a Transport feeds the packet codec, which feeds the EventBus, which feeds slice infrastructure, which writes to signals. From signals, the sdk-react hooks consume state and the UI renders. Outbound, slice application use-cases call MeshClient.sendPacket, which goes to the Queue and then to the Transport.
The codec is where the protocol knowledge lives: the README states it handles the 0x94 0xC3 frame parser, the FromRadio decoder, and the portnum router. This is the part you would otherwise write yourself, and getting it wrong produces failures that look like radio problems rather than parsing problems. Error handling is also specified rather than left to convention. Expected domain errors are returned as Result<T, E> via the better-result package, while exceptions are reserved for programmer errors and truly exceptional conditions. That choice forces callers to handle failure explicitly at the use-case boundary instead of catching a thrown error somewhere up the stack. It also means the SDK's public methods do not have a uniform shape: some return a Result, some return signals, and the distinction is not something the README tabulates.
Getting it running: commands and configuration
The prerequisites are pnpm and, only if you plan to regenerate protobufs, the Buf CLI. Setup is a clone followed by pnpm install at the repository root. To run the reference client, the README gives pnpm --filter @meshtastic/web dev. To build every package, pnpm -r build. Tests run with pnpm -r test, and lint plus format are two commands, pnpm check and pnpm check:fix.
There is no configuration file documented in the supplied material, no environment variable table, and no .env.example. Connection settings are handled through the transport packages rather than through a config surface, which is consistent with the architecture: a transport is constructed with its own connection parameters and handed to MeshClient. If you need to know which keys an HTTP or TCP transport accepts, that detail is not in the README and you would have to read the individual package source.
Adding a transport is a small interface. The README exports it from @meshtastic/sdk/transport as an object with toDevice: WritableStream<Uint8Array>, fromDevice: ReadableStream<DeviceOutput>, and disconnect(): Promise<void>. The SDK does the framing and decoding, so a transport only supplies raw bytes. That is the cleanest extension point in the project.
Testing without hardware, and the fake transport
The SDK ships a testing entry point at @meshtastic/sdk/testing exposing createFakeTransport(), and the contributing guide specifies how it should be used: domain invariants, use-cases against createFakeTransport(), and round-trip mapper fixtures. Vitest is wired at the repository root and picks up packages/* projects.
This is the strongest signal about the project's internal quality bar, because it tells you the maintainers expect slices to be testable without a radio attached. It also tells you what the tests cover. Round-trip mapper fixtures exercise the protobuf boundary in both directions, and use-case tests run against an in-memory transport, so the queue, the event bus and the signal stores are all exercised in CI without hardware. What that does not cover is the transports themselves. A Web Bluetooth connection that drops mid-transfer, or a serial device that re-enumerates, is outside what a fake transport can reproduce. If you are debugging a real link failure, the test suite will not reproduce it for you.
Where the design costs you: versioning, mobile and the API surface
The release history in the supplied material is thin and slightly awkward. Three releases are listed: v2.7.2 tagged as (v2.7.5) in August 2026, v2.7.1 as (v2.7.1) in June 2026, and v2.6.7 as (v2.6.7) in October 2025. The mismatch between the release name and the parenthesised tag on the most recent entry is not explained anywhere in the material, and I cannot tell from this alone whether it is a tagging artifact or two different version schemes. Treat that as an open question rather than a defect, but do check which version you are actually pulling.
The cadence is also uneven. Between October 2025 and June 2026 there is an eight-month gap, then two releases two months apart. For an SDK that maps a wire protocol, that gap matters: a protobuf change upstream during a quiet period leaves the published packages behind the protocol. The README says the protobufs package is generated from meshtastic/protobufs via buf generate, so regeneration is a build step someone has to run and publish.
The second cost is mobile. There is no React Native or mobile-native transport in the packages table, and Web Bluetooth and Web Serial are browser APIs with their own availability constraints. A phone browser is not the same target as a native app.
The third is the API surface itself. The README documents the slice layout and the transport interface, but it does not enumerate the signals or command methods each slice client exposes. You will be reading source to learn that, and the repository offers no compatibility promise for those methods. If you build against them, pin your versions.
The real alternative: talking to the device directly
The obvious alternative is not another web client. It is the protobuf definitions themselves. The meshtastic/protobufs repository is upstream of this one, and the README here states that packages/protobufs is generated from it via buf generate. If you generate your own stubs from those definitions and write your own framing, decoding and queue handling, you take on the packet codec, the 0x94 0xC3 frame parser, the FromRadio decoder, the portnum router, the ack and timeout pipeline, and the xmodem file transfer protocol.
That is the actual trade. Going direct removes the dependency, the slice abstractions, the signals layer and the GPL-3.0 obligation that comes with this repository. It also means every protocol change is yours to track, and the failure modes are yours to debug at the byte level. For a one-off script that sends a text message over TCP, generating stubs and writing a minimal encoder is a reasonable afternoon. For anything that needs channels, telemetry, traceroute and file transfer, reimplementing the codec and the queue is the bulk of the work, and this SDK has already done it. The other alternative worth naming is the official Python CLI, which is a different runtime and a different codebase entirely; it is not a drop-in substitute if your application is JavaScript, and the material here gives no basis for comparing the two feature by feature.
Licence and the cost of keeping up
The repository is GPL-3.0. That is a copyleft licence, and it applies to the SDK and transports you would import, not only to the hosted client. If you distribute software that links these packages, the licence terms follow your distribution. I am not giving legal advice, and the specific question of whether your use counts as distribution or as a derivative work is one for a lawyer. What I can say from the material is that this is not a permissive licence, and the README does not offer an alternative licensing path. For an internal tool that never leaves your network, this is usually a non-issue. For a commercial product, it is the first thing to settle.
Maintenance cost has two parts. The first is dependency churn: a pnpm workspace with eleven packages, a React app, a Tailwind and Radix component library, and generated protobuf stubs means upgrades ripple. The second is protocol drift, described above. The repository does provide the tooling to manage it: pnpm check and pnpm check:fix for lint and format, pnpm -r test for the suite, and a CI workflow referenced in the README badges. Publishing is scripted per package with build:npm, publish:npm, prepare:jsr and publish:jsr, and the README states that all publishable packages ship to both JSR and NPM. That dual-target publishing is unusual and worth knowing if you work in a Deno-first environment, since you can consume the same packages without a Node toolchain.
Editorial conclusion
Adopt meshtastic/web if you need to read or command a Meshtastic device from a browser, Node, or Deno and you want the wire format handled for you. Do not adopt it if you need an offline-first client, a stable public API surface, or a permissive licence. Before committing, verify that the transport you need exists for your runtime (there is no React Native or mobile-native package here), check the published version of each transport on NPM or JSR rather than the repository head, and confirm the GPL-3.0 terms against how you intend to distribute your own code, since the licence is not one you can relicense.
Community notes