{"record":{"id":"30be764f488ba02a","repo":"TryGhost/Ghost","slug":"too-large","errorCode":"too_large","errorMessage":"This theme archive is too large to open in the browser editor. Extracted files must stay under ${getThemeArchiveSizeLabel(THEME_EDITOR_ARCHIVE_LIMITS.maxExtractedBytes)}.","messagePattern":"This theme archive is too large to open in the browser editor\\. Extracted files must stay under (.+?)\\.","errorType":"validation","errorClass":"ThemeArchiveExtractionError","httpStatus":null,"severity":"warning","filePath":"apps/admin/src/settings/app/components/settings/site/theme/theme-editor-utils.ts","lineNumber":234,"sourceCode":"        throw new ThemeArchiveExtractionError('invalid_archive', invalidArchiveMessage);\n    }\n};\n\nconst getNormalizedArchivePath = (path: string) => {\n    const normalizedPath = normaliseRelativePath(path);\n\n    if (!normalizedPath || normalizedPath !== path) {\n        throw new ThemeArchiveExtractionError('invalid_archive', invalidArchiveMessage);\n    }\n\n    return normalizedPath;\n};\n\nconst trackExtractedBytes = (totalBytes: number, fileBytes: number) => {\n    const nextTotal = totalBytes + fileBytes;\n\n    if (nextTotal > THEME_EDITOR_ARCHIVE_LIMITS.maxExtractedBytes) {\n        throw new ThemeArchiveExtractionError(\n            'too_large',\n            `This theme archive is too large to open in the browser editor. Extracted files must stay under ${getThemeArchiveSizeLabel(THEME_EDITOR_ARCHIVE_LIMITS.maxExtractedBytes)}.`\n        );\n    }\n\n    return nextTotal;\n};\n\nexport const extractThemeArchive = async (arrayBuffer: ArrayBuffer): Promise<ThemeEditorSnapshot> => {\n    const zip = await loadThemeArchive(arrayBuffer);\n    const entries = collectArchiveEntries(zip);\n\n    assertThemeArchiveLimits(entries);\n\n    const rootPrefix = detectCommonRoot(entries.map(([path]) => path));\n    const files: Record<string, ThemeEditorFile> = {};\n    const textEncoder = new TextEncoder();\n    let extractedBytes = 0;","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/settings/app/components/settings/site/theme/theme-editor-utils.ts#L216-L252","documentation":"Thrown by trackExtractedBytes during extraction when the running total of extracted bytes exceeds THEME_EDITOR_ARCHIVE_LIMITS.maxExtractedBytes (32 MiB). This guards against zip-bomb-style or asset-heavy archives that would exhaust browser memory: each extracted file (text-encoded for editable files, raw bytes for binary) is added to a running total, and the limit is on the DECOMPRESSED size, not the archive size. Thrown as ThemeArchiveExtractionError with reason 'too_large'.","triggerScenarios":"extractThemeArchive accumulates > 32 MiB across extracted entries. Triggers: a theme with large uncompressed images/fonts/videos, a highly compressible archive (zip bomb), or many binary assets whose summed byteLength crosses 32 MiB even if the .zip itself is small.","commonSituations":"Theme bundles full-resolution hero images, woff2 fonts, or video files; developer included raw (unoptimised) assets; malicious or pathological archive with high compression ratio; bundled sample media.","solutions":["Reduce the extracted footprint: optimise/compress images, remove unused binary assets, and re-zip so the decompressed total stays under 32 MiB.","Pre-check by summing uncompressed sizes locally (unzip -l shows uncompressed bytes per entry) before uploading.","If the theme legitimately needs more, raise THEME_EDITOR_ARCHIVE_LIMITS.maxExtractedBytes, but reconsider whether the in-browser editor is the right tool — use server-side theme upload instead.","Strip large media from the edit-time archive and add them back through Ghost's media management."],"exampleFix":"// before — extraction throws ThemeArchiveExtractionError mid-flow\nconst snapshot = await extractThemeArchive(arrayBuffer);\n\n// after — catch and branch on the typed reason\nimport {ThemeArchiveExtractionError} from './theme-editor-utils';\ntry {\n    const snapshot = await extractThemeArchive(arrayBuffer);\n} catch (e) {\n    if (e instanceof ThemeArchiveExtractionError && e.reason === 'too_large') {\n        setUploadError(e.message); // 'This theme archive is too large ...'\n        return;\n    }\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":"// The limit is on DECOMPRESSED bytes, so you can't fully pre-validate from archive size,\n// but you can reject obviously oversized archives early as a coarse guard.\nconst COARSE_LIMIT = 64 * 1024 * 1024; // be generous vs the 32 MiB extracted cap\nif (arrayBuffer.byteLength > COARSE_LIMIT) {\n    setUploadError('Archive is too large for the browser editor.');\n}","typeGuard":"import {ThemeArchiveExtractionError} from './theme-editor-utils';\nfunction isTooLarge(e: unknown): e is ThemeArchiveExtractionError {\n    return e instanceof ThemeArchiveExtractionError && e.reason === 'too_large';\n}","tryCatchPattern":"import {extractThemeArchive, ThemeArchiveExtractionError} from './theme-editor-utils';\ntry {\n    const snapshot = await extractThemeArchive(arrayBuffer);\n} catch (e) {\n    if (e instanceof ThemeArchiveExtractionError && e.reason === 'too_large') {\n        setUploadError(e.message);\n        return;\n    }\n    throw e;\n}","preventionTips":["Sum uncompressed entry sizes locally (unzip -l) before uploading to the browser editor.","Optimise/compress large images, fonts, and media before zipping.","For asset-heavy themes, prefer server-side theme upload.","Branch on ThemeArchiveExtractionError.reason for specific messaging."],"tags":["theme","archive","zip","validation","browser"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}