MIME Type Reference — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# mime-types — Ruby 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 frozen 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.
# frozen_string_literal: true
module MimeTypes
# One curated extension ↔ MIME mapping (immutable value object).
Entry = Struct.new(:ext, :mime, :type, :name, keyword_init: true)
# The seven top-level media types, in display order (used by the UI filter).
CATEGORIES = %w[text image audio video application font multipart].freeze
# 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.
MIME = [
# ── text ──────────────────────────────────────────────────────────────
Entry.new(ext: 'txt', mime: 'text/plain', type: 'text', name: 'Plain text'),
Entry.new(ext: 'html', mime: 'text/html', type: 'text', name: 'HTML document'),
Entry.new(ext: 'htm', mime: 'text/html', type: 'text', name: 'HTML document'),
Entry.new(ext: 'xhtml', mime: 'application/xhtml+xml', type: 'application', name: 'XHTML'),
Entry.new(ext: 'css', mime: 'text/css', type: 'text', name: 'CSS stylesheet'),
Entry.new(ext: 'csv', mime: 'text/csv', type: 'text', name: 'Comma-separated values'),
Entry.new(ext: 'xml', mime: 'application/xml', type: 'application', name: 'XML document'),
Entry.new(ext: 'js', mime: 'text/javascript', type: 'text', name: 'JavaScript'),
Entry.new(ext: 'js', mime: 'application/javascript', type: 'application', name: 'JavaScript (legacy)'),
Entry.new(ext: 'mjs', mime: 'text/javascript', type: 'text', name: 'JavaScript module'),
Entry.new(ext: 'json', mime: 'application/json', type: 'application', name: 'JSON'),
Entry.new(ext: 'md', mime: 'text/markdown', type: 'text', name: 'Markdown'),
Entry.new(ext: 'yaml', mime: 'text/yaml', type: 'text', name: 'YAML'),
Entry.new(ext: 'yml', mime: 'text/yaml', type: 'text', name: 'YAML'),
Entry.new(ext: 'ics', mime: 'text/calendar', type: 'text', name: 'iCalendar'),
Entry.new(ext: 'rtf', mime: 'application/rtf', type: 'application', name: 'Rich text'),
Entry.new(ext: 'tsv', mime: 'text/tab-separated-values', type: 'text', name: 'Tab-separated values'),
# ── image ─────────────────────────────────────────────────────────────
Entry.new(ext: 'jpg', mime: 'image/jpeg', type: 'image', name: 'JPEG image'),
Entry.new(ext: 'jpeg', mime: 'image/jpeg', type: 'image', name: 'JPEG image'),
Entry.new(ext: 'png', mime: 'image/png', type: 'image', name: 'PNG image'),
Entry.new(ext: 'gif', mime: 'image/gif', type: 'image', name: 'GIF image'),
Entry.new(ext: 'webp', mime: 'image/webp', type: 'image', name: 'WebP image'),
Entry.new(ext: 'avif', mime: 'image/avif', type: 'image', name: 'AVIF image'),
Entry.new(ext: 'svg', mime: 'image/svg+xml', type: 'image', name: 'SVG image'),
Entry.new(ext: 'bmp', mime: 'image/bmp', type: 'image', name: 'Bitmap image'),
Entry.new(ext: 'ico', mime: 'image/x-icon', type: 'image', name: 'Icon'),
Entry.new(ext: 'tif', mime: 'image/tiff', type: 'image', name: 'TIFF image'),
Entry.new(ext: 'tiff', mime: 'image/tiff', type: 'image', name: 'TIFF image'),
Entry.new(ext: 'heic', mime: 'image/heic', type: 'image', name: 'HEIC image'),
# ── audio ─────────────────────────────────────────────────────────────
Entry.new(ext: 'mp3', mime: 'audio/mpeg', type: 'audio', name: 'MP3 audio'),
Entry.new(ext: 'wav', mime: 'audio/wav', type: 'audio', name: 'WAV audio'),
Entry.new(ext: 'ogg', mime: 'audio/ogg', type: 'audio', name: 'Ogg audio'),
Entry.new(ext: 'oga', mime: 'audio/ogg', type: 'audio', name: 'Ogg audio'),
Entry.new(ext: 'flac', mime: 'audio/flac', type: 'audio', name: 'FLAC audio'),
Entry.new(ext: 'aac', mime: 'audio/aac', type: 'audio', name: 'AAC audio'),
Entry.new(ext: 'm4a', mime: 'audio/mp4', type: 'audio', name: 'M4A audio'),
Entry.new(ext: 'weba', mime: 'audio/webm', type: 'audio', name: 'WebM audio'),
Entry.new(ext: 'mid', mime: 'audio/midi', type: 'audio', name: 'MIDI'),
Entry.new(ext: 'midi', mime: 'audio/midi', type: 'audio', name: 'MIDI'),
# ── video ─────────────────────────────────────────────────────────────
Entry.new(ext: 'mp4', mime: 'video/mp4', type: 'video', name: 'MP4 video'),
Entry.new(ext: 'webm', mime: 'video/webm', type: 'video', name: 'WebM video'),
Entry.new(ext: 'avi', mime: 'video/x-msvideo', type: 'video', name: 'AVI video'),
Entry.new(ext: 'mov', mime: 'video/quicktime', type: 'video', name: 'QuickTime video'),
Entry.new(ext: 'mkv', mime: 'video/x-matroska', type: 'video', name: 'Matroska video'),
Entry.new(ext: 'mpeg', mime: 'video/mpeg', type: 'video', name: 'MPEG video'),
Entry.new(ext: 'mpg', mime: 'video/mpeg', type: 'video', name: 'MPEG video'),
Entry.new(ext: 'ogv', mime: 'video/ogg', type: 'video', name: 'Ogg video'),
Entry.new(ext: 'm4v', mime: 'video/mp4', type: 'video', name: 'M4V video'),
# ── application ───────────────────────────────────────────────────────
Entry.new(ext: 'pdf', mime: 'application/pdf', type: 'application', name: 'PDF document'),
Entry.new(ext: 'zip', mime: 'application/zip', type: 'application', name: 'ZIP archive'),
Entry.new(ext: 'gz', mime: 'application/gzip', type: 'application', name: 'Gzip archive'),
Entry.new(ext: 'tar', mime: 'application/x-tar', type: 'application', name: 'Tar archive'),
Entry.new(ext: 'rar', mime: 'application/vnd.rar', type: 'application', name: 'RAR archive'),
Entry.new(ext: '7z', mime: 'application/x-7z-compressed', type: 'application', name: '7-Zip archive'),
Entry.new(ext: 'bin', mime: 'application/octet-stream', type: 'application', name: 'Binary data'),
Entry.new(ext: 'exe', mime: 'application/x-msdownload', type: 'application', name: 'Windows executable'),
Entry.new(ext: 'jar', mime: 'application/java-archive', type: 'application', name: 'Java archive'),
Entry.new(ext: 'wasm', mime: 'application/wasm', type: 'application', name: 'WebAssembly'),
Entry.new(ext: 'doc', mime: 'application/msword', type: 'application', name: 'Word document'),
Entry.new(ext: 'docx', mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', type: 'application', name: 'Word document (OOXML)'),
Entry.new(ext: 'xls', mime: 'application/vnd.ms-excel', type: 'application', name: 'Excel spreadsheet'),
Entry.new(ext: 'xlsx', mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', type: 'application', name: 'Excel spreadsheet (OOXML)'),
Entry.new(ext: 'ppt', mime: 'application/vnd.ms-powerpoint', type: 'application', name: 'PowerPoint'),
Entry.new(ext: 'pptx', mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', type: 'application', name: 'PowerPoint (OOXML)'),
# ── font ──────────────────────────────────────────────────────────────
Entry.new(ext: 'woff', mime: 'font/woff', type: 'font', name: 'WOFF font'),
Entry.new(ext: 'woff2', mime: 'font/woff2', type: 'font', name: 'WOFF2 font'),
Entry.new(ext: 'ttf', mime: 'font/ttf', type: 'font', name: 'TrueType font'),
Entry.new(ext: 'otf', mime: 'font/otf', type: 'font', name: 'OpenType font'),
Entry.new(ext: 'eot', mime: 'application/vnd.ms-fontobject', type: 'application', name: 'Embedded OpenType'),
# ── multipart ─────────────────────────────────────────────────────────
Entry.new(ext: 'mht', mime: 'multipart/related', type: 'multipart', name: 'MHTML web archive'),
Entry.new(ext: 'mhtml', mime: 'multipart/related', type: 'multipart', name: 'MHTML web archive')
].freeze
module_function
# Lowercase an extension and drop a leading dot so ".JSON" == "json".
# `sub(/\A\.+/, '')` removes one or more leading dots, matching the
# TypeScript /^\.+/ regex.
def normalize_ext(ext)
ext.strip.downcase.sub(/\A\.+/, '')
end
# 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.
def lookup_by_ext(ext)
e = normalize_ext(ext)
return [] if e.empty?
MIME.select { |m| m.ext == e }
end
# 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. Returns [] for
# unknown/empty.
def lookup_by_mime(mime)
m = mime.strip.downcase
return [] if m.empty?
MIME.select { |e| e.mime.downcase == m }
end
# Filter the full table by category and/or a free-text query matched
# (case-insensitively) against ext, mime, and name. Omitted (nil) options
# are ignored; an empty query returns the category-filtered set unchanged.
# An unknown category value yields nothing (it is not silently coerced).
def filter(type: nil, query: nil)
q = query.to_s.strip.downcase
MIME.select do |e|
next false if type && e.type != type
q.empty? || [e.ext, e.mime, e.name].any? { |s| s.downcase.include?(q) }
end
end
end
# .JSON resolves case-insensitively; IMAGE/JPEG maps back to every extension.
p MimeTypes.lookup_by_ext('.JSON').map(&:mime)
p MimeTypes.lookup_by_mime('IMAGE/JPEG').map(&:ext)
p MimeTypes.filter(query: 'font').length # entries whose row mentions "font"
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 →