ATProto’s inside out structure provides an opportunity for open, permissionless building on top of any component in the network, including its extensible data schema definition system, Lexicons.
One of the opportunities this presents is users sharing their data and records with multiple apps. Those apps may be direct substitutions (different clients for microblogging say), or apps that use that data in a variety of ways, such as Popfeed an app which aggregates posts about a variety of media types.
Separately ATProto apps provide mechanisms to embed other content types. Bluesky has an allow list for embeddable content (like youtube videos), or Leaflet.pub which has a variety of embedable block types that users can include when writing.
Many of us think this transcluding content can be made more powerful and we should discuss patterns for how ATProto apps might embed any record that has an at-uri.
People like the leaflet quote stuff. I still think aside from Lexicon Embeds – which I consider advanced – extending OpenGraph makes cool interactive units available to any website on the internet.
Here is an updated proposal based on my Bluesky thread on this topic.
Proposal: Lexicon publishers should be able to publish an optional com.atproto.lexicon.web record that acts as a bridge between the AT Protocol and the HTTP web, defining how records in that lexicon should be displayed or embedded.
It would include:
URL Template: A scheme for converting an at:// URI into a browseable HTTP URL (RFC 6570 URI Templates).
oEmbed Endpoint: The service URL for fetching JSON embed data.
Workflow: When a client encounters an unknown custom record (e.g., com.example.music.rating):
Finds the com.atproto.lexicon.web record (the same way it finds the com.atproto.lexicon.schema record)
It hydrates the URL template.
Path A (Simple): Fetch the HTTP URL to parse standard Open Graph meta tags.
Path B (Rich): Pass the HTTP URL to the provided oEmbed endpoint for an interactive widget.
Example Record: A configuration for app.bsky.feed.post might look like this:
Rationale: The lexicon publisher is the ultimate authority on how their data should be presented to the user.
While there are valid concerns about centralization here—this does give the lexicon publisher control over the default web URLs—the reality is that publishers already exercise this control de-facto.
For example, bsky.app URLs are already the standard web representation for posts, and Bluesky already serves the OG tags and oEmbed endpoints. This pattern will likely repeat for every new social app built on a custom schema. This proposal simply formalizes that relationship, making the default presentation discoverable for any client rather than hardcoded.
A thing I’ve found with e.g. Bluesky’s oEmbed endpoint is that it applies fairly opinionated styling and hydration via a script, which then mostly doesn’t fit into the thing that embeds it. I’ve had the same problem with GitHub Sponsor buttons, where using the canonical embed simply does not match the style of the page that surrounds it at all. This is to me the challenge of that sort of rich embed experience. App makers want their branding and that’s understandable, but that’s at odds with making your own cohesive thing while including their embedded content. And so if you want cohesion, then you are back to doing things manually.
For example, here is an essay with a number of embedded records.
Some have dedicated handling (e.g. the Bluesky posts, the embed of another blog post), some don’t (the cosmik records, for https://semble.so/). For those with dedicated handling, a link is created to that site. For others, the link is to a generic record viewer interface. Your proposal could help simplify handling here, as it would allow Weaver to, for example, determine how to link to a leaflet or a https://semble.so/ collection automatically. But because the likely embed styling wouldn’t fit at all with Weaver’s styling, it would still have to do a lot on its own.
I think to some degree the onus is on developers to be a bit smarter about this. Bluesky doesn’t set a good example here, only handling display of a small subset of possible record embeds, all their own, but that doesn’t mean we can’t do better. People name record fields with some amount of consistency and if we can resolve the lexicon for a record, we can then know things like constraints and descriptions. With some cleverness we can, from that information, construct on the fly a reasonable display for essentially any record (which is what the page renderer there does for the record embeds it doesn’t have special handling for, though without the lexicon resolution step, and what the generic viewer it links to also does).
@nonbinary.computer great point about the opinionated styling issue with oEmbed. Solving it generically (without forcing app devs to hardcode per-lexicon support, though they could do so) would be fantastic.
Open Frames and OpenGraph are not opinionated about styling. And the “web config” proposal aligns well with both of these, as it provides the HTTP URL bridge needed to fetch the “og:” and “of:” meta tags.
Open Graph falls short on interactivity, while Open Frames adds buttons/actions but offers limited layout control—but neither quite nails the sweet spot of pure semantic UI without imposed styling.
Something like Slack’s Block Kit hits that balance better: It supports headers, sections, basic markdown, images, buttons, and a few other elements, all declaratively in JSON, with the client handling native rendering (no publisher-side CSS opinions).
A pure atproto solution could build on this—e.g., lexicon publishers expose an endpoint (perhaps via a “blocksEndpoint” in the web config record) returning Block Kit-style JSON for a given at:// URI. Clients could then render it into HTML/JSX or native mobile views. While Block Kit is Slack-tied, its spec is open for adaptation. I wonder if we could adapt Block Kit directly or define an atproto-native equivalent?
I think there’s value in a native ATProto lexicon embedding.
And I also think the Open Frames approach of extending OpenGraph means unlocking millions of websites who could easily have richer bsky / ATProto display - which then motivates some of them to explore lexicons for things they can’t do with OF/OGP. OF as is doesn’t have the right display layer so needs work.
I mentioned the Slack unfurls to @jimray.bsky.team (who worked there) as something to learn from.
i don’t see why it would have to be centralized? couldn’t other people write altenative renderers and publish similar com.atproto.lexicon.webs in their own repos, even if it’s pointing at someone else’s NSID.
then you could use an indexer such as constellation to discover what third party renderers exist for a given record type
Good point, actually. That sort of discovery process would actually make it far easier for me to allow people to redirect to a Bluesky client of their choice, without having to figure out which clients are out there, and choose a subset to support.
@jonathanwarden.com Something a bit more balanced like the Block Kit does appeal to me as well. Allows someone to give a stronger hint about how to render their content than just a lexicon view type without dictating it entirely, in a way that is easy to render generically, and it has the benefit of working outside a web environment, to boot.
I would need to do more research on it to know if we could adapt it or not, but I would definitely be interested in pursuing that or something like it if others are.
Yes good point. So if the lexicon publisher never publishes a com.atproto.lexicon.web, someone else could do so. Though it would be good if there was a “default”, at least by convention.
Block Kit is mostly rich enough for these record types (text, images, authors, dates, action buttons). But some things are a little awkward.
Another key learning: you can’t just template the raw ATProto record. An endpoint returning Block Kit would need to hydrate, resolve blobs, process facets, truncate, etc.