Standard.site
Convert data into Standard.site lexicon records for the AT Protocol and validate them against the official schemas.
When to Use
- Creating publication, document, subscription, recommend, or theme records
- Converting blog posts, articles, or other content into standard.site format
- Validating existing records against standard.site lexicon schemas
- Working with AT Protocol publishing workflows that involve standard.site
- Mapping content from other formats (RSS, Markdown, JSON) to standard.site
Lexicons at a Glance
| Lexicon |
$type |
Required |
Optional |
| Publication |
site.standard.publication |
url, name |
icon, description, basicTheme, labels, preferences |
| Document |
site.standard.document |
site, title, publishedAt |
path, description, coverImage, content, textContent, bskyPostRef, tags, links, labels, contributors, updatedAt |
| Subscription |
site.standard.graph.subscription |
publication |
createdAt |
| Recommend |
site.standard.graph.recommend |
document, createdAt |
— |
| Theme |
site.standard.theme.basic |
background, foreground, accent, accentForeground |
— |
All colors in themes use site.standard.theme.color#rgb (r, g, b as integers 0–255) or site.standard.theme.color#rgba (adds a as integer 0–100).
Conversion Workflow
Identify the target lexicon. Determine which record type the data maps to:
- A blog/site with a URL and name → publication
- An individual article or post → document
- A user following a publication → subscription
- A user endorsing a document → recommend
- Color scheme for a publication → theme (embedded inside publication as
basicTheme)
Map required fields first. Every record must include $type and all required properties. See the quick reference table above.
Add optional fields. Include optional properties when the source data provides them. Do not invent values for missing optional fields.
Validate. Run the validation script before finalizing:
uv run scripts/validate.py --input record.json
Or pipe JSON via stdin:
cat record.json | uv run scripts/validate.py --stdin
- Fix any errors. The script reports each issue with the property path and expected format. Fix and re-validate until it passes.
Validation
The bundled scripts/validate.py checks records against the standard.site lexicon schemas:
- Detects
$type and validates against the matching schema
- Checks all required properties are present
- Checks property types (string, integer, datetime, blob, at-uri, ref, array, object)
- Checks constraints (maxLength, maxGraphemes, integer ranges for colors)
- Validates nested objects (basicTheme colors, contributors, preferences)
- Supports validating a single record or an array of records
# Validate a single record
uv run scripts/validate.py --input publication.json
# Validate from stdin
echo '{"$type":"site.standard.publication","url":"https://example.com","name":"Test"}' | uv run scripts/validate.py --stdin
# Validate multiple records in a JSON array
uv run scripts/validate.py --input records.json
Output is structured JSON to stdout, diagnostics to stderr. Exit code 0 = valid, 1 = invalid, 2 = usage error.
Record Templates
Publication
{
"$type": "site.standard.publication",
"url": "https://example.com",
"name": "Example Blog",
"description": "A blog about technology",
"basicTheme": {
"$type": "site.standard.theme.basic",
"background": { "$type": "site.standard.theme.color#rgb", "r": 255, "g": 255, "b": 255 },
"foreground": { "$type": "site.standard.theme.color#rgb", "r": 31, "g": 41, "b": 55 },
"accent": { "$type": "site.standard.theme.color#rgb", "r": 59, "g": 130, "b": 246 },
"accentForeground": { "$type": "site.standard.theme.color#rgb", "r": 255, "g": 255, "b": 255 }
},
"preferences": { "showInDiscover": true }
}
Document
{
"$type": "site.standard.document",
"site": "at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s",
"path": "/blog/getting-started",
"title": "Getting Started",
"description": "Learn how to use Standard.site",
"textContent": "Full text of the article...",
"tags": ["tutorial", "atproto"],
"publishedAt": "2024-01-20T14:30:00.000Z"
}
Subscription
{
"$type": "site.standard.graph.subscription",
"publication": "at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s",
"createdAt": "2026-05-19T14:30:00.000Z"
}
Recommend
{
"$type": "site.standard.graph.recommend",
"document": "at://did:plc:abc123/site.standard.document/3mbfqhezge25u",
"createdAt": "2026-05-19T14:30:00.000Z"
}
Gotchas
- Trailing slashes. The spec says avoid trailing slashes on
url (publication) and site (document), but some implementations include them. The validator trims trailing slashes before checking — do the same in your code.
site vs url. In documents, site points to a publication record via at:// URI, or to a publication URL via https:// for loose documents. Both are valid.
path must have a leading slash. Document path should start with / (e.g., /blog/post-slug).
publishedAt is required for documents. Unlike createdAt on subscriptions (optional), publishedAt is required and must be an ISO 8601 datetime.
createdAt is required for recommends. Unlike subscriptions where it's optional, recommends require createdAt.
- Tags should not have hashtag prefixes. Use
"tutorial" not "#tutorial".
- RGB color values are integers 0–255. Not hex strings, not floats. RGBA alpha is 0–100 (percentage), not 0–255.
- All four theme colors are required. A
basicTheme with only background and foreground is invalid — accent and accentForeground are also required.
basicTheme is embedded in the publication record. It is not a standalone record — it's a nested object inside site.standard.publication.
content is an open union. Each entry must specify a $type. The validator checks for $type presence but does not validate specific content formats.
maxLength vs maxGraphemes. maxLength counts UTF-16 code units; maxGraphemes counts user-perceived characters. Both constraints apply simultaneously — a string can violate maxGraphemes while being under maxLength.
Verification
Standard.site records can be verified against their web presence:
- Publications:
https://{url}/.well-known/site.standard.publication should return the publication's AT-URI. For non-root publications, append the path: /.well-known/site.standard.publication/path/to/publication.
- Documents: The document's HTML page should include a
<link rel="site.standard.document" href="at://..."> tag in the <head>.
Verification is separate from schema validation — a record can be schema-valid but unverified, and vice versa.
Detailed References
Load these when you need full schema details, edge cases, or examples for a specific lexicon:
references/publication.md — Full publication schema, blob handling, preferences, extensibility
references/document.md — Full document schema, contributor format, content union, links
references/subscription.md — Full subscription schema, AT-URI format
references/recommend.md — Full recommend schema, permissions
references/theme.md — Full theme schema, RGB/RGBA color types, contrast guidelines
Available Scripts
scripts/validate.py — Validates standard.site records against lexicon schemas. Run with uv run scripts/validate.py --input <file> or --stdin.
1---2name: standard-site3description: Convert data into Standard.site AT Protocol lexicon records and validate them against official schemas. Use when creating or verifying site.standard.publication, site.standard.document, site.standard.graph.subscription, site.standard.graph.recommend, or site.standard.theme.basic records — even if the user doesn't name Standard.site directly. Also use when working with AT Protocol blog or article publishing workflows, or when mapping content to standard.site format from other sources.4license: MIT5---6
7# Standard.site
8
9Convert data into Standard.site lexicon records for the AT Protocol and validate them against the official schemas.
10
11## When to Use
12
13- Creating publication, document, subscription, recommend, or theme records
14- Converting blog posts, articles, or other content into standard.site format
15- Validating existing records against standard.site lexicon schemas
16- Working with AT Protocol publishing workflows that involve standard.site
17- Mapping content from other formats (RSS, Markdown, JSON) to standard.site
18
19## Lexicons at a Glance
20
21| Lexicon | `$type` | Required | Optional |
22| --- | --- | --- | --- |
23| Publication | `site.standard.publication` | `url`, `name` | `icon`, `description`, `basicTheme`, `labels`, `preferences` |
24| Document | `site.standard.document` | `site`, `title`, `publishedAt` | `path`, `description`, `coverImage`, `content`, `textContent`, `bskyPostRef`, `tags`, `links`, `labels`, `contributors`, `updatedAt` |
25| Subscription | `site.standard.graph.subscription` | `publication` | `createdAt` |
26| Recommend | `site.standard.graph.recommend` | `document`, `createdAt` | — |
27| Theme | `site.standard.theme.basic` | `background`, `foreground`, `accent`, `accentForeground` | — |
28
29All colors in themes use `site.standard.theme.color#rgb` (r, g, b as integers 0–255) or `site.standard.theme.color#rgba` (adds `a` as integer 0–100).
30
31## Conversion Workflow
32
331. **Identify the target lexicon.** Determine which record type the data maps to:
34 - A blog/site with a URL and name → publication
35 - An individual article or post → document
36 - A user following a publication → subscription
37 - A user endorsing a document → recommend
38 - Color scheme for a publication → theme (embedded inside publication as `basicTheme`)
39
402. **Map required fields first.** Every record must include `$type` and all required properties. See the quick reference table above.
41
423. **Add optional fields.** Include optional properties when the source data provides them. Do not invent values for missing optional fields.
43
444. **Validate.** Run the validation script before finalizing:
45
46```bash
47uv run scripts/validate.py --input record.json
48```
49
50Or pipe JSON via stdin:
51
52```bash
53cat record.json | uv run scripts/validate.py --stdin
54```
55
565. **Fix any errors.** The script reports each issue with the property path and expected format. Fix and re-validate until it passes.
57
58## Validation
59
60The bundled `scripts/validate.py` checks records against the standard.site lexicon schemas:
61
62- Detects `$type` and validates against the matching schema
63- Checks all required properties are present
64- Checks property types (string, integer, datetime, blob, at-uri, ref, array, object)
65- Checks constraints (maxLength, maxGraphemes, integer ranges for colors)
66- Validates nested objects (basicTheme colors, contributors, preferences)
67- Supports validating a single record or an array of records
68
69```bash
70# Validate a single record
71uv run scripts/validate.py --input publication.json
72
73# Validate from stdin
74echo '{"$type":"site.standard.publication","url":"https://example.com","name":"Test"}' | uv run scripts/validate.py --stdin
75
76# Validate multiple records in a JSON array
77uv run scripts/validate.py --input records.json
78```
79
80Output is structured JSON to stdout, diagnostics to stderr. Exit code 0 = valid, 1 = invalid, 2 = usage error.
81
82## Record Templates
83
84### Publication
85
86```json
87{
88 "$type": "site.standard.publication",
89 "url": "https://example.com",
90 "name": "Example Blog",
91 "description": "A blog about technology",
92 "basicTheme": {
93 "$type": "site.standard.theme.basic",
94 "background": { "$type": "site.standard.theme.color#rgb", "r": 255, "g": 255, "b": 255 },
95 "foreground": { "$type": "site.standard.theme.color#rgb", "r": 31, "g": 41, "b": 55 },
96 "accent": { "$type": "site.standard.theme.color#rgb", "r": 59, "g": 130, "b": 246 },
97 "accentForeground": { "$type": "site.standard.theme.color#rgb", "r": 255, "g": 255, "b": 255 }
98 },
99 "preferences": { "showInDiscover": true }
100}
101```
102
103### Document
104
105```json
106{
107 "$type": "site.standard.document",
108 "site": "at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s",
109 "path": "/blog/getting-started",
110 "title": "Getting Started",
111 "description": "Learn how to use Standard.site",
112 "textContent": "Full text of the article...",
113 "tags": ["tutorial", "atproto"],
114 "publishedAt": "2024-01-20T14:30:00.000Z"
115}
116```
117
118### Subscription
119
120```json
121{
122 "$type": "site.standard.graph.subscription",
123 "publication": "at://did:plc:abc123/site.standard.publication/3lwafzkjqm25s",
124 "createdAt": "2026-05-19T14:30:00.000Z"
125}
126```
127
128### Recommend
129
130```json
131{
132 "$type": "site.standard.graph.recommend",
133 "document": "at://did:plc:abc123/site.standard.document/3mbfqhezge25u",
134 "createdAt": "2026-05-19T14:30:00.000Z"
135}
136```
137
138## Gotchas
139
140- **Trailing slashes.** The spec says avoid trailing slashes on `url` (publication) and `site` (document), but some implementations include them. The validator trims trailing slashes before checking — do the same in your code.
141- **`site` vs `url`.** In documents, `site` points to a publication record via `at://` URI, or to a publication URL via `https://` for loose documents. Both are valid.
142- **`path` must have a leading slash.** Document `path` should start with `/` (e.g., `/blog/post-slug`).
143- **`publishedAt` is required for documents.** Unlike `createdAt` on subscriptions (optional), `publishedAt` is required and must be an ISO 8601 datetime.
144- **`createdAt` is required for recommends.** Unlike subscriptions where it's optional, recommends require `createdAt`.
145- **Tags should not have hashtag prefixes.** Use `"tutorial"` not `"#tutorial"`.
146- **RGB color values are integers 0–255.** Not hex strings, not floats. RGBA alpha is 0–100 (percentage), not 0–255.
147- **All four theme colors are required.** A `basicTheme` with only `background` and `foreground` is invalid — `accent` and `accentForeground` are also required.
148- **`basicTheme` is embedded in the publication record.** It is not a standalone record — it's a nested object inside `site.standard.publication`.
149- **`content` is an open union.** Each entry must specify a `$type`. The validator checks for `$type` presence but does not validate specific content formats.
150- **`maxLength` vs `maxGraphemes`.** `maxLength` counts UTF-16 code units; `maxGraphemes` counts user-perceived characters. Both constraints apply simultaneously — a string can violate `maxGraphemes` while being under `maxLength`.
151
152## Verification
153
154Standard.site records can be verified against their web presence:
155
156- **Publications:** `https://{url}/.well-known/site.standard.publication` should return the publication's AT-URI. For non-root publications, append the path: `/.well-known/site.standard.publication/path/to/publication`.
157- **Documents:** The document's HTML page should include a `<link rel="site.standard.document" href="at://...">` tag in the `<head>`.
158
159Verification is separate from schema validation — a record can be schema-valid but unverified, and vice versa.
160
161## Detailed References
162
163Load these when you need full schema details, edge cases, or examples for a specific lexicon:
164
165- `references/publication.md` — Full publication schema, blob handling, preferences, extensibility
166- `references/document.md` — Full document schema, contributor format, content union, links
167- `references/subscription.md` — Full subscription schema, AT-URI format
168- `references/recommend.md` — Full recommend schema, permissions
169- `references/theme.md` — Full theme schema, RGB/RGBA color types, contrast guidelines
170
171## Available Scripts
172
173- **`scripts/validate.py`** — Validates standard.site records against lexicon schemas. Run with `uv run scripts/validate.py --input <file>` or `--stdin`.