Skip to content

MIME Type Reference — Python source

A searchable map of common file extensions to their MIME types - text, image, audio, video, application, font, and multipart. Look up by extension or by type, filter by category, and copy the exact Content-Type string for any format.

This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.

"""MIME / Content-Type reference — pure, deterministic data + lookups.

Language: Python (3.9+; uses `Literal`, `Final`, and PEP 585 generics).
CosmoDev polyglot showcase port of the ``mime-types`` tool, ported from
``src/lib/mime-types.ts``. Display source — part of CosmoDev's polyglot tool
pages.

A curated set of common file extensions mapped to their standard MIME types,
grouped by the seven top-level media types (RFC 2046): text, image, audio,
video, application, font, and multipart. Each entry's ``type`` always equals
the top-level segment of its own ``mime``, so the category filter and the
Content-Type never disagree.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Final, Literal, Optional

# The seven top-level media types a curated entry can belong to. ``Literal``
# plays the role of the TypeScript string union: mypy will flag any other value.
MimeCategory = Literal[
    "text",
    "image",
    "audio",
    "video",
    "application",
    "font",
    "multipart",
]


@dataclass(frozen=True)
class MimeEntry:
    """One curated extension ↔ MIME mapping (immutable value object)."""

    ext: str
    mime: str
    type: MimeCategory
    name: str


@dataclass(frozen=True)
class MimeFilter:
    """Optional constraints for :func:`filter`.

    ``None`` fields are ignored, mirroring ``undefined`` fields in the
    TypeScript source.
    """

    type: Optional[MimeCategory] = None
    query: Optional[str] = None


#: Display order of the categories (used by the UI filter).
MIME_CATEGORIES: Final[tuple[MimeCategory, ...]] = (
    "text",
    "image",
    "audio",
    "video",
    "application",
    "font",
    "multipart",
)

#: The curated extension ↔ MIME table. One entry is one (extension, type) pair,
#: so an extension with several MIME types (e.g. ``.js``) appears more than
#: once, and a MIME with several extensions (e.g. ``image/jpeg``) appears once
#: per extension. Values follow IANA / MDN conventions. Frozen tuple keeps the
#: registry immutable at runtime.
MIME: Final[tuple[MimeEntry, ...]] = (
    # ── text ──────────────────────────────────────────────────────────────
    MimeEntry("txt", "text/plain", "text", "Plain text"),
    MimeEntry("html", "text/html", "text", "HTML document"),
    MimeEntry("htm", "text/html", "text", "HTML document"),
    MimeEntry("xhtml", "application/xhtml+xml", "application", "XHTML"),
    MimeEntry("css", "text/css", "text", "CSS stylesheet"),
    MimeEntry("csv", "text/csv", "text", "Comma-separated values"),
    MimeEntry("xml", "application/xml", "application", "XML document"),
    MimeEntry("js", "text/javascript", "text", "JavaScript"),
    MimeEntry("js", "application/javascript", "application", "JavaScript (legacy)"),
    MimeEntry("mjs", "text/javascript", "text", "JavaScript module"),
    MimeEntry("json", "application/json", "application", "JSON"),
    MimeEntry("md", "text/markdown", "text", "Markdown"),
    MimeEntry("yaml", "text/yaml", "text", "YAML"),
    MimeEntry("yml", "text/yaml", "text", "YAML"),
    MimeEntry("ics", "text/calendar", "text", "iCalendar"),
    MimeEntry("rtf", "application/rtf", "application", "Rich text"),
    MimeEntry("tsv", "text/tab-separated-values", "text", "Tab-separated values"),

    # ── image ─────────────────────────────────────────────────────────────
    MimeEntry("jpg", "image/jpeg", "image", "JPEG image"),
    MimeEntry("jpeg", "image/jpeg", "image", "JPEG image"),
    MimeEntry("png", "image/png", "image", "PNG image"),
    MimeEntry("gif", "image/gif", "image", "GIF image"),
    MimeEntry("webp", "image/webp", "image", "WebP image"),
    MimeEntry("avif", "image/avif", "image", "AVIF image"),
    MimeEntry("svg", "image/svg+xml", "image", "SVG image"),
    MimeEntry("bmp", "image/bmp", "image", "Bitmap image"),
    MimeEntry("ico", "image/x-icon", "image", "Icon"),
    MimeEntry("tif", "image/tiff", "image", "TIFF image"),
    MimeEntry("tiff", "image/tiff", "image", "TIFF image"),
    MimeEntry("heic", "image/heic", "image", "HEIC image"),

    # ── audio ─────────────────────────────────────────────────────────────
    MimeEntry("mp3", "audio/mpeg", "audio", "MP3 audio"),
    MimeEntry("wav", "audio/wav", "audio", "WAV audio"),
    MimeEntry("ogg", "audio/ogg", "audio", "Ogg audio"),
    MimeEntry("oga", "audio/ogg", "audio", "Ogg audio"),
    MimeEntry("flac", "audio/flac", "audio", "FLAC audio"),
    MimeEntry("aac", "audio/aac", "audio", "AAC audio"),
    MimeEntry("m4a", "audio/mp4", "audio", "M4A audio"),
    MimeEntry("weba", "audio/webm", "audio", "WebM audio"),
    MimeEntry("mid", "audio/midi", "audio", "MIDI"),
    MimeEntry("midi", "audio/midi", "audio", "MIDI"),

    # ── video ─────────────────────────────────────────────────────────────
    MimeEntry("mp4", "video/mp4", "video", "MP4 video"),
    MimeEntry("webm", "video/webm", "video", "WebM video"),
    MimeEntry("avi", "video/x-msvideo", "video", "AVI video"),
    MimeEntry("mov", "video/quicktime", "video", "QuickTime video"),
    MimeEntry("mkv", "video/x-matroska", "video", "Matroska video"),
    MimeEntry("mpeg", "video/mpeg", "video", "MPEG video"),
    MimeEntry("mpg", "video/mpeg", "video", "MPEG video"),
    MimeEntry("ogv", "video/ogg", "video", "Ogg video"),
    MimeEntry("m4v", "video/mp4", "video", "M4V video"),

    # ── application ───────────────────────────────────────────────────────
    MimeEntry("pdf", "application/pdf", "application", "PDF document"),
    MimeEntry("zip", "application/zip", "application", "ZIP archive"),
    MimeEntry("gz", "application/gzip", "application", "Gzip archive"),
    MimeEntry("tar", "application/x-tar", "application", "Tar archive"),
    MimeEntry("rar", "application/vnd.rar", "application", "RAR archive"),
    MimeEntry("7z", "application/x-7z-compressed", "application", "7-Zip archive"),
    MimeEntry("bin", "application/octet-stream", "application", "Binary data"),
    MimeEntry("exe", "application/x-msdownload", "application", "Windows executable"),
    MimeEntry("jar", "application/java-archive", "application", "Java archive"),
    MimeEntry("wasm", "application/wasm", "application", "WebAssembly"),
    MimeEntry("doc", "application/msword", "application", "Word document"),
    MimeEntry(
        "docx",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application",
        "Word document (OOXML)",
    ),
    MimeEntry("xls", "application/vnd.ms-excel", "application", "Excel spreadsheet"),
    MimeEntry(
        "xlsx",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application",
        "Excel spreadsheet (OOXML)",
    ),
    MimeEntry("ppt", "application/vnd.ms-powerpoint", "application", "PowerPoint"),
    MimeEntry(
        "pptx",
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
        "application",
        "PowerPoint (OOXML)",
    ),

    # ── font ──────────────────────────────────────────────────────────────
    MimeEntry("woff", "font/woff", "font", "WOFF font"),
    MimeEntry("woff2", "font/woff2", "font", "WOFF2 font"),
    MimeEntry("ttf", "font/ttf", "font", "TrueType font"),
    MimeEntry("otf", "font/otf", "font", "OpenType font"),
    MimeEntry("eot", "application/vnd.ms-fontobject", "application", "Embedded OpenType"),

    # ── multipart ─────────────────────────────────────────────────────────
    MimeEntry("mht", "multipart/related", "multipart", "MHTML web archive"),
    MimeEntry("mhtml", "multipart/related", "multipart", "MHTML web archive"),
)


def normalize_ext(ext: str) -> str:
    """Lowercase an extension and drop a leading dot so ``.JSON`` == ``json``.

    ``lstrip(".")`` removes one or more leading dots, matching the TypeScript
    ``/^\\.+/`` regex.
    """
    return ext.strip().lower().lstrip(".")


def lookup_by_ext(ext: str) -> list[MimeEntry]:
    """Every entry whose extension matches.

    The dot is optional and matching is case-insensitive, so ``.js``, ``JS``,
    and ``js`` are equivalent. Returns ``[]`` for an unknown/empty extension.
    """
    e = normalize_ext(ext)
    if not e:
        return []
    return [m for m in MIME if m.ext == e]


def lookup_by_mime(mime: str) -> list[MimeEntry]:
    """Every entry whose MIME matches, case-insensitively, tolerating
    surrounding whitespace.

    One MIME commonly maps to several extensions (e.g. ``image/jpeg`` →
    ``jpg``, ``jpeg``), so all are returned. Returns ``[]`` for unknown/empty.
    """
    m = mime.strip().lower()
    if not m:
        return []
    return [e for e in MIME if e.mime.lower() == m]


def filter(opts: MimeFilter = MimeFilter()) -> list[MimeEntry]:  # noqa: A001
    """Filter the full table by category and/or a free-text query.

    Omitted (``None``) options are ignored; an empty query returns the
    category-filtered set unchanged. An unknown category value yields nothing
    (it is not silently coerced).

    ``opts.query or ""`` collapses both ``None`` and ``""`` to the empty query,
    matching the TypeScript ``(opts.query ?? "")`` coalescing.
    """
    q = (opts.query or "").strip().lower()
    out: list[MimeEntry] = []
    for e in MIME:
        if opts.type is not None and e.type != opts.type:
            continue
        if q:
            if not (q in e.ext.lower() or q in e.mime.lower() or q in e.name.lower()):
                continue
        out.append(e)
    return out

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →