AGENTS.md · facebook/lexical
AGENTS.md from facebook/lexical: 480 lines covering Build, Test, and Development Commands, High-Level Architecture, Type System, Backwards Compatibility and…
Template by facebook
Install
Works with Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code. Read the repository before you give any tool access to your data.
curl -fsSL https://raw.githubusercontent.com/facebook/lexical/main/AGENTS.md -o AGENTS.md
Read it and adapt it to your project before use.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: prompt-engineering
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
What's in it
An AGENTS.md file: instructions coding agents (Codex, Cursor, Copilot, Gemini CLI and others) read before working in a repo.
- For
- Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code
- Length
- 480 lines
- File
AGENTS.md
Sections
- Build, Test, and Development Commands
- High-Level Architecture
- Type System
- Backwards Compatibility
- Dependencies: Lexical is a singleton within one app
- Important Development Notes
First lines (shown under the repo's MIT license)
# Lexical Agent Guide
This file provides detailed guidance for AI agents and automated tools working with the Lexical codebase.
## Build, Test, and Development Commands
### Building
- `pnpm run build` - Build all packages in development mode
- `pnpm run build-prod` - Clean and build all packages in production mode
- `pnpm run build-release` - Build production release with error codes
- `pnpm run build-types` - Build TypeScript type definitions and validate them
### Testing
- `pnpm run test-unit` - Run all unit tests (Vitest, Node/jsdom)
- `pnpm run test-unit-watch` - Run unit tests in watch mode
- `pnpm run test-browser` - Run browser-mode unit tests (Vitest + Playwright, real browser)
- `pnpm run test-browser-watch` - Run browser-mode tests in watch mode
- `pnpm run test-e2e-chromium` - Run E2E tests in Chromium (requires dev server running)
- `pnpm run test-e2e-firefox` - Run E2E tests in Firefox
- `pnpm run test-e2e-webkit` - Run E2E tests in WebKit
- `pnpm run debug-test-e2e-chromium` - Run E2E tests in debug mode (headed)
- `pnpm run debug-test-unit` - Debug unit tests with inspector
For E2E testing workflow:
1. Start the dev server: `pnpm run start` (or `pnpm run dev` if you don't need collab)
2. In another terminal: `pnpm run test-e2e-chromium`
### Development Servers
- `pnpm run start` - Start playground dev server + collab server (http://localhost:3000)
- `pnpm run dev` - Start only the playground dev server (no collab)
- `pnpm run start:website` - Start Docusaurus website (http://localhost:3001)
- `pnpm run collab` - Start collab server on localhost:1234 (collab v1/v2, `@lexical/yjs`)
- `pnpm run collab:y` - Start the Yjs 14 collab server on localhost:1235 (collab Y, the experimental `@lexical/y`; open the playground with `?isCollab=true&useCollabY=true`)
- `pnpm run start:y` - Start playground dev server + the Yjs 14 collab server
- `pnpm run test-e2e-collab-y-chromium` - Run the collab Y E2E specs (`Collaboration.spec.mjs`, `CollabY*.spec.mjs`; needs `pnpm run start:y`)
### Code Quality
- `pnpm run lint` - Run ESLint on all files
- `pnpm run lint:fix` - Auto-fix lint issues
- `pnpm run prettier` - Check code formatting
- `pnpm run prettier:fix` - Auto-fix formatting issues
- `pnpm run flow` - Run a full foreground Flow check (does not reuse a server)
- `pnpm run tsc` - Run TypeScript compiler
- `pnpm run ci-check` - Run all checks (TypeScript, Flow, Prettier, ESLint)
**Never commit changes to `scripts/error-codes/codes.json`.**
That edit is not yours to make — revert it to the state of
`main` before staging, and never `git add` the file.
### Searching and refactoring
Prefer **ast-grep** over line-oriented regex (`grep`/`sed`) for anything
structural — matching or rewriting imports, call sites, JSX, type
annotations, etc. Regexes miss multi-line forms (a symbol on its own line
inside a multi-line `import { ... }` block) and produce false positives
(`IS_APPLE` matching inside `IS_APPLE_WEBKIT`); ast-grep matches the syntax
tree, so it does neither.
It isn't a dependency, so run it via npx (the CLI binary is `ast-grep`, not
the shadow-utils `sg` that may be on `PATH`):
```sh
# Find every import of something from '@lexical/utils' (ts and tsx are
# separate grammars, so pass -l for each; add --json=compact to post-process)
npx --package @ast-grep/cli ast-grep run \
-p "import { \$\$\$NAMES } from '@lexical/utils'" -l ts packages
```
Metavariables (`$NAME`, `$$$LIST`) capture nodes for reporting or rewriting
with `--rewrite`. Reach for it whenever a change spans many files and must be
precise — e.g. moving symbols that `@lexical/utils` merely re-exports back to
a direct `lexical` import.
### Extension source imports
Import from the `@lexical/extension` barrel across package boundaries. Within
`packages/lexical-extension`, use relative imports and re-exports. Do not
manually rewrite these source imports to `@lexical/extension/*`: ESLint
enforces the source convention, and `@lexical/compiler`'s `subpathImports`
pass rewrites them in all published package builds, including www. Public
sibling entries stay external so shared modules such as signals retain one
instance. Helpers with only one owning entry live under `src/internal` and
are bundled into that entry, without exposing additional public subpaths.
Downstream users may import either the barrel or public subpaths.
### Tree-shaking annotations
Module-scope calls to the side-effect-free factories (`defineExtension`,
`configExtension`, `safeCast`, `createCommand`, `createState`,
`defineImportRule`, etc.) need a `/* @__PURE__ */` annotation so bundlers
can drop unused definitions from application bundles. **Do not write those
annotations by hand** — they are injected at build time by
`@lexical/compiler` (`packages/lexical-compiler`), which
runs as a Rollup plugin in `scripts/build.mjs` for the published bundles
and as a Vite plugin in `scripts/vite/lexicalMonorepoPlugin.ts` for
everything the monorepo builds from source. The published package is also
what consumers add to their own build when they compile Lexical from its
`source` export condition rather than from `dist`.
A hand-written annotation on one of these calls is a lint error
(`@lexical/internal/no-pure-annotation`), and the rule is autofixable, so a
branch written before the transform existed migrates with
`pnpm run lint:fix`. Annotations on anything else — a third-party factory, or
a call inside a function body, where the build never injects one — are left
alone.
When adding a new factory of this kind, annotate its definition with
`@__NO_SIDE_EFFECTS__`. That alone is enough for calls in the same package to
be annotated; add its name to `PURE_FACTORY_FUNCTIONS` in
`packages/lexical-compiler/src/LexicalCompiler.mjs` so that
calls in code that imports it by package name are too. An object whose
methods build values and touch nothing else (like `@lexical/html`'s `sel`) is
marked `@lexical-pure-namespace` instead, so that `sel.tag('p')` is annotated
the way a factory call is.
The build runs with `strict`, which fails on a call inside one of these
definitions that nothing has established is side-effect free — such a call
pins the definition into every bundle that imports the module, however well
annotated the definition itself is. If it stops you, either make the call
lazy (a `nodes: () => [...]` callback is not evaluated at module scope), or
declare the function side-effect free so its calls are annotated too. A
third-party factory that cannot be declared is annotated by hand at the call
site, which is what the remaining hand-written annotations in the tree are.
Factory calls are not the only module-scope statements that pin a module.
esbuild and webpack keep, as a side effect, any property read at module scope
(`navigator.userAgent`, `Date.now`, `SomeExtension.name`), any call or `new`
they were not told is pure (`new RegExp(...)`, `Object.freeze(...)`,
`[...].join(',')`), an array or object spread, an `in` test, a `try`
statement, a mutation (`table.push(...)`, `Klass.static = ...`), a class with
a computed member (`[SOME_SYMBOL]?: number` — write `declare` on it), and an
IIFE — together with everything the statement references. One
`LexicalEditor.version = ...` after the class kept nearly all of `lexical` in
a bundle that imported only `createCommand` (#9120). Move such work into a
function declared `@__NO_SIDE_EFFECTS__` and call it at module scope, so the
build annotates the call; annotate a call to a builtin or to a third-party
function (`/* @__PURE__ */ forwardRef(...)`) by hand. Two tests enforce this
by bundling a bare `import 'pkg'` of every entry with esbuild and requiring
that nothing is retained: `scripts/__tests__/unit/treeShakingSource.test.ts`
on the sources (runs with `pnpm run test-unit`) and
`scripts/__tests__/integration/tree-shaking.test.mjs` on the published
builds. A failure prints the retained code; its first statement is usually
the culprit.
A factory whose body is a trivial expression over its own arguments (like
`safeCast`, `defineExtension`, or `configExtension`) is additionally marked
`@lexical-inline <form>` and listed in that file's `INLINE_FACTORIES`: the
build replaces calls to it with the expression it would have returned, which
needs no annotation at all. **The body of a marked function is reproduced by
the build**, so keep it trivial — `packages/lexical-compiler`'s unit
tests compare what the transform emits against what the real function returns
and will fail if the two drift apart.
## High-Level Architecture
### Core Concepts
Lexical is built around several key architectural concepts that work together:
**Editor Instance** - Created via `createEditor()`, wires everything together. Manages the EditorState, registers listeners/commands/transforms, and handles DOM reconciliation.Signals
- Stars
- 23,980
- Forks
- 2,264
- Open issues
- 301
- Last commit (this folder)
- Oct 2, 2026 (26 days ago)
- Last release
- Sep 28, 2026 (1 month ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/facebook/lexical
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from facebook
- 0.750score
Meta Social Technologies MCP
facebookOfficial
Manage Meta apps: creation, configuration, webhooks, permissions, compliance, and App Review.
ConnectorAFreshness: ARemote★ 16Claude Code +3 - 0.735score
AGENTS.md · facebook/docusaurus
facebook
AGENTS.md from facebook/docusaurus: 150 lines covering Docusaurus, Commands, Skills, Contributing, Issue and PR Guidelines.
TemplateAFreshness: A★ 66.6KCodex +5 - 0.674score
AGENTS.md · facebook/astryx
facebook
AGENTS.md from facebook/astryx: 241 lines covering Instruction surface, Start here, Authority, Judgment boundary and more.
TemplateAFreshness: A★ 13.7KCodex +5 - 0.397score
opt-125m
facebook
Open-weights text model from facebook on Hugging Face (other).
ModelFFreshness: FOpen weights - 0.397score
opt-350m
facebook
Open-weights text model from facebook on Hugging Face (other).
ModelFFreshness: FOpen weights - 0.370score
MobileLLM-R1-950M
facebook
Open-weights 0.95B-parameter text model from facebook on Hugging Face (fair-noncommercial-research).
ModelFFreshness: FOpen weights
Cite this page
AGENTS.md · facebook/lexical. Agent Mart. https://agentmart.dev/templates/facebook-lexical-agents-md (last updated 2026-10-24)