{"record":{"id":"9432ee89082a04c0","repo":"laurent22/joplin","slug":"validation-error-id-must-a-32-characters-lowercas","errorCode":null,"errorMessage":"Validation error: ID must a 32-characters lowercase hexadecimal string","messagePattern":"Validation error: ID must a 32-characters lowercase hexadecimal string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/BaseModel.ts","lineNumber":612,"sourceCode":"\n\t\t\tquery = Database.insertQuery(this.tableName(), o);\n\t\t} else {\n\t\t\tconst where = { id: o.id };\n\t\t\tconst temp = { ...o };\n\t\t\tdelete temp.id;\n\n\t\t\tquery = Database.updateQuery(this.tableName(), temp, where);\n\t\t}\n\n\t\tquery.id = modelId;\n\t\tquery.modObject = o;\n\n\t\treturn query;\n\t}\n\n\tpublic static userSideValidation(o: Record<string, unknown>) {\n\t\tif (typeof o.id === 'string' && !o.id.match(/^[a-f0-9]{32}$/)) {\n\t\t\tthrow new Error('Validation error: ID must a 32-characters lowercase hexadecimal string');\n\t\t}\n\n\t\tconst timestamps = ['user_updated_time', 'user_created_time'] as const;\n\t\tfor (const k of timestamps) {\n\t\t\tif ((k in o) && (typeof o[k] !== 'number' || isNaN(o[k] as number) || (o[k] as number) < 0)) throw new Error('Validation error: user_updated_time and user_created_time must be numbers greater than 0');\n\t\t}\n\n\t\tconst maxTitleLength = 4096;\n\t\tif (typeof o.title === 'string' && o.title.length > maxTitleLength) {\n\t\t\tthrow new Error(`Validation error: title must be ${maxTitleLength} characters or less`);\n\t\t}\n\n\t\t// Null bytes break Joplin's serialised note format and can cause silent\n\t\t// truncation in some HTTP clients (notably React Native on iOS).\n\t\tconst nul = String.fromCharCode(0);\n\t\tfor (const k of Object.keys(o)) {\n\t\t\tconst v = o[k];\n\t\t\tif (typeof v === 'string' && v.includes(nul)) {","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/BaseModel.ts#L594-L630","documentation":"Thrown by BaseModel.userSideValidation() when validating an entity before save. Joplin item IDs are 32-character lowercase hex strings (MD5-style). The regex `/^[a-f0-9]{32}$/` rejects anything with uppercase, wrong length, or non-hex characters. This guard runs for every BaseItem save path that calls userSideValidation.","triggerScenarios":"userSideValidation(o) is called with o.id being a string that does not match /^[a-f0-9]{32}$/ — e.g. a UUID with dashes, an uppercase hex, a 31- or 33-char string, or a random string.","commonSituations":"External importer generating its own IDs; test fixture with a hand-written id; data migration that left malformed IDs; uppercase hex from another system.","solutions":["Generate IDs using Joplin's own id utilities (e.g. md5 of content, or the shim's uuid helper that produces a 32-char lowercase hex).","If importing external data, normalize ids to lowercase and strip non-hex characters before save.","Confirm the id is exactly 32 chars with no dashes (unlike a canonical UUID)."],"exampleFix":"// before\nawait Note.save({ id: 'ABC123-4567', title: 'x' });\n// after\nimport { uuid } from '@joplin/lib/uuid';\nawait Note.save({ id: uuid(), title: 'x' });","handlingStrategy":"validation","validationCode":"const isValidId = (id) => typeof id === 'string' && /^[a-f0-9]{32}$/.test(id);\nif (!isValidId(entity.id)) throw new Error('Refusing to save: id is not a 32-char lowercase hex');","typeGuard":"const isJoplinId = (id) => typeof id === 'string' && /^[a-f0-9]{32}$/.test(id);","tryCatchPattern":"try { BaseModel.userSideValidation(entity); }\ncatch (e) { if (/ID must a 32-characters/.test(e.message)) { /* regenerate id with uuid() */ } else throw e; }","preventionTips":["Always generate IDs through Joplin's uuid helper rather than hand-rolling.","In importers, normalise external IDs to lowercase 32-char hex (or generate fresh IDs and keep a mapping).","Add a unit test fixture that asserts every saved entity passes userSideValidation."],"tags":["validation","data-integrity","typescript","joplin-core"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}