MongoDBGridFS — Storing Large Files

GridFS — Storing Large Files

MongoDB documents are capped at 16 MB. GridFS is the built-in convention for storing files larger than that limit by splitting them into chunks and spreading them across two collections. It's a MongoDB-native option, but it is not always the right one — know when to reach for it and when to reach for object storage instead.

When to Use GridFS
  • Files larger than 16 MB that must be stored inside MongoDB itself (e.g. no separate object storage service available).

  • You need to query file metadata (uploader, tags, content type) alongside the binary data using MongoDB queries.

  • You want file reads/writes to participate in the same replication and backup story as the rest of your data.

  • Range queries into part of a file matter (e.g. seeking into a video) without loading the whole file into memory.

When NOT to Use GridFS
Warning
For most production applications serving user-uploaded files (images, videos, PDFs), a dedicated object store like Amazon S3, Google Cloud Storage, or Azure Blob Storage is the better choice — cheaper at scale, built-in CDN integration, and it keeps large binary blobs out of your database's working set and backup footprint.
  • High-traffic public file serving — a CDN in front of object storage will always outperform serving files through your database.

  • Files that change frequently — GridFS has no in-place update; a changed file is a new set of chunks.

  • Cost-sensitive storage of very large volumes — dedicated object storage is significantly cheaper per GB than database storage/replication overhead.

The fs.files and fs.chunks Collections

GridFS splits a file into chunks (255 KB by default) stored in fs.chunks, with one metadata document per file in fs.files.

fs.files — one document per uploaded file

JS
{
  _id: ObjectId("..."),
  length: 4823221,
  chunkSize: 261120,
  uploadDate: ISODate("2026-07-10T12:00:00Z"),
  filename: "report.pdf",
  metadata: { contentType: "application/pdf", uploadedBy: "user123" }
}

fs.chunks — the actual binary data, split up

JS
{
  _id: ObjectId("..."),
  files_id: ObjectId("..."),   // references the fs.files document
  n: 0,                          // chunk sequence number
  data: BinData(0, "...")        // up to chunkSize bytes
}
// A 4.8MB file with a 255KB chunk size becomes ~19 chunk documents
Uploading a File (Node.js Driver)

JS
import { GridFSBucket } from 'mongodb'
import fs from 'fs'

const bucket = new GridFSBucket(db, { bucketName: 'uploads' })

fs.createReadStream('./report.pdf')
  .pipe(bucket.openUploadStream('report.pdf', {
    metadata: { contentType: 'application/pdf', uploadedBy: 'user123' }
  }))
  .on('finish', () => console.log('Upload complete'))
Downloading a File

JS
const downloadStream = bucket.openDownloadStreamByName('report.pdf')

downloadStream
  .pipe(fs.createWriteStream('./downloaded-report.pdf'))
  .on('finish', () => console.log('Download complete'))

// Or stream directly to an HTTP response in an API route
app.get('/files/:id', (req, res) => {
  bucket.openDownloadStream(new ObjectId(req.params.id)).pipe(res)
})
Deleting a File

JS
await bucket.delete(new ObjectId(fileId))
// Removes both the fs.files metadata document and every associated chunk
GridFS vs Storing a File Path / URL

GridFS

Store Path/URL to S3 etc.

File larger than 16MB

Handled natively

N/A — file lives outside Mongo entirely

Query file metadata via MongoDB

Yes, in fs.files

Yes, if you store metadata in your own collection

Serving via CDN

Not built-in

Native — S3/CloudFront, GCS/Cloud CDN, etc.

Storage cost at scale

Higher (DB storage + replication)

Lower — object storage is built for this

Operational complexity

One less external service

One more service to configure/manage

Best for

Small deployments, files tied tightly to DB records

Most production apps serving user files at scale

Chunk Size

The default chunk size is 255 KB, which works well for most files. You can tune it per bucket — larger chunks reduce the number of chunk documents (less overhead per read), while smaller chunks make partial/range reads cheaper.

JS
const bucket = new GridFSBucket(db, {
  bucketName: 'videos',
  chunkSizeBytes: 1024 * 1024   // 1MB chunks — fewer chunk docs for large video files
})
Tip
Index fs.files.filename and any metadata fields you query on — GridFS creates a compound index on { files_id, n } for fs.chunks automatically, but does not index your custom metadata fields.
Summary
  • GridFS splits files larger than 16MB across fs.files (metadata) and fs.chunks (binary data).

  • Use it when files must live inside MongoDB and be queried alongside other data — otherwise prefer S3/GCS/Azure Blob with a URL/path stored in your documents.

  • The driver exposes upload/download as Node.js streams, so large files never need to be fully loaded into memory.

  • Tune chunkSizeBytes for your access pattern — larger chunks for sequential full-file reads, smaller for range/seek-heavy access.