Build a Bonafide App
Getting Started
Build an app on Bonafide. A reference implementation exists, and it powers Pools in production.
Want to build this yourself? Everything on this page gets you there. Want help instead? We've been building on Bonafide longer than anyone. Build something with us →
What you're signing up for
Any app that people use over the internet has to solve the same handful of hard problems. Two parties need to establish a connection. Each one needs to control what the other can see. Shared content has to be created and changed without the two sides disagreeing about what happened. And that content has to live across two independent databases instead of one shared one.
Bonafide solves those problems once, at the protocol level, and puts the user in control of them. Neither the app nor a platform in the middle owns them. As a developer, you build on top of that. You define what your app shares, and Bonafide takes care of how it gets there safely.
What exists today
- A reference implementation:
bonafide-agent, the library that implements the spec, and a router service. It powers Pools, which is in production. - Tooling in progress: a
create-bonafide-apptemplate, so you won't be starting from a blank folder. - A documented protocol. See the Bonafide Protocol spec. The agent library exists so you don't have to read it cover to cover. Day to day, it hides the protocol behind a small registration and data mutation API, and the spec is there when you need ground truth.
Here's what building an app involves.
1. Define your schema, or use an existing one
If your app isn't built on a domain that already exists, define your own: what content types your app shares, what fields they carry, and how they relate to each other. The spec's content model chapter describes the mechanism every domain uses. The Pools domain vocabulary is a complete, real example, and a good template to work from.
2. Register your handlers
Your app tells the agent library what it shares and how to handle each kind of message. This is the entire integration surface, and it's a handful of function calls at startup.
import {
registerNamespace,
registerContentValidator,
registerMessageHandler,
MESSAGE_HEADER_TYPE_CONNECTION,
MESSAGE_HEADER_TYPE_SUBSCRIPTION,
MESSAGE_HEADER_TYPE_CONTENT,
MESSAGE_HEADER_TYPE_COMMAND
} from '@protegra/bonafide-agent-7';
import myDomainContentTypes, { MY_DOMAIN_NAMESPACE } from './namespace/my-domain-schema.js';
import { handleConnectionMessage } from './services/connection-handlers.js';
import { handleSubscriptionMessage } from './services/subscription-handlers.js';
import { handleContentMessage } from './services/content-handlers.js';
import { handleCommandMessage } from './services/command-handlers.js';
export function registerMyDomain() {
// Tell the library what content types your domain defines
registerNamespace(MY_DOMAIN_NAMESPACE, myDomainContentTypes);
// Wire up the four message types every domain handles
registerMessageHandler(
MESSAGE_HEADER_TYPE_CONNECTION,
(context, cxn, operation, messageContent) =>
handleConnectionMessage(context, cxn.cxnId, operation, messageContent)
);
registerMessageHandler(
MESSAGE_HEADER_TYPE_SUBSCRIPTION,
(context, cxn, operation, messageContent) =>
handleSubscriptionMessage(context, cxn.cxnId, operation, messageContent)
);
registerMessageHandler(
MESSAGE_HEADER_TYPE_CONTENT,
(context, cxn, operation, messageContent) =>
handleContentMessage(context, cxn.cxnId, operation, messageContent)
);
registerMessageHandler(MESSAGE_HEADER_TYPE_COMMAND, handleCommandMessage);
// Tell the library how your content types participate in access control
registerContentValidator(MY_DOMAIN_NAMESPACE, {
subbables: ['topLevelThing'], // own `ck` is the subscription label
subbableRelated: ['memberThing'], // travels under its parent's label
unshareables: [], // never shared under any subscription
subUnrelated: ['alias'], // outside the subscription system entirely
getParentCK: content => content.topLevelThingCK ?? null
});
}
The content validator is where you tell Bonafide how your content types relate to access control.
| Category | What it means | Example |
|---|---|---|
subbables |
People subscribe to these directly. The item's own key is the subscription label. | A pool, a thread |
subbableRelated |
Travels with its parent, under the parent's label. | A membership record |
unshareables |
Never shared under any subscription. | |
subUnrelated |
Outside the subscription system entirely. | alias |
getParentCK tells the library how to find an item's parent, so it knows which label applies.
Once this is registered, connection handshakes, access-control enforcement, propagation, and persistence are handled underneath. Your app code works against the content types you just declared.
Today, registering your domain means registering it against Bonafide's hosted infrastructure, the same infrastructure that runs Pools. We're building toward full federation, where independent operators run their own router and user database administration and interoperate with ours. We're targeting the end of 2026. Until then, building a Bonafide app means building on our infrastructure, and standing up your own isn't possible yet.
3. Conformance testing (coming soon)
If you're building on an existing domain like Pools, we're building a conformance test suite so you can check that your implementation matches the domain's contract before going live. It's the same process we're using to validate our own Pools implementation. It isn't available yet, and we'll update this page when it ships.
4. Go live
Once your implementation is ready, Bonafide checks it for conformance before it starts interoperating with other apps in the same domain. The automated version is coming alongside the test suite. For now it's a manual conversation with us.
Building an interoperable app in the Pools domain
Pools is a connection-scoped grouping. Members join a pool, post content into it, react to posts, and start threaded conversations under any post. No central server decides who can see what.
Posts and reactions carry ownership and propagation rules that say how far they can travel beyond the original pool. Threads get their membership from the post they're attached to. And everything (pools, posts, members, threads, messages, reactions) works the same way between any two apps that implement the bonafide.pools domain, the way two different email clients work together. See the Pools domain vocabulary for the full content model.
Building an interoperable app in the messaging domain
Coming soon. We haven't published the bonafide.messaging domain vocabulary yet. Check back, or tell us you're interested → and we'll let you know when it's ready.
Adding new domains
Pools and Contacts won't be the only domains, and we won't be the only ones defining them. New domains go through the same process we use for changes to the base spec, and it's public.
- Propose a new domain as a GitHub issue, or as a pull request that adds a file under
domains/in the Bonafide Protocol repository. - A proposal needs to show real engagement. Say what the domain is for, what content types and relationships it needs, and why an existing domain doesn't already cover it.
- Review and the final decision stay with Bonafide Technologies Inc. for now. Every submission, discussion, and decision, including the reasoning, is posted publicly in the issue thread. Nothing happens behind closed doors.