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

  1. Re-download the theme from its source and verify the file size/extension before uploading.
  2. Confirm the file is actually a zip: unzip -t theme.zip locally, or check the first bytes are the PK zip magic (50 4B).
  3. If the archive was created with encryption or a non-DEFLATE/store method, re-export it without encryption using a standard zip tool.
  4. 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

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


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