← All posts

Building on the AT Protocol: lessons learned

technicalAT Protocol

We've been building on the AT Protocol for a little while now, and we've learned some things the hard way. Here's an honest rundown of what it's like to build a real app on atproto in 2026.

What works really well

Identity is solved. Users sign in with their existing Bluesky handle and everything just works. No user table, no password hashing, no email verification. OAuth with PKCE and DPoP handles auth, the user's PDS handles the rest. This alone saves an enormous amount of work.

The firehose is powerful. We subscribe to the AT Protocol relay and get a real-time stream of every record create, update, and delete across the network. Filter for your lexicons, index into your database, done. It's like getting a change data capture pipeline for free.

Lexicons enforce structure. Defining your data types as schemas means you get validation at the protocol level. Our event records have required fields for sport, date, and location — any client that writes them has to conform. No more "oops, someone sent a null."

What's tricky

DPoP is fiddly. DPoP binds tokens to cryptographic key pairs, which is great for security but means managing key pairs in the browser, generating proof JWTs for every request, and handling nonce rotation. We had a fun bug where the nonce wasn't persisting across page reloads. The fix was one line of code. Finding it was not.

CBOR and CAR files have a learning curve. If you're used to JSON-everything, there's a ramp-up. The Indigo SDK helps, but you'll still find yourself staring at hex dumps occasionally.

The ecosystem is young. Documentation is improving but still thin in places. When we needed to implement blob uploads, we ended up reading Bluesky's client source code. That's fine for early adopters, but it's a real barrier for broader adoption.

Architecture decisions we're happy with

Single binary deployment. The Go backend embeds the compiled frontend, so we deploy one binary to Fly.io. No separate static hosting, no deploy coordination. Go's embed package makes this trivial and Cloudflare sits in front for caching and compression.

PostGIS for geospatial. "What events are near me?" is the core query of this app. PostGIS with spatial indexes means that's a simple database query instead of haversine math in application code.

Cursor-based pagination everywhere. Slightly more work than offset/limit up front, but it's consistent with the AT Protocol's patterns, performs well at scale, and doesn't break when new records get inserted mid-page.

What's next

We're still early. The core loop works — create events, share routes, RSVP, go outside — but there's a lot to build. Push notifications, better search, activity stats, maybe federation with other activity platforms. The AT Protocol gives us a solid foundation; now it's about building something people love to use.

If you're thinking about building on atproto: start with the Bluesky starter templates, get familiar with lexicons early, and don't fight the protocol's opinions about data ownership. They're good opinions.