Plaza, the flagship client
Plaza is a fast, local-first Nostr client for macOS and Linux, built natively in Zig on
the zig-nostr core. No browser, no
Electron, no interpreter in the binary: the toolkit draws every pixel, and the
event store runs inside the app’s own process rather than behind an IPC boundary.
Two things it tries to do well. Reading needs no account at all, and posting is four clicks away: sign in, create an identity, name yourself or skip it, post. And the feed comes off local disk, so it is already there when the window opens and reconciles with relays behind you.
What it looks like




Real windows, photographed from the running app against real notes from public relays. Every pixel inside the window is the app’s own.
Reading first, identity later
Plaza opens into a feed. Not a welcome wall, not a sign-up form: reading Nostr never needed a key, so the app does not ask for one until you reach for something that does. Browse as a guest for as long as you like.
When you do want in, there are three ways and you pick the one that fits:
- Create an identity. A key is minted for you, in seconds, with nothing to write down up front. Plaza never mints one itself: Notary does, in its own process, and Plaza is only told the public half.
- Bring a key you already have. The packaged app hands your paste straight to Notary through a separate ceremony window, so Plaza itself never sees it.
- Use your own signer. Paste a
bunker://link and Plaza signs through Notary or any other NIP-46 signer, with the key never leaving it.
What you reached for when you were asked is remembered, and completed as soon as signing in allows it, so signing in does not cost you the thing you were doing.
Your key is not in the app
Plaza does not hold your secret key. There is no field in it that can hold one. Notary, shipped inside the app, is started by Plaza as its own process and holds the key there; Plaza asks it to sign and never sees the secret.
The two talk over a channel nothing else on this Mac can reach: no shared file, no fixed port, and no name for anything else to find. Holding the channel is what proves who is asking, so there is nothing for another app to read or guess. Making a key goes the same way, so there is no moment at which Plaza has held one. The ceremony that mints or imports runs in its own window, which is how you get to see the process that is about to hold your key at the moment it starts holding it.
Backing the key up happens in that window too, and for the same reason. A key you cannot take a copy of is not yours, but Plaza asking for one so it could show it to you would make Plaza a process that holds a key. So the window that minted it is the one that hands it back.
There is no exception, including in a source build. Pasting a secret key into the sign-in field does not import it: Plaza refuses it without taking it into memory, leaves the text in the field you typed it in, and opens the keyholder so the key goes to the app that holds keys. A test asserts the old wording is not drawn anywhere, so this cannot quietly come back.
That is the same design as Notary, and it is why connecting an external signer is a first-class path rather than an advanced setting.
What is in it today
A follow-based feed with a curated starter pack so it is never empty · real names
and avatars from kind:0 · inline pictures and link previews · threads with the
chain above a note and replies nested under it · quotes and nostr: mentions
rendered inline · a composer with a mention picker and drafts that survive a quit
· reactions · notifications for what was aimed at you · profile pages with a
following count · your own relay list, read and write · a feed routed by
NIP-65 to where the people you follow actually publish · eight switches
that take parts of the app away for good · places you can enter and leave ·
settings, sessions, and sign-out without lock-in.
There is no follower count anywhere, on purpose. Following is a number someone states about themselves, and it is the length of their own contact list. Followers is not: nothing on your disk can know who follows a person, and the honest options are an indexer’s figure or none. Plaza does not print numbers it cannot check.
Private messages are two milestones out on the roadmap.
Where the feed comes from
Following somebody on Nostr does not mean you will see them. If they publish only to relays you are not connected to, they are simply absent: no error, no empty state, nothing to say a person is missing.
Plaza reads where the people you follow actually write. It takes their NIP-65 relay lists, works out which relays reach the most of them, connects to the ones you are not already on, and asks each relay only about the people who write there. A small relay is asked about its dozen writers rather than about everyone you follow, and anybody no chosen relay carries is asked of your own relays, so nobody falls through.
The relays it picks are not the popular ones. Popularity answers “what should I consider adding”, which is a question for you; connections answer “which relays reach people I cannot otherwise see”, and the popular relays all carry the same crowd. So the two questions get two answers: the suggestions in settings are ranked by how many of your follows use each relay, and the connections are chosen by who they reach.
Measured on my own account, which follows 257 people: between the relays I chose myself and the eight it routes to, 196 of them are covered, and 147 of those by two relays each, so one relay being down does not hide anybody. The remaining 61 are asked of every relay I have, which is what the app did for everyone before any of this. Adding the routed connections is what moved those numbers; without them the eight relays in my own pool were the whole answer.
Carrying people and answering the door are different things, so Plaza checks the second one too. The relay with the most of those writers on my account is paid, and I have not subscribed; it refuses the connection itself rather than the subscription, so there is nothing to authenticate and nothing to negotiate. A relay that will not have us after three tries loses its slot to the next one down and is tried again in six hours. Until Plaza did that, the same count read 202, and fifty of those people were behind a closed door.
It is bounded on purpose: eight routed connections alongside the eight in your own pool, and one is only opened while it would reach somebody not already covered twice, so a tidy follow list opens fewer.
Make it quiet
Which parts of Nostr deserve your attention is your call rather than ours. Settings carries eight switches that take things away: replying, reposting, reacting, zapping, and the count beside each one.
Hiding is not covering up, and that distinction is the whole point. Turn off
reactions and Plaza stops asking relays for them: the subscription drops
kind:7, those events never arrive, and that is less to download, less to parse
and less sitting on your disk. What you hid is absent rather than painted over,
which is also why a quieter Plaza is a faster one.
Two of the eight are honest exceptions, and the switch says so where you flip it. Your own reposts arrive in the same stream as everybody else’s, so hiding the repost verb changes what is drawn and nothing more. Saying the data was gone would be easier and untrue.
Notifications are a separate subscription and keep their own. Hide reaction counts in the feed and you will still hear when somebody likes your note.
This is the first piece of a larger idea. A client should bend to the person using it, and the settings that shape one person’s Plaza are the same shape as settings somebody could publish for a whole community.
Places
A place is somebody’s corner of Nostr: their relays, and what those relays
serve. A plaza://place/ link opens the one it names, from anywhere, and while
you are in a place you are reading it rather than your own feed with a banner on
top.
You arrive as a visitor, which means you can already read and post, and closing Plaza forgets you were ever there. Entering keeps it on a rail down the side of the window, one press away from then on, and leaving takes it off again. Nothing is gated on entering, posting included: whether a post lands is between you and that place’s relays.
A community can carry several feeds. The one you are reading names itself in the header, and where there is more than one that name is the switcher. A place that states a colour wears it: the accent, its tile on the rail, and the handles, mentions and links throughout its feed, in the colour that community chose rather than Plaza’s own violet. What does not move is anything that means a state, so a warning, a zap and an unread mark read the same in every room. A yellow room should not make an error look like weather.
Arriving opens the host’s own text once, in a card you can close and find again under Info.
The format is fiatjaf’s Hallway universe object and the field names are his
exactly, so a document written for one is read the same way by the other. The
two do not yet share a publishing path: Hallway’s deployer ships a site with the
object embedded in it, and Plaza reads a kind:30078 carrying that same
object.
Careful with what is yours
Follow lists, relay lists and profiles are replaceable events: publishing one replaces whatever the network already had. Plaza reads yours back before it writes and refuses to publish a list it has not seen, and for follow lists and profiles it carries forward everything in yours that it does not model itself. An app that drops half your follows because a relay was slow to answer is worse than an app that does nothing at all.
Fast on purpose
The feed is a windowed list: it builds only the rows near the viewport, so its cost follows the window rather than the length of the feed. Measured on the build that ships (ReleaseFast), scrolling hard through a fixed feed of 240 notes the harness seeds into a store of its own, so a run means the same thing twice:
| Stage | p90 | Budget |
|---|---|---|
| Rebuild | 275µs | 600µs |
| Layout | 1360µs | 2200µs |
| Patch | 55µs | 150µs |
A 120 Hz frame is 8333µs, so a hard scroll spends about a quarter of
one. A long feed mounts around 460 widget nodes rather than one per note. The
numbers are reproducible: scripts/frame-budget.sh in the repo measures them
and fails on a regression.
See performance for the library numbers underneath.
Getting it
curl -fsSL https://raw.githubusercontent.com/zig-nostr/plaza/main/scripts/install-macos.sh | bashmacOS on Apple Silicon. The installer verifies the download’s SHA-256, installs
Plaza.app, clears the download-quarantine flag so it opens without a Gatekeeper
detour, and launches it. Your session and local store live in ~/.plaza and
survive every upgrade. Your key is not there: it lives in the keyholder, which is
a separate process with its own storage, and Plaza has no field that can hold
one.
Plaza is ad-hoc signed and not notarized on purpose. It signs notes with your key, so the trust anchor is a build you can reproduce rather than an Apple signature you cannot inspect. Read the installer , or build the same artifact yourself:
git clone https://github.com/zig-nostr/plaza
cd plaza && scripts/package-macos.shLinux
curl -fsSL https://raw.githubusercontent.com/zig-nostr/plaza/main/scripts/install-linux.sh | bashx86_64 and aarch64, on a reasonably recent distribution: Ubuntu 23.10+, Debian
13+, or Fedora 39+. The binaries are built against glibc 2.38 and the toolkit’s
Linux host wants GTK 4.10, which land on the same generation, so Ubuntu 22.04 and
Debian 12 are too old. The installer checks that before it downloads anything,
rather than leaving you with a loader error after a successful-looking install.
GTK 4 is the one runtime dependency beyond that. It
verifies the SHA-256, installs into ~/.local so nothing needs root and nothing
lands outside your home directory, registers the desktop entry so plaza://
links open Plaza, and launches it. Pass --archive <file> to install a tarball
you already have, which needs no network.
The same app, with one thing to know. macOS hands text to the system to draw, and gets the system’s font fallback with it. On Linux there is nothing to hand it to, so Plaza draws every glyph from the faces it carries, and those cover Latin and Cyrillic. Emoji are drawn in colour. Everything else, Greek, CJK, Japanese, Korean, Arabic, Hebrew, Thai and Devanagari among them, is drawn as a solid block rather than as text. If you read Nostr in one of those scripts, the macOS build is the one to use today.
Built on the core
Everything Plaza does with Nostr comes from
zig-nostr/nostr: keys and BIP-340
signing, the event model, relay transport, the local store, and the NIP-46
signer protocol. The core’s outbox model is there too, and per-author routing
ships in Plaza today. Plaza is the product weight that proves the core
holds up. See the architecture and the
NIP-support table.
Source, issues and milestones live at
zig-nostr/plaza.