Implementing standard.site on a nonstandard site • Cory DransfeldtSkip to main content Featured July 7, 2026Implementing standard.site on a nonstandard site<br>/posts/2026/implementing-standardsite-on-a-nonstandard-sitedevelopmentwebdevtechatprotobluesky<br>Now that this site is written in Go1, I've turned to restoring and adding more features connecting it to the open web. While I maintain a healthy skepticism of Bluesky the company, I'm enamored with open protocols and ATProto falls under that umbrella.ATProto exists apart from Bluesky and Bluesky is built on top of ATProto.WikipediaThe AT Protocol (Authenticated Transfer Protocol, pronounced "at protocol", commonly shortened to ATproto or "ATP") is a protocol and set of open standards for decentralized publishing and distribution of self-authenticating data within the social web. It serves as the technical foundation of the Bluesky social network, originally developed as a reference implementation for the protocol, as well as an ecosystem of interoperable social applications and services collectively referred to as the ATmosphere.<br>After reluctantly joining Bluesky, I saw a host of folks implementing standard.site.Standard.site provides shared lexicons for long-form publishing on AT Protocol. Making content easier to discover, index, and move across the ATmosphere.<br>It's another protocol to support, another network, another implementation, another maintenance burden and all that, but it gets you a fancy button and avatar attached to posts. How could I resist?My implementation is hand-rolled against Bluesky's endpoints since that's where my PDS lives.2Wikipedia, again: Personal Data Servers (PDSes) host user repositories and their associated media. They also serve as the network access point for users, facilitating repository updates, backups, data queries, and user requests.July 7, 2026Publishing on the Atmosphere with Standard.site via Declan Chidlow<br>Standard.site provides a set of lexicons for publishing long-form content on the internet using the same protocol used under the hood by Bluesky.<br>techatprotobluesky
Getting things working<br>There are two record types that are required as part of this implementation: site.standard.publication which describes the site — name, description, url, theme and icon. site.standard.document is a record required for each piece of content, each pointing back to my site (the publication).When I post a link the AppView crawls the page looking for two tags: and . These tags have href attributes that are resolved against my PDS, verifying ownership by also fetching my /.well-known/site.standard.publication endpoint.I use a handful of River jobs to make this work. Records are authored as plain map[string]any objects that are marshalled to JSON and PUT via com.atproto.repo.putRecord.I post a bunch of different content types on this site, many of which are syndicated to Mastodon and Bluesky and each type that is syndicated is now published as part of the standard.site implementation. The shape of syndicated content is typically: title, description, hashtags and a link to the content. Only movies, books and concerts I've written something about are syndicated, while all posts are syndicated. The query to fetch movie data from my PostgreSQL database looks like this:SELECT title, COALESCE(description,''), url, last_watched, COALESCE(tags,'{}') FROM optimized_movies WHERE review IS NOT NULL AND review != '' AND last_watched IS NOT NULL ORDER BY last_watched DESC<br>The constructed document record looks like this:record := map[string]any{<br>"$type": "site.standard.document",<br>"site": publicationURI, // AT-URI back to the publication<br>"title": item.Title,<br>"publishedAt": item.Date.UTC().Format(time.RFC3339),<br>"path": item.URL, // also the rkey seed<br>The publication record looks like this:...<br>record := map[string]any{<br>"$type": "site.standard.publication",<br>"name": name,<br>"url": siteURL,<br>"preferences": map[string]any{<br>"showInDiscover": true,<br>},<br>"basicTheme": map[string]any{<br>"$type": "site.standard.theme.basic",<br>"background": rgbColor(239, 242, 246),<br>"foreground": rgbColor(10, 14, 19),<br>"accent": rgbColor(78, 83, 89),<br>"accentForeground": rgbColor(239, 242, 246),<br>},
if description != "" {<br>record["description"] = description
if icon != nil {<br>record["icon"] = icon
return record<br>...<br>The icon referenced here isn't a URL. atproto records reference blobs, so I fetched my existing OG image and uploaded it to the PDS first, then dropped the returned blob ref into the record. I derived the theme colors from the canonical vars.css used to style my site.I created a pair of standard.site specific commands in the CMS for this site. One to sync my site record to my PDS and another to sync my backlog of posts. I ran both, verified the records, tried a test post and nothing happened. I hoped it was a caching or propagation issue, waited and still nothing. I hit a few walls building this but, being unreasonably stubborn, I continued on.Bug 1: apex vs. www<br>As best I can...