{"record":{"id":"dcb192ed1adf7029","repo":"TryGhost/Ghost","slug":"failed-to-convert-html-to-lexical-dcb192","errorCode":null,"errorMessage":"Failed to convert HTML to Lexical","messagePattern":"Failed to convert HTML to Lexical","errorType":"validation","errorClass":"ValidationError","httpStatus":422,"severity":"error","filePath":"ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js","lineNumber":215,"sourceCode":"            defaultRelations(frame);\n        }\n    },\n\n    add(apiConfig, frame, options = {add: true}) {\n        debug('add');\n\n        if (_.get(frame,'options.source')) {\n            const html = frame.data.posts[0].html;\n\n            if (frame.options.source === 'html' && !_.isEmpty(html)) {\n                if (process.env.CI) {\n                    console.time('htmlToLexicalConverter (post)'); // eslint-disable-line no-console\n                }\n\n                try {\n                    frame.data.posts[0].lexical = JSON.stringify(lexical.htmlToLexicalConverter(html));\n                } catch (err) {\n                    throw new ValidationError({\n                        message: tpl(messages.failedHtmlToLexical),\n                        err\n                    });\n                }\n\n                if (process.env.CI) {\n                    console.timeEnd('htmlToLexicalConverter (post)'); // eslint-disable-line no-console\n                }\n            }\n        }\n\n        frame.data.posts[0] = url.forPost(Object.assign({}, frame.data.posts[0]), frame.options);\n\n        // @NOTE: force adding post\n        if (options.add) {\n            frame.data.posts[0].type = 'post';\n        }\n","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js#L197-L233","documentation":"Same HTML-to-Lexical conversion guard as pages, thrown by the posts input serializer when `source=html` and `htmlToLexicalConverter` throws. The post's HTML is converted to a Lexical JSON string; on failure a ValidationError wraps the underlying error. Used for HTML import into posts.","triggerScenarios":"A post add/edit request carries `source=html` with a non-empty `html` field that the Lexical converter rejects. The serializer catches the converter exception and rethrows as a 422 ValidationError.","commonSituations":"Importing posts from external sources with malformed HTML; embedding unsupported markup (iframes, scripts stripped by the converter); encoding artifacts; converter library regression after an upgrade.","solutions":["Pre-clean the HTML with a sanitizer before submitting the post.","Reduce the HTML to a minimal subset to isolate the offending element.","Examine the nested `err` returned by the API for the precise parse failure.","Send Lexical JSON directly (omit `source=html`) if you control the editor format."],"exampleFix":"// before\nawait api.posts.add({posts: [{html: rawHtml}], options: {source: 'html'}});\n\n// after\nconst lexical = convertHtmlToLexicalClientSide(rawHtml); // or sanitize first\nawait api.posts.add({posts: [{lexical: JSON.stringify(lexical)}]})","handlingStrategy":"validation","validationCode":"function isValidHtmlForConversion(html: string): boolean {\n    try {\n        new DOMParser().parseFromString(html, 'text/html');\n        return true;\n    } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n    await api.posts.add({posts: [{html}], options: {source: 'html'}});\n} catch (err) {\n    if (/Failed to convert HTML to Lexical/i.test(JSON.stringify((err as any).response?.body))) {\n        html = sanitizeHtml(html);\n        await api.posts.add({posts: [{html}], options: {source: 'html'}});\n    } else { throw err; }\n}","preventionTips":["Pre-clean external HTML with a sanitizer before importing.","Reduce to a minimal HTML subset to isolate unsupported elements.","Submit Lexical JSON directly if you do not need HTML import."],"tags":["api","lexical","html","posts","validation","import"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}