Cap'n Web: a JavaScript RPC system with no schemas and promise pipelining
JavaScript/TypeScript-native, low-boilerplate, object-capability RPC system
At a glance
- What is it?
- Cap'n Web is an object-capability RPC library for JavaScript and TypeScript that ships as a single npm package with no schema compiler. It is expressive, but the documentation is the real product surface here, so read it before you commit.
- Who is it for?
- Adopt Cap'n Web if you are building a JavaScript or TypeScript service where chatty method calls between client and server are the bottleneck, and you can accept a young API at version 0.12.0. Do not adopt it if you need a schema-first contract enforced outside the type system, or if your team is not already comfortable with TypeScript.
- 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 1 day 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 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Cap'n Web targets: RPC that does not need a schema compiler
Most RPC systems ask you to describe your interface twice: once in a schema language, and once in the code that implements it. Cap'n Web skips the first step. The README states that, unlike Cap'n Proto, Cap'n Web has no schemas and almost no boilerplate, and that the underlying serialization is human-readable JSON with some pre- and post-processing. That single design choice is the whole pitch.
The audience is JavaScript and TypeScript developers who already think in objects and promises. The README lists support for all major browsers, Cloudflare Workers, Node.js, Bun, Deno and other modern JavaScript runtimes, and says the package compresses to under 16 kB with no dependencies. If your service boundary is already a JavaScript object, you can expose it as an RPC endpoint without writing an interface definition file or running a code generator.
The second half of the pitch is the object-capability model. The README describes it as a system where passing functions and objects by reference is supported, and where holding a reference is the permission to use it. That is a different security shape from token-based RPC: there is no separate authorization layer to misconfigure, because the reference itself is the grant. Whether that is a benefit or a hazard depends on how carefully you hand out references.
How the object-capability model and promise pipelining actually work
The mechanism has three visible pieces. On the server, you extend RpcTarget, a class the README shows being returned from a fetch handler through newWorkersRpcResponse. On the client, you open a session with newWebSocketRpcSession or newHttpBatchRpcSession, and what you get back looks like a local object. The library handles the wire format, which the README describes as JSON plus pre- and post-processing.
The interesting part is pipelining. In a normal RPC client, three dependent calls mean three round trips, because each call needs the previous result before it can be issued. Cap'n Web lets you build a chain of calls on a promise that has not resolved yet, then await once. The README's example uses using declarations, no awaits between the calls, and a single await at the end, with the comment that everything above travelled together in one round trip. The documentation calls this the part that makes it fast, and points to packages/docs/src/content/docs/start/pipelining-tour.mdx.
Bidirectional calling is the third piece. Because both ends can hold references to objects on the other end, the server can call back into the client without a separate channel. The README frames this as a consequence of the capability model rather than a feature bolted on top. The docs directory lists separate pages for RpcTarget, RpcStub, RpcPromise, streaming, disposal and the magic map(), which suggests each of these has its own rules rather than being a thin wrapper over one primitive.
One thing to note about the transport layer: the README says it works over HTTP, WebSocket and postMessage() out of the box, and that custom transports are supported. The HTTP batch transport is the one that makes the pipelining example work, because batching is what collapses the dependent calls into one request.
Installing capnweb and making a first call
Installation is a single npm command. The README states there is no build step, no schema compiler and no code generation, so nothing else is required at this stage.
npm i capnwebThe package is published as capnweb, and the README notes that if you want to use `using` declarations in TypeScript, your tsconfig.json needs `"target": "esnext"` and matching `lib` values. That is the one build-level configuration the README calls out, and it matters because the pipelining example depends on `using` for disposal.
A minimal server is a class that extends RpcTarget with a method that returns a value, plus a fetch handler that routes a path to newWorkersRpcResponse. The README shows exactly this shape, with the API mounted at `/api` and a 404 for anything else. Node, Deno and Bun are supported as well, but the README's primary example is a Cloudflare Workers handler.
On the client, a WebSocket session is set up in one line, and then you call a method on the returned object as if it were local.
import { newWebSocketRpcSession } from "capnweb";
let api = newWebSocketRpcSession("wss://example.com/api");
let result = await api.hello("World");
console.log(result);What you should see is the string returned by the server's hello method, with no schema file and no generated client. If you want the pipelining behaviour instead of one call per round trip, the README's HTTP batch example is the one to copy, because it uses newHttpBatchRpcSession and a chain of calls before a single await. Runnable versions of both live in examples/batch-pipelining/ and examples/worker-react/, and the docs site can be run locally with `pnpm install && pnpm run dev:docs`, which the README says embeds the examples as live in-browser playgrounds.
Where Cap'n Web is the wrong tool
The absence of schemas is a trade-off, not a free win. Without a schema, there is no language-neutral contract that a non-JavaScript service can implement against. The README lists py capnweb among the related searches, but the repository describes a JavaScript and TypeScript library, and the docs index covers TypeScript integration rather than cross-language code generation. If your callers are written in Go, Java or Python, the schema-first systems exist precisely for that problem, and Cap'n Web does not solve it.
The second limitation is version maturity. The most recent release is [email protected], published on 2026-08-20, with [email protected] on the same date and [email protected] on 2026-08-12. A 0.x version number means the maintainers have not declared the API stable. The repository layout includes a .changeset/ directory, which is the tooling used to record and publish version bumps, so releases are managed deliberately rather than ad hoc, but that does not make the surface stable.
The third is the capability model itself. Holding a reference is the permission to use it, which means reference leakage is permission leakage. There is no separate token to revoke. The docs include a page at packages/docs/src/content/docs/guides/security.mdx, and the README links it under guides, but the README does not summarise its contents. If your threat model requires revocable, auditable grants that a third party can inspect, this model asks you to reason about object lifetimes instead.
Finally, session loss. The README mentions an example called session-recovery that shows a WebSocket session with a button that kills it, and asks what a disconnect destroys and what it takes to resume. That framing implies a disconnect is not transparent. You need to understand what state survives before you build on top of a long-lived session.
How Cap'n Web compares with tRPC and JSON-RPC
The repository has a comparison page at packages/docs/src/content/docs/guides/comparisons.mdx, and the README names tRPC, JSON-RPC, GraphQL and Cap'n Proto as the things it is measured against. The real difference is where the expressiveness lives.
tRPC gives you end-to-end TypeScript types over HTTP, and it is excellent at that, but each call is a separate request unless you batch explicitly. Cap'n Web's pipelining lets you chain dependent calls before any of them resolve, so the batching is a property of the call graph rather than something you configure. That is a genuine architectural difference, not a marketing one.
JSON-RPC is the closest thing to a lowest common denominator: a request object with a method name and parameters. Cap'n Web also sends JSON, but it can send references to live objects and functions, which JSON-RPC cannot express at all without inventing a handle registry. That is the capability model showing through the wire format.
Against Cap'n Proto, the split is the other way. Cap'n Proto has schemas, cross-language code generation and a binary encoding; Cap'n Web has none of those, and in exchange it works in a browser without a build step. The README is explicit that Cap'n Web is a spiritual sibling to Cap'n Proto by the same author, so this is a deliberate fork in design rather than an accident. If you need the schema for polyglot clients, Cap'n Proto is the answer. If your clients are all JavaScript, the schema is mostly overhead.
Maintenance, licensing and upgrade cost
The repository is not archived, and the last push was on 2026-09-16, which is recent. The release cadence visible in the repository is steady: [email protected] on 2026-08-12, then [email protected] and [email protected] on 2026-08-20. The presence of a .changeset/ directory and a CHANGELOG.md at the top level means version bumps are recorded in the repository, so you can read what changed between releases rather than guessing.
The package is licensed MIT, with the licence file at LICENSE.txt. That is permissive: you can use it commercially, modify it and redistribute it, provided you keep the copyright notice and licence text. It is not a copyleft licence, so it does not impose source-disclosure obligations on your own code. This is a description of the licence text, not legal advice; if your organisation has specific compliance requirements, the file itself is the thing to read.
Upgrade cost is the open question. At 0.x, minor version bumps can carry breaking changes, and the repository does not include a stability policy or a deprecation window. The practical mitigation is the CHANGELOG.md and the .changeset/ entries, which tell you what moved. The repository also ships a separate package, capnweb-validate, at version 0.3.0, which the README's docs index points to under runtime validation. If you depend on the validator, you are tracking two version numbers, not one.
The repository also has a SECURITY.md at the top level, which is where a project states how to report a vulnerability. Its contents are not described in the README, so check it directly if that matters to your process.
Editorial conclusion
Adopt Cap'n Web if you are building a JavaScript or TypeScript service where chatty method calls between client and server are the bottleneck, and you can accept a young API at version 0.12.0. Do not adopt it if you need a schema-first contract enforced outside the type system, or if your team is not already comfortable with TypeScript. Before you commit, read packages/docs/src/content/docs/guides/security.mdx and packages/docs/src/content/docs/guides/sessions.mdx, because those two pages define what holding a capability and losing a session actually mean in this model.
Frequently asked questions
What is Cap'n Web in JavaScript?
It is a JavaScript and TypeScript-native RPC library built on an object-capability model, with no schemas and no code generation. The README describes it as a spiritual sibling to Cap'n Proto that is designed to work over HTTP, WebSocket and postMessage() in browsers, Cloudflare Workers, Node.js, Bun and Deno.
How do I install Cap'n Web?
Install it from npm with the capnweb package. The README states there is no build step, no schema compiler and no code generation, so the install is the whole setup. If you want to use `using` declarations in TypeScript, tsconfig.json needs `"target": "esnext"` and matching `lib` values.
Does Cap'n Web need a schema or a code generator?
No. The README states it has no schemas and almost no boilerplate, and that there is no schema compiler and no code generation. The trade-off is that there is no language-neutral contract for non-JavaScript clients to implement against.
What is promise pipelining in Cap'n Web?
It lets you chain dependent calls on a promise that has not resolved yet, then await once, so the whole chain travels in a single network round trip. The README's HTTP batch example shows three dependent calls with no awaits between them and one await at the end.
Which transports does Cap'n Web support?
The README says it works over HTTP, WebSocket and postMessage() out of the box, and that it can be extended to other transports. The docs index lists separate pages for HTTP batch, WebSocket, MessagePort and custom transports.
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/cloudflare-capnweb)