"How big can a JSON document actually be?"
The JSON specification itself (RFC 8259) sets no size limit on documents, strings, numbers, or nesting depth — but every real system that stores or transmits JSON imposes its own practical ceiling, and these limits are scattered across vendor docs, GitHub issues, and hard-won production incidents rather than one obvious place. Here's what actually caps you, system by system.
Browser JavaScript strings and JSON.parse
V8 (Chrome, Node.js, Edge) caps a single string at just over 1 GB (String.length max is 2^29 - 24 characters on 64-bit builds) — in practice, you'll hit memory pressure and UI-freezing parse times long before that theoretical ceiling. JSON.parse is synchronous and blocks the main thread for its entire duration, so a multi-hundred-MB payload can visibly freeze a browser tab for seconds even if it technically parses successfully.
// A large synchronous parse blocks the main thread entirely
const data = JSON.parse(hugeJsonString); // UI frozen until this returnsFor genuinely large payloads in the browser, stream and parse incrementally (see our guide on parsing large JSON files) rather than one blocking JSON.parse call.
localStorage and sessionStorage
Browsers cap localStorage at roughly 5–10 MB per origin (the exact number varies by browser — Chrome and Firefox default to 10MB, Safari to 5MB), and since it only stores strings, every read/write is a full JSON.stringify/JSON.parse round trip of the *entire* stored value — there's no partial update. Exceeding the quota throws a QuotaExceededError, not a silent truncation:
try {
localStorage.setItem("cache", JSON.stringify(largeObject));
} catch (e) {
if (e.name === "QuotaExceededError") {
// Handle gracefully — don't let this crash the app
}
}PostgreSQL JSONB
PostgreSQL's jsonb column type has a hard ceiling of 1 GB per value (shared with PostgreSQL's general field size limit), but the practical limit is far lower — indexing and querying deeply nested or very large JSONB documents gets noticeably slower well before 1GB, and GIN indexes on JSONB columns work best on documents that are large in *count* (many rows) rather than large in *individual document size*. A JSONB column holding a handful of KB per row, queried across millions of rows, is the pattern PostgreSQL is actually optimized for.
MongoDB
MongoDB enforces a hard 16 MB per document limit (BSON document size, which JSON documents are stored as internally) — this is a deliberate design constraint, not a bug, meant to keep individual documents fitting comfortably in memory during processing. Documents that would naturally exceed this (a chat history, a large activity log) are expected to be split — the recommended pattern is GridFS for large binary content, or restructuring so unbounded arrays live in a separate, referenced collection instead of embedded in one ever-growing document.
// Anti-pattern: an array that grows without bound risks the 16MB ceiling
{ _id: "chat_1", messages: [ /* thousands of embedded messages */ ] }
// Better: messages in their own collection, referenced by chatId
{ _id: "msg_1", chatId: "chat_1", text: "..." }API gateways and web servers
Most API gateways and frameworks impose a default request body size limit specifically to prevent memory-exhaustion attacks — and these defaults are smaller than most developers expect:
| System | Common default limit |
|---|---|
| AWS API Gateway | 10 MB (hard limit, not configurable) |
Nginx (client_max_body_size) | 1 MB (must be raised explicitly) |
Express.js (body-parser/express.json) | 100 KB |
| Cloudflare (free/pro plans) | 100 MB (varies by plan) |
A request exceeding these returns 413 Payload Too Large — worth checking explicitly in your error handling, since a generic error handler that doesn't distinguish this from a validation failure makes an easy problem look like a mystery.
Number precision: a "limit" that isn't about size at all
Separate from document size, JSON's number type has its own ceiling: JavaScript can only exactly represent integers up to Number.MAX_SAFE_INTEGER (2^53 - 1, about 9 quadrillion). A 64-bit database ID or a large financial value serialized as a JSON number beyond that silently loses precision on parse — the fix, covered in more depth elsewhere, is to serialize very large integers as strings, not numbers.
A practical checklist before you hit a wall
- Browser-bound JSON: keep well under a few MB for anything parsed synchronously on the main thread; stream or paginate beyond that.
- localStorage: budget for 5MB total per origin, not per key — check current usage before writing a new large value.
- PostgreSQL JSONB: fine up to low tens of MB per document in practice; beyond that, reconsider whether it should be a document at all versus normalized rows.
- MongoDB: hard 16MB ceiling — design around it early rather than hitting it in production with a document that's grown organically.
- APIs: check your gateway/framework's actual configured limit (not just the library default) and return a clear, distinguishable error when a request exceeds it.