content-disposition and content-type are specialized, low-level Node.js packages designed to parse and format specific HTTP headers according to RFC standards. The content-disposition package handles the Content-Disposition header, which controls how browsers present responses (e.g., inline display vs. file download). The content-type package manages the Content-Type header, which specifies the media type of a resource (e.g., application/json, text/html). Both libraries provide robust, spec-compliant utilities for working with these headers in server-side JavaScript environments.
Both content-disposition and content-type solve focused problems in HTTP communication: they handle the parsing and formatting of two critical but distinct headers. While they share similar design philosophies — small, zero-dependency, spec-compliant utilities — they operate on entirely different parts of the HTTP protocol. Let’s examine how they differ in purpose, API, and real-world usage.
content-disposition works exclusively with the Content-Disposition header. This header tells browsers how to handle the response body — whether to display it inline (inline) or prompt a file download (attachment). It also carries filename information.
// content-disposition: Creating a download header
const contentDisposition = require('content-disposition');
const header = contentDisposition('report.pdf', { type: 'attachment' });
// Result: 'attachment; filename="report.pdf"'
content-type deals only with the Content-Type header. This header defines the media type of the resource (e.g., text/plain, application/json) and may include parameters like charset=utf-8.
// content-type: Parsing a Content-Type header
const contentType = require('content-type');
const parsed = contentType.parse('application/json; charset=utf-8');
// Result: { type: 'application/json', parameters: { charset: 'utf-8' } }
These packages never overlap in functionality — you’ll often use both in the same application, just for different tasks.
While both packages support parsing and formatting, their primary strengths differ based on typical usage patterns.
content-disposition shines when generating safe, RFC-compliant Content-Disposition headers, especially with filenames containing spaces or non-ASCII characters:
// content-disposition handles tricky filenames
const header = contentDisposition('résumé.txt');
// Result: 'inline; filename="resume.txt"; filename*=UTF-8\'\'r%C3%A9sum%C3%A9.txt'
content-type focuses on parsing existing Content-Type headers reliably, but can also format them:
// content-type formatting
const formatted = contentType.format({
type: 'text/html',
parameters: { charset: 'utf-8' }
});
// Result: 'text/html; charset=utf-8'
content-disposition can parse headers too, though this is less common:
// content-disposition parsing
const parsed = contentDisposition.parse('attachment; filename="data.csv"');
// Result: { type: 'attachment', parameters: { filename: 'data.csv' } }
content-type excels at parsing, automatically normalizing and validating input:
// content-type strict parsing
try {
const parsed = contentType.parse('application/json; charset=utf-8');
} catch (err) {
// Throws if header is invalid
}
Both packages throw errors on malformed input during parsing, but their tolerance differs slightly.
content-disposition will throw if the header doesn’t conform to expected syntax:
// This throws
try {
contentDisposition.parse('invalid header');
} catch (err) {
// TypeError: invalid Content-Disposition
}
content-type is similarly strict but provides clearer error messages about what part of the header failed:
// This throws
try {
contentType.parse('not-a-type');
} catch (err) {
// TypeError: invalid media type
}
Neither package attempts to “fix” bad input — they enforce correctness by design.
When implementing a file download endpoint, you’ll use content-disposition to set the correct header:
// Express.js example
app.get('/download/:filename', (req, res) => {
const header = contentDisposition(req.params.filename, { type: 'attachment' });
res.set('Content-Disposition', header);
// ... send file
});
You might also use content-type in the same handler to ensure the correct MIME type:
const mimeType = getMimeType(req.params.filename); // e.g., 'text/csv'
res.set('Content-Type', contentType.format({ type: mimeType }));
When building an API that expects JSON, use content-type to verify the request:
// Middleware to check Content-Type
function jsonOnly(req, res, next) {
try {
const parsed = contentType.parse(req.headers['content-type']);
if (parsed.type !== 'application/json') {
return res.status(400).send('JSON only');
}
next();
} catch (err) {
return res.status(400).send('Invalid Content-Type');
}
}
You wouldn’t use content-disposition here — it’s irrelevant to request validation.
In file upload/download systems, you’ll often combine both:
// File download endpoint
app.get('/files/:id', async (req, res) => {
const file = await getFile(req.params.id);
// Set Content-Type
res.set('Content-Type', contentType.format({ type: file.mimeType }));
// Set Content-Disposition
res.set('Content-Disposition',
contentDisposition(file.originalName, { type: 'attachment' })
);
res.send(file.data);
});
Each package handles its own header without interference.
Myth: “I can use content-type to handle file downloads.”
Content-Type tells the browser what the file is, but Content-Disposition tells it what to do with it. Both are needed for proper file handling.Myth: “These packages work in browsers.”
| Aspect | content-disposition | content-type |
|---|---|---|
| Header Managed | Content-Disposition | Content-Type |
| Primary Use Case | File download prompts, filename handling | MIME type parsing/formatting |
| Special Strength | Safe filename encoding for all browsers | Strict RFC-compliant parsing |
| Typical Direction | Mostly outgoing (response headers) | Both incoming (request) and outgoing |
Don’t think of these as competitors — they’re teammates. Use content-disposition whenever you’re serving files that should be downloaded or displayed inline with custom filenames. Use content-type whenever you need to inspect or set the media type of a resource. In most web applications that handle file I/O, you’ll end up using both, each doing its own job precisely and without overlap.
Choose content-disposition when you need to generate or parse the Content-Disposition header for file downloads or attachment handling. It correctly formats filenames with proper encoding for special characters and supports both inline and attachment dispositions. This package is essential for any server logic that serves user-uploaded files or generates downloadable content where filename integrity matters across different browsers.
Choose content-type when you need to parse or format Content-Type headers accurately according to RFC 7231. It cleanly separates MIME types from parameters (like charset) and validates inputs against the specification. Use this package whenever your application must inspect request/response media types, set appropriate response headers, or implement content negotiation based on MIME types.
Create and parse HTTP Content-Disposition header
$ npm install content-disposition
const contentDisposition = require('content-disposition')
Create an attachment Content-Disposition header value using the given file name,
if supplied. The filename is optional and if no file name is desired, but you
want to specify options, set filename to undefined.
res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
note HTTP headers are of the ISO-8859-1 character set. If you are writing this
header through a means different from setHeader in Node.js, you'll want to specify
the 'binary' encoding in Node.js.
contentDisposition accepts these properties in the options object.
If the filename option is outside ISO-8859-1, then the file name is actually
stored in a supplemental field for clients that support Unicode file names and
a ISO-8859-1 version of the file name is automatically generated.
This specifies the ISO-8859-1 file name to override the automatic generation or
disables the generation all together, defaults to true.
false will disable including a ISO-8859-1 file name and only include the
Unicode version (unless the file name is already ISO-8859-1).true will enable automatic generation if the file name is outside ISO-8859-1.If the filename option is ISO-8859-1 and this option is specified and has a
different value, then the filename option is encoded in the extended field
and this set as the fallback field, even though they are both ISO-8859-1.
Specifies the disposition type, defaults to "attachment". This can also be
"inline", or any other value (all values except inline are treated like
attachment, but can convey additional information if both parties agree to
it). The type is normalized to lower-case.
const disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
Parse a Content-Disposition header string. This automatically handles extended
("Unicode") parameters by decoding them and providing them under the standard
parameter name. This will return an object with the following properties (examples
are shown for the string 'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'):
type: The disposition type (always lower case). Example: 'attachment'
parameters: An object of the parameters in the disposition (name of parameter
always lower case and extended versions replace non-extended versions). Example:
{filename: "€ rates.txt"}
const contentDisposition = require('content-disposition')
const destroy = require('destroy')
const fs = require('fs')
const http = require('http')
const onFinished = require('on-finished')
const filePath = '/path/to/public/plans.pdf'
http.createServer(function onRequest (req, res) {
// set headers
res.setHeader('Content-Type', 'application/pdf')
res.setHeader('Content-Disposition', contentDisposition(filePath))
// send file
const stream = fs.createReadStream(filePath)
stream.pipe(res)
onFinished(res, function () {
destroy(stream)
})
})
$ npm test