TryGhost/Ghost · error · ThemeValidationError
Theme is not compatible or contains errors.
Error message
Theme is not compatible or contains errors.
What it means
ThemeValidationError, thrown by handleResponse when errors[0].type === 'ThemeValidationError'. It signals that a theme being uploaded or activated failed Ghost's theme validation (GScan): missing required templates, invalid package.json, deprecated helpers, or structural defects. The detailed GScan report is in error.data.
Source
Thrown at apps/admin-x-framework/src/utils/api/handle-response.ts:33
}
throw new UnauthorizedError(response, await response.text());
} else if (!response.ok) {
if (!response.headers.get('content-type')?.includes('json')) {
throw new APIError(response, await response.text());
}
const data = await response.json() as ErrorResponse;
if (response.status === 403 && data.errors?.[0]?.message === 'Authorization failed') {
throw new UnauthorizedError(response, data);
} else if (data.errors?.[0]?.type === 'VersionMismatchError') {
throw new VersionMismatchError(response, data);
} else if (data.errors?.[0]?.type === 'ValidationError') {
throw new ValidationError(response, data);
} else if (data.errors?.[0]?.type === 'NoPermissionError') {
throw new ValidationError(response, data);
} else if (data.errors?.[0]?.type === 'ThemeValidationError') {
throw new ThemeValidationError(response, data);
} else if (data.errors?.[0]?.type === 'HostLimitError') {
throw new HostLimitError(response, data);
} else if (data.errors?.[0]?.type === 'EmailError') {
throw new EmailError(response, data);
} else {
throw new JSONError(response, data);
}
} else if (response.status === 204) {
return;
} else if (response.headers.get('content-type')?.includes('text/csv')) {
return await response.text();
} else {
return await response.json();
}
};
export default handleResponse;
View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Inspect error.data.errors for the GScan report — it lists each failed check with file/line context.
- Run GScan locally (npx gscan <theme>) and fix all errors before re-uploading.
- Correct package.json (version, engines.ghost, api.compatibility) and ensure required templates exist.
Defensive patterns
Strategy: type-guard
Validate before calling
// Validate the theme zip locally before upload (run GScan). // npx gscan ./my-theme → exits non-zero on errors.
Type guard
import {ThemeValidationError} from '@tryghost/admin-x-framework/utils/errors';
function isThemeValidationError(e: unknown): e is ThemeValidationError {
return e instanceof ThemeValidationError;
} Try / catch
import {ThemeValidationError} from '@tryghost/admin-x-framework/utils/errors';
try {
await api.themes.upload(file);
} catch (e) {
if (e instanceof ThemeValidationError) {
// data.errors carries the GScan report
showThemeErrors(e.data?.errors ?? []);
} else throw e;
} Prevention
- Run Gscan locally (npx gscan <theme>) and resolve all errors before uploading.
- Keep package.json (engines.ghost, api.compatibility) in sync with the target Ghost version.
- Ensure required templates (default.hbs, post.hbs, page.hbs, etc.) exist in the zip.
When it happens
Trigger: Uploading a .zip theme whose contents fail GScan, or activating a theme whose package.json/templates are invalid. Ghost returns the validation report as errors data.
Common situations: Custom theme missing required files (e.g., post.hbs); package.json with invalid/capabilities/engines fields; theme built for a newer Ghost API version; deprecated template usage flagged as errors.
Related errors
- too_many_files
- invalid_archive
- too_large
- A view with this name already exists
- The URL must be in a format like @username@instance.tld or h
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/256496a6f9f8f206.
Report an issue: GitHub.