facebook/lexical · error

Invalid state 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.

Solutions

  1. Ensure all attributes written to shared Yjs elements that represent node state use the stateKeyToAttrKey helper (adds the 's_' prefix).
  2. Filter the attribute map before conversion: only pass keys matching /^s_/ to attrKeyToStateKey.
  3. Check for version mismatch between the code writing attributes and the code reading them; upgrade both clients.
  4. 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

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


AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31). Data as JSON: /api/errors/509c847280bd7625. Report an issue: GitHub.

Appendix: 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)