How to set a Content-Disposition filename
By Flavio
Write a Content-Disposition header for a download or an inline file, including the UTF-8 filename form, and read a header back.
Free toolThis tutorial uses the Content-Disposition Builder. It runs in your browser, so open it in another tab and follow along.
The Content-Disposition header names a file for the browser. It also says whether to save the file or show it.
attachment asks for a download. inline asks the browser to show the file when it can, such as a PDF in the tab. The filename is a parameter on that same line.
You can build the line in the Content-Disposition builder. The rules are the ones Express uses for res.download(), from the content-disposition package. Send files using Express covers that method.
A plain name
Type invoice.pdf and leave the type on attachment. The tool writes:
Type: attachment
Filename: invoice.pdf
filename parameter: invoice.pdf
filename*: no
Content-Disposition: attachment; filename="invoice.pdf"
The quotes wrap the name even when there is no space. Copy that line onto the response, next to the Content-Type. The Content-Type parser is the other half of that pair, and the header list is the wider reference.
A space in the name
Switch the type to inline and type November report.pdf. The tool writes:
Type: inline
Filename: November report.pdf
filename parameter: November report.pdf
filename*: no
Content-Disposition: inline; filename="November report.pdf"
The space stays inside the quotes. You do not percent-encode it in the filename parameter.
A character outside Latin-1
The euro sign is not Latin-1. A quoted filename cannot hold it. The header then sends two parameters. filename replaces the euro sign with ?. filename* carries the real name as UTF-8, percent-encoded. RFC 8187 defines that second form.
Type price-list-€.pdf. The tool writes:
Type: attachment
Filename: price-list-€.pdf
filename parameter: price-list-?.pdf
filename*: yes
Content-Disposition: attachment; filename="price-list-?.pdf"; filename*=UTF-8''price-list-%E2%82%AC.pdf
A character is outside Latin-1. filename uses ? for it, and filename* carries the UTF-8 name.
| Filename you type | Header the tool writes |
|---|---|
invoice.pdf |
attachment; filename="invoice.pdf" |
November report.pdf |
inline; filename="November report.pdf" |
price-list-€.pdf |
attachment; filename="price-list-?.pdf"; filename*=UTF-8''price-list-%E2%82%AC.pdf |
reports/invoice.pdf |
attachment; filename="invoice.pdf" |
When both parameters are present, a browser uses filename*. Paste the euro header into the read box on the tool, with or without the Content-Disposition: prefix. The decoded name is price-list-€.pdf. The ? copy is the fallback for an old client.
Type: attachment
Filename: price-list-€.pdf
Parameters: 1
filename = price-list-€.pdf
Content-Disposition: attachment; filename="price-list-?.pdf"; filename*=UTF-8''price-list-%E2%82%AC.pdf
filename* replaces the earlier filename value.
é is Latin-1, so café.pdf stays in the quoted parameter and the tool does not add filename*.
Type: attachment
Filename: café.pdf
filename parameter: café.pdf
filename*: no
Content-Disposition: attachment; filename="café.pdf"
A path is not a filename
reports/invoice.pdf becomes invoice.pdf. The summary is the same plain header as invoice.pdf, and the page adds a note.
The directory was dropped. A filename is not a path.
The header names the file. It does not pick a folder on the visitor’s disk.
A quote inside the name is escaped. say "hi".pdf is written as:
Type: attachment
Filename: say "hi".pdf
filename parameter: say "hi".pdf
filename*: no
Content-Disposition: attachment; filename="say \"hi\".pdf"
Read a header you already have
Paste a header into the second box, with or without the Content-Disposition: prefix. The page lists the type and the filename.
If filename is repeated, the first value is kept. Paste attachment; filename="a.pdf"; filename="b.pdf".
Type: attachment
Filename: a.pdf
Parameters: 1
filename = a.pdf
Content-Disposition: attachment; filename="a.pdf"; filename="b.pdf"
A repeated filename was ignored. The first one is kept.
If filename* is also there, it replaces that value. That is the euro header above.
An unclosed quote is an error, and the page says so instead of guessing the name. Paste attachment; filename="invoice.pdf and the page says: A quoted parameter value never closed.
Try the Content-Disposition Builder
Build or read a Content-Disposition header. A non-Latin-1 filename gets a filename* parameter. The name stays in the browser.
Open the Content-Disposition Builder →
Related tools
- Content-Type Parser: Parse a Content-Type header into a media type, charset, and boundary. Quoted parameters are unescaped. The header stays in the browser.
- MIME Type Lookup: Extension to MIME type and back — 150+ entries with Content-Type examples, charset notes, and gotchas.
- HTTP Response Headers Explainer: Paste response headers and get each one explained — plus warnings for missing security headers like HSTS and CSP.
Want me to talk about your product? You can sponsor this site.