- Home
- HTTP Headers
- Content-Disposition Header
Header
Content-Disposition Header
Learn how the Content-Disposition header controls whether content displays inline or downloads as an attachment. Set custom filenames for file downloads.
Content-Disposition Header
TL;DR: Controls whether content displays inline in the browser or downloads as a file. Use attachment to force downloads with a suggested filename.
What is Content-Disposition?
The Content-Disposition header tells the browser whether content should be displayed inline (in the browser) or downloaded as a file. It’s like telling someone “read this here” versus “take this home with you.”
This header is crucial for file downloads, attachments, and controlling how browsers handle different types of content.
How Content-Disposition Works
Server sends file for download:
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"
Content-Length: 524288
[PDF file contents]
```text
**Browser opens download dialog instead of displaying the PDF inline.**
**Server sends for inline display:**
```http
HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Disposition: inline; filename="photo.jpg"
[Image data]
Browser displays the image directly in the page.
Syntax
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.ext"
Content-Disposition: attachment; filename*=UTF-8''filename.ext
```text
### Directives
- **inline** - Display content in the browser
- **attachment** - Prompt user to download
- **filename** - Suggested filename for download
- **filename\*** - Encoded filename for internationalization
## Common Examples
### Force Download
```http
Content-Disposition: attachment; filename="document.pdf"
Triggers download dialog with suggested filename.
Display Inline
Content-Disposition: inline
```text
Browser attempts to display content (if supported type).
### Download with Special Characters
```http
Content-Disposition: attachment; filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf
Handles non-ASCII characters in filename (文件.pdf).
Both ASCII and Encoded Filenames
Content-Disposition: attachment;
filename="file.pdf";
filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf
```text
Fallback for older browsers that don't support filename\*.
## Real-World Scenarios
### Document Download Service
```http
GET /api/invoices/12345/download HTTP/1.1
Host: billing.example.com
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice-12345.pdf"
Content-Length: 245678
[PDF content]
Image Gallery
GET /images/photo.jpg HTTP/1.1
Host: gallery.example.com
HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Disposition: inline
Cache-Control: public, max-age=31536000
[Image data]
```text
### CSV Export
```http
GET /api/reports/sales?format=csv HTTP/1.1
Host: analytics.example.com
HTTP/1.1 200 OK
Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename="sales-report-2026-01.csv"
Month,Sales,Revenue
January,150,$45000
Backup Download
GET /backup/database HTTP/1.1
Host: admin.example.com
HTTP/1.1 200 OK
Content-Type: application/gzip
Content-Disposition: attachment; filename="database-backup-2026-01-18.sql.gz"
Content-Length: 10485760
[Compressed backup data]
```javascript
## Server Implementation
### Express.js (Node.js)
```javascript
const express = require('express')
const path = require('path')
const app = express()
// Force download
app.get('/download/:filename', (req, res) => {
const filename = req.params.filename
const filepath = path.join(__dirname, 'files', filename)
res.download(filepath, filename, (err) => {
if (err) {
res.status(404).send('File not found')
}
})
})
// Manual implementation
app.get('/files/:id', (req, res) => {
const filename = 'report.pdf'
res.setHeader('Content-Type', 'application/pdf')
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`)
// Send file
fs.createReadStream(filepath).pipe(res)
})
// Inline display
app.get('/view/:filename', (req, res) => {
res.setHeader('Content-Disposition', 'inline')
res.sendFile(filepath)
})
// With UTF-8 filename
app.get('/download-intl/:filename', (req, res) => {
const filename = '文档.pdf'
const encodedFilename = encodeURIComponent(filename)
res.setHeader(
'Content-Disposition',
`attachment; filename="${filename}"; filename*=UTF-8''${encodedFilename}`
)
res.sendFile(filepath)
})
Express with Dynamic Filename
app.get('/export/users', async (req, res) => {
const users = await getUsersFromDatabase()
const csv = convertToCSV(users)
const timestamp = new Date().toISOString().split('T')[0]
const filename = `users-export-${timestamp}.csv`
res.setHeader('Content-Type', 'text/csv')
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`)
res.send(csv)
})
```javascript
### FastAPI (Python)
```python
from fastapi import FastAPI
from fastapi.responses import FileResponse, StreamingResponse
import io
app = FastAPI()
@app.get("/download/{filename}")
async def download_file(filename: str):
file_path = f"./files/{filename}"
return FileResponse(
file_path,
media_type="application/octet-stream",
filename=filename
)
@app.get("/export/csv")
async def export_csv():
csv_data = "Name,Email\nJohn,john@example.com\n"
return StreamingResponse(
io.StringIO(csv_data),
media_type="text/csv",
headers={
"Content-Disposition": "attachment; filename=export.csv"
}
)
@app.get("/view/image/{image_id}")
async def view_image(image_id: str):
return FileResponse(
f"./images/{image_id}.jpg",
media_type="image/jpeg",
headers={"Content-Disposition": "inline"}
)
Django
from django.http import FileResponse, HttpResponse
import urllib.parse
def download_file(request, file_id):
file_path = f'/path/to/files/{file_id}.pdf'
filename = 'document.pdf'
response = FileResponse(open(file_path, 'rb'))
response['Content-Type'] = 'application/pdf'
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
def export_csv(request):
data = get_data_from_database()
csv_content = generate_csv(data)
response = HttpResponse(csv_content, content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="export.csv"'
return response
def download_intl(request):
filename = '文档.pdf'
encoded = urllib.parse.quote(filename)
response = FileResponse(open(file_path, 'rb'))
response['Content-Disposition'] = \
f'attachment; filename="{filename}"; filename*=UTF-8\'\'{encoded}'
return response
```nginx
### Nginx
```nginx
server {
listen 80;
server_name files.example.com;
# Force download for PDFs
location /downloads/ {
root /var/www;
add_header Content-Disposition "attachment";
}
# Inline display for images
location /images/ {
root /var/www;
add_header Content-Disposition "inline";
}
# Dynamic filename based on path
location ~ ^/exports/(.+)\.csv$ {
root /var/www/exports;
add_header Content-Disposition "attachment; filename=$1.csv";
}
}
Best Practices
For Servers
1. Always set Content-Type with Content-Disposition
# ✅ Both headers together
Content-Type: application/pdf
Content-Disposition: attachment; filename="doc.pdf"
# ❌ Missing Content-Type
Content-Disposition: attachment; filename="doc.pdf"
```javascript
**2. Use safe filenames**
```javascript
// ✅ Sanitize filename
function sanitizeFilename(filename) {
return filename.replace(/[^a-zA-Z0-9.-]/g, '_').substring(0, 255)
}
const safeFilename = sanitizeFilename(userProvidedName)
res.setHeader('Content-Disposition', `attachment; filename="${safeFilename}"`)
// ❌ Using unsanitized user input
res.setHeader('Content-Disposition', `attachment; filename="${req.query.name}"`)
3. Support international filenames
function getContentDisposition(filename) {
const ascii = filename.replace(/[^\x00-\x7F]/g, '_')
const encoded = encodeURIComponent(filename)
return `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`
}
```text
**4. Quote filenames with spaces**
```http
# ✅ Quoted filename
Content-Disposition: attachment; filename="my document.pdf"
# ❌ Unquoted (will break)
Content-Disposition: attachment; filename=my document.pdf
5. Set appropriate inline vs attachment
// Downloadable files
if (ext === '.pdf' || ext === '.doc' || ext === '.zip') {
disposition = 'attachment'
}
// Viewable in browser
else if (ext === '.jpg' || ext === '.png' || ext === '.txt') {
disposition = 'inline'
}
```javascript
### For Clients
**1. Handle both inline and attachment**
```javascript
fetch('/api/file/123').then((response) => {
const disposition = response.headers.get('Content-Disposition')
if (disposition && disposition.includes('attachment')) {
// Download file
return response.blob().then((blob) => {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = getFilenameFromHeader(disposition) || 'download'
a.click()
})
} else {
// Display inline
return response.blob().then((blob) => {
const url = URL.createObjectURL(blob)
window.open(url)
})
}
})
2. Parse filename from header
function getFilenameFromHeader(disposition) {
// Try filename* first (RFC 5987)
const filenameStarMatch = disposition.match(/filename\*=UTF-8''(.+?)(?:;|$)/)
if (filenameStarMatch) {
return decodeURIComponent(filenameStarMatch[1])
}
// Fall back to filename
const filenameMatch = disposition.match(/filename="(.+?)"/)
if (filenameMatch) {
return filenameMatch[1]
}
return 'download'
}
```text
## Filename Encoding
### ASCII Filenames
```http
Content-Disposition: attachment; filename="simple.pdf"
International Filenames (RFC 5987)
Content-Disposition: attachment; filename*=UTF-8''%E6%96%87%E6%A1%A3.pdf
```text
### Both for Maximum Compatibility
```http
Content-Disposition: attachment;
filename="document.pdf";
filename*=UTF-8''%E6%96%87%E6%A1%A3.pdf
Example Encoding Function
function encodeFilename(filename) {
// ASCII fallback
const asciiFilename = filename.replace(/[^\x00-\x7F]/g, '_')
// UTF-8 encoded
const utf8Filename = encodeURIComponent(filename)
return `attachment; filename="${asciiFilename}"; filename*=UTF-8''${utf8Filename}`
}
// Usage
res.setHeader('Content-Disposition', encodeFilename('文档.pdf'))
```text
## Form Data Usage
Content-Disposition is also used in multipart form data:
```http
POST /upload HTTP/1.1
Host: api.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
[file data]
------WebKitFormBoundary--
Testing Content-Disposition
Using curl
# Check Content-Disposition header
curl -I https://api.example.com/download/file.pdf
# Download and save with suggested filename
curl -OJ https://api.example.com/download/file.pdf
# View header value
curl -s -D - https://api.example.com/file -o /dev/null | grep Content-Disposition
```javascript
### Using JavaScript
```javascript
// Download file with proper filename
async function downloadFile(url) {
const response = await fetch(url)
const blob = await response.blob()
const disposition = response.headers.get('Content-Disposition')
const filename = getFilenameFromHeader(disposition) || 'download'
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
downloadFile('/api/export/report')
Browser Testing
// Test inline vs attachment
const testUrl = 'https://api.example.com/file'
fetch(testUrl).then((res) => {
console.log('Content-Disposition:', res.headers.get('Content-Disposition'))
console.log('Content-Type:', res.headers.get('Content-Type'))
})
Related Headers
- Content-Type - MIME type of the content
- Content-Length - Size of the content
- Accept - Client’s acceptable content types
- Content-Encoding - How content is compressed
Frequently Asked Questions
What is Content-Disposition?
Content-Disposition tells browsers how to handle the response. "inline" displays in browser, "attachment" triggers download. You can also specify a filename for downloads.
How do I force a file download?
Use Content-Disposition: attachment; filename="file.pdf". The attachment value triggers download instead of display, and filename suggests the save name.
How do I handle special characters in filenames?
Use filename* with UTF-8 encoding: filename*=UTF-8 double-quote-percent-encoded-name. For compatibility, include both filename and filename* parameters.
What is the difference between inline and attachment?
inline displays content in the browser if possible (PDFs, images). attachment always triggers the download dialog. Use inline for preview, attachment for download.