From atproto-skills
This skill should be used when the user asks to "define a lexicon", "create a lexicon schema", "write a record type", "design an XRPC endpoint", "publish a lexicon", "install a lexicon", "generate types from a lexicon", "validate a lexicon", "add a query endpoint", "add a procedure endpoint", "what is a lexicon", "how do lexicon types work", "use goat to publish", "use @atproto/lex", or mentions NSID naming, lexicon evolution rules, ATProto schema design, record key types, lexicon style guide, knownValues vs enum, open vs closed unions, or lexicon codegen. Covers schema definition, type system, naming conventions, publishing, and installation — for identity and OAuth, see atproto-domain and atproto-oauth.
How this skill is triggered — by the user, by Claude, or both
Slash command
/atproto-skills:atproto-lexiconThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Lexicons are the schema language of the AT Protocol. They define XRPC endpoints (queries, procedures, subscriptions) and repository record types using a JSON-based format similar to JSON Schema. Every API call and every record stored in a user's repository conforms to a lexicon.
Lexicons are the schema language of the AT Protocol. They define XRPC endpoints (queries, procedures, subscriptions) and repository record types using a JSON-based format similar to JSON Schema. Every API call and every record stored in a user's repository conforms to a lexicon.
Lexicons enable interoperability — any client that understands a lexicon can work with any server implementing it. Custom applications define their own lexicons under their own namespace and deploy corresponding App Views.
A lexicon file is a JSON object with three required fields:
{
"lexicon": 1,
"id": "com.example.thing",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["name", "createdAt"],
"properties": {
"name": { "type": "string", "maxLength": 256 },
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
}
}
lexicon: always 1 (current version)id: the NSID (Namespaced Identifier) in reverse-DNS formatdefs: map of named definitions; main is the primary definitionA file must contain at least one definition. Non-main definitions are referenced as nsid#name.
Namespaced Identifiers use reverse-DNS notation to prevent collisions:
com.atproto.* — core protocol (identity, repo, sync, server)app.bsky.* — Bluesky social app (feed, actor, graph, notification)dev.myapp.* — custom application namespaceAuthority over an NSID namespace is rooted in DNS domain ownership. Register authority via DNS TXT record: _lexicon.<reversed-authority> with value did=<DID>.
Five types can serve as a main definition:
| Type | Purpose | XRPC Method |
|---|---|---|
record | Data stored in repositories | N/A |
query | Read endpoints | HTTP GET |
procedure | Write endpoints | HTTP POST |
subscription | Event streams | WebSocket |
permission-set | Auth permission bundles | N/A |
Define the shape of data stored in user repositories:
{
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["subject", "createdAt"],
"properties": {
"subject": { "type": "string", "format": "did" },
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
Records always include a $type field in their encoded form, set to the lexicon NSID. The key field specifies the record key type (tid for timestamp-based, nsid, any, or a literal value like "self").
{
"type": "query",
"parameters": {
"type": "params",
"required": ["actor"],
"properties": {
"actor": { "type": "string", "format": "at-identifier" }
}
},
"output": {
"encoding": "application/json",
"schema": { "type": "ref", "ref": "#profileView" }
},
"errors": [{ "name": "AccountNotFound" }]
}
params properties are limited to primitives: boolean, integer, string, or arrays of theseat-identifier format in params to accept both DIDs and handlesinput field (same shape as output)output with encoding| Type | Key Constraints |
|---|---|
boolean | default, const |
integer | minimum, maximum, enum, default (signed 64-bit; 53-bit recommended for JS) |
string | format, maxLength, minLength, maxGraphemes, minGraphemes, enum, knownValues |
bytes | minLength, maxLength |
cid-link | No constraints (content hash reference) |
blob | accept (MIME globs), maxSize |
No floating-point type exists — floats produce inconsistent results across architectures during re-encoding.
object — keyed properties with required and nullable arraysarray — homogeneous items with minLength/maxLengthparams — like object but limited to primitives (for query parameters)ref — reuse a definition from the same or another lexicon:
{ "type": "ref", "ref": "#localDef" }
{ "type": "ref", "ref": "com.example.defs#sharedType" }
union — discriminated union with $type field on each variant:
{
"type": "union",
"refs": ["#imageEmbed", "#videoEmbed", "#externalEmbed"],
"closed": false
}
Prefer open unions (closed: false, the default) to allow third-party extension. All union variants must be object or record types.
token — named symbolic constant, used in knownValues arrays. Cannot be referenced via ref or union.
enum: closed set — adding values is a breaking changeknownValues: open set — new values can be added without breaking existing consumersPrefer knownValues for extensibility. Token definitions can serve as knownValues entries:
{ "type": "string", "knownValues": ["com.example.defs#public", "com.example.defs#private"] }
For the complete type reference, see references/type-system.md.
| Context | Style | Example |
|---|---|---|
| Schema & field names | lowerCamelCase | getProfile, displayName |
| API error names | UpperCamelCase | AccountNotFound |
| Fixed strings / known values | kebab-case | content-warning |
| Records | Singular nouns | post, like, profile |
| Queries | verb + noun | getPost, listLikes |
| Procedures | verb + noun | createRecord, deleteRecord |
Fields prefixed with $ are reserved for the protocol. Application schemas must not define $-prefixed fields.
For detailed naming rules and API design patterns, see references/style-guide.md.
Published lexicons are effectively frozen once adopted by third parties:
V2)Loosening a constraint breaks old validators; tightening breaks new producers. Only optional constraints may be added to previously unconstrained fields.
Standard cursor-based pagination for list endpoints:
limit (integer) + cursor (string, optional)cursorcursor in response = no more pagesbrew install goat
goat lex new record dev.myapp.thing # scaffold a new lexicon
goat lex lint # validate schema structure
goat lex diff # check evolution rules
goat lex publish # publish to AT Protocol network
Authenticate with goat account login or GOAT_USERNAME/GOAT_PASSWORD environment variables. Lexicons are published as com.atproto.lexicon.schema records in the user's repository.
npm install -g @atproto/lex
lex install app.bsky.feed.post app.bsky.feed.like # download schemas
lex build # generate TypeScript
Generated code in ./src/lexicons/ provides type-safe API clients:
import { Client } from '@atproto/lex'
import * as app from './lexicons/app.js'
const client = new Client('https://public.api.bsky.app')
const response = await client.call(app.bsky.actor.getProfile, {
actor: 'pfrazee.com',
})
For full tooling details, see references/publishing-installing.md.
knownValues instead of enum to allow adding values without breaking consumers.required is irreversible. Start optional, promote to required only when certain.{ "items": [{ "uri": "..." }] }) to allow adding per-item fields later.at-identifier).format when a semantic type exists (datetime, DID, handle, AT URI). Omitting it loses validation.goat lex diff before publishing. A breaking change to a published lexicon requires a new NSID.references/type-system.md — Complete type reference: all field types, container types, meta types, string formats, $type rules, validation modesreferences/style-guide.md — Naming conventions, field design guidelines, pagination pattern, evolution rules, advanced patterns (hydrated views, richtext facets, sidecar records)references/publishing-installing.md — Full goat CLI workflow, @atproto/lex codegen, TypeScript usage, DNS authority setupnpx claudepluginhub zentered/atproto-skills --plugin atproto-skillsThis skill should be used when the user is authoring, validating, or invoking AT Protocol lexicons in Rust, TypeScript, or Go — the JSON schema layer that governs record shapes and XRPC methods. Triggers on phrases like "lexicon", "lexicon doc", "LexiconDoc", "NSID", "defs", "$type", "$type dispatch", "main def", "open union", "closed union", "knownValues", "enum", "strongRef", "blob ref", "cid-link", "record-key", "rkey", "tid", "at-uri", "at://<did>/<collection>/<rkey>", "record validation", "assertValidRecord", "ValidateRecord", "validate_record", "query", "procedure", "subscription", "XRPC", "XRPC method", "invoke XRPC", "xrpc call", "params", "parameters", "input.schema", "output.schema", "subscription frame", "MessageFrame", "ErrorFrame", "firehose consumer", "Jetstream", "lex-cli", "gen-api", "gen-server", "lexgen", "cbor-gen", "backward-compat", "breaking change", "add optional field", "closed union evolution", "InvalidRequest", "XRPCError", "XRPCInvalidResponseError", "AuthRequiredError", "RateLimitExceeded". Also triggers on dependency/import names like `atproto-lexicon`, `atproto-client`, `atproto-record`, `atproto-jetstream`, `@atproto/lexicon`, `@atproto/xrpc`, `@atproto/xrpc-server`, `@atproto/api`, `@atproto/lex-cli`, `@atproto/syntax`, `indigo/atproto/lexicon`, `indigo/atproto/data`, `indigo/atproto/syntax`, `indigo/xrpc`, `indigo/api/atproto`, `indigo/api/agnostic`, `indigo/events`, `indigo/lex/util`, or API names like `BaseCatalog`, `Lexicons`, `XrpcClient`, `AtpAgent`, `createServer`, `streamMethod`, `ResolvingCatalog`, `DefaultLexiconResolver`, `RepoGetRecord`, `RepoCreateRecord`, `HandleRepoStream`, `RepoStreamCallbacks`, `LexiconTypeDecoder`, `BlobRef`, `DataValue`. Use this skill to author a new lexicon, run a validator against records (strict on write, lenient on read), call any `com.atproto.*` XRPC method, consume the firehose, stand up an XRPC server, or plan a backward-compatible lexicon change. Covers lexicon document structure, NSID grammar, AT-URI shape inside records, `$type` dispatch, strongRef vs. blob refs, XRPC HTTP/WebSocket wire format, validation strictness modes, and the backward-compat change matrix. Does NOT cover CID parsing/construction (see `atproto-cid`), DID resolution / handle lookup (see `atproto-identity-resolution`), CAR / MST / commit signing at the repo layer (see `atproto-repository`), OAuth token flows / DPoP (see `atproto-oauth`), Bluesky-domain record idioms (`app.bsky.*` facets, richtext, embeds, threadgates, labels — out of scope for this plugin entirely; point users at the Bluesky appview or `@atproto/api` docs).
Guides building on AT Protocol (atproto/Bluesky): authoring Lexicons, app views, firehose consumption, DIDs/handles, repositories, records, XRPC endpoints, OAuth.
Guides REST API route and URL design with conventions from Stripe, GitHub, Twilio: plural nouns, max 2-level nesting, snake_case fields, path vs query params.