TryGhost/Ghost · error · ThemeArchiveExtractionError
invalid_archive
invalid_archive
Error message
Failed to open the theme archive. Download the theme again and retry.
What it means
Thrown by loadThemeArchive when JSZip.loadAsync rejects (the ArrayBuffer is not a valid zip), and by readThemeBinaryFile/readThemeTextFile/getNormalizedArchivePath when an individual entry can't be read or its path is malformed (contains ./.. or doesn't survive normalisation). It signals the downloaded/uploaded file is corrupt or not a zip archive at all. Thrown as ThemeArchiveExtractionError with reason 'invalid_archive'.
Source
Thrown at apps/admin/src/settings/app/components/settings/site/theme/theme-editor-utils.ts:200
});
return entries;
};
const assertThemeArchiveLimits = (entries: Array<[string, JSZip.JSZipObject]>) => {
if (entries.length > THEME_EDITOR_ARCHIVE_LIMITS.maxFiles) {
throw new ThemeArchiveExtractionError(
'too_many_files',
`This theme archive contains too many files for the browser editor (${entries.length}/${THEME_EDITOR_ARCHIVE_LIMITS.maxFiles}).`
);
}
};
const loadThemeArchive = async (arrayBuffer: ArrayBuffer) => {
try {
return await JSZip.loadAsync(arrayBuffer);
} catch {
throw new ThemeArchiveExtractionError('invalid_archive', invalidArchiveMessage);
}
};
const readThemeBinaryFile = async (entry: JSZip.JSZipObject) => {
try {
return await entry.async('uint8array');
} catch {
throw new ThemeArchiveExtractionError('invalid_archive', invalidArchiveMessage);
}
};
const readThemeTextFile = async (entry: JSZip.JSZipObject) => {
try {
return await entry.async('string');
} catch {
throw new ThemeArchiveExtractionError('invalid_archive', invalidArchiveMessage);
}
};View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Re-download the theme from its source and verify the file size/extension before uploading.
- Confirm the file is actually a zip: unzip -t theme.zip locally, or check the first bytes are the PK zip magic (50 4B).
- If the archive was created with encryption or a non-DEFLATE/store method, re-export it without encryption using a standard zip tool.
- Catch ThemeArchiveExtractionError and show the bundled 'Download the theme again and retry' message to the user.
Example fix
// before — uncaught rejection surfaces as a generic error
const snapshot = await extractThemeArchive(arrayBuffer);
// after — branch on the typed reason for invalid archives
import {ThemeArchiveExtractionError} from './theme-editor-utils';
try {
const snapshot = await extractThemeArchive(arrayBuffer);
} catch (e) {
if (e instanceof ThemeArchiveExtractionError && e.reason === 'invalid_archive') {
setUploadError('Failed to open the theme archive. Download the theme again and retry.');
return;
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
import {ThemeArchiveExtractionError} from './theme-editor-utils';
function isInvalidArchive(e: unknown): e is ThemeArchiveExtractionError {
return e instanceof ThemeArchiveExtractionError && e.reason === 'invalid_archive';
} Try / catch
import {extractThemeArchive, ThemeArchiveExtractionError} from './theme-editor-utils';
try {
const snapshot = await extractThemeArchive(arrayBuffer);
} catch (e) {
if (e instanceof ThemeArchiveExtractionError && e.reason === 'invalid_archive') {
setUploadError('Failed to open the theme archive. Download the theme again and retry.');
return;
}
throw e;
} Prevention
- Verify the file is a real zip before upload (check the PK magic bytes / unzip -t).
- Re-download the theme if the download may have been truncated.
- Avoid encrypted or non-DEFLATE/store zip methods — JSZip can't read them.
- Branch on ThemeArchiveExtractionError.reason for specific messaging.
When it happens
Trigger: JSZip.loadAsync(arrayBuffer) rejects — truncated download, a file that isn't a zip (e.g. an HTML error page saved as .zip), an unsupported compression method, or CRC errors. Also thrown when a zip entry path contains '.' or '..' segments or doesn't equal its normalised form, or when entry.async('string'|'uint8array') throws for a specific corrupted entry.
Common situations: Theme download was interrupted/truncated; server returned an HTML 404/500 page that got saved with a .zip extension; zip created with a tool/encryption JSZip can't read (e.g. AES-encrypted, unsupported codec); a path inside the archive uses backslashes or relative segments that fail normalisation.
Related errors
- too_many_files
- too_large
- Theme is not compatible or contains errors.
- 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/b327d51d916e6f6a.
Report an issue: GitHub.