application/json
application/json is the default format for REST API communication. Defined in RFC 8259, it instructs HTTP clients and middleware that a message body contains JSON-encoded data.1 Setting Content-Type: application/json on a request tells the server what you are sending; setting it on a response tells the client how to parse the body. Consequently, both directions require the header independently. Many frameworks set the response Content-Type automatically when serializing with a JSON method, but request body parsing often requires explicit middleware. Serving static .json files also requires the correct MIME type. Without it, some CDNs classify and compress responses by Content-Type, so serving JSON as text/plain can result in uncompressed responses even when server compression settings appear enabled. The charset parameter is unnecessary: RFC 8259 mandates UTF-8.2
What is application/json?
RFC 8259, which obsoletes RFC 7159 and RFC 4627. The type applies to documents structured according to the JSON data interchange format. Vendor-specific JSON formats use the +json suffix pattern: application/problem+json (RFC 9457), application/vnd.api+json, and application/merge-patch+json (RFC 7396) are common examples.3 RFC 8259 specifies UTF-8 as the required encoding; charset parameters are unnecessary and should be omitted. Compliant parsers must treat the body as UTF-8 regardless of any charset value present in the header.Content-Type and Accept in API requests
In REST API design, the Content-Type header on a request declares the format of the body you are sending. The Accept header declares the format you expect in return. For a POST request sending JSON, set Content-Type: application/json. For any request where you want a JSON response, set Accept: application/json. Many APIs serve JSON by default and skip strict content negotiation, but setting both headers explicitly makes your client robust against configuration changes.
Omitting the charset parameter
RFC 8259 designates UTF-8 as the only required encoding for inter-system JSON exchange, which means adding "; charset=utf-8" to the Content-Type header is redundant and may trigger strict parsers that reject unexpected parameters. Frameworks that append the charset automatically produce harmless but noisy headers that add bytes to every response without providing any parsing benefit. Omit the charset parameter entirely unless a specific legacy API requires it, and if you control the framework configuration, disable the automatic charset append to keep response headers clean. CapyToolkit's MIME reference tool flags unnecessary charset parameters on JSON responses so you can identify and remove them during development.
The practical impact shows up most clearly during automated MIME audits and contract tests, where a response that carries an unexpected charset parameter fails an exact string assertion even though the payload parses correctly. Tests that compare the full Content-Type value surface the extra parameter as a difference, which wastes engineering time triaging a non-issue and can mask a genuine mismatch elsewhere. Standardizing on the bare media type across every JSON endpoint keeps header assertions clean and removes that noise from your test output.
Serving static JSON files
Static web servers derive Content-Type from file extensions. Nginx, Apache, and Caddy ship with built-in type tables that include application/json for .json files in recent versions, but older installs may be missing the entry. Verify the header explicitly with curl -I https://example.com/data.json and confirm the response shows Content-Type: application/json. Furthermore, some CDNs apply Brotli compression only to specific MIME types and use Content-Type for cache classification. Serving JSON with text/plain can produce uncompressed, uncached responses even when CDN compression settings appear enabled. Cloudflare applies Brotli automatically to application/json but not to text/plain.4 Building on this, always audit Content-Type headers on JSON endpoints as part of deployment verification rather than assuming server configuration is correct.
Common misconfigurations
Two misconfigurations appear frequently in production systems, and both cause real problems for API clients that depend on consistent Content-Type headers to decide how to parse response bodies. The first is returning text/plain for error responses while sending application/json for successes, which happens when an unhandled exception reaches the framework's default error handler before any custom middleware has a chance to apply the correct Content-Type header. Clients that check Content-Type before parsing will fail on error bodies while parsing success bodies correctly, producing confusing error messages that reference a JSON parse failure on what is actually an HTML or plain-text error page.
Monitoring tools that index response bodies by Content-Type will also miss the error payload entirely, leaving gaps in your observability data that make production incidents harder to diagnose. Conversely, the second misconfiguration adds an unnecessary charset parameter, producing Content-Type: application/json; charset=utf-8 on every response. This is harmless in most clients but signals misconfiguration to MIME auditing tools and adds bytes to every response header without providing any parsing benefit, since RFC 8259 already mandates UTF-8 as the required encoding for all JSON interchange. Both issues are detectable with curl -I or browser developer tools.
Newline-delimited JSON for streaming API responses
Newline-delimited JSON sends one complete JSON object per line rather than wrapping the entire dataset in a single array. The format, commonly identified as NDJSON, uses the media type application/x-ndjson for explicit typing.5 Each line in the response body is a standalone, independently parseable JSON value, so a client can begin processing data as soon as the first line arrives rather than waiting for the complete response body to close.
Servers returning large datasets benefit from this pattern because clients can stream objects into a processing pipeline without holding the full response in memory. The Fetch API exposes response.body as a ReadableStream; piping it through a TextDecoder and splitting on newline characters lets you parse each chunk as it arrives.6 Libraries such as ndjson-parse and fetchNDJSON automate this splitting and parsing without manual stream handling.
NDJSON in AI streaming APIs
OpenAI's streaming completions endpoint and Anthropic's Messages streaming API both use a variant of NDJSON where each line carries a data: prefix following Server-Sent Events conventions. Each token or content block arrives as a self-contained JSON event, allowing your application to update the UI with partial output before the response finishes. For endpoints that must return early output, NDJSON streaming over application/x-ndjson is the standard HTTP approach that avoids WebSocket infrastructure entirely.
ETag and Cache-Control strategies for JSON API responses
JSON API responses support standard HTTP caching through Cache-Control and ETag headers. Setting Cache-Control: max-age=60, must-revalidate on a JSON endpoint tells browsers and CDNs to cache the response for 60 seconds and revalidate before serving a stale copy. Short max-age values keep client data reasonably fresh while still reducing server load from repeated requests to the same endpoint.
ETag values identify a specific version of a response body. When your server generates a JSON response, hashing the serialised body and returning that hash as an ETag header enables efficient conditional requests. On subsequent calls, the client sends If-None-Match: "hash-value" and the server returns 304 Not Modified when the content has not changed, saving bandwidth and reducing serialisation work. This pattern works well for reference data endpoints and configuration objects that change infrequently but are requested often.
Vary header for multi-format API endpoints
When an endpoint returns different formats depending on the Accept header (JSON for application/json, CSV for text/csv), the response must include Vary: Accept to prevent CDNs from serving a cached JSON response to a client that requested CSV. Without the Vary header, a CDN treats all requests to the endpoint as equivalent regardless of Accept value. Cloudflare respects Vary: Accept and caches separate versions per distinct Accept value; verify by submitting two requests with different Accept values and confirming both responses carry the correct Content-Type.
Try in the tool
Open the MIME Type Reference tool pre-filled to application/json to verify it or try a different one.
Check application/json in the tool →- 1.
T. Bray, Ed., "The JavaScript Object Notation (JSON) Data Interchange Format," RFC 8259, IETF, December 2017. https://www.rfc-editor.org/rfc/rfc8259.html
- 2.
"application/json," IANA Media Types Registry, ietf.org, accessed June 2026. https://www.iana.org/assignments/media-types/application/json
- 3.
M. Nottingham, E. Wilde, and S. Dalal, "Problem Details for HTTP APIs," RFC 9457, IETF, July 2023. https://www.rfc-editor.org/rfc/rfc9457.html
- 4.
Cloudflare, "Content compression," developers.cloudflare.com, accessed June 2026. https://developers.cloudflare.com/speed/optimization/content/compression/
- 5.
"NDJSON — Newline Delimited JSON," ndjson.org, 2014. https://github.com/ndjson/ndjson-spec
- 6.
Mozilla Developer Network, "Using readable streams," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
No. RFC 8259 specifies UTF-8 as the required encoding for JSON. Adding "; charset=utf-8" is redundant: compliant parsers assume UTF-8 regardless. Some older frameworks append the charset automatically, which is harmless in practice but adds unnecessary header bytes and may trigger warnings in strict MIME auditing environments.
application/json is the base type for any JSON document. application/problem+json (RFC 9457) is a standard schema for HTTP error responses, defining type, title, status, detail, and instance fields. APIs use problem+json on error responses to give clients a consistent, machine-readable error structure while keeping success responses as plain application/json.
Browsers send Accept: */* for fetch requests by default because they cannot predict the response format. Set the Accept header explicitly in your code: fetch(url, { headers: { Accept: "application/json" } }). This matters when the server performs content negotiation and can return multiple formats for the same endpoint.
Yes. The MIME type is set by the Content-Type header, not the filename. However, most servers derive Content-Type from the file extension, so a file named data.txt receives text/plain even if it contains JSON. Override this with an explicit AddType directive in Apache, a types block entry in Nginx, or a mime directive in Caddy.
CDNs cache based on Cache-Control headers, not MIME type alone. However, many CDNs use Content-Type to decide whether to apply compression. Serving JSON with the correct application/json type ensures Brotli or gzip compression is applied. Without the correct type, CDNs may cache the response uncompressed, increasing bandwidth costs for large payloads. CapyToolkit offers a MIME reference tool that lets you look up the correct Content-Type for every format and verify your server is sending the right headers.
application/pdf
application/pdf is the MIME type for Portable Document Format files. Defined in RFC 8118 (which updates RFC 3778), it signals to browsers and HTTP clients that a response body contains a PDF document.1 Most desktop browsers render PDFs inline using a built-in viewer when the response carries this content type and no conflicting Content-Disposition header is present.
Without the correct MIME type, some servers fall back to application/octet-stream, which forces a download regardless of the user's browser settings. Consequently, inline display depends on both the correct MIME type and the absence of Content-Disposition: attachment. Security deserves attention here: user-supplied PDF files can contain embedded JavaScript, cross-site scripting payloads, and exploit code targeting PDF readers.2 Serving untrusted PDFs requires sandboxing and should not rely on the browser's built-in viewer alone. Furthermore, static PDF generation with tools like Puppeteer and wkhtmltopdf requires setting the correct Content-Type when the output is served over HTTP.
What is application/pdf?
RFC 8118, which updates RFC 3778. The type applies to documents conforming to the Portable Document Format specification maintained by Adobe and ISO 32000. PDF files carry the magic bytes %PDF at the start of the file body.1 RFC 8118 obsoletes the earlier application/pdf registration and clarifies security considerations for serving user-generated PDFs. No charset parameter applies to application/pdf, as PDF is a binary format with its own internal encoding.Inline display versus forced download
Whether a browser renders a PDF inline or triggers a download depends on the combination of Content-Type and Content-Disposition headers. Serving application/pdf without a Content-Disposition header tells the browser to use its default behaviour, which on modern desktop browsers means inline rendering via the built-in PDF viewer. Adding Content-Disposition: inline explicitly requests inline display regardless of browser settings. Conversely, Content-Disposition: attachment; filename="document.pdf" forces a download prompt regardless of the MIME type.3
Mobile browser inconsistencies
The filename parameter in Content-Disposition also controls the suggested save name the browser presents. Building on this, mobile browsers handle PDF display inconsistently: some open a download manager rather than rendering inline, so testing across device types is necessary before assuming inline delivery works for all users. iOS Safari, for example, renders PDFs in a built-in viewer but may switch to a full-screen mode that behaves differently from desktop inline rendering. Android browsers vary even more, with some defaulting to an external PDF application rather than any in-browser display at all.
This inconsistency is why a single serving configuration cannot guarantee a uniform experience across the device matrix your users actually run. A PDF that renders inline on a desktop browser may open a download manager on a phone, so document your assumption explicitly and test on both classes of device before relying on inline delivery. CapyToolkit's MIME reference confirms the exact header combination needed for inline rendering so you can verify your server's behaviour against the expected result.
Security risks when serving user-supplied PDFs
PDFs from untrusted sources carry significant security risks. The format supports embedded JavaScript executed by Adobe Reader and other PDF viewers, cross-site scripting attacks via URI actions, and exploit payloads targeting specific reader versions. Serving user-uploaded PDFs directly from your origin means any visitor can be served a malicious document from your domain. Two mitigations reduce this risk. First, serve user-supplied PDFs from a separate domain or subdomain that carries no session cookies for your main application, isolating any scripting execution from your user data.
Content-Security-Policy for PDF iframes
Second, when embedding PDFs in an iframe, apply a restrictive sandbox attribute that blocks JavaScript execution inside the embedded document and prevents form submissions or top-level navigation.4 The sandbox attribute without any allow-* tokens creates the tightest possible restriction set, and you can selectively re-enable capabilities like allow-scripts or allow-same-origin only when the embedded content requires them. Furthermore, enabling X-Content-Type-Options: nosniff prevents browsers from executing a PDF that is misidentified as a script, closing a secondary attack vector that relies on MIME type confusion.
Generating and serving PDFs server-side
Server-generated PDF delivery requires setting Content-Type: application/pdf on the response. When using Puppeteer's page.pdf() method, the resulting binary buffer must be sent with the correct header; Puppeteer does not set HTTP headers itself. Similarly, wkhtmltopdf and iText write binary streams that need Content-Type applied by your web framework before sending to the client. For static PDF files on Nginx, the built-in mime.types file includes application/pdf for .pdf extensions in all commonly deployed versions. Apache's mod_mime performs the same mapping automatically. Yet always verify the actual response header with curl -I rather than assuming the server configuration is correct, particularly after a server upgrade or when a CDN sits in front of the origin.
Serving PDFs from cloud storage and presigned URLs
Cloud storage services like Amazon S3, Google Cloud Storage, and Azure Blob Storage require explicit Content-Type metadata at upload time. An S3 object uploaded without a ContentType parameter receives application/octet-stream by default, which triggers a download prompt rather than inline browser rendering. Set ContentType to application/pdf in the PutObject or UploadPart call. For objects already stored with the wrong type, copy the object to itself using the CopyObject API with the MetadataDirective parameter set to REPLACE and the correct ContentType in the new metadata.
Presigned URLs for PDF delivery also interact with Content-Disposition. The AWS SDK's getSignedUrl method accepts a ResponseContentDisposition parameter that overrides the stored header on the resulting URL without modifying the S3 object. Specifying ResponseContentDisposition=inline delivers the PDF inline even when the stored object carries Content-Disposition: attachment. This parameter is available in both SDK v2 and SDK v3's GetObjectCommand options.
CloudFront MIME type caching for S3-backed distributions
CloudFront distributions backed by an S3 origin cache the Content-Type stored in the S3 object metadata. Correcting a MIME type on a cached object requires two steps: fix the S3 metadata with a CopyObject call and then invalidate the relevant CloudFront cache paths. A CloudFront invalidation submitted via the console or the CLI forces edge nodes to fetch the corrected metadata on the next request. Run curl -sI against the CloudFront URL after propagation completes to confirm the corrected Content-Type header reaches the client.
Try in the tool
Open the MIME Type Reference tool pre-filled to application/pdf to verify it or try a different one.
Check application/pdf in the tool →- 1.
D. Benham, Ed., "The application/pdf Media Type," RFC 8118, IETF, March 2017. https://www.rfc-editor.org/rfc/rfc8118.html
- 2.
Mozilla, "CVE-2024-4367: Arbitrary JavaScript execution in PDF.js," github.com, May 2024. https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq
- 3.
Mozilla Developer Network, "Content-Disposition," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Disposition
- 4.
Mozilla Developer Network, "Iframe," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
The most common cause is a Content-Disposition: attachment header overriding the MIME type. Remove the attachment directive or change it to Content-Disposition: inline. If no Content-Disposition header is present, check that the server is sending application/pdf and not application/octet-stream, which triggers a download regardless of browser PDF support.
Not without precautions. PDFs can contain embedded JavaScript, URI actions, and exploit payloads. Serve user-supplied PDFs from a separate domain with no session cookies for your main app. Apply X-Content-Type-Options: nosniff and consider stripping active content with a PDF sanitiser before storing and serving user uploads.
No. PDF is a binary format with internal encoding defined by the ISO 32000 specification. Charset parameters do not apply. Servers that append a charset to application/pdf are misconfigured and may confuse some HTTP clients and content inspection proxies.
Set Content-Disposition: attachment; filename="yourfile.pdf" on the response alongside Content-Type: application/pdf. The attachment directive tells the browser to download rather than render. The filename parameter sets the suggested save name in the download dialog.
Yes, using an object element or the PDF.js library. The object element accepts type="application/pdf" and displays the browser's native PDF viewer. PDF.js renders the document using HTML5 canvas, giving you full control over the rendering without relying on browser PDF support, and it works in environments where built-in PDF viewing is disabled. CapyToolkit's MIME reference confirms that application/pdf is the correct Content-Type for both iframe and object-based PDF embedding.
application/javascript
When a browser fetches a JavaScript file, the Content-Type response header determines whether the engine will execute it or silently discard it. If your server sends text/plain or application/octet-stream for a .js file, the browser refuses to run that script, and in production the failure is invisible: the page simply stops working without a clear explanation. This is why the correct JavaScript MIME type matters.
Defined in RFC 9239, text/javascript is the sole standard MIME type for JavaScript, with a COMMON intended usage.1 RFC 9239 formally obsoletes application/javascript, application/ecmascript, and application/x-javascript as script types. In practice, all major browsers accept both text/javascript and application/javascript for backwards compatibility, but new server configurations should use text/javascript to align with the current standard. Content Security Policy enforcement and ES module serving both interact with MIME type checking in ways that can silently break scripts when the type is wrong.
What is application/javascript?
RFC 9239 as the sole standard MIME type for JavaScript, obsoleting the earlier RFC 4329.2 The type covers ECMAScript source code intended for execution in a runtime environment. RFC 9239 formally obsoletes application/javascript, application/ecmascript, application/x-javascript, and text/javascript1.0 through text/javascript1.5, all of which remain recognised for backwards compatibility. The registration does not specify a version of ECMAScript; any ES5, ES2015+, or later source text uses the same MIME type. No charset parameter is required; UTF-8 is the conventional encoding for source files.text/javascript deprecation history
text/javascript was the original MIME type for JavaScript, registered in the early web when JavaScript was considered a text format rather than an application-level language. RFC 4329 registered application/javascript and application/ecmascript as additional types in 2006, and for over a decade application/javascript was treated as the preferred type.2 RFC 9239, published in 2022, reversed this by designating text/javascript as the sole common type and marking application/javascript, application/ecmascript, and application/x-javascript as obsolete aliases.1 Furthermore, RFC 9239 notes that browsers must continue treating these obsolete types as equivalent to text/javascript for the foreseeable future, so legacy content does not break. New server configurations should specify text/javascript to align with the current standard and avoid triggering deprecation warnings in future browser auditing tools.
CSP script-src enforcement and MIME type checking
Content Security Policy's script-src directive controls which scripts the browser may execute, but it does not enforce MIME type correctness by itself. The browser evaluates MIME type against execution rules independently of CSP. Serving a script with text/plain or an unrecognised type causes the browser to refuse execution regardless of whether the script URL matches the CSP allowlist.
Strict MIME checking in module scripts
ES modules apply stricter MIME checking than classic scripts. A script element with type="module" requires the response to carry a valid JavaScript MIME type. Serving a module script as application/octet-stream or text/plain causes the browser to silently reject it, often with no visible error in older developer tools. Browsers that enforce strict MIME checking for classic scripts also block scripts served with incorrect types when X-Content-Type-Options: nosniff is present.
Serving ES modules and .mjs files
ES modules require a valid JavaScript MIME type on both the entry point and all imported modules. The .mjs extension is a convention indicating an ES module, and some build tools and runtimes treat it differently from .js. Browsers do not distinguish .mjs from .js at the HTTP level; both require application/javascript in the Content-Type response header. Consequently, your server's MIME table must map .mjs to application/javascript explicitly if the built-in table maps only .js. Nginx's default mime.types file maps text/javascript to .js but may not include .mjs in older releases.3 Apache requires a separate AddType directive for .mjs. Building on this, bundlers like Vite and Webpack serve modules with the correct type automatically during development, but production static file serving depends entirely on the server's MIME type configuration.
Dynamic imports and MIME requirements for code-split chunks
Dynamic import() expressions load additional modules on demand rather than including them in the initial bundle. When a bundler such as Webpack, Rollup, or Vite processes dynamic imports, it outputs separate chunk files that the browser fetches at runtime. Each chunk is a JavaScript module and must be served with application/javascript; without the correct MIME type, browsers applying strict module MIME checking refuse to execute the fetched chunk.
CDNs that serve code-split chunks must have application/javascript in their MIME type table for .js files. This requirement is identical to the base script file but deserves explicit verification for chunks because they often live in asset directories with version hashes in the filename, such as /assets/vendor.b3d72f1.js. Some CDN configurations apply MIME types based on path prefix rather than file extension, which can leave asset-directory chunks mistyped while the main entry script serves correctly.
Preloading modules with rel=modulepreload
The rel=modulepreload link hint tells the browser to fetch and compile a module before it is needed, reducing latency when the module is eventually imported. Browsers enforce the same MIME type requirement for modulepreload fetches as for type="module" scripts: the preloaded file must return application/javascript or a recognised JavaScript MIME type. Adding a modulepreload hint for a chunk served with the wrong MIME type produces a preload-to-load mismatch error in the console, preventing the performance benefit while adding an unnecessary network request.
Service worker registration and strict MIME enforcement
Service workers have the strictest MIME type requirement of any browser API that processes JavaScript.4 Browsers refuse to register a service worker script not served with application/javascript (or the accepted text/javascript); this rejection occurs regardless of the file's actual content. The error message varies by browser but consistently states that the script was blocked due to MIME type mismatch.
Registering a service worker with navigator.serviceWorker.register('/sw.js') triggers a fetch of /sw.js at registration time. If your server maps .js files to application/octet-stream or any non-JavaScript type, registration fails silently from the page's perspective but logs an error to the console. Verify the service worker script's Content-Type with curl -sI https://your-domain.com/sw.js before testing registration in the browser.
Service worker scope and stable path requirements
Service workers served from a path determine their scope: a worker at /app/sw.js controls /app/ by default. Deployment pipelines that hash asset filenames may place the service worker at a versioned path, but the registration URL must always point to a stable, non-hashed path so clients that cached an older worker version can still reach it. Confirm your server configuration serves the stable service worker path with application/javascript regardless of any content-addressable asset pipeline running alongside it.
A common production failure happens when the service worker file is hashed for cache busting on every deploy, so the registered path changes and previously installed workers can no longer be found at their expected location. Keeping the registration URL on a stable path while hashing only the imported scripts avoids this breakage. The correct MIME type on that stable path remains the constant requirement that every other part of the service worker lifecycle depends on.
Try in the tool
Open the MIME Type Reference tool pre-filled to application/javascript to verify it or try a different one.
Check application/javascript in the tool →- 1.
M. A. Miller, M. Borins, M. Bynens, and B. Farias, "Updates to ECMAScript Media Types," RFC 9239, IETF, May 2022. https://www.rfc-editor.org/rfc/rfc9239.html
- 2.
"Scripting Media Types," RFC 4329, IETF, April 2006. https://www.rfc-editor.org/rfc/rfc4329.html
- 3.
"Nginx 1.25.4 changelog," nginx.org, 2024. https://nginx.org/en/CHANGES
- 4.
Mozilla Developer Network, "Service Workers," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
It is the current standard. RFC 9239 designates text/javascript as the sole common MIME type for JavaScript and marks application/javascript as obsolete. Browsers still accept application/javascript for backwards compatibility, but new server configurations should use text/javascript to align with the current standard.
The most common cause is an incorrect MIME type. Browsers applying strict MIME checking for type="module" scripts refuse execution if the Content-Type is not a valid JavaScript type. Check the Network panel for the script response headers and confirm Content-Type is application/javascript.
Not in older Nginx versions. The bundled mime.types file maps text/javascript to .js but many releases do not include .mjs. Add "text/javascript mjs;" inside a types {} block in your nginx.conf, then reload Nginx and verify with curl -sI.
Yes. CSP and MIME type checking are independent mechanisms. CSP controls whether the script URL is trusted; MIME type checking controls whether the browser will execute the response. Both must pass for a script to run. A correct MIME type does not override a missing script-src allowlist entry.
Browsers do not execute TypeScript directly. You must transpile TypeScript to JavaScript before serving it. The resulting .js files should carry application/javascript. Some build tools output .ts extensions during development using bundler-specific module loaders that handle transpilation, but production builds should always output JavaScript with the correct MIME type. CapyToolkit's MIME reference confirms application/javascript as the standard type for all ECMAScript source files regardless of the ECMAScript version or transpilation source.
application/wasm
application/wasm is a hard requirement for WebAssembly in modern browsers. Unlike most MIME types where an incorrect value causes degraded behaviour, serving a .wasm binary with the wrong Content-Type produces a hard compile-time error with no fallback or recovery path. The W3C WebAssembly Core Specification, in its fetch integration section, requires browsers to validate the MIME type before attempting compilation.1 Browsers refuse to instantiate the module and throw a WebAssembly.CompileError if the Content-Type is absent or incorrect. Consequently, application/wasm must appear in your server's MIME type table before any WebAssembly content can function. Many server distributions shipped before 2021 predate the IANA registration of application/wasm and require manual configuration. CDN and static hosting platforms vary: some add the type automatically, others require explicit configuration. Building on this, Emscripten's generated loader scripts expect the host to serve .wasm files with this exact type at runtime.
What is application/wasm?
.wasm). The format begins with the magic bytes 0x00 0x61 0x73 0x6D followed by a four-byte version field.2 Browsers enforce this MIME type strictly during WebAssembly.instantiateStreaming() calls. No charset parameter applies. The IANA registration was completed in 2021, which is why older server MIME type tables do not include it.Why browsers enforce application/wasm strictly
WebAssembly modules go through a compilation step before execution. The browser's WebAssembly engine validates the binary format during compilation, and the MIME type check is part of the streaming compilation pipeline in WebAssembly.instantiateStreaming(). If the Content-Type is not application/wasm, the browser refuses to pass the response to the compiler and throws a hard error: "WebAssembly.instantiateStreaming() failed because your server does not serve wasm with application/wasm MIME type." This error has no fallback.
Zero tolerance compared to JavaScript MIME handling
Unlike JavaScript MIME type mismatches, which some browsers tolerate for classic scripts, WebAssembly has zero tolerance for incorrect MIME types in the streaming API. Furthermore, there is no MIME sniffing fallback for .wasm files: the browser does not inspect the file's magic bytes as an alternative to trusting the Content-Type header. This strict approach exists because WebAssembly modules are compiled to native code before execution, and allowing a module compiled from a misidentified format would create a sandbox escape vector that undermines the browser's security model.
Serving .wasm from CDN and static hosts
CDN and static hosting platforms handle WebAssembly MIME types inconsistently, so you cannot assume that a platform which works correctly for JavaScript and CSS will also serve .wasm files with the right Content-Type header. Cloudflare passes through application/wasm for .wasm files when the origin sends the correct type, but does not add the type itself if the origin responds with application/octet-stream. Vercel and Netlify both include application/wasm in their default MIME tables, making them reliable choices for WebAssembly projects without additional configuration. GitHub Pages, however, does not serve .wasm files with the correct MIME type by default and does not support custom headers configuration, so a CDN or reverse proxy in front of GitHub Pages is required to set the correct Content-Type for .wasm files.3
Nginx configuration for WebAssembly
For Nginx, add "application/wasm wasm;" inside a types {} block in the http {} or server {} context and run nginx -t followed by nginx -s reload to apply the change. Verify the result with curl -sI https://your-host/app.wasm and confirm Content-Type: application/wasm appears in the response headers before deploying to production. If the types block already contains other custom entries, append the wasm entry alongside them rather than creating a second block, which would replace the first one entirely and remove all previously defined custom types from the merged table.
MIME type in Emscripten and wasm-pack workflows
Emscripten generates a JavaScript loader file alongside the .wasm binary. The loader calls WebAssembly.instantiateStreaming() with the .wasm URL, which means the host must serve the binary with application/wasm. When the MIME type is wrong, the loader throws the compile error described above. Emscripten also provides a fallback path using WebAssembly.instantiate() with an ArrayBuffer when streaming compilation fails, but this fallback does not recover from MIME type errors during development. Wasm-pack outputs a similar structure: a JavaScript glue file and a .wasm binary. Both files must be served from a host with application/wasm configured. Consequently, testing locally with a bare file:// URL fails because file protocol requests do not send HTTP headers; use a local server such as npx serve or python -m http.server with MIME type support for accurate local testing.
SharedArrayBuffer and cross-origin isolation for multithreaded WebAssembly
Multithreaded WebAssembly requires SharedArrayBuffer for thread synchronisation and shared memory between workers, but browsers restrict this API to cross-origin isolated contexts as a security measure against speculative execution attacks like Spectre. Two response headers on the HTML document enable cross-origin isolation: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.4 Both must appear together on the page that loads the WebAssembly module; setting only one of them is insufficient and the browser will not grant access to SharedArrayBuffer. Without both headers, accessing SharedArrayBuffer throws a ReferenceError at runtime regardless of the application/wasm MIME type being correct.
Cross-origin isolation affects every embedded resource on the page. Every third-party script, iframe, and fetched resource must either send Cross-Origin-Resource-Policy: cross-origin or be hosted on the same origin. Running the page with both headers enabled before integrating multithreaded WebAssembly reveals which embedded resources break under these restrictions. The browser's performance.crossOriginIsolated property returns true when isolation is active, providing a reliable runtime check before attempting SharedArrayBuffer allocation.
Verifying isolation with DevTools before deployment
Chrome DevTools displays a cross-origin isolation status in the Application panel under Frames. When COOP and COEP headers are present but a subresource fails the isolation requirements, DevTools flags the blocking resource by name. Resolving each flagged subresource before deploying multithreaded WebAssembly prevents silent allocation failures at runtime. For Workers that deliver wasm modules, apply the same COOP and COEP headers on the Worker's HTML entry point rather than on the wasm binary itself.
This same isolation check applies whenever you deploy a worker that fetches or instantiates wasm on behalf of the page, because the worker's own fetch context must also satisfy the COOP and COEP requirements. A worker served without those headers can load the binary but will fail the cross-origin isolation contract that multithreaded execution depends on. Running the DevTools verification step before release catches both the page and the worker in a single pass.
Try in the tool
Open the MIME Type Reference tool pre-filled to application/wasm to verify it or try a different one.
Check application/wasm in the tool →- 1.
W3C, "WebAssembly Web API," w3.org, accessed June 2026. https://www.w3.org/TR/wasm-web-api-1/
- 2.
Mozilla Developer Network, "Understanding WebAssembly text format," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Understanding_the_text_format
- 3.
"Creating a GitHub Pages site," docs.github.com, accessed June 2026. https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site
- 4.
web.dev, "Making your website cross-origin isolated using COOP and COEP," web.dev, 2022. https://web.dev/articles/coop-coep
The browser throws: "WebAssembly.instantiateStreaming() failed because your server does not serve wasm with application/wasm MIME type." This is a hard compile error with no recovery. The module fails to load regardless of try/catch blocks around the instantiateStreaming call, though catching the error allows you to fall back to the non-streaming WebAssembly.instantiate() API.
Yes. Every server in the delivery chain, including origin servers, CDNs, and reverse proxies, must pass through or set the correct Content-Type. A CDN that strips or overrides the Content-Type from the origin will cause WebAssembly compilation to fail even if the origin is configured correctly.
Local development servers typically include application/wasm in their MIME tables. Production servers running older distributions may not. Run curl -sI against the production URL and check the Content-Type header. If it shows application/octet-stream, add "application/wasm wasm;" to the server MIME configuration and reload.
No. application/octet-stream causes the same hard compile error as any other incorrect type. There is no fallback path for MIME type mismatches in WebAssembly.instantiateStreaming(). You must use application/wasm. The only alternative is the non-streaming WebAssembly.instantiate() API, which accepts an ArrayBuffer and bypasses the MIME type check, at the cost of streaming performance.
GitHub Pages does not serve application/wasm by default and does not support custom headers configuration. You can work around this by placing a CDN like Cloudflare or Netlify in front of GitHub Pages to override the Content-Type header for .wasm files. CapyToolkit's MIME reference lists the correct Content-Type for WebAssembly and every other modern format so you can verify your hosting platform is configured correctly.
application/octet-stream
application/octet-stream is the generic binary fallback type. Defined in RFC 2046, Section 4.5.1, it signals to HTTP clients that a response body contains arbitrary binary data with no specific format.1 Browsers respond to this type by triggering a file download rather than attempting to render or execute the content. The type functions as a "unknown binary" declaration: it does not mean "binary file" in general, but rather "binary file of unknown or unspecified type." Consequently, using it as a catch-all for all binary responses is an antipattern that masks format information from clients, CDNs, and security tools. When a specific type exists for a format, such as image/webp, application/wasm, or font/woff2, that specific type should always be used instead. Furthermore, serving WebAssembly or fonts with application/octet-stream causes hard failures in browsers that enforce MIME type requirements for those formats.
What is application/octet-stream?
RFC 2046, Section 4.5.1. The type designates an arbitrary sequence of binary octets with no inherent structure. RFC 2046 recommends it as the fallback type for binary data whose specific format is unknown. The type does not carry any meaning about the content's actual format. Content-Disposition: attachment is commonly paired with this type to trigger a download, but the download behaviour is inherent to the type itself in most browsers even without Content-Disposition.2Forced download behaviour and Content-Disposition
Browsers treat application/octet-stream as a download trigger because the type explicitly declares the content to be unknown binary data rather than something renderable. Most modern browsers initiate a file save dialog immediately upon receiving this type, without waiting for a Content-Disposition header. Adding Content-Disposition: attachment; filename="file.bin" reinforces this behaviour and sets the suggested filename in the download dialog. Without the filename parameter, browsers derive a name from the URL path. Conversely, if you want to serve a binary file inline for a known renderable format, use the correct MIME type rather than application/octet-stream. Furthermore, setting X-Content-Type-Options: nosniff alongside application/octet-stream prevents browsers from attempting to detect the actual format via content sniffing, which ensures consistent download behaviour across all browsers.3
When not to use application/octet-stream
application/octet-stream should only appear when you genuinely do not know the format of a file. Known binary formats all have registered MIME types: images use image/jpeg, image/png, image/webp, or image/avif; audio uses audio/mpeg or audio/ogg; video uses video/mp4 or video/webm; fonts use font/woff2; WebAssembly uses application/wasm. Substituting the generic type for any of these registered formats silently degrades the user experience in ways that are difficult to diagnose because the file still downloads correctly.
Format-specific failures caused by generic typing
Serving these formats as application/octet-stream causes specific, measurable failures. Browsers refuse to compile WebAssembly modules served with the generic type because the WebAssembly specification requires an explicit application/wasm or application/wasm Content-Type as a security gate before the compilation step. Cross-origin font loading fails because browsers enforce CORS checks for font/ and application/ types using different rules, and the generic type does not match the expected font MIME category. CDNs that identify compressible types by MIME string will skip compression for application/octet-stream, even when the underlying content is text-based JSON or XML that would benefit significantly from gzip or brotli compression.
Security considerations for user-uploaded files
Serving user-uploaded files presents the most critical security case for application/octet-stream. When a user uploads a file and you serve it back to other users, the safest approach is to force application/octet-stream with Content-Disposition: attachment. This prevents browsers from rendering HTML, executing scripts, or processing SVGs that could contain cross-site scripting payloads. Without this approach, an attacker can upload a malicious HTML file or SVG with embedded scripts and have it execute in another user's browser context. Consequently, serving user-generated content always requires an explicit MIME type policy. Furthermore, X-Content-Type-Options: nosniff must accompany the Content-Type to prevent browsers from overriding your declared type by sniffing the file's content. Relying on application/octet-stream without nosniff leaves a gap in some older browsers.
Content-Disposition filename encoding for non-ASCII characters
Standard ASCII filenames in Content-Disposition work across all HTTP clients, but international characters require explicit encoding. RFC 5987 defines the ext-value syntax for the filename* parameter, which carries a charset label, an optional language tag, and a percent-encoded value: filename*=UTF-8''caf%C3%A9-menu.pdf.4 The asterisk suffix in filename* signals to clients that the value follows RFC 5987 encoding rather than the traditional quoted-string format, and most modern browsers correctly interpret filename* when it is present.
For maximum compatibility with older clients, include both the traditional filename parameter and the filename* parameter in the same Content-Disposition header. The traditional filename parameter carries the closest ASCII approximation of the intended name, while filename* carries the precisely encoded value. Clients that understand RFC 5987 prefer filename* and ignore filename; older clients that do not understand filename* fall back to the ASCII approximation. This dual-parameter approach avoids corrupted download filenames across the range of HTTP clients your users may employ.
Constructing percent-encoded filename values correctly
Spaces in filenames require percent-encoding as %20, not a literal space, in the filename* value. The percent-encoding applies to the UTF-8 byte representation of the character, not the Unicode code point directly: for the character é (U+00E9), the UTF-8 bytes are 0xC3 0xA9, producing %C3%A9. Constructing these values manually is error-prone; use a URL encoding function in your language's standard library applied to the UTF-8-encoded filename string rather than attempting to encode individual characters by hand.
Object storage services and MIME type metadata
Object storage services do not infer Content-Type from file extensions at serve time. Amazon S3 stores Content-Type as object metadata under the ContentType field; when retrieving the object, S3 serves that stored value directly as the HTTP Content-Type response header.5 Uploading a file without specifying ContentType causes S3 to default to application/octet-stream for all object types, including files that should carry image/webp, application/wasm, or font/woff2.
Fixing MIME metadata on existing S3 objects requires a copy operation because object metadata is immutable after upload. Use the AWS CLI command aws s3 cp s3://bucket/file.wasm s3://bucket/file.wasm with the content-type and metadata-directive flags set, which rewrites the object in place with the correct ContentType. For bulk corrections across many objects, the AWS SDK's CopyObject API accepts a Metadata parameter and a MetadataDirective of REPLACE, enabling scripted corrections without manual intervention.
Cloudflare R2 and Google Cloud Storage metadata
Cloudflare R2 uses the same object metadata model as S3. When putting objects via the R2 API or the wrangler CLI, set the httpMetadata.contentType field; R2 reads this field and sends it as Content-Type when serving objects. Google Cloud Storage stores Content-Type per object as well; the gsutil cp command accepts the content-type flag for single uploads, and the JSON API's Objects.insert method accepts a contentType field. For all three platforms, verify the served Content-Type with curl -sI after upload rather than assuming the upload metadata was accepted correctly.
A subtle failure mode appears when the SDK silently coerces an unknown content type to application/octet-stream on upload, which then triggers a download instead of the intended inline render even though the API call reported success. Treating the upload response as authoritative rather than the request parameters prevents this class of bug. Adding a post-upload curl check to your deployment script turns a silent misconfiguration into a visible, fixable error.
Try in the tool
Open the MIME Type Reference tool pre-filled to application/octet-stream to verify it or try a different one.
Check application/octet-stream in the tool →- 1.
N. Freed and N. Borenstein, "Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types," RFC 2046, IETF, November 1996. https://www.rfc-editor.org/rfc/rfc2046.html
- 2.
Mozilla Developer Network, "MIME types," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
- 3.
Mozilla Developer Network, "X-Content-Type-Options," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options
- 4.
K. Moore, "Character Set and Language Encoding for HTTP Header Field Parameters," RFC 5987, IETF, September 2009. https://www.rfc-editor.org/rfc/rfc5987.html
- 5.
"PutObject," Amazon S3 API Reference, docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
The server is likely sending Content-Type: application/octet-stream, which browsers treat as a forced download. Identify the correct MIME type for the format and configure the server to send it. For PDF files, use application/pdf. For images, use image/jpeg, image/png, or the appropriate type. Only use application/octet-stream when the format is genuinely unknown.
Yes, with one additional header. Add X-Content-Type-Options: nosniff alongside application/octet-stream to prevent browsers from overriding your declared type by sniffing the file content. Without nosniff, some browsers may detect and render HTML or SVG content as the detected type rather than the declared type, creating a cross-site scripting vector.
The browser throws a hard compile error when attempting to load the module via WebAssembly.instantiateStreaming(). The error message states that the server does not serve wasm with application/wasm MIME type. Unlike most MIME type mismatches, this one has no browser tolerance or fallback. You must use application/wasm for WebAssembly files.
You can, but application/zip is the correct type. Both types trigger a download in most browsers. Using application/zip gives browsers, proxies, and CDNs accurate information about the content, which matters for compression decisions (ZIP is already compressed, so CDNs should not re-compress it) and for tools that inspect content types.
Most CDNs skip compression for application/octet-stream because binary content is not predictably compressible. This is the correct behaviour for truly unknown binary data. However, if you are inadvertently serving compressible content like JSON as application/octet-stream, you lose automatic compression. Always use the specific MIME type to let CDNs make the right compression decision. CapyToolkit's MIME reference provides the correct Content-Type for every format so you can replace generic types with specific ones that CDNs handle optimally.
image/webp
image/webp delivers smaller files than JPEG and PNG at equivalent quality. Developed by Google and registered with IANA, the WebP format uses both lossy and lossless compression modes and supports transparency and animation.1 Browser adoption started with Chrome 23 in 2012 and remained limited to Chromium-based browsers for years.
Firefox added support in version 65 (2019) and Safari in version 14 (2020), making WebP effectively universal among modern browsers. Consequently, serving WebP without a fallback now reaches nearly all users. The picture element provides a clean fallback mechanism for the small percentage of users on older browsers that still need JPEG or PNG. Nginx and Apache both include image/webp in their bundled MIME type tables in recent versions, but installs predating 2020 may require an explicit entry. Cloudflare Polish converts JPEG and PNG to WebP automatically based on the incoming Accept header.
What is image/webp?
RIFF container with a WEBP marker followed by either a lossy (VP8), lossless (VP8L), or extended (VP8X) bitstream. Magic bytes for WebP detection are: 52 49 46 46 at offset 0 and 57 45 42 50 at offset 8.2 No charset parameter applies. Both animated WebP and static WebP use the same MIME type.Browser support timeline and reach
WebP support arrived in three distinct waves. Google Chrome added support in version 23 (November 2012), followed by other Chromium-based browsers. Opera added support shortly after Chrome. Firefox held out until version 65 in January 2019, citing concerns about the royalty situation of the VP8 codec.3 Safari delayed the longest, adding WebP support in version 14 alongside the release of macOS Big Sur and iOS 14 in September 2020.3
iOS browser engine unification
Furthermore, the iOS browser restriction that requires all browsers to use the WebKit rendering engine means that Safari's adoption of WebP effectively enabled WebP on all iOS browsers simultaneously. Building on this history, serving WebP without a JPEG or PNG fallback currently reaches the vast majority of web users, with the remaining gap concentrated in users who have not updated their browsers in several years.
picture element fallback pattern
The picture element provides graceful WebP delivery with automatic fallback for browsers that lack native WebP decoding support, ensuring that every visitor sees an image regardless of their browser version. Place a source element with type="image/webp" before the img element, which serves as the universal fallback that all browsers can render. Browsers that support WebP select the source element and ignore the img; older browsers skip the unrecognised source type and fall through to the img src. The srcset attribute on source elements supports resolution descriptors and width descriptors for responsive image delivery, letting you serve different pixel densities within each format.
Content negotiation via Accept header
An alternative to the picture element is server-side content negotiation, which moves the format-selection logic from the HTML markup into the server or CDN layer. When the Accept request header includes image/webp, the server can respond with a WebP file even when the URL ends in .jpg, keeping your HTML clean and your image URLs format-agnostic. This approach is transparent to the HTML markup but requires server-side logic or CDN format negotiation. Cloudflare Polish applies this pattern automatically, inspecting the Accept header and serving WebP to supporting browsers without any markup changes on your end.
Nginx, Apache, and Cloudflare configuration
Modern Nginx releases include image/webp in the bundled mime.types file for the .webp extension. Verify the active configuration with grep "webp" /etc/nginx/mime.types; if the line is absent, add "image/webp webp;" inside a types {} block and reload. Apache 2.4's default mime.types database includes image/webp in versions shipped after 2020. For earlier versions, add AddType image/webp .webp to httpd.conf or an .htaccess file. Cloudflare Polish, available on Pro and Business plans, automatically converts eligible images to WebP for browsers that send Accept: image/webp in the request. Consequently, you can delegate WebP conversion and delivery entirely to Cloudflare rather than maintaining two separate image assets. Furthermore, Cloudflare sets the correct Content-Type header on converted responses automatically.
WebP in CSS with image-set() and background-image
CSS background images cannot use the picture element for format selection because the picture element only creates a context for img elements, not for CSS properties that reference URLs. The image-set() function fills this gap, letting you list multiple format alternatives inside a background-image declaration so the browser can choose the most efficient format it supports. Browsers select the first format they support from the list, evaluating each type() hint against their internal list of decodable image formats before issuing the HTTP request. Chrome and Firefox support image-set() with type() hints widely; Safari added type() hint support in Safari 17.2.4 For older Safari versions, the most reliable fallback is stacking a plain JPEG url() declaration before the image-set() declaration: browsers that cannot parse image-set() skip it and apply the plain declaration instead.
The syntax for background WebP delivery is: background-image: image-set(url("image.webp") type("image/webp"), url("image.jpg") type("image/jpeg")). Browsers evaluate the type() hints against their supported format list and request the winning format, which means a WebP-capable browser fetches the .webp URL while an older browser fetches the .jpg URL from the same CSS declaration. The server must return Content-Type: image/webp on the WebP file for the hint selection to function correctly, because a mismatched Content-Type causes some browsers to reject the response even though the type() hint indicated the format was supported.
The -webkit- prefix for older mobile Safari
Safari required the -webkit-image-set() prefix before unprefixed support shipped, and Mobile Safari on iOS 15 and earlier understands only the prefixed form, which means sites targeting older iOS devices must include both declarations to cover the full range of supported browsers. For sites with meaningful legacy iOS traffic, declare -webkit-image-set() before the standard image-set() in the property value so that older Safari versions pick up the prefixed version while newer ones use the standard form. Autoprefixer handles this transformation automatically in most build pipelines. For new projects targeting Safari 17.2 and later, the unprefixed form is sufficient.
The same prefix consideration applies to other CSS functions that gained type() hint support only recently, so a broad stylesheet using cutting-edge image features should include both prefixed and unprefixed declarations to cover older engines. Autoprefixer handles this automatically during the build, but verifying the output in the deployed CSS confirms the fallback is present. CapyToolkit's MIME reference documents the correct image/webp Content-Type so the server side of this delivery stays correct.
Try in the tool
Open the MIME Type Reference tool pre-filled to image/webp to verify it or try a different one.
Check image/webp in the tool →- 1.
J. Zern, Ed., "WebP Image Format," RFC 9649, IETF, May 2024. https://www.rfc-editor.org/rfc/rfc9649.html
- 2.
Google Developers, "WebP Container Specification," developers.google.com, accessed June 2026. https://developers.google.com/speed/webp/docs/riff_container
- 3.
"WebP," Can I use, caniuse.com, accessed June 2026. https://caniuse.com/webp
- 4.
Mozilla Developer Network, "image-set()," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/CSS/image-set
For modern browsers, WebP support is near-universal since Safari 14 shipped in 2020. However, users on very old browsers without WebP support will see broken images. Use the picture element with a JPEG or PNG fallback in the img tag to cover this case without serving larger files to modern browsers.
Lossy WebP uses VP8 encoding and is suited to photographs where some quality loss is acceptable in exchange for smaller file size. Lossless WebP preserves every pixel exactly and is suited to logos, icons, and screenshots. Both use image/webp as the MIME type. The encoding mode is declared inside the file container, not in the Content-Type header.
In Nginx versions shipped after approximately 2020, yes. Earlier versions may be missing the entry in mime.types. Run grep "webp" /etc/nginx/mime.types to check. If absent, add "image/webp webp;" in a types {} block, run nginx -t, and reload with nginx -s reload.
Yes. When a browser sends Accept: image/webp in the request, the server or CDN can respond with a WebP file. This approach requires server-side content negotiation or CDN-level format selection. The response must carry Content-Type: image/webp. Cloudflare Polish and Imgix handle this automatically without changes to your HTML.
Yes to both. Lossless WebP supports full alpha transparency like PNG. Animated WebP supports frame-by-frame animation like GIF but with much smaller file sizes. All variants use the same image/webp MIME type. The extended format (VP8X) handles both transparency in lossy images and animation. CapyToolkit's MIME reference confirms image/webp as the single MIME type for all WebP variants, whether static, animated, or lossless.
image/avif
When a photograph looks sharp at 40 KB but the JPEG equivalent needs 120 KB at the same visual quality, the format responsible is almost certainly AVIF. image/avif is the most compressed modern image format for the web, based on the AV1 video codec's intra-frame encoding technology defined by the Alliance for Open Media. It achieves perceptually equivalent quality to JPEG and WebP at smaller file sizes particularly at low bitrates1, and it also supports HDR, wide colour gamut, and both lossy and lossless modes.
Browser support arrived later than WebP: Chrome added AVIF in version 85 (2020), Firefox in version 93 (2021), and Safari in version 16 (2022).2 Consequently, the picture element fallback to WebP or JPEG remains important for users on older Safari versions. Encoding is slower than WebP, which affects build pipelines that convert images during deployment. CDN-level format negotiation via Cloudflare Polish, Imgix, and Next.js Image handles the conversion and MIME type assignment automatically.
What is image/avif?
AV1 bitstream. The format supports still images, image sequences, and auxiliary images such as depth maps. Both high-dynamic-range and standard-dynamic-range content use image/avif. No charset parameter applies. The magic bytes pattern for AVIF detection involves the ftyp box at the start of the file container.AV1 compression and encoding trade-offs
AVIF uses AV1 intra-frame encoding, the same algorithm used in AV1 video but applied to still images. At low bitrates, AVIF preserves perceptual sharpness better than JPEG, avoiding the blocky artefacts that appear in heavily compressed JPEG files. At equivalent file sizes, AVIF and WebP both outperform JPEG on photographic content.
Build pipeline speed versus compression trade-off
The practical trade-off for build pipelines is encoding speed: AV1 encoding is computationally intensive compared to JPEG or WebP encoding, meaning that converting large image libraries to AVIF during a build step adds significant time. Consequently, many teams delegate AVIF conversion to CDN-level tooling rather than maintaining pre-converted AVIF files in the repository. Furthermore, AVIF supports HDR content and wide colour gamut, making it the only web format capable of displaying the full colour range of modern HDR displays without conversion.
Browser support matrix and picture element fallbacks
Chrome 85 (August 2020) added AVIF support as the first major browser. Firefox followed with version 93 in October 2021. Safari 16, released with macOS Ventura and iOS 16 in September 2022, added AVIF support. Older Safari versions including Safari 15 require a WebP or JPEG fallback, which matters because Mobile Safari holds a significant share of page views and those devices may not receive major OS updates for years after release.
picture element ordering for AVIF delivery
In a picture element, place the AVIF source first, followed by WebP, then the img fallback.3 Browsers iterate through the source elements in document order and select the first format they support, so AVIF-capable browsers pick the most compressed version while WebP-capable browsers skip AVIF and receive WebP, and the remainder fall through to the img element with JPEG or PNG. Each source element specifies type="image/avif" or type="image/webp" to enable this selection mechanism without any JavaScript or server-side detection logic. Reversing the order would cause AVIF-capable browsers to receive the larger WebP file, defeating the purpose of format negotiation.
CDN and build-tool support for automatic AVIF delivery
Cloudflare Polish, on Pro and Business plans, converts eligible images to AVIF for browsers that send Accept: image/avif in the request. Cloudflare sets the correct Content-Type: image/avif header on converted responses automatically. Imgix appends f=auto to image URLs to enable automatic format selection including AVIF for supporting browsers. The Next.js Image component serves AVIF when the requesting browser supports it, leveraging the Accept header to select the format. Vite provides AVIF conversion plugins that generate both AVIF and WebP versions during the build step. Building on this, the simplest path to AVIF delivery for most projects is enabling CDN-level format negotiation rather than managing two sets of image assets manually, since the CDN handles conversion, caching, and Content-Type header assignment.
Encoding AVIF files with Sharp and controlling quality settings
Generating AVIF files for web delivery requires an encoder that wraps the libaom or libavif library. Sharp, the most widely used Node.js image processing library, added AVIF output via its .avif() method in version 0.27. Calling sharp(input).avif({ quality: 60 }).toFile('output.avif') produces a lossy AVIF file at quality 60 on a 1-to-100 scale. Sharp's AVIF quality values are not equivalent to JPEG quality values because the underlying AV1 compression algorithm differs significantly. A quality of 60 in AVIF frequently produces smaller files at comparable perceptual quality to JPEG quality 80.4
Encoding speed is the main operational constraint for AVIF pipelines. AV1 encoding at high-quality settings runs significantly slower than JPEG or WebP encoding. Sharp exposes an effort parameter ranging from 0 to 9 that controls the speed versus compression trade-off: lower effort encodes faster with slightly larger output, while the default effort of 4 provides a usable balance for most build pipelines.
Validating AVIF output with curl and DevTools
After generating AVIF files, confirm the server returns Content-Type: image/avif using curl -sI against the file URL. In Chrome DevTools, filter the Network panel to Img requests and check the Type column to confirm AVIF files are loading and not silently falling back to JPEG. An incorrect MIME type causes the picture element to serve the JPEG fallback without any visible error, making the curl check necessary for verifying delivery.
A second useful check is to confirm the file size actually beat the JPEG equivalent at the same perceptual quality, since a misconfigured encoder can produce an AVIF that is both larger and visually worse than the fallback. Comparing the byte counts in your build output catches this regression before it ships to users. Keeping the AVIF source generation in CI also ensures the verification step runs on every image rather than only on the sample you tested manually.
Try in the tool
Open the MIME Type Reference tool pre-filled to image/avif to verify it or try a different one.
Check image/avif in the tool →- 1.
Alliance for Open Media, "AV1 Image File Format (AVIF) v1.2.0," aomedia.org, November 2025. https://aomediacodec.github.io/av1-avif/v1.2.0.html
- 2.
"AVIF," Can I use, caniuse.com, accessed June 2026. https://caniuse.com/avif
- 3.
Mozilla Developer Network, "image-set()," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/CSS/image-function
- 4.
web.dev, "Compress images with AVIF," web.dev, 2023. https://web.dev/articles/compress-images-avif
Chrome 85+, Firefox 93+, and Safari 16+ support AVIF. Users on Safari 15 or earlier require a WebP or JPEG fallback. Use the picture element with type="image/avif" on the source element and a JPEG or PNG img fallback to serve AVIF to supporting browsers while degrading gracefully for others.
AVIF generally achieves smaller files at equivalent quality, especially at low bitrates where JPEG and WebP produce artefacts. However, AVIF encoding is slower and browser support is slightly narrower. For projects that can use CDN-level format conversion, serving AVIF to supporting browsers with WebP fallback is the optimal strategy.
Not in most installed versions. The entry was absent from many Linux distribution packages for years. Add "image/avif avif;" inside a types {} block in nginx.conf, run nginx -t, and reload. Verify with curl -sI https://your-host/image.avif and confirm Content-Type: image/avif in the response.
Yes, but without the picture element fallback mechanism. Use CSS image-set() to provide format alternatives in background-image declarations. Browser support for image-set() with AVIF mirrors the AVIF image support timelines. For broader compatibility, specify a WebP alternative in image-set() as well.
image/avif covers both still images and sequences in the specification, but IANA has also registered image/avif-sequence for animated AVIF files. In practice, most browsers and tools treat both still and animated AVIF files as image/avif. The image/avif-sequence registration exists for formal completeness and is rarely used in HTTP responses. CapyToolkit's MIME reference lists both registrations so you can choose the appropriate type for your content.
image/svg+xml
An SVG file can execute JavaScript, load external resources, and access the parent document's cookies, or it can be completely sandboxed with no scripting capability at all. The difference depends entirely on how the browser loads it, and the MIME type image/svg+xml is the starting point for both paths. Defined in the W3C SVG specification and registered with IANA, it signals that a response body contains XML-formatted vector graphics.1
SVG's XML nature makes its security model more complex than raster image formats: SVG files can contain script elements, event handlers, external resource references, and foreignObject elements that embed HTML. Inline SVG in an HTML page shares the document's origin and JavaScript context, while an SVG loaded as an external resource in an img element is sandboxed with no script execution.2 This difference drives most of the access control decisions around SVG serving. Furthermore, CORS headers are required for cross-origin SVGs loaded in img elements in some browsers.
What is image/svg+xml?
RFC 7303 recommends omitting charset for UTF-8 XML types when the encoding is declared inside the document.3 The +xml suffix follows the structured syntax suffix registration in RFC 6838.Inline SVG versus external SVG security model
When you embed inline SVG directly in your HTML, it shares your parent document's origin and execution context. Scripts inside your inline SVG execute as if they are part of the surrounding HTML, with full access to your DOM and cookies. Event handlers on SVG elements fire in your document context. This is expected behaviour when you control the SVG content yourself, but it becomes a serious security risk if you render user-supplied SVG inline without sanitising it first.
img sandboxing for untrusted SVG
When you load an external .svg file through an img element, your browser sandboxes it: your scripts inside the SVG do not execute, external resources cannot be loaded, and the SVG cannot access your parent document. Consequently, img is the safe choice for you when displaying SVG from untrusted sources. The foreignObject element inside SVG can embed HTML content, but your browser applies script sandboxing to foreignObject content regardless of whether you load the SVG externally or inline.
Serving SVG with correct headers
Both Nginx and Apache include image/svg+xml for .svg files in their bundled MIME type tables, so your server likely already serves SVG with the correct type out of the box. The charset parameter in Content-Type is optional for UTF-8 SVG; the IANA guidance in RFC 7303 says you can omit it when the XML encoding declaration inside your file already declares UTF-8. Adding charset=utf-8 is harmless but adds header bytes you do not need. You should always include X-Content-Type-Options: nosniff when serving SVG to prevent your browser from treating misidentified SVG as HTML.4
CORS for cross-origin SVG
When you load SVG files cross-origin in img elements, you need Access-Control-Allow-Origin on the response, particularly when you draw the SVG onto a canvas element. Canvas operations that draw a cross-origin SVG image treat your canvas as tainted unless the SVG response includes the appropriate CORS header, which blocks subsequent toDataURL and getImageData calls. Set Access-Control-Allow-Origin: * on SVG files served from a CDN if you need to draw them onto canvas elements or apply CSS filters that reference the image data.
SVG in img vs CSS background-image vs inline
You have three rendering methods for SVG, and each one behaves differently. When you use an img element, your browser sandboxes the SVG, preventing script execution and external resource loading. When you use CSS background-image, your browser applies the same sandboxing as img, though SVG animations using SMIL continue to work in most browsers with this delivery method.
Inline SVG gives you full access to your document and supports all SVG features including scripting, animations, filters, and gradients that reference definitions inside the same document. From a performance perspective, inline SVG saves you an HTTP request, which benefits above-the-fold icons and logos on your pages. Building on this, when you serve external SVG via img or background-image, your browser caches the file and you can reuse it across multiple pages without embedding the markup in each HTML document. You should choose inline for interactive or animated SVGs that require script access and external for static decorative graphics where caching matters most.
Optimising SVG files with SVGO before delivery
Your SVG files are text-based XML and compress well under gzip and Brotli, but when you reduce file size at the source level before applying HTTP-level compression, you get significantly smaller starting files that compress even further. SVGO is a Node.js command-line tool you can use to remove redundant elements, collapse path data into more efficient curve commands, strip editor metadata from tools like Illustrator and Figma, and eliminate unnecessary attributes that add bytes without affecting your visual output.
When you run npx svgo with the multipass flag enabled, it applies all optimisation passes repeatedly, which is important because each pass can reveal new optimisation opportunities that a single pass misses. For example, when you remove a group element on pass one, it may expose redundant path data that pass two can then collapse. The output from SVGO is valid SVG that renders identically to your original in all browsers, so the optimisation is a safe, lossless transformation.
When you then configure your server to apply Brotli or gzip compression to image/svg+xml responses, you capture the HTTP-level compression benefit on top of your SVGO-reduced source size, giving you two layers of size reduction that compound for significant bandwidth savings. When you use Nginx, you can enable gzip for SVG by adding image/svg+xml to the gzip_types directive. You can verify your compression setup with curl -sI against your SVG URL and check for the Content-Encoding header. If you use Cloudflare, Brotli compression for SVG responses is applied automatically without any configuration on your part.
Integrating SVGO into a build pipeline
You can integrate SVGO into your build pipeline through plugins for Vite, webpack, and Rollup. The vite-plugin-svgr package applies SVGO optimisation when you process SVG imports and the svgo option is enabled in your plugin configuration. When you run SVGO as a pre-commit hook, it prevents unoptimised SVGs from entering your repository. For icon sets, you should optimise each SVG before combining them into an SVG sprite to reduce the total sprite file size proportionally, which matters for sprites that bundle hundreds of icons.
The optimisation savings compound when SVGs are inlined into JavaScript bundles, because a large raw SVG embedded in a component adds to the parsed script weight on every page load. Running SVGO before inlining shrinks both the source file and the bundle it feeds. Pairing the build step with gzip or Brotli on the server layer keeps the two compression strategies working together rather than competing. CapyToolkit's MIME reference confirms image/svg+xml as the correct Content-Type so the served file matches the optimised markup.
Try in the tool
Open the MIME Type Reference tool pre-filled to image/svg+xml to verify it or try a different one.
Check image/svg+xml in the tool →- 1.
W3C, "Scalable Vector Graphics (SVG) 1.1," w3.org, accessed June 2026. https://www.w3.org/TR/SVG11/
- 2.
Mozilla Developer Network, "SVG: Image and Multimedia," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/SVG
- 3.
J. K. Arnhold and C. Malamane, "Update to MIME Type Registration for image/svg+xml," RFC 7303, IETF, July 2014. https://www.rfc-editor.org/rfc/rfc7303.html
- 4.
Mozilla Developer Network, "X-Content-Type-Options," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options
The SVG may reference external resources like fonts or images that are blocked by the img sandboxing model. Inline SVG loads external resources because it shares the document origin. Check the browser console for blocked resource errors when loading the SVG in an img element, and move any external dependencies to data URIs or inline them in the SVG.
Only when sandboxed. Never render user-supplied SVG inline in your HTML, as it executes scripts in your document context. Use an img element, which sandboxes the SVG and prevents script execution. Better still, sanitise SVG uploads with a library that strips script elements and event handlers before storage, since even sandboxed SVG can contain misleading visual content.
No, not for UTF-8 content. RFC 7303 recommends omitting charset when the XML encoding declaration inside the file already specifies UTF-8. Including charset=utf-8 is harmless but adds unnecessary bytes. If the SVG file is encoded in ISO-8859-1 or another non-UTF-8 encoding, the charset parameter is required for correct rendering.
Drawing a cross-origin image onto a canvas taints the canvas, blocking toDataURL() and getImageData(). To avoid taint, the SVG server must send Access-Control-Allow-Origin: * (or the requesting origin), and the img element must carry the crossorigin="anonymous" attribute. Both conditions are required: the CORS header alone is insufficient without the attribute.
SMIL animations work in img, background-image, and inline SVG in most modern browsers, though Chrome deprecated SMIL at one point before reversing the decision. CSS animations inside SVG work everywhere. JavaScript-driven animations only work in inline SVG. For reliable cross-browser animation, use CSS animations inside the SVG or use inline SVG with JavaScript for interactive animations. CapyToolkit's MIME reference confirms image/svg+xml as the correct type regardless of how the SVG is delivered.
video/mp4
A user clicks play on your video, and nothing happens. The file downloaded completely, but the player shows a spinner instead of the first frame. The cause is often a missing moov atom at the start of the MP4 file, or a server that does not support byte-range requests. Both issues are invisible without testing.
video/mp4 is the most compatible video format on the web, registered with IANA and defined by ISO/IEC 14496-12. H.264 video in an MP4 container has universal browser support across all modern platforms.1 H.265 offers better compression but is limited to Safari, and AV1 inside MP4 provides open-codec efficiency with growing support. Byte-range request support on the server is critical: without it, browsers download the entire file before seeking. CORS headers are required for cross-origin video loaded via Media Source Extensions.
What is video/mp4?
ISO/IEC 14496-12 (MPEG-4 Part 12), which specifies the ISO Base Media File Format. The container carries video, audio, subtitles, and metadata tracks. The MIME type does not specify which codec is used; codec information lives inside the file or can be expressed in a codecs parameter in the Content-Type header. Common codec parameter values include codecs="avc1.42E01E" for H.264 baseline profile and codecs="av01.0.08M.08" for AV1.MP4 container and codec combinations
The MP4 container separates the container format from the video codec, meaning video/mp4 describes the wrapper, not the encoding algorithm inside it. H.264 (AVC) in an MP4 container delivers the broadest browser support: every modern browser on desktop and mobile plays H.264 MP4 without plugins. H.265 (HEVC) achieves roughly double the compression efficiency of H.264 but is supported only in Safari on Apple hardware, due to patent licensing requirements that prevent Firefox and Chrome from including the decoder.
AV1 and the codec string for Media Source Extensions
AV1, the open and royalty-free alternative, provides compression efficiency comparable to H.265 and is supported in Chrome and Firefox with growing support in Safari. Furthermore, mixing container and codec information matters for Media Source Extensions: addSourceBuffer() requires specifying both the container and codec string, such as 'video/mp4; codecs="avc1.42E01E"', to select the correct decoder. The codec string encodes the profile, level, and constraints in a compact format that the MSE implementation maps to a specific decoder configuration, which means an incorrect codec string causes the browser to reject the source buffer entirely rather than attempting to decode with the wrong parameters.2
Byte-range requests for seeking
Browser video players seek by requesting specific byte ranges of the video file rather than downloading sequentially. The Range request header and 206 Partial Content response enable this behaviour.3 Servers must support byte-range requests for usable video seeking, or the browser downloads the entire file before the seek operation can complete, which produces an unresponsive scrub bar and frustrated viewers on long-form content.
Configuring nginx for byte-range delivery
Nginx supports byte-range requests by default for static files. Apache also supports them natively. Problems arise when a reverse proxy or application server intercepts video requests and strips Range headers, or when the server responds with 200 OK instead of 206 Partial Content for range requests. Use curl -r 0-1 -sI https://example.com/video.mp4 to test whether your server correctly returns a 206 response with the Content-Range header for byte-range requests. A correct response includes Content-Range: bytes 0-1/1234567, where the final number represents the total file size in bytes, confirming the server acknowledged the range and reported the complete resource length.
Preload, Content-Length, and CORS for video
The preload attribute on the video element controls how much data the browser fetches before playback. preload="none" fetches nothing; preload="metadata" fetches enough to show duration and dimensions; preload="auto" fetches the full file. Each level increases bandwidth consumption for users who never play the video. Browsers use the Content-Length header to show a progress bar during buffering; without it, the progress indicator cannot display fill percentage. Cross-origin video loaded via the Media Source Extensions API requires Access-Control-Allow-Origin on the video server. Building on this, video loaded directly in a video element with a src attribute does not trigger CORS for playback, but canvas operations that draw from a cross-origin video element require CORS headers and the crossorigin attribute on the video element.
MP4 faststart and moov atom positioning for instant playback
The moov atom in an MP4 file contains all the metadata the browser needs to begin playback: frame count, duration, codec parameters, and the sample table mapping each frame to its byte offset in the file. When the moov atom sits at the end of the file (the default output for most encoding tools), the browser must download the entire file before reading the metadata and initiating playback. Moving the moov atom to the beginning of the file, known as faststart, allows the browser to start playing immediately after downloading the metadata header section.4
FFmpeg applies faststart with a single output flag: ffmpeg -i input.mp4 -movflags faststart -acodec copy -vcodec copy output.mp4. The -acodec copy and -vcodec copy flags repackage the file without re-encoding, which completes in seconds regardless of video duration. The output file is functionally identical to the input; only the atom order changes. MP4Box and qt-faststart are alternative tools that perform the same operation.
Verifying moov atom position before deployment
Run ffprobe -v quiet -print_format json -show_format input.mp4 and check the reported start time and format metadata. Alternatively, open the file in Chrome DevTools Network panel and observe whether the video element begins playback before the full file downloads. An MP4 without faststart applied shows significant buffering delay before the first frame renders, even over a fast connection, which confirms the moov atom is positioned at the end.
A quick local equivalent of the ffprobe check is to inspect the first bytes of the file for the moov box before upload, because once the file is distributed to a CDN the metadata order cannot be fixed at the edge. Repackaging with faststart during the build step removes the dependency on manual verification for every release. CapyToolkit's MIME reference documents the correct video/mp4 Content-Type so the streaming server delivers the container with the type browsers expect.
Try in the tool
Open the MIME Type Reference tool pre-filled to video/mp4 to verify it or try a different one.
Check video/mp4 in the tool →- 1.
ISO, "Information technology — Coding of audio-visual objects — Part 12: ISO base media file format," ISO/IEC 14496-12, ISO, 2015. https://www.iso.org/standard/68960.html
- 2.
Mozilla Developer Network, "Media Source Extensions," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API
- 3.
R. Fielding, Ed., and J. Reschke, Ed., "Hypertext Transfer Protocol (HTTP/1.1): Range Requests," RFC 7233, IETF, June 2014. https://www.rfc-editor.org/rfc/rfc7233.html
- 4.
Mozilla Developer Network, "Preload," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video
The server is likely not supporting byte-range requests correctly. Test with curl -r 0-1 -sI https://your-video-url.mp4 and look for a 206 Partial Content response with Content-Range in the headers. A 200 OK response to a range request indicates the server is ignoring the Range header, forcing the browser to download the full file before seeking.
H.264 (AVC) provides universal browser support across desktop and mobile. Use the baseline or main profile for maximum compatibility. For better compression with full browser support, serve both an H.264 MP4 and a WebM file with AV1 or VP9, letting the browser pick via the source element type attribute.
Yes, but audio/mp4 is the more accurate MIME type for MP4 files that contain only audio tracks. Browsers handle video/mp4 for audio-only content correctly in most cases, but using audio/mp4 gives clients accurate content-type information and may affect how audio players and download managers identify the file.
Drawing a cross-origin video frame onto canvas taints the canvas unless CORS is configured. The video server must send Access-Control-Allow-Origin with a value matching the requesting origin, and the video element must carry the crossorigin="anonymous" attribute. Both are required. Without both, the canvas becomes tainted and toDataURL() and getImageData() throw security errors.
With preload="metadata", the browser fetches the beginning and sometimes the end of the video file to extract duration, dimensions, and codec information. This usually amounts to a few kilobytes to a few hundred kilobytes depending on where the moov atom (metadata container) is positioned in the MP4 file. Moving the moov atom to the beginning of the file, known as faststart, reduces the metadata fetch size. CapyToolkit's MIME reference lists video/mp4 as the standard type to pair with preload attributes for correct video delivery.
font/woff2
font/woff2 is the preferred web font format. Defined in RFC 8081, WOFF2 applies per-table Brotli compression to font data, achieving significant size reductions over the older WOFF 1.0 format and over raw TTF or OTF files served over HTTP.1 Every modern browser has supported font/woff2 since around 2016, making it the only font format worth serving in a modern CSS @font-face declaration for most projects.
Two operational concerns dominate font/woff2 serving: CORS enforcement and MIME type correctness. Browsers enforce CORS for fonts loaded from a different origin, requiring an Access-Control-Allow-Origin header on the font server. Unlike most CORS failures that appear in the browser console, cross-origin font failures are silent: no error message is shown, the font simply fails to load and the browser falls back to the CSS font stack. Consequently, verifying CORS configuration on fonts served from a CDN requires checking the Network panel rather than waiting for visible errors.
What is font/woff2?
font/woff2 is defined in RFC 8081, which registers the font/ top-level type and formalises the WOFF2 format's MIME type. WOFF2 uses the WOFF2 container format wrapping an SFNT font (TrueType or OpenType) with per-table Brotli compression. The format is defined by the W3C WOFF 2.0 specification. No charset parameter applies. The font/ top-level type was formally registered in RFC 8081, replacing earlier experimental application/ registrations for font formats.CORS enforcement for cross-origin fonts
Browsers enforce CORS for fonts loaded from a different origin than the page.2 When a CSS @font-face rule references a font URL on a CDN or separate domain, the browser sends an Origin header with the font request. The font server must respond with Access-Control-Allow-Origin set to either the specific origin or * for the font to load. This check applies regardless of whether the font is served with the correct MIME type.
Silent failure detection via the Network panel
Failing both conditions produces a silent failure: the browser drops the font, no console error appears, and the page renders with its fallback font stack. Detecting this requires checking the Network panel for the font request and looking for a CORS error in the request headers. Consequently, deploying fonts to a CDN always requires verifying both the Content-Type header and the Access-Control-Allow-Origin header before considering the configuration complete.
Brotli compression inside the WOFF2 container
WOFF2 applies Brotli compression at the table level within the container, compressing each font table independently.3 This per-table approach is more efficient than applying gzip to the entire font file because different font tables have different data characteristics. Glyph outlines compress well with Brotli; font metric tables compress differently. The result is a significant qualitative improvement in file size compared to WOFF 1.0, which uses zlib compression, and over uncompressed TTF or OTF files. Building on this, you should not apply additional HTTP-level compression to WOFF2 files when serving them. Configuring gzip or Brotli compression on the server for .woff2 files adds a second compression pass over already-compressed data, producing no size benefit and wasting CPU cycles. Most server and CDN configurations exclude font/woff2 from HTTP compression for this reason.
WOFF2 versus WOFF versus TTF versus OTF in @font-face
The CSS @font-face src descriptor accepts multiple format hints, letting the browser select the most efficient format it supports. For modern browsers, WOFF2 is the only format worth including for web delivery. WOFF 1.0 (font/woff) covered the gap before WOFF2 had broad support, but that gap closed around 2016. TTF and OTF are installation formats not optimised for HTTP delivery; they lack the compression applied by WOFF2. Including TTF in a @font-face declaration as a fallback adds bandwidth cost with no benefit for any browser that supports WOFF2.
Legacy IE support with EOT
Internet Explorer required the EOT format (application/vnd.ms-fontobject) instead of WOFF or WOFF2, using a proprietary compression method that no other browser ever adopted. Since IE is end-of-life and its market share is negligible, including EOT in @font-face src declarations is no longer justified for new projects and only adds dead weight to your stylesheets. Removing legacy font format entries from @font-face rules reduces HTTP requests, simplifies configuration, and eliminates a MIME type that serves no purpose for any currently supported browser.
Preloading WOFF2 files with link rel=preload
Font preloading with <link rel="preload"> instructs the browser to fetch the font file early in the page lifecycle, before the CSS parser discovers the @font-face rule. Without preloading, the browser encounters the font reference only after downloading and parsing the stylesheet, introducing a delay that produces Flash of Unstyled Text on slower connections. Adding <link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin> in the HTML <head> initiates the font download in parallel with other early resources, eliminating the FOUT window for preloaded fonts.
The crossorigin attribute is required even for same-origin fonts.4 Browsers fetch fonts with CORS semantics regardless of origin, and a preload request without crossorigin sends credentials that conflict with the anonymous request the browser makes when it later encounters the @font-face rule. A mismatched credentials mode causes the browser to download the font twice: once for the preload and once for the stylesheet reference.
Limiting preload to above-the-fold fonts
Preloading every font on a page increases the number of parallel high-priority requests and can delay other critical resources like render-blocking scripts. Preload only the fonts used by above-the-fold text in the first viewport. Fonts used only in footers, sidebars, or expandable sections load adequately through the normal @font-face mechanism with font-display: swap providing the fallback during the download window.
The font-display property on the @font-face rule interacts with preloading by controlling how long the fallback stays visible, so a preloaded font with font-display: optional may still swap in if it arrives within the swap window. Coordinating the display strategy with the preload set avoids both the flash of unstyled text and the wasted bandwidth of preloading fonts that never render. CapyToolkit's MIME reference confirms font/woff2 as the correct Content-Type for the preloaded file.
Try in the tool
Open the MIME Type Reference tool pre-filled to font/woff2 to verify it or try a different one.
Check font/woff2 in the tool →- 1.
W3C, "WOFF File Format 2.0," w3.org, accessed June 2026. https://www.w3.org/TR/WOFF2/
- 2.
Mozilla Developer Network, "font-face," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
- 3.
Mozilla Developer Network, "CORS," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- 4.
W3C, "WOFF File Format 1.0," w3.org, accessed June 2026. https://www.w3.org/TR/WOFF/
Cross-origin font requests require an Access-Control-Allow-Origin header from the CDN. Localhost requests go to the same origin, so CORS does not apply. Configure the CDN to send Access-Control-Allow-Origin: * (or the specific origin) for font files. Since font failures are silent, always verify the CDN configuration by checking the font request in the browser Network panel.
No. WOFF2 already applies Brotli compression internally. Applying HTTP-level gzip or Brotli on top produces no size benefit and wastes server CPU. Disable HTTP compression for font/woff2 in your server configuration. Most modern Nginx and Apache configs exclude WOFF2 from the list of compressible types for this reason.
Not for modern browsers. WOFF2 support is universal in browsers released after 2016, including all current versions of Chrome, Firefox, Safari, and Edge. If you need to support IE 11, include WOFF as a fallback. For projects targeting modern browsers only, a single WOFF2 format() hint in the src descriptor is sufficient.
The most likely cause is a CORS failure or an incorrect MIME type. Check the font request response headers in the Network panel. If Access-Control-Allow-Origin is missing and the font URL is on a different domain, that is the cause. If the Content-Type shows a wrong type, configure the server to serve font/woff2 for .woff2 files.
Yes. WOFF2 is supported in Chrome 36+, Firefox 39+, Safari 10+, and Edge 14+. For the vast majority of current web traffic, WOFF2 is the only font format you need to serve. Browsers that do not support WOFF2 will fall back to whichever other format you include as a second option in the @font-face src descriptor. CapyToolkit's MIME reference confirms font/woff2 as the standard type and lists every browser version that supports it.
multipart/form-data
multipart/form-data is required for HTML file uploads. Defined in RFC 7578 (which obsoletes RFC 2388), it encodes form fields and file attachments as a sequence of parts separated by a boundary string.1 The boundary parameter is automatically generated by the browser when an HTML form uses enctype="multipart/form-data" or when a fetch request is built using the FormData API. Setting Content-Type manually on a multipart request without including the boundary string breaks server-side parsing entirely. Consequently, the most common multipart debugging mistake is trying to set Content-Type: multipart/form-data manually in JavaScript, which overrides the browser's auto-generated boundary. The boundary must be unique and must match the string embedded between parts in the request body. Frameworks like Multer in Node.js, python-multipart, and Spring Boot's MultipartFile handle boundary parsing automatically when the incoming Content-Type is correct.
What is multipart/form-data?
multipart/form-data is defined in RFC 7578, which obsoletes RFC 2388. The type encodes a sequence of named parts, each with its own headers and body. The boundary parameter in the Content-Type header identifies the delimiter string that separates parts in the body. Each part carries a Content-Disposition: form-data header with the field name, and optionally a filename parameter for file parts. RFC 7578 specifies that the boundary must not appear inside any part's body. The encoding is binary-safe, making it suitable for file uploads alongside text fields.Boundary parameter and the common manual-setting mistake
The boundary parameter is a string that the browser generates automatically when submitting a multipart form.2 It appears in the Content-Type header as Content-Type: multipart/form-data; boundary=WebKitFormBoundaryXYZ123. The same string delimits every part in the request body. Setting Content-Type: multipart/form-data manually in a fetch or XMLHttpRequest call omits the boundary parameter, making the request unparseable by the server.
Boundary format and the manual header mistake
The browser includes the boundary only when it generates the Content-Type automatically from the FormData object. Consequently, never set Content-Type manually for multipart requests built with FormData. Delete any custom Content-Type header from the fetch options and let the browser derive it. Building on this, debugging a multipart parsing failure should start by checking whether the request headers contain a boundary value and whether it matches the actual delimiters in the body.
multipart/form-data versus application/x-www-form-urlencoded
HTML forms default to application/x-www-form-urlencoded for text fields and switch to multipart/form-data only when the form includes a file input or when enctype="multipart/form-data" is set explicitly. For text-only form submissions, urlencoded is more compact because it encodes only field values without multipart overhead headers and boundary strings. For binary data or file uploads, urlencoded is impractical: binary bytes require percent-encoding, which can triple the payload size.
When to choose each encoding
multipart/form-data is the correct choice for any form that includes file uploads, binary data, or mixed content where the payload contains non-text parts.3 application/x-www-form-urlencoded is the right default for login forms, search inputs, and other text-only fields because it produces a compact payload without boundary overhead. Some APIs that accept both formats for text fields parse urlencoded more efficiently, but file upload endpoints require multipart because urlencoded cannot safely encode binary data.
Parsing multipart bodies on the server
Server-side multipart parsing requires a library in most frameworks because the raw body is a binary stream interleaved with boundary strings. In Node.js, Multer is the standard middleware for Express, accepting file uploads to memory or disk storage via the upload.single(), upload.array(), and upload.fields() methods. Python projects use python-multipart with Starlette or FastAPI, or rely on Django's built-in multipart parser which handles file parts through request.FILES. In Java with Spring Boot, the @RequestParam annotation with MultipartFile type receives the uploaded file bytes. Consequently, the framework's multipart handling must be enabled and the correct Content-Type must arrive from the client. Furthermore, always set a maximum file size limit in the multipart parser to prevent denial-of-service through oversized uploads.
Streaming multipart uploads directly to cloud storage
Buffering uploaded files to local disk before transferring them to cloud storage doubles the I/O cost and increases peak memory usage. A more efficient pattern streams the multipart body directly to the storage destination as it arrives, without writing temporary files. Busboy, the underlying multipart parser that Multer wraps in Node.js, exposes each file as a readable stream.4 Piping this stream to an AWS SDK v3 S3 Upload call transfers the file to S3 as the client sends it, keeping server-side memory usage limited to the multipart boundary parsing overhead rather than the full file size.
The AWS SDK v3 S3 Upload class (from @aws-sdk/lib-storage) accepts a ReadableStream as the Body parameter. Constructing the Upload object inside Busboy's file event handler and calling upload.done() starts the transfer immediately. The done() promise resolves with the S3 response once the final chunk arrives. This approach works at any file size without adjusting Node.js memory limits.
Setting Content-Type on the S3 object from the upload
When streaming a multipart upload to S3, pass the ContentType parameter on the Upload constructor using the magic-byte-validated MIME type, not the client-supplied Content-Type from the multipart part header. Applying magic byte detection to the first chunk of the Busboy stream and passing the detected type to the S3 Upload constructor ensures the stored object carries an accurate MIME type that your server verified independently.
Relying on the client-supplied type would let a renamed file determine the stored format, which then propagates to every download and every browser that trusts the stored header. Validating against the first chunk keeps the check cheap while still catching the most common type-confusion attempts before the object lands in storage. Adding this step to the upload handler closes the gap between the multipart part header and the authoritative metadata the object carries.
Try in the tool
Open the MIME Type Reference tool pre-filled to multipart/form-data to verify it or try a different one.
Check multipart/form-data in the tool →- 1.
L. Masinter, "Returning Values from Forms: multipart/form-data," RFC 7578, IETF, May 2015. https://www.rfc-editor.org/rfc/rfc7578.html
- 2.
Mozilla Developer Network, "FormData," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/FormData
- 3.
Mozilla Developer Network, "Fetch," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
- 4.
Busboy, "Busboy README," github.com, accessed June 2026. https://github.com/mscdex/busboy
Setting Content-Type: multipart/form-data manually omits the boundary parameter that the browser generates automatically. Without the boundary, the server cannot parse the multipart body. Remove the Content-Type header entirely from your fetch options when using FormData; the browser will set the correct header including the boundary string automatically.
The boundary is a unique string that the browser generates to separate each part in the multipart body. It appears in the Content-Type header and as a delimiter in the request body. The same string must appear at the start of each part, and the final boundary has two trailing hyphens. RFC 7578 requires the boundary not appear inside any part body.
Yes. multipart/form-data is binary-safe and the correct encoding for all file uploads regardless of size. For very large files, use chunked upload strategies where the file is split into parts and each part is uploaded in a separate request, reassembled on the server. This reduces memory pressure and allows resumable uploads on connection failure.
Use FormData to build a multipart request. Append the JSON as a string field or a Blob with application/json type, and append the file using FormData.append(). The browser encodes everything as multipart/form-data with the correct Content-Type and boundary. The server reads the JSON field and the file field as separate parts of the same multipart body.
Yes. Create a FormData object, append fields and files using FormData.append(), and pass the FormData as the body of a fetch request. Do not set a Content-Type header manually. The browser derives the correct multipart Content-Type with the boundary parameter from the FormData object automatically. CapyToolkit's MIME reference documents the multipart/form-data type and its boundary parameter so you can verify your requests are formatted correctly.
text/event-stream
text/event-stream is the MIME type for Server-Sent Events. Defined in the WHATWG HTML specification's Server-Sent Events section, it signals that a response body is a persistent stream of text-formatted events pushed from the server to the client.1 Unlike WebSocket, SSE is strictly server-to-client: the browser cannot send data over an established SSE connection.
The protocol consists of plain text lines with field prefixes: data:, event:, id:, and retry:. A blank line terminates each event. The browser's EventSource API implements the SSE protocol, providing automatic reconnection using the Last-Event-ID header to resume a stream after a dropped connection.2 Consequently, SSE suits use cases like live dashboards, feed updates, and AI streaming responses where the client only needs to receive data. Setting the correct Content-Type is critical: browsers only apply the EventSource protocol to responses identified as text/event-stream.
What is text/event-stream?
text/event-stream is defined in the WHATWG HTML Living Standard, Server-Sent Events section. The format consists of UTF-8 encoded text lines. Field lines begin with a field name, a colon, an optional space, and the field value. The defined field names are data, event, id, and retry. A blank line (two consecutive newlines) dispatches the buffered event to the client. The MIME type has no charset parameter; UTF-8 is required by the specification.SSE protocol format and event fields
The SSE wire format is deliberately simple. Each event is a block of text lines followed by a blank line. The data: field provides the event payload; multiple consecutive data: lines are concatenated with a newline character in the event's data property. The event: field assigns a name, enabling the client to listen for specific event types using addEventListener() rather than the generic onmessage handler. The id: field sets the last event ID, which the browser includes in the Last-Event-ID header when reconnecting after a connection drop.
Named events versus the onmessage handler
The retry: field specifies the reconnection interval in milliseconds, overriding the default.3 Consequently, an event stream that sends id: values with every event enables the server to resume the stream at the correct position after a reconnection. Building on this, events without an event: field dispatch through the onmessage handler, while named events dispatch through their corresponding addEventListener listeners. This separation lets you route different event types to dedicated handler functions without writing conditional logic inside a single onmessage callback, keeping your SSE client code clean and maintainable as the number of event types grows.
Browser EventSource API
The EventSource API creates a persistent SSE connection to a server URL. new EventSource(url) opens the connection with a GET request carrying Accept: text/event-stream. The connection persists until the client calls close() or the page unloads. The readyState property reflects the connection state: 0 (CONNECTING), 1 (OPEN), or 2 (CLOSED). Automatic reconnection is built in: when the connection closes unexpectedly, the browser waits for the retry interval and re-requests the URL, including Last-Event-ID if an id was received. This reconnection happens without application code.
Authentication with SSE
EventSource does not support custom request headers, which complicates authentication because you cannot attach a Bearer token or API key directly to the SSE request. Workarounds include passing a token as a query parameter on the EventSource URL, using cookie-based authentication that the browser sends automatically with every request, or wrapping the EventSource in a fetch-based polyfill that reads the response as a ReadableStream and dispatches events manually.
SSE versus WebSocket versus long-polling
Server-Sent Events, WebSocket, and long-polling solve similar real-time problems with different tradeoffs. SSE is strictly server-to-client over HTTP, supports automatic reconnection, and works through standard HTTP proxies and load balancers that handle persistent connections. WebSocket is bidirectional over a separate protocol upgrade, supports both server-to-client and client-to-server messaging at lower overhead per message, and requires proxy and load balancer configuration for WebSocket upgrade support. Long-polling is a fallback pattern where the client makes a standard HTTP request and the server holds it open until data is available, then closes the connection. Consequently, SSE outperforms long-polling in overhead because a single persistent connection replaces repeated reconnects. Yet WebSocket outperforms SSE when the application needs bidirectional messaging. Furthermore, load balancers set default connection timeout values that may close idle SSE connections; sending a periodic comment line (a line beginning with :) prevents timeout-based drops.
Nginx proxy configuration for SSE connections
Nginx's default proxy configuration buffers upstream responses before forwarding them to clients. For SSE connections, buffering defeats the real-time purpose of the stream4: events arrive at clients in batches when the buffer flushes rather than immediately as the server emits them. Disabling proxy buffering for SSE endpoints requires setting proxy_buffering off inside the location block handling the SSE route. Without this directive, Nginx accumulates events and delivers them at unpredictable intervals regardless of the text/event-stream Content-Type.
The proxy_read_timeout directive controls how long Nginx waits for upstream data between writes before closing the connection. The default is 60 seconds. SSE connections often remain idle between events for longer than this on low-traffic streams, causing Nginx to close the connection and force a client reconnect. Setting proxy_read_timeout to 600s or higher keeps connections alive through quiet periods. Combining proxy_buffering off with a raised proxy_read_timeout correctly handles long-lived SSE connections through an Nginx reverse proxy.
HTTP/1.1 requirement between Nginx and the upstream
SSE requires HTTP/1.1 or HTTP/2 for persistent connections. Adding proxy_http_version 1.1 to the SSE location block forces HTTP/1.1 between Nginx and the upstream server. Without this directive, Nginx may downgrade the proxy connection to HTTP/1.0, which does not support persistent connections or chunked transfer encoding. A downgraded connection breaks SSE delivery silently, with no error visible in the browser and the connection appearing active until the upstream closes it.
The same downgrade risk affects any proxy hop in the path, so a misconfigured upstream behind Nginx can reintroduce the HTTP/1.0 problem even after the Nginx side is corrected. Tracing the connection protocol at each layer with curl -sI against the origin confirms where the downgrade enters. CapyToolkit's MIME reference documents the text/event-stream Content-Type so the endpoint advertises the stream type the EventSource client expects.
Try in the tool
Open the MIME Type Reference tool pre-filled to text/event-stream to verify it or try a different one.
Check text/event-stream in the tool →- 1.
WHATWG, "HTML Living Standard: Server-Sent Events," whatwg.org, accessed June 2026. https://html.spec.whatwg.org/multipage/server-sent-events.html
- 2.
Mozilla Developer Network, "EventSource," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/EventSource
- 3.
Mozilla Developer Network, "Using server-sent events," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
- 4.
Nginx, "Nginx Admin Guide," nginx.org, accessed June 2026. https://docs.nginx.com/nginx/admin-guide/
The browser will not apply the SSE protocol to the response. EventSource requires text/event-stream in the Content-Type header; any other type causes the connection to be treated as a plain text response rather than an event stream. The onmessage handler will never fire, and no events will be dispatched. Always verify the Content-Type header when debugging SSE issues.
Not natively via headers, because the EventSource API does not support custom request headers. Pass authentication tokens as a URL query parameter, use cookie-based authentication (cookies are sent automatically), or use a fetch-based EventSource polyfill that constructs the request differently. For server-to-client streaming in environments requiring custom auth headers, consider using the Fetch API with ReadableStream as an alternative to EventSource.
When an SSE connection drops, the browser waits for the retry interval (default 3 seconds, configurable via the retry: field) and re-requests the stream URL. If the server sent an id: field in any prior event, the browser includes Last-Event-ID in the reconnection request, allowing the server to resume the stream from that point rather than from the beginning.
Yes, but load balancers must be configured to allow persistent HTTP connections rather than timing them out. AWS ALB, Nginx proxy_pass, and HAProxy all have connection timeout settings that can close idle SSE connections. Send a periodic comment line (a line starting with a colon, such as ": heartbeat") from the server to prevent idle timeouts on load balancers and proxies.
Both deliver streamed tokens to the browser in real time. SSE is simpler to implement server-side because it uses plain HTTP with the text/event-stream MIME type and no protocol upgrade. OpenAI and Anthropic streaming APIs use SSE for token delivery. WebSocket requires a protocol upgrade and bidirectional handling but offers lower per-message overhead for very high-frequency streams. CapyToolkit's MIME reference lists text/event-stream as the standard type for all SSE implementations.