Using UUID for File Names in File Uploads
- Never store uploaded files under their original names — user-supplied filenames are a security risk.
- UUID v4 filenames are unguessable, prevent path traversal attacks, and avoid name collisions across users.
- The pattern: strip the original name, generate a UUID, append the original extension, store the UUID→original mapping in your database.
- For CDN and cloud storage (S3, R2, GCS), UUID filenames map naturally to object keys.
- UUID v7 filenames sort by upload time — useful for ordered file listings without querying a database.
Table of Contents
When a user uploads a file named photo.jpg, storing it as photo.jpg on your server is a security problem. Path traversal attacks, name collisions between users, and leaking internal structure are all enabled by preserving original filenames. The solution is simple: generate a UUID v4, append the original extension, store that as the filename. This guide shows the complete pattern with code.
Why You Should Never Store Original Filenames
Path traversal: A malicious user uploads a file named ../../etc/passwd. If your code naively joins paths, this overwrites a system file.
Name collisions: Two users upload resume.pdf. The second upload overwrites the first. With UUID filenames, each upload gets its own unique path.
Information disclosure: Original filenames reveal user behavior — project-secret-v3-final-REAL.docx in your logs or CDN access logs exposes business information.
Extension abuse: A user uploads malware.php as an "image." If the original filename is preserved and served via a PHP-capable server, it executes as code.
UUID filenames fix all of these: unguessable, no collisions, no information leakage, no path traversal possible via the filename itself.
The UUID File Upload Pattern
// The pattern:
// 1. Accept file upload
// 2. Extract original extension (validated)
// 3. Generate UUID
// 4. Store as UUID + extension
// 5. Save original name + UUID mapping in database
// Node.js with multer:
import crypto from 'crypto';
import path from 'path';
import multer from 'multer';
const storage = multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
const allowedExts = ['.jpg', '.jpeg', '.png', '.pdf', '.docx'];
if (!allowedExts.includes(ext)) {
return cb(new Error('File type not allowed'));
}
const uuid = crypto.randomUUID();
cb(null, uuid + ext); // e.g., "550e8400-e29b-41d4-a716-446655440000.jpg"
}
});
const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } });
// Save the mapping:
app.post('/upload', upload.single('file'), async (req, res) => {
await db.files.insert({
uuid: req.file.filename.replace(/.[^.]+$/, ''), // UUID without ext
originalName: req.file.originalname,
uploadedBy: req.user.id,
uploadedAt: new Date(),
});
res.json({ fileId: req.file.filename });
});
Sell Custom Apparel — We Handle Printing & Free Shipping
UUID File Names in Python (Flask / Django)
import uuid
import os
from pathlib import Path
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.pdf', '.docx'}
def secure_filename(original_name: str) -> str:
ext = Path(original_name).suffix.lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValueError(f"Extension not allowed: {ext}")
return str(uuid.uuid4()) + ext
# Flask example:
from flask import request
from werkzeug.utils import secure_filename as ws_secure
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files['file']
filename = secure_filename(file.filename) # our UUID function
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
# Save mapping to database:
db.execute(
'INSERT INTO uploads (uuid, original_name, user_id) VALUES (?, ?, ?)',
(filename.split('.')[0], file.filename, current_user.id)
)
return jsonify({'fileId': filename})
UUID Filenames on S3, R2, and Cloud Storage
// AWS S3 — UUID as object key:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import crypto from 'crypto';
import path from 'path';
async function uploadToS3(fileBuffer, originalName, mimeType) {
const ext = path.extname(originalName).toLowerCase();
const key = crypto.randomUUID() + ext;
// e.g., "uploads/550e8400-e29b-41d4-a716-446655440000.jpg"
const prefixedKey = "uploads/" + key;
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: prefixedKey,
Body: fileBuffer,
ContentType: mimeType,
// Do NOT make objects public by default
}));
return prefixedKey; // Store this in your database
}
// Generate a pre-signed URL for the user to download:
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const url = await getSignedUrl(s3, new GetObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: storedKey,
}), { expiresIn: 3600 }); // URL expires in 1 hour
Pre-signed URLs are the right pattern for user file downloads — they expire, they require no authentication config in the CDN, and they keep your S3 bucket private.
UUID v7 for Time-Ordered File Listings
UUID v4 filenames sort randomly — you cannot reconstruct upload order from filenames alone without a database query.
UUID v7 filenames embed a timestamp — files sort chronologically by filename, which is useful for:
- Listing files in upload order without a separate
uploaded_atcolumn - Efficient range queries: "all files uploaded after 2026-04-01" can be answered by filename prefix scan
- S3 key ordering — S3 lists objects in lexicographic order; UUID v7 keys give you time-ordered listing for free
// Node.js with uuid v7:
import { v7 as uuidv7 } from 'uuid';
const filename = uuidv7() + ext;
// Files uploaded later always sort after files uploaded earlier
// S3 bucket listing is now in chronological order:
// 01956b10-... (older)
// 01956b13-... (newer)
Use UUID v7 filenames when file order matters to your application. Use UUID v4 when order does not matter and you want completely opaque, non-time-revealing filenames.
Generate UUIDs for File Upload Testing
Get 1 or 10 UUID v4 filenames instantly from the Cheetah UUID Generator — use as mock file keys to test your upload storage logic.
Open Free UUID GeneratorFrequently Asked Questions
Why should I use UUID for file upload names?
Original filenames are unsafe — they enable path traversal attacks, name collisions between users, and information disclosure. UUID filenames are unguessable, guaranteed unique, and reveal nothing about the file's origin or content.
Should I keep the original file extension when using a UUID filename?
Yes, but validate it. Keep the extension for MIME type hints and browser rendering. But validate the extension against an allowlist — never trust the extension the user provides without checking the actual file content type.
How do I let users download files by their original name?
Store the original name in your database alongside the UUID filename. When serving the file, set the Content-Disposition header: Content-Disposition: attachment; filename="original-name.pdf"
Is UUID v4 or v7 better for S3 object keys?
UUID v7 if you want chronological ordering of objects by key. UUID v4 if ordering does not matter and you want completely unpredictable keys. UUID v4 also distributes more evenly across S3 partitions, which can be marginally better for extremely high write throughput.
Can two uploaded files get the same UUID filename?
Cryptographically impossible in practice. UUID v4 has 122 bits of randomness — the probability of collision across billions of uploads is negligible. In production systems, UUID collisions are not a concern.

