TryGhost/Ghost · error · ValidationError

Subject is required

Error message

Subject is required

What it means

A ValidationError thrown by the automation email preview input validator when the request frame's `data.subject` is missing, not a string, or whitespace-only. The validator runs for both the `preview` and `sendTestEmail` automation endpoints before any email rendering happens, so a blank subject short-circuits the whole call. It is a client-side input error, not a server fault.

Source

Thrown at ghost/core/core/server/api/endpoints/utils/validators/input/automation_email_previews.js:21

const validator = require('@tryghost/validator');
const {ValidationError} = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const lexicalLib = require('../../../../../lib/lexical');

const messages = {
    invalidEmailReceived: 'The server did not receive a valid email',
    invalidLexical: 'Lexical must be a well-formed Lexical document',
    subjectRequired: 'Subject is required',
    lexicalRequired: 'Email content is required'
};

const validatePreviewData = async (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'
        });
    }

    if (!await lexicalLib.validate(lexical)) {
        throw new ValidationError({
            message: tpl(messages.invalidLexical),
            property: 'lexical'
        });
    }

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Ensure `frame.data.subject` (or the JSON body `subject`) is a non-empty trimmed string before submitting the request.
  2. Validate on the client that `subject.trim().length > 0` and surface a form error instead of hitting the API.
  3. If driving the preview from a post, map the post title into `subject` and default it to a placeholder when the title is blank.
  4. Confirm the request `Content-Type` is `application/json` so the body parses into `frame.data` correctly.

Example fix

// before
await api.preview({data: {lexical, subject: post.title}}); // post.title is undefined

// after
const subject = (post.title || '').trim();
if (!subject) throw new Error('Cannot preview: post has no title');
await api.preview({data: {lexical, subject}});
Defensive patterns

Strategy: validation

Validate before calling

function validatePreviewPayload({subject, lexical, email} = {}) {
  const errors = {};
  if (typeof subject !== 'string' || !subject.trim()) errors.subject = 'Subject is required';
  if (typeof lexical !== 'string' || !lexical.trim()) errors.lexical = 'Email content is required';
  if (email !== undefined && (typeof email !== 'string' || !validator.isEmail(email))) errors.email = 'Invalid email';
  return errors;
}
// const errs = validatePreviewPayload(payload); if (Object.keys(errs).length) throw new Error(JSON.stringify(errs));

Type guard

const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;
const hasValidSubject = (p) => isNonEmptyString(p?.subject);

Try / catch

try {
  await api.preview({data: {subject, lexical}});
} catch (err) {
  if (err.type === 'ValidationError' && err.property === 'subject') showFieldError('subject', err.message);
  else throw err;
}

Prevention

When it happens

Trigger: POSTing to the automation email preview (or send-test-email) endpoint with a body where `subject` is omitted, null, a number/boolean, or an empty/whitespace string. For example `{"lexical": "..."}` with no `subject`, or `{"subject": " "}`.

Common situations: An integration or Zapier-style automation submits a post's title as the subject but the title field is empty on the source post; a frontend form forgets to bind the subject input; the caller sends `subject` as a number (e.g. an ID) by mistake; trailing whitespace from a textarea is the only content.

Related errors


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