{"record":{"id":"45c8bb71cd89ac27","repo":"overleaf/overleaf","slug":"referenced-element-not-a-number","errorCode":null,"errorMessage":"Referenced element not a number","messagePattern":"Referenced element not a number","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/document-updater/app/js/sharejs/types/json.js","lineNumber":110,"sourceCode":"      let parentkey = null\n      let elem = container\n      let key = 'data'\n\n      for (const p of Array.from(c.p)) {\n        parent = elem\n        parentkey = key\n        elem = elem[key]\n        key = p\n\n        if (parent == null) {\n          throw new Error('Path invalid')\n        }\n      }\n\n      if (c.na !== undefined) {\n        // Number add\n        if (typeof elem[key] !== 'number') {\n          throw new Error('Referenced element not a number')\n        }\n        elem[key] += c.na\n      } else if (c.si !== undefined) {\n        // String insert\n        if (typeof elem !== 'string') {\n          throw new Error(\n            `Referenced element not a string (it was ${JSON.stringify(elem)})`\n          )\n        }\n        parent[parentkey] = elem.slice(0, key) + c.si + elem.slice(key)\n      } else if (c.sd !== undefined) {\n        // String delete\n        if (typeof elem !== 'string') {\n          throw new Error('Referenced element not a string')\n        }\n        if (elem.slice(key, key + c.sd.length) !== c.sd) {\n          throw new Error('Deleted string does not match')\n        }","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/document-updater/app/js/sharejs/types/json.js#L92-L128","documentation":"For 'na' (number add) components, the json type verifies the target element is of type number before applying the increment. If the value at elem[key] is a string, null, boolean, or undefined, applying would silently corrupt the document, so it throws instead.","triggerScenarios":"Submitting na against a field holding a non-number — e.g. counter initialized to null or '0' (string), or the field was never created so it is undefined; another client replaced the counter with a string.","commonSituations":"Counters initialized from user/config input as strings; missing field on first use (undefined); a migration that changed the counter to a different type.","solutions":["Initialize numeric fields to 0 before any na op runs","Coerce or fix the field to a number first (submit an oi op setting a number), then use na","Validate client-side that snapshot value at the path is typeof 'number' before submitting na","Ensure all writers treat the field strictly as a number"],"exampleFix":"// before\ndoc.at(['views']).increment(1) // views === null\n// after\nif (typeof doc.get().views !== 'number') doc.at(['views']).set(0)\ndoc.at(['views']).increment(1)","handlingStrategy":"type-guard","validationCode":"const v = doc.get().views // resolve path to target\nif (typeof v !== 'number') throw new Error(`views is not a number: ${typeof v}`)","typeGuard":"const isNumber = (v) => typeof v === 'number' && !Number.isNaN(v)","tryCatchPattern":"try {\n  doc.at(['views']).increment(1)\n} catch (err) {\n  if (err.message === 'Referenced element not a number') {\n    doc.at(['views']).set(0)\n    doc.at(['views']).increment(1)\n    return\n  }\n  throw err\n}","preventionTips":["Initialize numeric fields to 0 before any na ops","Never initialize counters from raw string input; coerce with Number() first","Check typeof === 'number' before submitting na ops","Restrict writers of the field to code paths that preserve numeric type"],"tags":["json-ot","type-mismatch","number"],"backgroundTag":"ot-element-type-mismatch","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}