Skip to content

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

<?php
/**
 * MIME / Content-Type reference — pure, deterministic data + lookups.
 *
 * Language: PHP (8.0+, uses `str_contains`, arrow functions, named args).
 * 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.
 */

declare(strict_types=1);

namespace CosmoDev\MimeTypes;

/**
 * Curated extension ↔ MIME reference table and the pure lookups over it.
 *
 * Static API: the table is immutable constant data, so every method is static
 * and stateless — no need to instantiate.
 */
final class MimeTypes
{
    /** Display order of the categories (used by the UI filter). */
    public const MIME_CATEGORIES = [
        'text',
        'image',
        'audio',
        'video',
        'application',
        'font',
        'multipart',
    ];

    /**
     * The curated extension ↔ MIME table. One row 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.
     *
     * Each row is an associative array shaped like the TypeScript `MimeEntry`:
     *   ['ext' => string, 'mime' => string, 'type' => string, 'name' => string]
     */
    public const MIME = [
        // ── text ────────────────────────────────────────────────────────────
        ['ext' => 'txt', 'mime' => 'text/plain', 'type' => 'text', 'name' => 'Plain text'],
        ['ext' => 'html', 'mime' => 'text/html', 'type' => 'text', 'name' => 'HTML document'],
        ['ext' => 'htm', 'mime' => 'text/html', 'type' => 'text', 'name' => 'HTML document'],
        ['ext' => 'xhtml', 'mime' => 'application/xhtml+xml', 'type' => 'application', 'name' => 'XHTML'],
        ['ext' => 'css', 'mime' => 'text/css', 'type' => 'text', 'name' => 'CSS stylesheet'],
        ['ext' => 'csv', 'mime' => 'text/csv', 'type' => 'text', 'name' => 'Comma-separated values'],
        ['ext' => 'xml', 'mime' => 'application/xml', 'type' => 'application', 'name' => 'XML document'],
        ['ext' => 'js', 'mime' => 'text/javascript', 'type' => 'text', 'name' => 'JavaScript'],
        ['ext' => 'js', 'mime' => 'application/javascript', 'type' => 'application', 'name' => 'JavaScript (legacy)'],
        ['ext' => 'mjs', 'mime' => 'text/javascript', 'type' => 'text', 'name' => 'JavaScript module'],
        ['ext' => 'json', 'mime' => 'application/json', 'type' => 'application', 'name' => 'JSON'],
        ['ext' => 'md', 'mime' => 'text/markdown', 'type' => 'text', 'name' => 'Markdown'],
        ['ext' => 'yaml', 'mime' => 'text/yaml', 'type' => 'text', 'name' => 'YAML'],
        ['ext' => 'yml', 'mime' => 'text/yaml', 'type' => 'text', 'name' => 'YAML'],
        ['ext' => 'ics', 'mime' => 'text/calendar', 'type' => 'text', 'name' => 'iCalendar'],
        ['ext' => 'rtf', 'mime' => 'application/rtf', 'type' => 'application', 'name' => 'Rich text'],
        ['ext' => 'tsv', 'mime' => 'text/tab-separated-values', 'type' => 'text', 'name' => 'Tab-separated values'],

        // ── image ───────────────────────────────────────────────────────────
        ['ext' => 'jpg', 'mime' => 'image/jpeg', 'type' => 'image', 'name' => 'JPEG image'],
        ['ext' => 'jpeg', 'mime' => 'image/jpeg', 'type' => 'image', 'name' => 'JPEG image'],
        ['ext' => 'png', 'mime' => 'image/png', 'type' => 'image', 'name' => 'PNG image'],
        ['ext' => 'gif', 'mime' => 'image/gif', 'type' => 'image', 'name' => 'GIF image'],
        ['ext' => 'webp', 'mime' => 'image/webp', 'type' => 'image', 'name' => 'WebP image'],
        ['ext' => 'avif', 'mime' => 'image/avif', 'type' => 'image', 'name' => 'AVIF image'],
        ['ext' => 'svg', 'mime' => 'image/svg+xml', 'type' => 'image', 'name' => 'SVG image'],
        ['ext' => 'bmp', 'mime' => 'image/bmp', 'type' => 'image', 'name' => 'Bitmap image'],
        ['ext' => 'ico', 'mime' => 'image/x-icon', 'type' => 'image', 'name' => 'Icon'],
        ['ext' => 'tif', 'mime' => 'image/tiff', 'type' => 'image', 'name' => 'TIFF image'],
        ['ext' => 'tiff', 'mime' => 'image/tiff', 'type' => 'image', 'name' => 'TIFF image'],
        ['ext' => 'heic', 'mime' => 'image/heic', 'type' => 'image', 'name' => 'HEIC image'],

        // ── audio ───────────────────────────────────────────────────────────
        ['ext' => 'mp3', 'mime' => 'audio/mpeg', 'type' => 'audio', 'name' => 'MP3 audio'],
        ['ext' => 'wav', 'mime' => 'audio/wav', 'type' => 'audio', 'name' => 'WAV audio'],
        ['ext' => 'ogg', 'mime' => 'audio/ogg', 'type' => 'audio', 'name' => 'Ogg audio'],
        ['ext' => 'oga', 'mime' => 'audio/ogg', 'type' => 'audio', 'name' => 'Ogg audio'],
        ['ext' => 'flac', 'mime' => 'audio/flac', 'type' => 'audio', 'name' => 'FLAC audio'],
        ['ext' => 'aac', 'mime' => 'audio/aac', 'type' => 'audio', 'name' => 'AAC audio'],
        ['ext' => 'm4a', 'mime' => 'audio/mp4', 'type' => 'audio', 'name' => 'M4A audio'],
        ['ext' => 'weba', 'mime' => 'audio/webm', 'type' => 'audio', 'name' => 'WebM audio'],
        ['ext' => 'mid', 'mime' => 'audio/midi', 'type' => 'audio', 'name' => 'MIDI'],
        ['ext' => 'midi', 'mime' => 'audio/midi', 'type' => 'audio', 'name' => 'MIDI'],

        // ── video ───────────────────────────────────────────────────────────
        ['ext' => 'mp4', 'mime' => 'video/mp4', 'type' => 'video', 'name' => 'MP4 video'],
        ['ext' => 'webm', 'mime' => 'video/webm', 'type' => 'video', 'name' => 'WebM video'],
        ['ext' => 'avi', 'mime' => 'video/x-msvideo', 'type' => 'video', 'name' => 'AVI video'],
        ['ext' => 'mov', 'mime' => 'video/quicktime', 'type' => 'video', 'name' => 'QuickTime video'],
        ['ext' => 'mkv', 'mime' => 'video/x-matroska', 'type' => 'video', 'name' => 'Matroska video'],
        ['ext' => 'mpeg', 'mime' => 'video/mpeg', 'type' => 'video', 'name' => 'MPEG video'],
        ['ext' => 'mpg', 'mime' => 'video/mpeg', 'type' => 'video', 'name' => 'MPEG video'],
        ['ext' => 'ogv', 'mime' => 'video/ogg', 'type' => 'video', 'name' => 'Ogg video'],
        ['ext' => 'm4v', 'mime' => 'video/mp4', 'type' => 'video', 'name' => 'M4V video'],

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

        // ── font ────────────────────────────────────────────────────────────
        ['ext' => 'woff', 'mime' => 'font/woff', 'type' => 'font', 'name' => 'WOFF font'],
        ['ext' => 'woff2', 'mime' => 'font/woff2', 'type' => 'font', 'name' => 'WOFF2 font'],
        ['ext' => 'ttf', 'mime' => 'font/ttf', 'type' => 'font', 'name' => 'TrueType font'],
        ['ext' => 'otf', 'mime' => 'font/otf', 'type' => 'font', 'name' => 'OpenType font'],
        ['ext' => 'eot', 'mime' => 'application/vnd.ms-fontobject', 'type' => 'application', 'name' => 'Embedded OpenType'],

        // ── multipart ───────────────────────────────────────────────────────
        ['ext' => 'mht', 'mime' => 'multipart/related', 'type' => 'multipart', 'name' => 'MHTML web archive'],
        ['ext' => 'mhtml', 'mime' => 'multipart/related', 'type' => 'multipart', 'name' => 'MHTML web archive'],
    ];

    /**
     * Lowercase an extension and drop a leading dot so `.JSON` and `json` match.
     * `ltrim($s, '.')` strips one or more leading dots, matching the TypeScript
     * `/^\.+/` regex.
     */
    public static function normalizeExt(string $ext): string
    {
        return ltrim(strtolower(trim($ext)), '.');
    }

    /**
     * Find 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.
     *
     * @return list<array{ext: string, mime: string, type: string, name: string}>
     */
    public static function lookupByExt(string $ext): array
    {
        $e = self::normalizeExt($ext);
        if ($e === '') {
            return [];
        }
        // array_values reindexes to a 0-based list so the result matches the
        // contiguous array shape callers expect from the TypeScript port.
        return array_values(array_filter(
            self::MIME,
            static fn (array $m): bool => $m['ext'] === $e,
        ));
    }

    /**
     * Find every entry whose MIME type matches, case-insensitively (also
     * tolerating surrounding whitespace). One MIME commonly maps to several
     * extensions — e.g. `image/jpeg` → `jpg`, `jpeg` — so all are returned.
     * `[]` for unknown/empty.
     *
     * @return list<array{ext: string, mime: string, type: string, name: string}>
     */
    public static function lookupByMime(string $mime): array
    {
        $m = trim(strtolower($mime));
        if ($m === '') {
            return [];
        }
        return array_values(array_filter(
            self::MIME,
            static fn (array $e): bool => strtolower($e['mime']) === $m,
        ));
    }

    /**
     * Filter the full table by category and/or a free-text query. Omitted
     * options are ignored; an empty query returns the category-filtered set
     * unchanged. An unknown category value yields nothing (it is not silently
     * coerced).
     *
     * `array_key_exists` (not `isset`) is used for `type` so that a `null` value
     * is treated as a real constraint that matches nothing, mirroring the
     * TypeScript `!== undefined` semantics exactly.
     *
     * @param array{type?: string|null, query?: string|null} $opts
     * @return list<array{ext: string, mime: string, type: string, name: string}>
     */
    public static function filter(array $opts = []): array
    {
        $query = array_key_exists('query', $opts) ? (string) $opts['query'] : '';
        $q = trim(strtolower($query));

        return array_values(array_filter(
            self::MIME,
            static function (array $e) use ($opts, $q): bool {
                // Present-but-null type is a real (impossible) constraint → no match,
                // matching TypeScript's `e.type !== opts.type` against null.
                if (array_key_exists('type', $opts) && $e['type'] !== $opts['type']) {
                    return false;
                }
                if ($q !== '') {
                    return str_contains(strtolower($e['ext']), $q)
                        || str_contains(strtolower($e['mime']), $q)
                        || str_contains(strtolower($e['name']), $q);
                }
                return true;
            },
        ));
    }
}

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 →