TryGhost/Ghost · error · ValidationError

Email content is required

Error message

Email content is required

What it means

Thrown by `validatePreviewData` when `frame.data.lexical` is not a non-empty trimmed string. The Lexical field holds the email body content; a blank/missing value means there is nothing to render, so Ghost rejects with a 422 ValidationError on the `lexical` property. Checked after the subject requirement.

Source

Thrown at ghost/core/core/server/api/endpoints/utils/validators/input/automated_emails.js:85

        throw new ValidationError({
            message: errorMessage
        });
    }
};

const validatePreviewData = (frame) => {
    const subject = frame.data.subject;
    const lexical = frame.data.lexical;

    if (typeof subject !== 'string' || !subject.trim()) {
        throw new ValidationError({
            message: tpl(messages.subjectRequired),
            property: 'subject'
        });
    }

    if (typeof lexical !== 'string' || !lexical.trim()) {
        throw new ValidationError({
            message: tpl(messages.lexicalRequired),
            property: 'lexical'
        });
    }

    try {
        JSON.parse(lexical);
    } catch (e) {
        throw new ValidationError({
            message: tpl(messages.invalidLexical),
            property: 'lexical'
        });
    }
};

module.exports = {
    async add(apiConfig, frame) {
        await validateAutomatedEmail(frame);

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Include a non-empty `lexical` string (the Lexical editor JSON) in the request body.
  2. Verify the editor state is serialized into the `lexical` field before submit.
  3. Ensure the field name matches exactly (`lexical`).

Example fix

// before
await api.emailPreview.send({data: {subject: 'Hi', email: 'a@b.com'}}); // lexical missing

// after
await api.emailPreview.send({data: {subject: 'Hi', lexical: JSON.stringify(lexicalDoc), email: 'a@b.com'}});
Defensive patterns

Strategy: validation

Validate before calling

function isValidLexicalString(s: unknown): s is string {
    return typeof s === 'string' && s.trim().length > 0;
}
if (!isValidLexicalString(payload.lexical)) {
    throw new Error('lexical must be a non-empty JSON string');
}

Type guard

function isNonEmptyString(v: unknown): v is string {
    return typeof v === 'string' && v.trim().length > 0;
}

Prevention

When it happens

Trigger: A preview/test-email request omits `lexical`, sends it as a non-string type, or sends whitespace only.

Common situations: Editor sent the preview with an empty Lexical document; payload key misspelled; client constructs the request before the editor content is populated.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/cdac738e06030a43. Report an issue: GitHub.