facebook/lexical · error
Invalid state key: ${key}
Error message
Invalid state key: ${key} What it means
SyncV2 stores Lexical node state (node.__state) in Yjs element attributes under keys prefixed with 's_'. attrKeyToStateKey converts an attribute key back to a state key and throws if the attribute does not carry the required prefix. This guards against reading foreign attributes (user data, other libs) as editor state.
Source
Thrown at packages/lexical-yjs/src/SyncV2.ts:970
const propertiesToAttributes = (node: LexicalNode, meta: BindingV2) => {
const defaultProperties = getDefaultNodeProperties(node, meta);
const attrs: Record<string, unknown> = {};
Object.entries(defaultProperties).forEach(([property, defaultValue]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value = (node as any)[property];
if (value !== defaultValue) {
attrs[property] = value;
}
});
return attrs;
};
const STATE_KEY_PREFIX = 's_';
const stateKeyToAttrKey = (key: string): `s_${string}` => `s_${key}`;
const attrKeyToStateKey = (key: string) => {
if (!key.startsWith(STATE_KEY_PREFIX)) {
throw new Error(`Invalid state key: ${key}`);
}
return key.slice(STATE_KEY_PREFIX.length);
};
const stateToAttributes = (node: LexicalNode) => {
const state = node.__state;
if (!state) {
return {};
}
const [unknown = {}, known] = state.getInternalState();
const attrs: Record<string, unknown> = {};
for (const [k, v] of Object.entries(unknown)) {
attrs[stateKeyToAttrKey(k)] = v;
}
for (const [stateConfig, v] of known) {
attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
}
return attrs;View on GitHub (pinned to 76a22dcba9)
Solutions
- Ensure all attributes written to shared Yjs elements that represent node state use the stateKeyToAttrKey helper (adds the 's_' prefix).
- Filter the attribute map before conversion: only pass keys matching /^s_/ to attrKeyToStateKey.
- Check for version mismatch between the code writing attributes and the code reading them; upgrade both clients.
- If migrating old documents, run a one-time migration to rename attributes to the prefixed form.
Example fix
// before
const stateKey = attrKeyToStateKey(attrName);
// after
if (attrName.startsWith('s_')) {
const stateKey = attrKeyToStateKey(attrName);
} Defensive patterns
Strategy: validation
Validate before calling
function isStateAttr(key: string): boolean {
return typeof key === 'string' && key.startsWith('s_');
}
Object.keys(attrs).filter(isStateAttr).map(attrKeyToStateKey); Type guard
function isStateAttrKey(key: string): key is `s_${string}` {
return key.startsWith('s_');
} Try / catch
try {
stateKey = attrKeyToStateKey(key);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Invalid state key')) {
console.warn('Skipping non-state attribute', key);
return;
}
throw e;
} Prevention
- Always write node state attributes through stateKeyToAttrKey.
- Filter attribute maps to the s_ prefix before parsing.
- Version-check documents written by older library versions.
When it happens
Trigger: attrKeyToStateKey is called on an attribute key from a Yjs XmlElement that does not start with 's_' — i.e. stateToAttributes/attribute iteration encounters an attribute written outside the state-key convention.
Common situations: Hand-edited or migrated Yjs documents with attributes lacking the 's_' prefix; a custom sync integration writing raw attribute names; version skew where the prefix scheme changed between library versions.
Related errors
- Unexpected delta format
- $createOrUpdateNodeFromYElement: Node ${type} is not registe
- $createTextNodesFromYText: Node ${type} is not registered
- $createTextNodesFromYText: Node ${type} is not a TextNode
- node name mismatch!
AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31).
Data as JSON: /api/errors/509c847280bd7625.
Report an issue: GitHub.