Skip to content

MIME Type Reference — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// mime-types — Swift port of the curated extension ↔ MIME table, ported from
// src/lib/mime-types.ts. Display source for CosmoDev's polyglot tool pages.
//
// The table is a `let` array of 71 entries grouped by the seven top-level
// media types (RFC 2046). Each entry's `type` always equals the top-level
// segment of its own `mime`, so the category filter and the Content-Type
// never disagree.

/// The seven top-level media types a curated entry can belong to.
enum MimeCategory: String, CaseIterable, Sendable {
    case text, image, audio, video, application, font, multipart
}

/// One curated extension ↔ MIME mapping (immutable value type).
struct MimeEntry: Equatable, Sendable {
    /// File extension, lowercase, no leading dot (e.g. "json").
    let ext: String
    /// Full MIME / Content-Type string (e.g. "application/json").
    let mime: String
    /// Top-level media type — matches the `mime` string's first segment.
    let type: MimeCategory
    /// Human-readable format name (e.g. "JSON").
    let name: String
}

/// Optional constraints — nil fields are ignored, mirroring `undefined`
/// fields in the TypeScript source.
struct MimeFilter: Sendable {
    var type: MimeCategory?
    var query: String?

    init(type: MimeCategory? = nil, query: String? = nil) {
        self.type = type
        self.query = query
    }
}

enum MimeTable {

    /// Display order of the categories (used by the UI filter).
    static let mimeCategories: [MimeCategory] = MimeCategory.allCases

    /// 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.
    static let mime: [MimeEntry] = [
        // ── text ────────────────────────────────────────────────────────
        .init(ext: "txt", mime: "text/plain", type: .text, name: "Plain text"),
        .init(ext: "html", mime: "text/html", type: .text, name: "HTML document"),
        .init(ext: "htm", mime: "text/html", type: .text, name: "HTML document"),
        .init(ext: "xhtml", mime: "application/xhtml+xml", type: .application, name: "XHTML"),
        .init(ext: "css", mime: "text/css", type: .text, name: "CSS stylesheet"),
        .init(ext: "csv", mime: "text/csv", type: .text, name: "Comma-separated values"),
        .init(ext: "xml", mime: "application/xml", type: .application, name: "XML document"),
        .init(ext: "js", mime: "text/javascript", type: .text, name: "JavaScript"),
        .init(ext: "js", mime: "application/javascript", type: .application, name: "JavaScript (legacy)"),
        .init(ext: "mjs", mime: "text/javascript", type: .text, name: "JavaScript module"),
        .init(ext: "json", mime: "application/json", type: .application, name: "JSON"),
        .init(ext: "md", mime: "text/markdown", type: .text, name: "Markdown"),
        .init(ext: "yaml", mime: "text/yaml", type: .text, name: "YAML"),
        .init(ext: "yml", mime: "text/yaml", type: .text, name: "YAML"),
        .init(ext: "ics", mime: "text/calendar", type: .text, name: "iCalendar"),
        .init(ext: "rtf", mime: "application/rtf", type: .application, name: "Rich text"),
        .init(ext: "tsv", mime: "text/tab-separated-values", type: .text, name: "Tab-separated values"),

        // ── image ───────────────────────────────────────────────────────
        .init(ext: "jpg", mime: "image/jpeg", type: .image, name: "JPEG image"),
        .init(ext: "jpeg", mime: "image/jpeg", type: .image, name: "JPEG image"),
        .init(ext: "png", mime: "image/png", type: .image, name: "PNG image"),
        .init(ext: "gif", mime: "image/gif", type: .image, name: "GIF image"),
        .init(ext: "webp", mime: "image/webp", type: .image, name: "WebP image"),
        .init(ext: "avif", mime: "image/avif", type: .image, name: "AVIF image"),
        .init(ext: "svg", mime: "image/svg+xml", type: .image, name: "SVG image"),
        .init(ext: "bmp", mime: "image/bmp", type: .image, name: "Bitmap image"),
        .init(ext: "ico", mime: "image/x-icon", type: .image, name: "Icon"),
        .init(ext: "tif", mime: "image/tiff", type: .image, name: "TIFF image"),
        .init(ext: "tiff", mime: "image/tiff", type: .image, name: "TIFF image"),
        .init(ext: "heic", mime: "image/heic", type: .image, name: "HEIC image"),

        // ── audio ───────────────────────────────────────────────────────
        .init(ext: "mp3", mime: "audio/mpeg", type: .audio, name: "MP3 audio"),
        .init(ext: "wav", mime: "audio/wav", type: .audio, name: "WAV audio"),
        .init(ext: "ogg", mime: "audio/ogg", type: .audio, name: "Ogg audio"),
        .init(ext: "oga", mime: "audio/ogg", type: .audio, name: "Ogg audio"),
        .init(ext: "flac", mime: "audio/flac", type: .audio, name: "FLAC audio"),
        .init(ext: "aac", mime: "audio/aac", type: .audio, name: "AAC audio"),
        .init(ext: "m4a", mime: "audio/mp4", type: .audio, name: "M4A audio"),
        .init(ext: "weba", mime: "audio/webm", type: .audio, name: "WebM audio"),
        .init(ext: "mid", mime: "audio/midi", type: .audio, name: "MIDI"),
        .init(ext: "midi", mime: "audio/midi", type: .audio, name: "MIDI"),

        // ── video ───────────────────────────────────────────────────────
        .init(ext: "mp4", mime: "video/mp4", type: .video, name: "MP4 video"),
        .init(ext: "webm", mime: "video/webm", type: .video, name: "WebM video"),
        .init(ext: "avi", mime: "video/x-msvideo", type: .video, name: "AVI video"),
        .init(ext: "mov", mime: "video/quicktime", type: .video, name: "QuickTime video"),
        .init(ext: "mkv", mime: "video/x-matroska", type: .video, name: "Matroska video"),
        .init(ext: "mpeg", mime: "video/mpeg", type: .video, name: "MPEG video"),
        .init(ext: "mpg", mime: "video/mpeg", type: .video, name: "MPEG video"),
        .init(ext: "ogv", mime: "video/ogg", type: .video, name: "Ogg video"),
        .init(ext: "m4v", mime: "video/mp4", type: .video, name: "M4V video"),

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

        // ── font ────────────────────────────────────────────────────────
        .init(ext: "woff", mime: "font/woff", type: .font, name: "WOFF font"),
        .init(ext: "woff2", mime: "font/woff2", type: .font, name: "WOFF2 font"),
        .init(ext: "ttf", mime: "font/ttf", type: .font, name: "TrueType font"),
        .init(ext: "otf", mime: "font/otf", type: .font, name: "OpenType font"),
        .init(ext: "eot", mime: "application/vnd.ms-fontobject", type: .application, name: "Embedded OpenType"),

        // ── multipart ───────────────────────────────────────────────────
        .init(ext: "mht", mime: "multipart/related", type: .multipart, name: "MHTML web archive"),
        .init(ext: "mhtml", mime: "multipart/related", type: .multipart, name: "MHTML web archive"),
    ]

    /// Lowercase an extension and drop a leading dot so ".JSON" == "json".
    /// The while loop drops one or more leading dots, matching /^\.+/.
    static func normalizeExt(_ ext: String) -> String {
        var e = ext.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        while e.hasPrefix(".") { e.removeFirst() }
        return e
    }

    /// 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.
    static func lookupByExt(_ ext: String) -> [MimeEntry] {
        let e = normalizeExt(ext)
        guard !e.isEmpty else { return [] }
        return mime.filter { $0.ext == e }
    }

    /// Every entry whose MIME matches, case-insensitively, tolerating
    /// surrounding whitespace. One MIME commonly maps to several extensions
    /// (image/jpeg → jpg, jpeg), so all are returned. [] for unknown/empty.
    static func lookupByMime(_ mimeType: String) -> [MimeEntry] {
        let m = mimeType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        guard !m.isEmpty else { return [] }
        return mime.filter { $0.mime.lowercased() == m }
    }

    /// Filter the full table by category and/or a free-text query matched
    /// (case-insensitively) against ext, mime, and name. Omitted options are
    /// ignored; an empty query returns the category-filtered set unchanged.
    /// An unknown category value yields nothing (it is not silently coerced).
    static func filter(_ opts: MimeFilter = MimeFilter()) -> [MimeEntry] {
        let q = (opts.query ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        return mime.filter { e in
            if let type = opts.type, e.type != type { return false }
            guard !q.isEmpty else { return true }
            return e.ext.lowercased().contains(q)
                || e.mime.lowercased().contains(q)
                || e.name.lowercased().contains(q)
        }
    }
}

// .JSON resolves case-insensitively; IMAGE/JPEG maps back to every extension.
for e in MimeTable.lookupByExt(".JSON") { print(".JSON -> \(e.mime) (\(e.name))") }
for e in MimeTable.lookupByMime("IMAGE/JPEG") { print("image/jpeg -> .\(e.ext)") }
print("query \"font\": \(MimeTable.filter(MimeFilter(query: "font")).count) of \(MimeTable.mime.count) entries")

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 →