langflow-ai/langflow · error · ValueError

Uploaded file is not a valid ZIP archive: {exc}

Error message

Uploaded file is not a valid ZIP archive: {exc}

What it means

Raised by _extract_flows_sync in zip_utils.py when zipfile.ZipFile cannot parse the uploaded bytes — Python raises zipfile.BadZipFile for corrupt, truncated, or non-ZIP content, and the helper converts it to ValueError with this message. The flows-import endpoint catches it and returns a 4xx to the client. It means the bytes received were not a readable ZIP archive.

Source

Thrown at src/backend/base/langflow/api/utils/zip_utils.py:38

    """Result of synchronous ZIP extraction, including warnings to log after."""

    flows: list[dict] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)


def _extract_flows_sync(contents: bytes) -> _ZipExtractionResult:
    """Synchronous helper that performs all blocking ZIP I/O.

    Raises:
        ValueError: If the ZIP is corrupt or contains more than MAX_ZIP_ENTRIES JSON files.
    """
    result = _ZipExtractionResult()

    try:
        zf = zipfile.ZipFile(io.BytesIO(contents), "r")
    except zipfile.BadZipFile as exc:
        msg = f"Uploaded file is not a valid ZIP archive: {exc}"
        raise ValueError(msg) from exc

    with zf:
        json_entries = [info for info in zf.infolist() if info.filename.lower().endswith(".json")]

        if len(json_entries) > MAX_ZIP_ENTRIES:
            msg = f"ZIP contains {len(json_entries)} JSON entries, exceeding the limit of {MAX_ZIP_ENTRIES}"
            raise ValueError(msg)

        for info in json_entries:
            if info.file_size > MAX_ENTRY_UNCOMPRESSED_BYTES:
                result.warnings.append(
                    f"Skipping ZIP entry '{info.filename}': uncompressed size "
                    f"{info.file_size} exceeds limit of {MAX_ENTRY_UNCOMPRESSED_BYTES} bytes"
                )
                continue
            try:
                raw = zf.read(info.filename)
                if len(raw) > MAX_ENTRY_UNCOMPRESSED_BYTES:

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Confirm the uploaded file actually is a ZIP (unzip -t file.zip locally) and re-export a real ZIP bundle
  2. If you have a single .json flow, use the JSON import endpoint or wrap it: zip flows.zip flow.json
  3. Check for proxy/upload truncation: compare the file size/hash before upload vs on the server
  4. Verify the multipart field name and filename match what the endpoint expects (with .zip extension)

Example fix

# before
curl -F "file=@myflow.json" /api/v1/flows/upload/
# after
curl -F "file=@flows.zip" /api/v1/flows/upload/
Defensive patterns

Strategy: validation

Validate before calling

import zipfile, io

def is_valid_flow_zip(path: str) -> bool:
    try:
        with zipfile.ZipFile(path) as zf:
            return zf.testzip() is None
    except zipfile.BadZipFile:
        return False

Try / catch

try:
    result = import_flows(zip_bytes)
except ValueError as e:
    if "not a valid ZIP archive" in str(e):
        if looks_like_json(zip_bytes):
            result = import_flow_json(zip_bytes)   # single-flow fallback path
        else:
            raise
    else:
        raise

Prevention

When it happens

Trigger: POST /api/v1/flows/upload/ (or any flows-import route using _extract_flows_sync) with a file that is not a ZIP: a bare .json flow file, a .tar.gz, a text file, or a ZIP truncated by a proxy/upload limit mid-transfer.

Common situations: Exporting a single flow from the Langflow UI (which downloads JSON, not ZIP) and re-uploading it directly; client-side encoding setting the wrong multipart field name so the server reads a different part; uploads truncated by nginx client_max_body_size or similar proxies.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/13fbfef5546dfb62. Report an issue: GitHub.