langflow-ai/langflow · error · HTTPException

Extension not found for file {file_name}

Error message

Extension not found for file {file_name}

What it means

HTTP 500 from GET /files/download/{flow_id}/{file_name} when the file_name has no extension — i.e. file_name.split('.')[-1] equals the whole name because there is no dot. The router param is a ValidatedFileName, so in practice this only fires if the validator permits extensionless names; the guard is a last-resort check because content-type resolution is impossible without an extension.

Source

Thrown at src/backend/base/langflow/api/v1/files.py:136

        folder = str(flow.id)
        await storage_service.save_file(flow_id=folder, file_name=full_file_name, data=file_content)
        return UploadFileResponse(flow_id=str(flow.id), file_path=f"{folder}/{full_file_name}")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e)) from e


@router.get("/download/{flow_id}/{file_name}")
async def download_file(
    file_name: ValidatedFileName,
    flow: Annotated[Flow, Depends(get_flow)],
    storage_service: Annotated[StorageService, Depends(get_storage_service)],
):
    # Authorization handled by get_flow dependency
    flow_id_str = str(flow.id)
    extension = file_name.split(".")[-1]

    if not extension:
        raise HTTPException(status_code=500, detail=f"Extension not found for file {file_name}")
    try:
        content_type = build_content_type_from_extension(extension)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e)) from e

    if not content_type:
        raise HTTPException(status_code=500, detail=f"Content type not found for extension {extension}")

    try:
        file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
        headers = {
            "Content-Disposition": build_content_disposition(file_name),
            "Content-Type": "application/octet-stream",
            "Content-Length": str(len(file_content)),
        }
        return StreamingResponse(BytesIO(file_content), media_type=content_type, headers=headers)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e)) from e

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Request the file by its exact stored name including extension (the upload response's file_path contains it)
  2. Ensure uploaded files always carry an extension; add one at upload time if the original lacks it
  3. List the flow's files to recover the exact file_name before constructing the download URL

Example fix

# before
GET /api/v1/files/download/{flow_id}/data

# after
GET /api/v1/files/download/{flow_id}/2024-01-01_00-00-00_data.csv
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def has_extension(file_name: str) -> bool:
    return bool(Path(file_name).suffix)

Type guard

def is_downloadable_name(file_name: str) -> bool:
    name = file_name.strip()
    return "." in name and name.rsplit(".", 1)[1].isalnum()

Try / catch

try:
    client.get(f"/files/download/{flow_id}/{file_name}").raise_for_status()
except HTTPError as e:
    if "Extension not found" in e.response.text:
        file_name = lookup_stored_name(flow_id, file_name)  # find real stored name
        client.get(f"/files/download/{flow_id}/{file_name}").raise_for_status()

Prevention

When it happens

Trigger: GET /files/download/{flow_id}/README (no dot in name); filenames ending in a dot where split yields an empty-ish segment; URLs where the filename got truncated before the extension.

Common situations: Hand-constructed download URLs; files saved without extensions by external tooling; URL-encoding issues stripping the extension.

Related errors


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