MIME Type Reference: Code Examples

Searchable reference for MIME types with extension mapping, RFC sources, and ready-to-paste Nginx, Apache, and Caddy config snippets. Runs entirely in your browser, works offline.

ZERO UPLOAD · ALL LOCAL
  1. Type a MIME string (e.g. application/json), file extension (e.g. .wasm), or keyword (e.g. "protobuf") to search the database.
  2. Use the category pills (Application, Audio, Font, Image, Model, Text, Video, Multipart) to browse all types in a category.
  3. Click Details on any card to expand the description, spec link, and server configuration snippets in a full-width panel.
  4. Click Copy next to any snippet block to copy the Nginx, Apache, .htaccess, or Caddy directive to your clipboard.

APPLICATION (100 types)

application/json
.json
application/xml
.xml .xsl .xslt
application/pdf
.pdf
application/wasm
.wasm
application/javascript
.js .mjs .cjs
application/zip
.zip
application/gzip
.gz .tgz
application/x-tar
.tar
application/x-7z-compressed
.7z
application/vnd.rar
.rar
application/x-bzip2
.bz2
application/vnd.openxmlformats-officedocument.wordprocessingml.document
.docx
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xlsx
application/vnd.openxmlformats-officedocument.presentationml.presentation
.pptx
application/msword
.doc
application/vnd.ms-excel
.xls
application/vnd.ms-powerpoint
.ppt
application/octet-stream
.bin .exe .dll
application/x-www-form-urlencoded
application/cbor
.cbor
application/x-protobuf
.proto .pb
application/grpc
application/graphql-response+json
application/vnd.api+json
application/ld+json
.jsonld
application/hal+json
application/jwt
application/manifest+json
.webmanifest
application/rtf
.rtf
application/problem+json
application/json-patch+json
application/merge-patch+json
application/vnd.geo+json
.geojson
application/yaml
.yaml .yml
application/sql
.sql
application/epub+zip
.epub
application/java-archive
.jar
application/vnd.oasis.opendocument.text
.odt
application/vnd.oasis.opendocument.spreadsheet
.ods
application/vnd.oasis.opendocument.presentation
.odp
application/x-ndjson
.ndjson .jsonl
application/vnd.apple.mpegurl
.m3u8
application/dash+xml
.mpd
application/rss+xml
.rss
application/atom+xml
.atom
application/x-apple-diskimage
.dmg
application/x-debian-package
.deb
application/x-rpm
.rpm
application/x-sh
.sh
application/ecmascript
application/toml
.toml
application/zstd
.zst
application/x-xz
.xz
application/x-bzip
.bz
application/x-lzma
.lzma
application/x-iso9660-image
.iso
application/vnd.ms-cab-compressed
.cab
application/vnd.android.package-archive
.apk
application/x-msdownload
.msi
application/vnd.apple.installer+xml
.mpkg
application/x-httpd-cgi
.cgi
application/vnd.google-earth.kml+xml
.kml
application/vnd.google-earth.kmz
.kmz
application/vnd.sqlite3
.sqlite .sqlite3 .db
application/msgpack
.msgpack
application/schema+json
application/wsdl+xml
.wsdl
application/jose+json
application/x-pkcs12
.p12 .pfx
application/pkix-cert
.cer .der
application/x-pem-file
.pem .crt .key
application/pkcs8
.p8
application/pkcs10
.p10 .csr
application/postscript
.ps .eps .ai
application/x-latex
.latex .ltx
application/x-tex
.tex
application/x-dvi
.dvi
application/fits
.fits .fit .fts
application/vnd.oasis.opendocument.graphics
.odg
application/vnd.oasis.opendocument.chart
.odc
application/vnd.oasis.opendocument.formula
.odf
application/vnd.ms-project
.mpp .mpt
application/vnd.visio
.vsd .vst .vss .vsw
application/vnd.ms-access
.mdb
application/vnd.openxmlformats-officedocument.wordprocessingml.template
.dotx
application/vnd.ms-word.document.macroenabled.12
.docm
application/vnd.ms-excel.sheet.macroenabled.12
.xlsm
application/vnd.apple.pages
.pages
application/vnd.apple.numbers
.numbers
application/vnd.apple.keynote
.key
application/typescript
.ts
application/x-perl
.pl .pm
application/x-python-code
.pyc .pyo
application/x-csh
.csh
application/x-troff
.tr .roff .man
application/trig
.trig
application/n-triples
.nt
application/smil+xml
.smil .smi
application/vnd.mozilla.xul+xml
.xul
application/vnd.lotus-1-2-3
.123 .wks

AUDIO (23 types)

audio/mpeg
.mp3 .mpga
audio/ogg
.ogg .oga
audio/wav
.wav
audio/flac
.flac
audio/aac
.aac
audio/opus
.opus
audio/webm
.weba
audio/midi
.mid .midi
audio/mp4
.m4a .m4b .mp4a
audio/3gpp
.3gp .3gpp
audio/aiff
.aif .aiff
audio/x-ms-wma
.wma
audio/amr
.amr
audio/speex
.spx
audio/ac3
.ac3
audio/vorbis
audio/basic
.au .snd
audio/x-caf
.caf
audio/mpegurl
.m3u
audio/vnd.dts
.dts
audio/mp2
.mp2
audio/3gpp2
.3g2
audio/x-realaudio
.ra .ram

FONT (10 types)

font/woff
.woff
font/woff2
.woff2
font/ttf
.ttf
font/otf
.otf
application/vnd.ms-fontobject
.eot
font/collection
.ttc
font/sfnt
.sfnt
application/x-font-truetype
.ttf
application/x-font-opentype
.otf
application/x-font-woff
.woff

IMAGE (35 types)

image/jpeg
.jpg .jpeg .jfif
image/png
.png
image/gif
.gif
image/webp
.webp
image/avif
.avif
image/svg+xml
.svg .svgz
image/vnd.microsoft.icon
.ico
image/x-icon
.ico
image/bmp
.bmp
image/tiff
.tiff .tif
image/heic
.heic
image/heif
.heif
image/apng
.apng
image/jxl
.jxl
image/jp2
.jp2 .j2k .jpf
image/jpx
.jpx
image/vnd.djvu
.djvu .djv
image/vnd.adobe.photoshop
.psd
image/x-portable-bitmap
.pbm
image/x-portable-graymap
.pgm
image/x-portable-pixmap
.ppm
image/x-xcf
.xcf
image/ktx
.ktx
image/ktx2
.ktx2
image/x-exr
.exr
image/x-rgb
.rgb .rgba .sgi
image/x-xbitmap
.xbm
image/x-pcx
.pcx
image/vnd.wap.wbmp
.wbmp
image/x-tga
.tga .tpic
image/vnd.ms-photo
.jxr .hdp .wdp
image/x-win-bitmap
.cur
image/x-emf
.emf
image/wmf
.wmf
image/vnd.radiance
.hdr .rgbe

MODEL (19 types)

model/gltf+json
.gltf
model/gltf-binary
.glb
model/obj
.obj
model/stl
.stl
model/usd
.usd .usda .usdc
model/vnd.collada+xml
.dae
model/vnd.usdz+zip
.usdz
model/mtl
.mtl
model/vnd.dwf
.dwf
model/iges
.igs .iges
model/step
.stp .step .p21
model/step+xml
.stpx .stpxz
model/x3d+xml
.x3d
model/x3d+binary
.x3db .x3dbz
model/x3d-vrml
.x3dv .x3dvz
model/vnd.3mf
.3mf
model/vnd.fbx
.fbx
model/vnd.opengex
.ogex
model/JT
.jt

TEXT (33 types)

text/html
.html .htm
text/css
.css
text/csv
.csv
text/plain
.txt .text .conf .log
text/markdown
.md .markdown
text/calendar
.ics .ical .ifb
text/vcard
.vcf .vcard
text/javascript
.js
text/event-stream
text/tab-separated-values
.tsv
text/xml
.xml
text/x-python
.py .pyw
text/x-java-source
.java
text/x-c
.c .h
text/x-ruby
.rb
text/x-go
.go
text/x-rust
.rs
text/x-kotlin
.kt .kts
text/x-swift
.swift
text/x-scala
.scala .sc
text/x-php
.php .php3 .php4 .php5 .phtml
text/x-diff
.diff .patch
text/uri-list
.uri .urls .uris
text/x-rst
.rst
text/x-asciidoc
.adoc .asciidoc
text/x-nfo
.nfo
text/x-asm
.asm .s
text/troff
.roff .me .ms .mm
text/x-ini
.ini .cfg .inf
text/x-tcl
.tcl .tk
text/x-fortran
.f .f90 .for .f95
text/x-yaml
.yaml .yml
text/cache-manifest
.appcache .manifest

VIDEO (21 types)

video/mp4
.mp4 .m4v
video/webm
.webm
video/ogg
.ogv
video/x-msvideo
.avi
video/quicktime
.mov .qt
video/x-matroska
.mkv .mk3d
video/mp2t
.ts .mts .m2ts
video/mpeg
.mpeg .mpg
video/3gpp
.3gp .3gpp
video/3gpp2
.3g2 .3gp2
video/x-ms-wmv
.wmv
video/x-ms-asf
.asf .asx
video/vnd.avi
.avi
video/iso.segment
.m4s
video/x-dv
.dv .dif
video/x-ms-vob
.vob
video/H264
video/H265
video/AV1
video/x-flv
.flv
video/x-f4v
.f4v

MULTIPART (9 types)

multipart/form-data
multipart/byteranges
multipart/mixed
multipart/alternative
multipart/digest
multipart/related
multipart/signed
multipart/encrypted
multipart/report
No MIME types match your search.

Nginx MIME Type Configuration

Nginx manages MIME types through the types block directive. Every Nginx installation ships a mime.types file that maps file extensions to Content-Type values. Including it in the http {} block with include mime.types loads the full built-in type table.1 Custom types go in a nested types {} block inside http {}, server {}, or location {} contexts. Adding AVIF, WebAssembly, and WOFF2 requires explicit entries because these formats postdate many Nginx mime.types releases. The default_type directive sets the fallback Content-Type for extensions not in the types table; most configurations set default_type application/octet-stream to trigger a file download for unknown types rather than serving them as plain text.

The mime.types include pattern

The canonical Nginx configuration includes the bundled MIME type file at the http {} level and sets a default type for unrecognised extensions. The include mime.types directive loads all common types from the file shipped with Nginx, typically at /etc/nginx/mime.types or /usr/local/nginx/conf/mime.types. Building on this, a nested types {} block inside the same http {} context adds or overrides individual entries without replacing the entire bundled file. Nginx merges the types block with the included file when evaluating the final MIME table for a request; conversely, placing only a types {} block without the include line removes all built-in types and requires you to list every supported type manually. The default_type directive applies to requests that match no entry in the merged types table, and application/octet-stream is the conventional value because it triggers a download rather than an attempted render.1

Adding custom types for AVIF, WASM, and WOFF2

Three modern formats require explicit types {} entries in most Nginx installations released before 2021 because their IANA registrations postdate the bundled mime.types files shipped with those Nginx versions. AVIF images must be declared as image/avif2 with the .avif extension so that browsers receive the correct Content-Type and can decode the AV1-compressed image data. WebAssembly binaries must be declared as application/wasm3 with the .wasm extension, since browsers enforce this type strictly and throw a hard compile-time error for any other value. WOFF2 fonts must be declared as font/woff24 with the .woff2 extension to ensure cross-origin font loading works correctly with CORS.

Scoping types to server or location contexts

Furthermore, if you serve both .woff and .woff2 files, declare font/woff for .woff separately. All three entries go inside a types {} block at the http {}, server {}, or location {} level depending on how narrowly you need to scope the override. After adding the entries, run nginx -t to validate the configuration syntax before applying it. Reload with nginx -s reload after a successful test. Yet be aware that a types {} block in a location {} context replaces the parent block entirely rather than extending it, so you must repeat all needed types within that block or requests matching that location will fail to resolve any MIME type not explicitly listed.

Testing with curl and nginx -t

Validating MIME type configuration requires two distinct steps: first confirming that the Nginx configuration file parses without syntax errors, and second verifying that the running server actually sends the correct Content-Type header for each file type you have configured. Run nginx -t5 to check the configuration file syntax and confirm the types {} entries parse correctly; a successful test prints "configuration file /etc/nginx/nginx.conf syntax is ok" and exits with status code 0.

Sending HEAD requests to verify each type

Second, reload Nginx and send a HEAD request to a file of each type you configured. The command curl -sI http://localhost/assets/app.wasm reads the Content-Type header without downloading the file body.6 Confirm the response shows Content-Type: application/wasm. Repeat for .avif and .woff2 files. If the response shows application/octet-stream instead of the expected type, the extension is not in the active types table for that request's context. Building on this, check whether the request path matches a location {} block that defines its own types {} block, which replaces the http {} level configuration for those URLs and may omit the entries you added at the server level.

Combining CORS headers with MIME types for cross-origin fonts in Nginx

Font files served from an Nginx host on a different origin than the page require both the correct MIME type and an Access-Control-Allow-Origin header. Adding font/woff2 to the types {} block ensures Nginx sends Content-Type: font/woff2, but CORS enforcement requires an additional response header on the same response. The add_header directive in Nginx adds response headers to matching requests. Placing both the types configuration and the add_header directive inside the same location {} block ensures they appear together on every font response.

A common pattern is a location block matching font file extensions that sets the CORS header and a long-term cache policy simultaneously.7 The add_header Access-Control-Allow-Origin "*" directive inside the font location block broadcasts permission to all origins. For servers that must restrict font access to specific origins, Nginx's map module can evaluate the $http_origin variable against an allowed-origins list and assign the matching value to a variable used in the Access-Control-Allow-Origin header value.

charset_types for text/event-stream responses

The charset_types directive controls which MIME types receive an automatic charset parameter appended to the Content-Type header. Nginx appends charset by default to text/html, text/xml, text/plain, and a small set of other types. Adding text/event-stream to charset_types ensures SSE responses include a charset parameter required by some proxy configurations. Conversely, removing image/svg+xml from charset_types prevents an unnecessary charset parameter from appearing on SVG responses where the XML declaration already declares the encoding.

The directive is global to the server block, so a single addition affects every matching response rather than only the one endpoint you tested, which makes a broader curl sweep a good follow-up after changing it. Removing a type from charset_types prevents an incorrect charset from appearing on responses where it breaks strict parsers. CapyToolkit's MIME reference lists the correct Content-Type for each format so your type table and charset policy stay consistent.

Notes

The types {} block merges with include mime.types when both appear in the same context. Custom entries inside types {} override entries in the included file for the same extension. Run nginx -t to validate configuration syntax without reloading. Use "curl -sI http://localhost/file.avif | grep -i content-type" to test the MIME type of a specific file after reload. A types {} block inside a location {} context replaces the parent types table entirely rather than merging with it.

Examples

http {} block: include mime.types and add modern types

http {
    include       mime.types;
    default_type  application/octet-stream;

    types {
        image/avif    avif;
        application/wasm  wasm;
        font/woff2    woff2;
    }
}

The types {} block merges with include mime.types. Add only the entries missing from your bundled mime.types file.

server {} block: scope MIME type overrides to a virtual host

server {
    listen 80;
    server_name example.com;

    types {
        image/avif  avif;
        application/wasm  wasm;
    }
}

A types {} block in server {} applies only to that virtual host. Without include mime.types here, all other types fall back to the http {} level table.

Verify with curl after reload

nginx -s reload
curl -sI http://localhost/assets/app.wasm | grep -i content-type
# Expected: content-type: application/wasm

Try in the tool

Nginx MIME directives

  • loads the bundled extension-to-type table
  • adds or overrides individual entries, merges with the included file
  • fallback Content-Type for unmatched extensions — usually application/octet-stream
  • nginx -t to check syntax, curl -sI to confirm the served header

A types {} block placed inside a location {} context replaces the parent types table entirely instead of merging with it.

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    Nginx, "ngx_http_core_module," nginx.org, accessed June 2026. https://nginx.org/en/docs/http/ngx_http_core_module.html

  2. 2.

    IANA, "image/avif — Media Type Registration," iana.org, January 2021. https://www.iana.org/assignments/media-types/image/avif

  3. 3.

    IANA, "application/wasm — Media Type Registration," iana.org, April 2021. https://www.iana.org/assignments/media-types/application/wasm

  4. 4.

    W3C, "WOFF 2.0 — Web Open Font Format 2.0 — IANA Registration," w3.org, August 2024. https://www.w3.org/TR/WOFF2/

  5. 5.

    Nginx, "Command Line Switches," nginx.org, accessed June 2026. https://nginx.org/en/docs/switches.html

  6. 6.

    curl, "curl — Man Page," curl.se, accessed June 2026. https://curl.se/docs/manpage.html

  7. 7.

    ServerFault, "How can I make nginx support @font-face formats and allow access-control-allow-origin," serverfault.com, October 2010. https://serverfault.com/questions/186965/how-can-i-make-nginx-support-font-face-formats-and-allow-access-control-allow-o

FAQ

Apache MIME Type Configuration

Apache configures MIME types through the AddType directive.1 Unlike Nginx's types block approach, Apache uses AddType statements that can appear in httpd.conf, VirtualHost blocks, Directory blocks, or per-directory .htaccess files. The TypesConfig directive in httpd.conf sets the path to the global MIME type database, typically /etc/mime.types or /etc/apache2/mime.types. mod_mime must be enabled for AddType and TypesConfig to function; it is included in the default Apache installation but can be disabled on minimal installs. Adding AVIF, WebAssembly, and WOFF2 requires explicit AddType lines because older Apache mime.types databases predate these format registrations. Consequently, verifying whether your Apache version includes these types by default is the first step in a MIME type audit. The apachectl -t command validates configuration syntax, and curl -sI verifies the actual Content-Type header served after a reload.

AddType in httpd.conf and .htaccess

The AddType directive maps a MIME type to one or more file extensions, and its scope depends entirely on which configuration context you place it in.1 Placed in httpd.conf at the global level outside any VirtualHost or Directory block, it applies to every virtual host and every directory on the server. Placed inside a VirtualHost block, it applies only to requests handled by that specific virtual host. Inside a Directory block, it scopes to requests matching that filesystem path and all paths beneath it.

Using .htaccess for per-directory MIME overrides

In a .htaccess file, AddType applies to the directory containing the file and all subdirectories. The syntax is AddType mime/type .ext, with the extension including the leading dot. Multiple AddType lines for the same MIME type with different extensions are valid. Furthermore, AddType directives in .htaccess override any conflicting type from the parent configuration for that directory scope, giving you fine-grained control without touching the main server configuration.2 Building on this, AddType in .htaccess is the simplest approach for shared hosting environments where you cannot modify httpd.conf directly and need per-project MIME type control.

Common types missing from default Apache installs

The global Apache mime.types file lists hundreds of MIME types, but several modern formats may be absent depending on the distribution and when it was last updated. AVIF (image/avif)3 is absent from many Apache 2.4 installs that shipped before 2021. WebAssembly (application/wasm)4 is absent from distributions that packaged Apache before the IANA registration in 2019.

Verifying which types your Apache install includes

WOFF2 (font/woff2) was absent from many distributions until recently. Checking the current mime.types file before adding AddType lines avoids duplicate entries. Run grep for avif, wasm, and woff2 in /etc/mime.types to identify which types are already present. Add an AddType line only for types that are absent. Yet even if a type is in the global database, testing the actual response with curl -sI confirms that mod_mime is active and the configuration is being applied correctly.

Testing with curl and apachectl

After adding or modifying AddType directives, validate the configuration with apachectl -t. A clean result prints "Syntax OK".5 Reload Apache without dropping active connections using apachectl graceful. After the reload, test the actual Content-Type response with curl -sI https://example.com/file.avif6 and read the Content-Type line in the output. Conversely, a full restart with apachectl restart is needed after changes to TypesConfig, since the MIME database is read at startup rather than on each request.5 For .htaccess changes, no reload is required because Apache reads .htaccess files on each request. Furthermore, ensure that AllowOverride All (or AllowOverride FileInfo) is set in the Directory block covering your .htaccess file's location; without it, .htaccess AddType directives are silently ignored.

ForceType and mod_headers for precise MIME override in Apache

AddType registers an extension-to-type mapping globally, but ForceType overrides Content-Type for all files matched by the enclosing Directory, Location, or Files block regardless of extension.7 This distinction matters when you need to change the MIME type for a specific directory path rather than a file extension: a Location /api/data block with ForceType application/json forces the Content-Type on every response from that path without touching the global extension table. ForceType is less granular than AddType but avoids accidentally affecting other extensions that share the same server-wide type.

For even finer control, mod_headers lets you set or rewrite Content-Type via the Header directive. Using Header set Content-Type "application/wasm" inside a FilesMatch ".wasm$" block applies the type only to matched files and runs after the response headers are generated, overriding any type set by the content handler. Combining FilesMatch with a Header directive7 is the only reliable way to override a type that mod_mime would otherwise set from its own table, since AddType entries from mime.types load before .htaccess and can conflict with FilesMatch-level overrides in some configurations.

Choosing between AddType, ForceType, and mod_headers

AddType is the right choice for server-wide extension mappings, such as ensuring all .woff2 files return font/woff2. ForceType is the right choice for directory- or location-scoped overrides where you need to normalise Content-Type regardless of file extension. Header set Content-Type is the right choice when you need to overwrite a type after it has been assigned, or when the content originates from a CGI or proxy that sends its own Content-Type that you must replace. Using the wrong directive for the scope produces unpredictable precedence behaviour when Apache evaluates overlapping configuration blocks, so matching the directive to the scope avoids hard-to-diagnose MIME mismatches in production.

Documenting the chosen directive in your configuration comments makes the reasoning visible to the next operator who might otherwise add a conflicting rule at a different scope. Apache's precedence rules across Directory, Location, and Files blocks are subtle, so an explicit note prevents the accidental override that produces the exact mismatch this section warns against. CapyToolkit's MIME reference lists the correct Content-Type for each format so the override targets the right value.

Notes

AddType syntax is: AddType mime-type extension (e.g., AddType image/avif .avif). Multiple extensions per type are comma-separated. TypesConfig sets the global MIME database path; the default is /etc/mime.types on Debian/Ubuntu and /etc/httpd/conf/mime.types on Red Hat/CentOS. mod_mime must be active (check with apachectl -M | grep mime). apachectl -t checks syntax; apachectl graceful reloads without dropping connections. In .htaccess, AddType applies to the current directory and subdirectories.

Examples

httpd.conf: add AVIF, WASM, and WOFF2 globally

# Add modern MIME types not in the default mime.types database
AddType image/avif .avif
AddType application/wasm .wasm
AddType font/woff2 .woff2

Place outside any VirtualHost block to apply globally. Reload with apachectl graceful after saving.

.htaccess: add MIME types per directory

# Use in .htaccess when you cannot modify httpd.conf
AddType image/avif .avif
AddType application/wasm .wasm
AddType font/woff2 .woff2

No server reload required. Requires AllowOverride FileInfo or AllowOverride All in the parent Directory block.

Verify types with curl after reload

apachectl -t && apachectl graceful
curl -sI https://example.com/app.wasm | grep content-type
# Expected: content-type: application/wasm

Try in the tool

Apache MIME directives

  • AddType AddType mime/type .ext — scope depends on httpd.conf, VirtualHost, Directory, or .htaccess placement
  • TypesConfig sets the path to the global MIME database, typically /etc/mime.types
  • mod_mime must be enabled for AddType/TypesConfig to work — check with apachectl -M | grep mime
  • Validate + reload apachectl -t to check syntax, apachectl graceful to reload without dropping connections

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    Apache HTTP Server, "mod_mime," httpd.apache.org, accessed June 2026. https://httpd.apache.org/docs/current/mod/mod_mime.html

  2. 2.

    ServerFault, "Apache: AddType in .htaccess and AllowOverride FileInfo," serverfault.com, accessed June 2026. https://serverfault.com/questions/186965/

  3. 3.

    IANA, "image/avif — Media Type Registration," iana.org, January 2021. https://www.iana.org/assignments/media-types/image/avif

  4. 4.

    IANA, "application/wasm — Media Type Registration," iana.org, April 2021. https://www.iana.org/assignments/media-types/application/wasm

  5. 5.

    Apache HTTP Server, "Stopping and Restarting," httpd.apache.org, accessed June 2026. https://httpd.apache.org/docs/current/stopping.html

  6. 6.

    curl, "curl — Man Page," curl.se, accessed June 2026. https://curl.se/docs/manpage.html

  7. 7.

    ServerFault, "Apache: ForceType vs AddType vs mod_headers for MIME Configuration," serverfault.com, March 2014. https://serverfault.com/questions/582155/apache-force-type-vs-add-type-vs-mod-headers-for-mime-configuration

FAQ

Express.js MIME Types

Express.js sets MIME types through res.type() and express.static(). In route handlers, res.type() accepts a MIME type string or file extension shorthand and sets the Content-Type header before calling res.send() or res.end(). The static file middleware express.static() uses the mime-types package internally to derive Content-Type from file extensions. For files not in the default mime database, express.static() falls back to application/octet-stream. The setHeaders option on express.static() provides a per-response callback for overriding specific headers including Content-Type, which is useful for serving newer formats like AVIF or WASM from a static directory without modifying the global mime database.1

Setting MIME type on route handlers

In Express route handlers, the response MIME type must be set before writing the body, because once Express sends the first chunk of the response it can no longer modify the Content-Type header that the client has already received.2 res.type(type) accepts both extension shortcuts like 'json', 'html', 'png' and full MIME type strings like 'application/json', and calling it sets Content-Type by overwriting any previously set value via res.set().1

The underlying mime-types package appends a charset parameter for any MIME type that has a defined charset in mime-db, which is convenient for HTML but undesirable for binary formats where a charset parameter is meaningless. Alternatively, res.set('Content-Type', 'application/wasm') sets the header explicitly without the shorthand processing or automatic charset append.

For binary responses, using the full MIME type string is clearer: res.set('Content-Type', 'application/wasm') followed by res.send(buffer) sends a WebAssembly binary with the correct type. Consequently, res.type() is idiomatic for common types where extension shortcuts are readable, and res.set() is preferable for uncommon or binary types where the full string is more explicit.

Serving static files with correct types

express.static() resolves Content-Type by calling the mime-types package's lookup functions with the file extension, looking up the extension in a built-in database that maps hundreds of file suffixes to their IANA-registered MIME types.3 For common extensions like .js, .json, .html, .jpg, .png, and .css, the built-in mime database includes the correct types and has done so for many years. Extensions that postdate the mime-types version bundled with your Express installation, such as .avif or .wasm, may fall back to application/octet-stream because the bundled database predates their IANA registrations (image/avif was registered in 2021 and application/wasm in 2021).45

To override the type for a specific extension without modifying the global database, use the setHeaders callback in the options object passed to express.static(). The setHeaders function receives the response object, the resolved file path, and the stat object, and can call res.set() to override Content-Type before Node.js streams the file to the client.

Detecting file extension for conditional override

Use path.extname(filePath) inside the setHeaders callback to extract the file extension from the resolved path, then conditionally call res.set() only for extensions that need an explicit override such as .avif, .wasm, or .woff2.6 This approach lets you target specific formats without disturbing the default mime-types lookups for common extensions like .js, .png, or .css, and it avoids the need to modify the global mime database for formats that are not yet in mime-db.

Adding MIME types not in the bundled mime-db database

The mime-types package that Express uses has no define() method. Custom MIME types cannot be registered at runtime on the shared mime-types instance. Instead, the recommended approach for serving formats that are not yet in the bundled mime-db (such as .avif or .wasm on older Express installations) is the setHeaders callback in express.static().3 For projects that need a type recognized globally across the Node.js process, open a pull request against the mime-db project on GitHub; once merged, the type propagates to every package that depends on mime-db, including mime-types and Express.7 At application startup, you can also construct a custom Mime instance from the mime package (a separate dependency) and pass it to serve-static via the mime option, but this requires adding the mime package as an explicit dependency and is rarely necessary when setHeaders handles the common AVIF and WASM cases.

Server-Sent Events response type and compression middleware

Server-Sent Events use text/event-stream as their Content-Type and require the response to remain open while the server pushes data.2 In Express, setting res.setHeader('Content-Type', 'text/event-stream') and calling res.flushHeaders() sends the response headers to the client immediately before any data arrives, establishing the persistent connection the SSE protocol requires. Without res.flushHeaders(), Express buffers the headers until the first write call, which can delay connection establishment.

Compression middleware such as the compress package needs an explicit filter to exclude text/event-stream responses.8 Compressing an SSE stream introduces buffering that prevents the browser from receiving events until the compression buffer fills. Pass a custom filter function to the compression middleware options: the filter receives the request and response objects, checks res.getHeader('Content-Type'), and returns false for text/event-stream to skip compression for those responses. All other content types continue to be compressed normally.

Heartbeat lines and connection cleanup for SSE endpoints

SSE connections through proxies and load balancers face idle timeout disconnections when no data flows for a period. Sending a comment line (a colon-prefixed line such as : keepalive) every 15 to 30 seconds prevents the proxy from closing the connection due to inactivity. Your Express handler should set an interval with setInterval that writes the comment line and calls res.flush() to push it through any internal buffers.8 When the client disconnects, listening for the req.on('close') event lets you clear the interval and release any associated resources, preventing memory leaks from accumulating intervals in long-running SSE endpoints.

CORS preflight and the application/json Content-Type

Setting Content-Type: application/json on a fetch request turns it into a non-simple CORS request, triggering a preflight OPTIONS request before the actual method.9 Browsers classify a request as non-simple when the Content-Type is neither application/x-www-form-urlencoded, multipart/form-data, nor text/plain; application/json falls outside these three, so every JSON POST from a browser to a cross-origin API generates a preflight. Your Express server must respond to the OPTIONS request with the appropriate Access-Control-Allow-Headers header that includes Content-Type.

When using the cors package, the allowedHeaders option must include 'Content-Type' to allow the preflight to succeed.10 The cors package default behaviour on allowedHeaders reflects the request's Access-Control-Request-Headers back to the client, which can mask missing configuration during development. Set allowedHeaders: ['Content-Type', 'Authorization'] explicitly so the behaviour is predictable and audit-ready rather than relying on reflection.

Credentialed requests and CORS header constraints

Cross-origin requests that include credentials (cookies or HTTP authentication) require Access-Control-Allow-Credentials: true on the response and cannot use a wildcard Access-Control-Allow-Origin value.10 When your Express API serves credentialed clients, set origin to the explicit client origin rather than '*' in the cors options object, and ensure the credentials: true option is present. Sending application/json bodies with credentialed requests is common in same-company frontend and API deployments on separate subdomains; in those cases the preflight is unavoidable, and confirming that Access-Control-Allow-Headers includes Content-Type is a necessary step during initial integration testing.

The same credential rules apply to preflight responses, so the OPTIONS handler must also return Access-Control-Allow-Credentials: true or the browser rejects the entire exchange even when the GET or POST response is correct. A mismatch between the preflight and the actual response is a frequent cause of credentialed fetch failures that pass in curl but fail in the browser. CapyToolkit's MIME reference documents the application/json Content-Type so the preflight and the request stay consistent.

Notes

res.type('json') is shorthand for res.set('Content-Type', 'application/json'). res.type() accepts extension strings without the dot (e.g., 'html', 'json', 'png') or full MIME type strings (e.g., 'application/json'). express.static() uses the mime-types package for type lookup; require the mime-types package directly to query or extend the database. The mime-types package has no define() method; to add custom types at runtime, use the setHeaders callback in express.static() or open a PR against the mime-db project that underlies mime-types. The setHeaders callback in express.static(dir, { setHeaders }) receives (res, path, stat) and can call res.set() to override headers per file.

Examples

res.type() and res.set() in route handlers

// Shorthand for application/json
app.get('/data', (req, res) => {
  res.type('json');
  res.send({ key: 'value' });
});

// Explicit MIME type for WebAssembly
app.get('/app.wasm', (req, res) => {
  res.set('Content-Type', 'application/wasm');
  res.sendFile(path.resolve('public/app.wasm'));
});

res.type() accepts shorthand extensions or full MIME strings. res.set() is clearer for binary types.

express.static() with setHeaders for AVIF and WASM

import path from 'path';
import express from 'express';

app.use(express.static('public', {
  setHeaders(res, filePath) {
    const ext = path.extname(filePath).toLowerCase();
    if (ext === '.avif') res.set('Content-Type', 'image/avif');
    if (ext === '.wasm') res.set('Content-Type', 'application/wasm');
  }
}));

setHeaders overrides Content-Type for specific extensions without modifying the global mime table.

Per-extension override via setHeaders for AVIF and WASM

import path from 'path';
import express from 'express';

app.use(express.static('public', {
  setHeaders(res, filePath) {
    const ext = path.extname(filePath).toLowerCase();
    if (ext === '.avif') res.set('Content-Type', 'image/avif');
    if (ext === '.wasm') res.set('Content-Type', 'application/wasm');
    if (ext === '.woff2') res.set('Content-Type', 'font/woff2');
  }
}));

setHeaders overrides Content-Type per file without modifying the global mime-db. Preferred over a custom Mime instance for most projects.

Try in the tool

Express MIME patterns

  • accepts extension shortcuts ('json', 'html') or full MIME strings
  • uses the mime-types package, falls back to application/octet-stream for unknown extensions
  • per-file override for formats missing from the bundled mime database
  • has no define() method — no runtime registration on the shared instance

Content-Type must be set before the first response chunk is sent — Express can't rewrite it afterward.

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    Express, "Express 5 Response API," expressjs.com, accessed June 2026. https://expressjs.com/en/5x/api/response/

  2. 2.

    Express, "serve-static," github.com/expressjs/serve-static, accessed June 2026. https://github.com/expressjs/serve-static

  3. 3.

    IANA, "Media Types," iana.org, accessed June 2026. https://www.iana.org/assignments/media-types/image/avif

  4. 4.

    IANA, "Media Types," iana.org, accessed June 2026. https://www.iana.org/assignments/media-types/application/wasm

  5. 5.

    Node.js, "path.extname()," nodejs.org, accessed June 2026. https://nodejs.org/api/path.html

  6. 6.

    jshttp, "mime-types," github.com/jshttp/mime-types, accessed June 2026. https://github.com/jshttp/mime-types

  7. 7.

    MDN, "Server-Sent Events," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events

  8. 8.

    npm, "compression," npmjs.com, accessed June 2026. https://www.npmjs.com/package/compression

  9. 9.

    WHATWG, "Fetch Standard," fetch.spec.whatwg.org, accessed June 2026. https://fetch.spec.whatwg.org/#http-responses

  10. 10.

    Express, "CORS Middleware," expressjs.com, accessed June 2026. https://expressjs.com/en/resources/middleware/cors.html

FAQ

Next.js MIME Types

Next.js does not expose a direct MIME type configuration API. Custom Content-Type headers require the headers() function in next.config.js for static assets in the /public directory, or explicit header setting in App Router route handlers using NextResponse. The headers() function returns an array of header rules, each with a source path pattern and a list of headers to apply. Files served from /public use the send package's inferred MIME types via the mime module, which includes common formats but may not include newer registrations without explicit overrides or a Next.js update. In App Router route handlers, NextResponse.json() sets application/json automatically. Binary responses and streaming responses require either new Response(body, { headers: { 'Content-Type': '...' } }) or a NextResponse with explicit headers. Consequently, understanding which layer serves each resource determines where to apply MIME type configuration.1

Custom headers in next.config.js

The headers() function in next.config.js returns a promise resolving to an array of header rule objects, and each object in that array contains a source path pattern plus a headers array of name/value pairs that Next.js applies to matching responses. The source field uses path-to-regexp syntax, supporting patterns like /assets/:path* to match all files under a directory and :slug to capture a single path segment as a named parameter.1 For overriding MIME types of files served from the /public directory, match on the file extension path pattern and add a Content-Type header with the correct IANA-registered value. Path patterns support :param wildcards for single segments, :param* for zero-or-more matching across any number of path segments, and :param? for optional segments that may or may not be present.

Why headers() overrides the static file handler

Consequently, adding Content-Type overrides for AVIF and WASM files in /public requires two separate source patterns, one for each extension, because each source pattern can only match one path expression at a time. Building on this, headers() rules are evaluated before the static file handler processes the request, so the override applies even when the underlying server would otherwise send a fallback type. This ordering matters because Next.js evaluates headers() rules before the static file handler reads the file from disk and infers a type from its extension, which means your explicit Content-Type always takes precedence over any heuristically determined type.

NextResponse content-type in App Router route handlers

App Router route handlers export named async functions corresponding to HTTP methods. Returning a NextResponse or Response from a handler sends the response to the client. NextResponse.json(data) serialises the data to JSON and sets Content-Type: application/json automatically.2 For other content types, construct a Response with explicit headers. A binary file response requires reading the file as a Buffer and returning new Response(buffer, { headers: { 'Content-Type': 'application/wasm' } }).

Server-Sent Events in route handlers

Returning a streaming Response with Content-Type: text/event-stream enables Server-Sent Events from App Router route handlers, giving you a persistent server-to-client connection over standard HTTP without WebSocket infrastructure.3 Use a ReadableStream as the response body, and write an async generator function that yields SSE-formatted strings to the stream controller at whatever interval your application requires. Set Cache-Control: no-cache, no-transform alongside text/event-stream to prevent proxy buffering of the stream, and add X-Accel-Buffering: no if your deployment sits behind an NGINX reverse proxy.4

Serving binary files from /public

Files placed in the /public directory are served by Next.js's built-in static file handler. This handler infers Content-Type from the file extension using the send package and its bundled mime database, which includes image/avif in all modern Next.js versions (Next.js manually defines it via send.mime.define()).5 For formats not in the bundled database, add explicit Content-Type overrides in next.config.js headers() to ensure the correct type is sent. Verify the override with curl -sI http://localhost:3000/public-file.avif before deploying. Furthermore, the /public directory does not execute any middleware, so headers() is the only mechanism to add Content-Type overrides; route handlers do not apply to static files in /public. For large binary files, Next.js also supports streaming responses from route handlers for programmatic control over chunked delivery.

Middleware and Edge runtime Content-Type constraints in Next.js

Next.js Middleware runs on the Edge runtime and intercepts requests before they reach page or route handler code. You can set Content-Type headers inside middleware.ts using the NextResponse constructor, but the Edge runtime's Response API has constraints that differ from Node.js: the fs module is unavailable, and response body size is limited by the edge provider.6 For MIME type overrides that apply broadly across a path pattern (such as all routes under /api/v2/), Middleware is the right place to attach or modify the Content-Type header without duplicating logic across individual route handlers.

The trade-off between using next.config.js headers() and Middleware for MIME overrides comes down to dynamism. The headers() configuration in next.config.js applies statically defined header rules at build time and suits cases where the MIME type for a path never varies at runtime. Middleware runs on every request and can inspect request headers, cookies, or query parameters before deciding what Content-Type to set, making it the right choice when the correct MIME type depends on runtime context (for example, returning application/json vs. text/csv based on a query parameter).

Edge runtime Response constructor for custom Content-Type responses

Route handlers in the Edge runtime return a standard Response object rather than using Next.js helper methods. Constructing a response with new Response(body, { headers: { 'Content-Type': 'application/geo+json' } }) sets the MIME type precisely for the response body. When the body is a ReadableStream and the Content-Type is text/event-stream, the Edge runtime keeps the connection open and streams chunks as the generator produces them, which enables Server-Sent Events from Edge-deployed route handlers. Confirm the edge provider's timeout configuration allows long-lived SSE connections, since providers such as Vercel Edge Functions impose maximum response duration limits that terminate SSE streams after a fixed period.5

Choosing the Edge runtime for an SSE route means accepting those provider limits, so a long-lived stream may need a fallback to the Node runtime where duration ceilings are far higher. Matching the runtime to the expected connection lifetime avoids silent truncations that are hard to reproduce in local development. CapyToolkit's MIME reference documents the text/event-stream Content-Type so the Edge response advertises the correct type.

Notes

headers() in next.config.js uses path-to-regexp syntax with modifiers like :slug* (zero or more segments), :slug+ (one or more), and :slug? (zero or one). NextResponse.json(data) sets Content-Type: application/json automatically. For binary responses: new Response(buffer, { headers: { 'Content-Type': 'application/wasm' } }). For streaming responses: return new Response(readable, { headers: { 'Content-Type': 'text/event-stream' } }). Files in /public are served by Next.js's static file handler; use headers() to override their Content-Type. The App Router's route.ts file exports named HTTP method functions (GET, POST, etc.) that return Response or NextResponse objects.

Examples

next.config.js: add Content-Type headers for AVIF and WASM in /public

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: '/:path*.avif',
        headers: [{ key: 'Content-Type', value: 'image/avif' }],
      },
      {
        source: '/:path*.wasm',
        headers: [{ key: 'Content-Type', value: 'application/wasm' }],
      },
    ];
  },
};

export default nextConfig;

headers() rules override Content-Type for matching paths before Next.js serves the file from /public.

App Router route handler: binary WASM response

// app/api/app.wasm/route.ts
import { readFile } from 'fs/promises';
import path from 'path';

export async function GET() {
  const wasmBuffer = await readFile(path.resolve('public/app.wasm'));
  return new Response(wasmBuffer, {
    headers: {
      'Content-Type': 'application/wasm',
      'Cache-Control': 'public, max-age=31536000',
    },
  });
}

Return a Response with explicit Content-Type for binary files served from a route handler.

App Router route handler: Server-Sent Events stream

// app/api/stream/route.ts
export async function GET() {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue('data: connected\n\n');
      // Push events as: controller.enqueue('data: payload\n\n');
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

Set Content-Type: text/event-stream and Cache-Control: no-cache for SSE responses in App Router.

Try in the tool

Next.js MIME configuration points

  • headers() in next.config.js overrides Content-Type for static files in /public, evaluated before the static handler
  • NextResponse.json() sets application/json automatically in App Router route handlers
  • Binary responses new Response(buffer, { headers: { 'Content-Type': '...' } })
  • /public directory served by the built-in static handler — route handlers don't apply to it

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    Vercel, "Next.js headers()," nextjs.org, accessed June 2026. https://nextjs.org/docs/app/api-reference/config/next-config-js/headers

  2. 2.

    Vercel, "Next.js," nextjs.org, accessed June 2026. https://nextjs.org/docs/app/api-reference/functions/next-response

  3. 3.

    MDN, "Server-Sent Events," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events

  4. 4.

    WHATWG, "Streams Standard," streams.spec.whatwg.org, accessed June 2026. https://streams.spec.whatwg.org/

  5. 5.

    Vercel, "send," github.com/vercel/next.js, accessed June 2026. https://raw.githubusercontent.com/vercel/next.js/main/packages/next/src/server/serve-static.ts

  6. 6.

    Sentry, "Next.js Module not found: Can't resolve 'fs'," sentry.io, accessed June 2026. https://sentry.io/answers/next-js-middleware-module-not-found-can-t-resolve-fs/

FAQ

FastAPI MIME Types

FastAPI controls MIME types through the media_type parameter on response classes. The framework provides several response classes that handle Content-Type in different ways. Response(content, media_type="image/avif") sets Content-Type explicitly and is the most direct approach1. FileResponse infers MIME type from the file extension via Python's mimetypes stdlib module, using mimetypes.guess_type(path) internally2; this works for common formats but may return None for newer formats that are not in the system's MIME database. StreamingResponse requires explicit media_type because it streams bytes without a file path to infer from. JSONResponse sets application/json automatically and is the default response type for route functions that return dictionaries. Understanding which response class to use for each format is the key to correct MIME type assignment in FastAPI applications.

Response with explicit media_type

The base Response class in FastAPI accepts content, status_code, headers, media_type, and background parameters. Setting media_type explicitly overrides any inferred type and sets the Content-Type header of the response. This is the correct approach when serving binary content with a known but uncommon MIME type, or when the inferred type would be incorrect for the content. For an AVIF image read from disk, return Response(content=image_bytes, media_type="image/avif") to send the binary data with the correct type.

Custom MIME types beyond application/json

For a JSON-like structure that should use a custom MIME type such as application/problem+json, return Response(content=json.dumps(data), media_type="application/problem+json") instead of JSONResponse, which always uses application/json. Consequently, the base Response class is the most flexible option when you need full control over the Content-Type header. You can return text/csv for data exports, application/octet-stream for generic binary downloads, or any vendor-specific type that your API clients expect, all without being forced into the default JSON serialisation that applies when you return a plain dictionary from your route function.

FileResponse MIME inference and override

FileResponse serves a file from the filesystem and sets Content-Type by calling mimetypes.guess_type(path) on the file path, which returns a MIME type string derived from the file extension rather than from inspecting the file's actual content bytes.2 Python's mimetypes module reads its type mappings from the system MIME database, typically sourced from /etc/mime.types on Linux or the Windows registry, combined with a small set of built-in fallback types that ship with the Python standard library.3

Newer formats such as image/avif may not be present in the mimetypes database on all systems, particularly servers running older Linux distributions whose /etc/mime.types file has not been updated since before AVIF's IANA registration. When mimetypes.guess_type() returns None for the extension, FileResponse falls back to application/octet-stream, which triggers a download prompt in browsers instead of inline rendering for images and other displayable formats. Override this inference failure by passing media_type explicitly: FileResponse(path="image.avif", media_type="image/avif") ignores the guess_type result entirely and uses the provided type.

Building on this, you can register the missing type at application startup with mimetypes.add_type("image/avif", ".avif"), which updates the inference database for the entire process lifetime.3 Once registered, all subsequent FileResponse calls for .avif files return the correct type without per-call overrides, and you avoid the risk of forgetting the media_type parameter on any individual route that serves AVIF content.

StreamingResponse for binary streams

StreamingResponse streams an iterator or async generator to the client without buffering the entire response in memory. Because the response body is a generator that yields bytes, there is no file path to infer a MIME type from. The media_type parameter is therefore required for binary StreamingResponse instances.1 For large file downloads, a generator that yields chunks from a file produces memory-efficient delivery: the generator reads blocks of the file and yields them, and StreamingResponse sends each block as it arrives. For AVIF image proxying, yield the bytes from an upstream response inside a generator and return StreamingResponse(generator(), media_type="image/avif"). Furthermore, StreamingResponse with media_type="text/event-stream" implements Server-Sent Events: the generator yields SSE-formatted lines, and the response streams them to the client without buffering.

ORJSONResponse and high-performance JSON serialisation in FastAPI

FastAPI's default JSONResponse uses Python's built-in json module, which is reliable but slower than alternatives for large payloads. The orjson library serialises Python objects 3 to 10 times faster than the default json module and natively handles dataclasses, numpy arrays, and UUID objects without custom encoders.4 FastAPI previously exposed this through ORJSONResponse, but that class is deprecated as of FastAPI v0.130.0; the recommended approach now is to declare a response model as the return type, which lets FastAPI serialise directly to JSON bytes via Pydantic without an intermediate response class.

For applications that still use ORJSONResponse, pass default_response_class=ORJSONResponse to the FastAPI() constructor. Individual routes can override this default by specifying response_class=JSONResponse for endpoints that require standard json module behaviour, such as routes returning values that orjson rejects (for example, sets, which orjson does not serialise). UJSONResponse is an alternative backed by the ujson library, though orjson benchmarks consistently outperform ujson for typical API response shapes.

Returning non-JSON media types from FastAPI routes

Not every FastAPI route should return JSON. For routes that serve HTML, plain text, or binary data, pass the correct media_type to the Response constructor: Response(content=html_string, media_type="text/html") or Response(content=csv_bytes, media_type="text/csv"). FastAPI does not wrap these responses in JSONResponse when you return a Response object directly, so the Content-Type you specify is exactly what the client receives. Using the correct media_type avoids the mismatch that occurs when FastAPI's default JSON serialisation wraps a binary blob or markup string in a JSON-encoded string value.

Content negotiation using the Accept header in FastAPI

Content negotiation lets a single endpoint return different formats based on the client's Accept header. In FastAPI, access the Accept header with request.headers.get('accept') inside a route function that declares Request as a parameter. Comparing the Accept value to a set of supported types lets you return JSONResponse for application/json, Response with media_type="text/csv" for text/csv, or raise an HTTPException with status code 406 for formats your endpoint does not support.

Implementing 406 responses correctly requires returning a response body that describes the supported types; RFC 9110 specifies that the server MAY include a list of acceptable representations in the response content.5 For FastAPI routes, returning HTTPException(status_code=406, detail="Supported types: application/json, text/csv") provides actionable feedback to API clients that send unsupported Accept values. Without explicit 406 handling, FastAPI falls through to the default JSON serialiser for all Accept values, which silently ignores the client's preference.

application/problem+json for structured error responses

RFC 9457 defines application/problem+json as a standard Content-Type for HTTP error responses.6 Returning a Problem Details object from a FastAPI exception handler gives API clients a predictable structure for error parsing: the type field is a URI identifying the error category, title is a human-readable summary, and detail gives the specific occurrence information. Returning this type requires a custom exception handler that sets response media_type to application/problem+json and serialises the problem object explicitly, since FastAPI's default HTTPException handler returns application/json for error bodies.

Adopting this type consistently across your exception handlers gives API clients one predictable shape to parse regardless of which endpoint raised the error. Pairing it with a 406 response for unsupported Accept values completes a content-negotiation story that degrades gracefully instead of silently returning JSON. CapyToolkit's MIME reference documents the application/problem+json Content-Type so your error responses match the registered standard.

Notes

Response(content, media_type="image/avif") sets Content-Type exactly as specified. FileResponse(path) infers type via mimetypes.guess_type(path); override with media_type parameter when inference fails. StreamingResponse(generator, media_type="video/mp4") requires explicit media_type for binary streams. JSONResponse is the default; return a dict from a route function and FastAPI uses JSONResponse automatically. mimetypes.add_type("image/avif", ".avif") registers a custom mapping in the stdlib mimetypes module for the process lifetime.

Examples

Explicit media_type on Response for AVIF and WASM

from fastapi import FastAPI
from fastapi.responses import Response

app = FastAPI()

@app.get("/image.avif")
async def serve_avif():
    with open("image.avif", "rb") as f:
        content = f.read()
    return Response(content=content, media_type="image/avif")

@app.get("/app.wasm")
async def serve_wasm():
    with open("app.wasm", "rb") as f:
        content = f.read()
    return Response(content=content, media_type="application/wasm")

Use Response with explicit media_type for binary files where inference may fail or be incorrect.

FileResponse with media_type override for AVIF

from fastapi.responses import FileResponse

@app.get("/static/image.avif")
async def get_avif():
    return FileResponse(
        path="static/image.avif",
        media_type="image/avif",
    )

Override FileResponse MIME inference by passing media_type explicitly when the system mimetypes database lacks the type.

StreamingResponse for large binary downloads

from fastapi.responses import StreamingResponse

def iter_file(path: str, chunk_size: int = 65536):
    with open(path, "rb") as f:
        while chunk := f.read(chunk_size):
            yield chunk

@app.get("/download/large-file.bin")
async def download_large():
    return StreamingResponse(
        iter_file("large-file.bin"),
        media_type="application/octet-stream",
        headers={"Content-Disposition": "attachment; filename=large-file.bin"},
    )

StreamingResponse with a generator yields chunks without loading the full file into memory.

Try in the tool

FastAPI response classes

  • sets Content-Type explicitly — most direct approach
  • infers type via mimetypes.guess_type() — can return None for newer formats
  • requires explicit media_type — no file path to infer from
  • registers a custom mapping for the process lifetime

FileResponse falls back to application/octet-stream when guess_type() can't identify the extension, which triggers a download prompt instead of inline rendering.

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    FastAPI, "Custom Response - HTML, Stream, File, others," fastapi.tiangolo.com, accessed June 2026. https://fastapi.tiangolo.com/advanced/custom-response/

  2. 2.

    Encode, "responses.py," github.com/encode/starlette, accessed June 2026. https://github.com/encode/starlette/blob/main/starlette/responses.py

  3. 3.

    Python Software Foundation, "mimetypes — Map filenames to MIME types," docs.python.org, accessed June 2026. https://docs.python.org/3/library/mimetypes.html

  4. 4.

    ijl, "orjson," github.com/ijl/orjson, accessed June 2026. https://github.com/ijl/orjson

  5. 5.

    R. Fielding, Ed., "RFC 9110: HTTP Semantics," rfc-editor.org, June 2024. https://www.rfc-editor.org/rfc/rfc9110#section-15.5.7

  6. 6.

    T. Berners-Lee, Ed., "RFC 9457: Problem Details for HTTP APIs," rfc-editor.org, March 2025. https://www.rfc-editor.org/rfc/rfc9457

FAQ

Python requests and Content-Type

The requests library reads and sets Content-Type through response headers and request keyword arguments. Reading Content-Type from a response is straightforward: response.headers['Content-Type'] returns the raw header string including any parameters such as charset1. Setting Content-Type on outgoing requests depends on which keyword argument carries the body. Passing json= to requests.post() serialises the payload and sets Content-Type: application/json automatically2. Passing data= with a dictionary sets application/x-www-form-urlencoded. Passing files= sets multipart/form-data with a generated boundary. Overriding any of these automatic types requires passing headers={'Content-Type': 'application/xml'} explicitly. The automatic type assignment is one of the most common sources of confusion for developers new to the requests library: using data= instead of json= sends an incorrect Content-Type without any visible error from the library.

Reading Content-Type from responses

The response object returned by any requests HTTP method exposes all response headers through response.headers, which is a case-insensitive CaseInsensitiveDict that lets you access header names in any casing without worrying about the exact capitalisation the server used. Accessing response.headers['Content-Type'] or response.headers['content-type'] returns the same value because the dict normalises all keys to lowercase internally. The raw header string includes the MIME type and any parameters appended after a semicolon, such as "application/json; charset=utf-8" where the charset parameter specifies the text encoding of the response body.

Before calling response.json(), check that the Content-Type is actually application/json or another JSON-compatible type to avoid a JSONDecodeError on non-JSON responses like HTML error pages. Conversely, response.json() calls json.loads(response.text) internally and raises JSONDecodeError if the body is not valid JSON regardless of what the declared Content-Type header claims1. Building on this, response.content returns the raw bytes, response.text returns the body decoded according to response.encoding, and response.json() returns the parsed Python object; choose the appropriate accessor based on the expected content type.

Setting Content-Type on requests

Which keyword argument you use to carry the request body determines the automatic Content-Type that requests assigns, and choosing the wrong keyword is the single most common source of Content-Type errors in Python API client code. The json= argument accepts any JSON-serialisable Python object, serialises it with json.dumps(), and sets Content-Type: application/json on the outgoing request without any additional configuration2. The data= argument behaves differently depending on the type of value you pass: a dict sets application/x-www-form-urlencoded, while bytes or a string sets no Content-Type at all, leaving the server to guess. The files= argument accepts a dict mapping field names to file tuples and sets multipart/form-data with a generated boundary parameter that the server needs to parse the multipart body.

Overriding automatic Content-Type

To send a raw body with a specific type that does not match any of the automatic assignments, pass the body as bytes or a string via data= and provide headers={'Content-Type': 'application/xml'} to override whatever type requests would otherwise infer from the argument type. Passing both json= and headers={'Content-Type': ...} lets the json= argument serialise the body while the headers dict overrides the automatically assigned type, but this combination is rarely needed in practice.

Parsing the charset from Content-Type header

The charset parameter in the Content-Type header specifies the text encoding of the response body, and correctly interpreting this parameter is essential for decoding non-ASCII text without corruption or mojibake. The raw value from response.headers['Content-Type'] might be "text/html; charset=iso-8859-1" or "application/json; charset=utf-8", where the portion after the semicolon is the parameter that tells your code which byte-to-character mapping to apply. Extracting the charset manually requires splitting the string on the semicolon delimiter and parsing the second segment to isolate the encoding name from any surrounding whitespace or quotes.

The requests library performs this parsing automatically and exposes the result through response.encoding, which it sets from the charset parameter if present in the response headers. When no charset is declared, response.encoding defaults to ISO-8859-1 for text/* types per RFC 2616, which is often incorrect for modern UTF-8 content and produces garbled output for any non-ASCII characters3. Consequently, check response.apparent_encoding (from chardet or charset-normaliser) alongside response.encoding when the server omits the charset parameter. Setting response.encoding = 'utf-8' before accessing response.text overrides the inferred encoding and forces UTF-8 decoding.

Session-level headers and Content-Type defaults in requests

requests.Session persists headers, cookies, and authentication across multiple requests to the same host. Setting session.headers.update({'Content-Type': 'application/json'}) on a Session object applies that header to every request the session sends. This pattern is useful for API clients where every request carries the same Content-Type, eliminating the need to pass headers= on each individual call.

A conflict arises when you set Content-Type at the session level and then use the files= parameter on a specific request. When files= is present, requests must set Content-Type to multipart/form-data with a generated boundary parameter; if a conflicting Content-Type is already set at the session level, the boundary parameter may be omitted, producing a malformed multipart body that the server cannot parse4. For sessions that mix JSON and multipart requests, do not set Content-Type at the session level; instead set it per-request for JSON calls and let requests generate the multipart Content-Type automatically for file uploads.

Sending Authorization at session level without Content-Type conflicts

The safe pattern for session-level configuration is to set only headers that must apply uniformly: Accept, Authorization, and custom API key headers. Combining session.headers.update({'Authorization': 'Bearer token', 'Accept': 'application/json'}) with per-request Content-Type headers avoids the multipart conflict while still reducing boilerplate across the API client code. CapyToolkit's MIME reference documents the exact Content-Type values your chosen server expects so you can configure session defaults confidently.

Streaming downloads and Content-Type validation with requests

Downloading large files with requests requires stream=True to prevent the entire response from loading into memory before your code processes it. Passing stream=True to requests.get() returns a Response object whose content attribute is not yet consumed; call response.iter_content(chunk_size=8192) inside a with open(path, 'wb') as f loop to write chunks to disk incrementally3. Without stream=True, requests buffers the full response body in memory, which is impractical for files larger than available RAM.

Validating Content-Type before consuming the streamed body protects your application from server configuration errors that return the wrong file type. Check response.headers.get('Content-Type') immediately after the request completes but before calling iter_content. If the Content-Type does not match the expected type (for example, if you expected application/zip but received text/html, indicating an error page), raise an exception or return early rather than writing potentially useless or harmful content to disk.

Parsing Content-Disposition filenames from streamed responses

When a server sends a Content-Disposition header with the response, parsing it correctly extracts the intended download filename. Python's standard library provides cgi.parse_header() for this purpose: passing the raw Content-Disposition value to parse_header() returns a tuple of the directive string and a parameters dict, from which you read the filename key. For servers that use RFC 5987 encoding (filename* parameter with charset prefix), the standard cgi module does not decode the ext-value format5; instead, use the requests-toolbelt package's ContentDisposition utility or parse the filename* value manually by splitting on the '' separator and percent-decoding the UTF-8 bytes.

Failing to parse the filename correctly can cause your download routine to write a corrupted or empty file name, which then breaks the on-disk layout your application relies on for later retrieval. Handling both the filename and filename* forms in one helper keeps the logic portable across servers with different encoding conventions. CapyToolkit's MIME reference documents the Content-Type values your streamed responses should carry so the parser receives the expected type.

Notes

response.headers['Content-Type'] returns the full Content-Type string including parameters. response.headers is a case-insensitive dict. response.json() decodes a JSON response body. response.encoding is set from the charset parameter if present. requests.post(url, json=data) sets Content-Type: application/json. requests.post(url, data=dict) sets application/x-www-form-urlencoded. requests.post(url, files=filedict) sets multipart/form-data. requests.post(url, data=json_string, headers={'Content-Type': 'application/json'}) overrides the automatic type for a raw string body. Use response.headers.get('Content-Type', '') for safe access when the header may be absent.

Examples

Read Content-Type and call the right accessor

import requests

response = requests.get('https://api.example.com/data')
content_type = response.headers.get('Content-Type', '')

if 'application/json' in content_type:
    data = response.json()
elif 'text/' in content_type:
    data = response.text
else:
    data = response.content  # raw bytes

Check Content-Type before calling response.json() to avoid JSONDecodeError on non-JSON responses.

POST with json= vs data= and resulting Content-Type

import requests

# Sets Content-Type: application/json automatically
r1 = requests.post('https://api.example.com/json', json={'key': 'value'})

# Sets Content-Type: application/x-www-form-urlencoded
r2 = requests.post('https://api.example.com/form', data={'key': 'value'})

# Override: send raw XML with explicit Content-Type
r3 = requests.post(
    'https://api.example.com/xml',
    data='<root><key>value</key></root>',
    headers={'Content-Type': 'application/xml'},
)

json= and data= set different Content-Types automatically. Pass headers= to override.

Parse charset from Content-Type header

response = requests.get('https://example.com/page')
ct = response.headers.get('Content-Type', '')

# Extract charset if present
charset = None
for part in ct.split(';'):
    part = part.strip()
    if part.startswith('charset='):
        charset = part.split('=', 1)[1].strip()
        break

# Override encoding if server declared UTF-8 but requests set ISO-8859-1
if charset:
    response.encoding = charset
text = response.text

response.encoding defaults to ISO-8859-1 for text/* when charset is absent. Override to force UTF-8.

Try in the tool

How requests sets Content-Type automatically

  • json= serialises the payload and sets application/json
  • data= (dict) sets application/x-www-form-urlencoded
  • data= (bytes/string) sets no Content-Type at all — the server has to guess
  • files= sets multipart/form-data with a generated boundary parameter

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    PSF, "Quickstart," docs.python-requests.org, accessed June 2026. https://docs.python-requests.org/en/stable/user/quickstart/

  2. 2.

    PSF, "models.py," github.com/psf/requests, accessed June 2026. https://github.com/psf/requests/blob/v2.32.5/src/requests/models.py

  3. 3.

    PSF, "Advanced Usage," docs.python-requests.org, accessed June 2026. https://docs.python-requests.org/en/latest/user/advanced/

  4. 4.

    PSF, "POST Multipart-Encoding does not work if the Content-Type header has been previously defined in the session," github.com/psf/requests, accessed June 2026. https://github.com/psf/requests/issues/6992

  5. 5.

    Python Software Foundation, "cgi.parse_header does not decode the extended filename* parameter," bugs.python.org, accessed June 2026. https://bugs.python.org/issue23434

FAQ

Cloudflare MIME Type Overrides

Cloudflare Workers provide full control over response Content-Type headers.1 A Worker intercepts HTTP requests and can set any response header, including Content-Type, on responses built from scratch or on responses fetched from the origin. For teams that cannot modify origin server configuration directly, Workers offer a programmable layer to fix incorrect MIME types without changing the upstream. Transform Rules in the Cloudflare dashboard provide a no-code alternative for simple header overrides without deploying Worker code.2 For static assets served from Cloudflare R2 or a misconfigured origin, Workers can rewrite Content-Type based on file extension or any other request attribute.3 Cloudflare also applies compression and caching decisions based on Content-Type, making correct type configuration important for performance, not just correctness.

Setting Content-Type in a Cloudflare Worker

A Cloudflare Worker intercepts requests and returns a Response object. To build a response with an explicit Content-Type, pass the header in the headers object of the Response constructor: new Response(body, { headers: { 'Content-Type': 'image/avif' } }).1 To modify the Content-Type of a response fetched from the origin, clone the response with mutable headers and set the new type.

Cloning an origin response with mutable headers

Fetching the origin response with await fetch(request) returns an immutable Response; call new Response(response.body, { headers: new Headers(response.headers) }) to create a mutable copy, then call headers.set('Content-Type', 'image/avif') before returning it.4 Building on this, the Worker approach is the right choice when the origin server is outside your control or when you need conditional Content-Type logic based on request attributes like query parameters or user agent.

Using Transform Rules to fix MIME mismatches

Transform Rules in the Cloudflare dashboard modify request or response headers based on matching criteria without requiring Worker code. To override Content-Type for AVIF files, create a Response Header Modification rule matching requests where the URL path ends with .avif and set Content-Type to image/avif. The dashboard path is Rules then Transform Rules then Modify Response Header.2 Rules support wildcard and regex matching on URL path, hostname, and other request attributes. Consequently, Transform Rules are the simplest solution when the type mismatch applies uniformly to all requests matching a path pattern, without conditional logic. Furthermore, Cloudflare evaluates caching behaviour before applying response header modifications, so modifying Content-Type with a Transform Rule changes the header sent to the client but does not alter how the original response is classified in cache. Verify the override by clearing the Cloudflare cache for the URL after creating the rule.

Caching and compression behaviour with Content-Type

Cloudflare applies automatic Brotli compression to responses based on Content-Type.5 Types that servers and CDNs compress by default include text/html, application/json, application/javascript, text/css, application/xml, and several others.6 image/avif and font/woff2 are already compressed internally and Cloudflare correctly excludes them from HTTP-level compression.7 application/wasm receives Brotli compression from Cloudflare on responses that arrive uncompressed from the origin. Serving content with application/octet-stream instead of the specific type causes Cloudflare to skip compression for types that would otherwise be compressed, and may cause incorrect cache classification.

Cache rules and Content-Type

Cloudflare Cache Rules use request URL patterns, not Content-Type, for primary cache matching,8 which means a rule targeting /*.avif caches all AVIF files regardless of their specific Content-Type value. However, the Content-Type of cached responses affects which browser receives which version when Vary: Accept headers are present on the origin response. For format-negotiated content where the Accept header varies the response between AVIF and JPEG, ensure the origin sends Vary: Accept so caches serve the correct format to each browser.9

Cloudflare Pages _headers file for static asset MIME types

Cloudflare Pages supports a _headers file placed at the root of the published output directory. This file sets HTTP response headers for URL patterns matching each block, without requiring a Worker or a Transform Rule. Each block begins with a URL pattern, followed by indented header assignments. A pattern of /*.avif matches all AVIF file requests and allows you to set Content-Type: image/avif for the matched paths. Cloudflare reads the _headers file at deploy time and applies its rules on the edge for every subsequent request.

The syntax assigns one header per indented line beneath the URL pattern. Multiple headers can appear in a single block. Setting both Content-Type and Cache-Control in a _headers block for font files is common: Content-Type: font/woff2 corrects the MIME type, and Cache-Control: public, max-age=31536000, immutable applies long-term caching in one configuration step. Entries in the _headers file take precedence over Cloudflare Pages platform defaults for matched paths.

_headers versus Workers for MIME type overrides on Pages

The _headers file is simpler for straightforward extension-to-type mappings in static Cloudflare Pages deployments.3 Workers are more appropriate when MIME type assignment requires conditional logic based on request context, or when serving dynamic content from R2 or a KV namespace. For purely static sites deployed directly to Cloudflare Pages, the _headers file achieves the same result as a content-type-fixing Worker with no code, no deployment step beyond committing the file, and no Worker CPU usage billing.

The _headers file also applies at the edge without a build step in most cases, so a type correction reaches users faster than a Worker redeploy that must propagate across the platform. For static deployments the file is the lower-maintenance choice, while Workers stay reserved for the conditional logic that a flat mapping cannot express. CapyToolkit's MIME reference lists the correct Content-Type for each format so your _headers rules set the right value.

Notes

Cloudflare Workers use new Response(body, { headers: { 'Content-Type': '...' } }) to set response headers. response.headers.set('Content-Type', 'image/avif') modifies a fetched origin response. fetch(request) in a Worker fetches from the origin. Transform Rules are in the Cloudflare dashboard under Rules > Transform Rules > Modify Response Header. Workers receive the request context via the fetch event; the response is returned from the event handler. Cloudflare applies Brotli compression automatically to application/json, text/html, application/javascript, and other compressible types based on Content-Type.

Examples

Worker: set Content-Type on an origin response

export default {
  async fetch(request) {
    const response = await fetch(request);
    const url = new URL(request.url);

    // Override Content-Type for AVIF and WASM files
    if (url.pathname.endsWith('.avif') || url.pathname.endsWith('.wasm')) {
      const mimeType = url.pathname.endsWith('.avif')
        ? 'image/avif'
        : 'application/wasm';

      const headers = new Headers(response.headers);
      headers.set('Content-Type', mimeType);
      return new Response(response.body, { ...response, headers });
    }

    return response;
  },
};

Clone the origin response with mutable headers to override Content-Type based on file extension.

Worker: serve from R2 with explicit Content-Type

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const key = url.pathname.slice(1); // remove leading /
    const object = await env.BUCKET.get(key);

    if (!object) {
      return new Response('Not Found', { status: 404 });
    }

    const ext = key.split('.').pop()?.toLowerCase();
    const mimeMap = {
      avif: 'image/avif',
      wasm: 'application/wasm',
      woff2: 'font/woff2',
    };
    const contentType = mimeMap[ext] ?? 'application/octet-stream';

    return new Response(object.body, {
      headers: {
        'Content-Type': contentType,
        'Cache-Control': 'public, max-age=31536000',
      },
    });
  },
};

Map file extensions to MIME types when serving from R2 to avoid application/octet-stream fallbacks.

Transform Rule: add Content-Type for AVIF (dashboard config)

# Create in: Rules > Transform Rules > Modify Response Header
Field:    URI Path
Operator: ends with
Value:    .avif

Set header:
  Name:  Content-Type
  Value: image/avif

Transform Rules fix MIME types without Worker code for simple extension-based overrides.

Try in the tool

Three ways to override Content-Type on Cloudflare

  • new Response(body, { headers: { 'Content-Type': '...' } })
  • dashboard-based Response Header Modification, no code required
  • URL pattern blocks with indented header assignments, applied at deploy time

image/avif and font/woff2 are already compressed internally — Cloudflare correctly excludes them from further Brotli compression.

Verify with the MIME Type Reference tool.

Try it in the tool ↑
Sources
  1. 1.

    Cloudflare, "How to set custom Content-Type for workers static assets," community.cloudflare.com, accessed June 2026. https://community.cloudflare.com/t/how-to-set-custom-content-type-for-workers-static-assets/780188

  2. 2.

    Cloudflare, "Modifying HTTP response headers with Transform Rules," blog.cloudflare.com, accessed June 2026. https://blog.cloudflare.com/transform-http-response-headers/

  3. 3.

    Cloudflare, "R2 is now Generally Available," blog.cloudflare.com, accessed June 2026. https://blog.cloudflare.com/r2-ga/

  4. 4.

    Cloudflare, "Modify response," developers.cloudflare.com, accessed June 2026. https://developers.cloudflare.com/workers/examples/modify-response/

  5. 5.

    Cloudflare, "Content compression," developers.cloudflare.com, accessed June 2026. https://developers.cloudflare.com/speed/optimization/content/compression/

  6. 6.

    MDN Web Docs, "Compression in HTTP," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression

  7. 7.

    Traefik, "Compress middleware should exclude already-compressed types," github.com/traefik/traefik, accessed June 2026. https://github.com/traefik/traefik/issues/9229

  8. 8.

    Cloudflare, "Cache by file extension and CF-Cache-Status: DYNAMIC," community.cloudflare.com, accessed June 2026. https://community.cloudflare.com/t/cache-by-file-extension-and-cf-cache-status-dynamic/138666

  9. 9.

    web.dev, "Use image CDNs to optimize images," web.dev, accessed June 2026. https://web.dev/articles/image-cdns

FAQ