Skip to Content
Plaza

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.

Plaza: a native feed read from disk. Zig and Metal, no Electron, and the feed is a local query.

What it looks like

The feed is a local query: it renders from disk, so it is there before the network isEveryone resolved: names, faces and verification, straight from the local storeConversations in full: replies nest where they belong, reconciled in the backgroundPlaces you enter: a link opens somebody else's relay and what it serves, and your own stay where they are

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:

Stagep90Budget
Rebuild275µs600µs
Layout1360µs2200µs
Patch55µs150µ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 | bash

macOS 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.sh

Linux

curl -fsSL https://raw.githubusercontent.com/zig-nostr/plaza/main/scripts/install-linux.sh | bash

x86_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.

Last updated on