These documents are composed of blocks (structural content elements) which contain spans (text segments with rich formatting).
"Hello world, this is bold and this is a link."
{
"$type": "com.example.block#text",
"spans": [
{ "text": "Hello world, this is " },
{ "text": "bold", "bold": true },
{ "text": " and this is a " },
{ "text": "link", "features": [{ "$type": "com.example.span#link", "uri": "https://example.com" }] },
{ "text": "." }
]
}"To be or not to be, that is the question."
[
{
"$type": "com.example.block#header",
"level": 2,
"spans": [
{ "text": "Introduction" }
]
},
{
"$type": "com.example.block#blockquote",
"spans": [
{ "text": "To be or " },
{ "text": "not to be", "bold": true, "italic": true },
{ "text": ", that is the question." }
]
}
]
- Use
getRecord()to fetch data- See @alice's guide for details
{
"$type": "com.example.block#list",
"children": [
{
"content": {
"$type": "com.example.block#text",
"spans": [
{ "text": "Use " },
{ "text": "getRecord()", "code": true },
{ "text": " to fetch data" }
]
}
},
{
"content": {
"$type": "com.example.block#text",
"spans": [
{ "text": "See " },
{ "text": "@alice", "features": [{ "$type": "com.example.span#mention", "did": "did:plc:alice123" }] },
{ "text": "'s guide for details" }
]
}
}
]
}A span is a segment of rich text with its own text content and optional features. Blocks that contain rich text use an array of spans — each span carries its text and the features that apply to the entire span. No byte-slice indexing is needed; formatting boundaries are expressed by splitting text into separate spans.
| Field | Type | Required | Description |
|---|---|---|---|
text |
string | yes | The text content of this span |
bold |
boolean | no | Bold text |
italic |
boolean | no | Italic text |
underline |
boolean | no | Underlined text |
strike |
boolean | no | Strikethrough text |
code |
boolean | no | Inline code |
highlight |
boolean | no | Highlighted text |
features |
array of feature union (#link, #mention, #bold, #italic, #underline, #strikethrough, #code, #highlight) |
no | Additional features applied to this span's text |
Features are rich text annotations that can be applied to a span.
| Feature | ID | Properties | Description |
|---|---|---|---|
| Bold | #bold |
(none) | Bold text |
| Italic | #italic |
(none) | Italic text |
| Underline | #underline |
(none) | Underlined text |
| Strikethrough | #strikethrough |
(none) | Strikethrough text |
| Code | #code |
(none) | Inline code |
| Highlight | #highlight |
(none) | Highlighted text |
| Link | #link |
uri (string, required) |
Hyperlink. The display text may be simplified but the facet URI should be the complete URL. |
| Mention | #mention |
did (string, format: did, required) |
Mention of a DID identity |
A paragraph of rich text.
| Field | Type | Required | Description |
|---|---|---|---|
spans |
array of com.example.span |
yes | The rich text content |
textSize |
string enum: "default", "small", "large" |
no | Text size variant |
A heading element (h1-h6).
| Field | Type | Required | Description |
|---|---|---|---|
spans |
array of com.example.span |
yes | The heading text |
level |
integer (1-6) | no | Heading level |
id |
string, optional | Identifier string, used for linking to a specific heading |
A block quotation with rich text.
| Field | Type | Required | Description |
|---|---|---|---|
spans |
array of com.example.span |
yes | The quoted text |
An embedded image with required aspect ratio.
| Field | Type | Required | Description |
|---|---|---|---|
image |
blob (accept: image/*, max 1MB) |
yes | The image data |
aspectRatio |
object: { width: integer, height: integer } |
yes | Width and height for layout |
alt |
string | no | Alt text for accessibility |
A code block with optional syntax highlighting.
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | The code content |
language |
string | no | Programming language identifier |
syntaxHighlightingTheme |
string | no | Theme for syntax highlighting |
A list. Each list item contains a content block which can be a nested list.
| Field | Type | Required | Description |
|---|---|---|---|
children |
array of union: #text | #header | #image | #list |
yes | The list items |
style |
string numbers or bullets |
no | What kind of list |
A clickable button/link.
| Field | Type | Required | Description |
|---|---|---|---|
text |
string | yes | Button label |
url |
string (format: uri) | yes | Destination URL |
A website embed/link card with optional preview.
| Field | Type | Required | Description |
|---|---|---|---|
src |
string (format: uri) | yes | The website URL |
title |
string | no | Page title |
description |
string | no | Page description |
previewImage |
blob (accept: image/*, max 1MB) |
no | Preview/OG image |
An embedded atproto object.
| Field | Type | Required | Description |
|---|---|---|---|
ref |
com.atproto.repo.strongRef |
yes | Reference to the record (URI + CID) |
An embedded atproto actor.
| Field | Type | Required | Description |
|---|---|---|---|
did |
string |
yes | Reference to the user (DID) |
An embedded iframe.
| Field | Type | Required | Description |
|---|---|---|---|
url |
string (format: uri) | yes | The iframe source URL |
height |
integer (16-1600) | no | Height in pixels |
A LaTeX math block.
| Field | Type | Required | Description |
|---|---|---|---|
tex |
string | yes | LaTeX source |
A horizontal divider/separator. Has no properties.
Bonus premise: a "Fallbacker" block that handles multiple supported formats
[ { "$type": "com.example.block#header", "level": 2, "spans": [ { "text": "Check out this post" } ] }, { "$type": "com.example.block#fallbacker", "blocks": [ {"$type": "pub.leaflet.blocks.bskyPost", ...}, {"$type": "com.example.block#text", "spans": [{"text": "Polls not supported, sorry :("}]} ] } ]