In JavaScript, seeing the literal string "[object Object]" in your UI, logs, alerts, or network payloads is a classic sign that something object-shaped was coerced into a string at the wrong time.
That string is not random: it’s the default output of calling Object.prototype.toString() on a plain object (or forcing an object into a string context). This guide explains exactly why it happens, how to trace where it’s coming from, how to fix it cleanly, and how to prevent it—across the browser, Node.js, React, Vue, templating engines, logging systems, and HTTP layers.
1) What "[object Object]" actually means
When JavaScript needs a string but you give it an object, it attempts to convert the object to a primitive value. For many objects, the default conversion ends up calling .toString().
Example:
jsconst user = { id: 42, name: "Ada" }; console.log("User: " + user); // User: [object Object]
Why?
"User: " + usertriggers string concatenation.- The
+operator with a string coerces the other operand into a string. - Plain objects’ default
.toString()returns"[object Object]".
You can see it directly:
js({}).toString(); // "[object Object]"
This is a symptom, not a bug by itself. The bug is: you expected meaningful output (like JSON or a name), but you got the generic default string representation.
2) Common places it shows up (and why)
2.1 String concatenation and template strings
jsconst payload = { ok: true }; const msg = `Result: ${payload}`; // "Result: [object Object]"
Template literals call the same coercion rules (roughly String(value)). For objects, that’s often [object Object].
2.2 DOM APIs: innerHTML, textContent, setAttribute
jsconst el = document.querySelector("#output"); el.textContent = { a: 1 }; // the element shows: [object Object]
The DOM expects strings. It will call String(value).
2.3 Alerts, toasts, and UI messaging
jsalert({ error: "Boom" }); // alerts "[object Object]"
Many notification systems accept string | ReactNode (or similar), but if you pass a plain object and it gets stringified implicitly, you’ll see this.
2.4 Logging pitfalls
Some logging environments handle objects well; others don’t.
console.log(obj)usually prints an interactive object inspector.console.log("" + obj)forces string conversion.- Some log aggregators accept only strings and force coercion.
2.5 Query strings and URL building
jsconst params = new URLSearchParams({ filter: { active: true } }); params.toString(); // "filter=%5Bobject+Object%5D"
URLSearchParams values are strings. Passing an object gives [object Object].
2.6 FormData
jsconst fd = new FormData(); fd.append("meta", { a: 1 }); // becomes "[object Object]" (as a string)
2.7 HTTP requests: JSON vs form encoding
If you send JSON, you should JSON.stringify the body. If you send application/x-www-form-urlencoded, any object-valued fields will stringify poorly unless you flatten or encode them.
3) Reproducing the coercion rules (quick mental model)
JavaScript converts objects to primitives using the ToPrimitive algorithm. In practical terms:
- If the object has a
Symbol.toPrimitivemethod, that’s used. - Otherwise it tries
valueOf()and/ortoString()depending on the hint. - For plain objects, default
toString()returns[object Object].
Example:
jsconst obj = { valueOf() { return 123; }, toString() { return "custom"; } }; String(obj); // "custom" obj + ""; // "123" or "custom" depending on coercion path
In most UI contexts (DOM, template interpolation, concatenation), you effectively get String(obj) → obj.toString() → [object Object].
4) The right fixes (choose based on intent)
There is no single “correct” replacement for [object Object]. The fix depends on what you meant to display or transmit.
4.1 You meant to display a specific field
Instead of dumping the whole object:
jsconst user = { id: 42, name: "Ada" }; el.textContent = user.name; // "Ada"
In React:
jsxfunction UserBadge({ user }) { return <span>{user.name}</span>; }
In Vue:
html<span>{{ user.name }}</span>
4.2 You meant to show structured data for debugging
Use JSON.stringify with formatting:
jsel.textContent = JSON.stringify(payload, null, 2);
In React:
jsx<pre>{JSON.stringify(payload, null, 2)}</pre>
Caveats:
- Fails on circular references.
- Drops functions and
undefined. - Doesn’t represent
Map,Set,Dateperfectly without custom handling.
4.3 You meant to log an object (don’t force string coercion)
Prefer:
jsconsole.log("payload", payload); console.dir(payload);
Avoid:
jsconsole.log("payload: " + payload); // coercion
For Node.js, you can use util.inspect:
jsimport util from "node:util"; console.log(util.inspect(payload, { depth: null, colors: true }));
4.4 You meant to send JSON over HTTP
For fetch:
jsawait fetch("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "widget", meta: { active: true } }), });
For Axios:
jsimport axios from "axios"; await axios.post("/api/items", { name: "widget", meta: { active: true } });
Axios will serialize to JSON by default when sending objects (with the appropriate headers), but verify with the Network tab.
4.5 You meant to put it in a query string
You have options:
Option A: encode JSON (simple, but can get long):
jsconst filter = { active: true, tags: ["a", "b"] }; const url = new URL("https://example.com/search"); url.searchParams.set("filter", JSON.stringify(filter));
On the server, parse filter as JSON.
Option B: flatten params (more conventional):
jsurl.searchParams.set("active", "true"); url.searchParams.append("tag", "a"); url.searchParams.append("tag", "b");
Option C: use a query-string library (best for nested objects):
qs(popular; supports nesting)
jsimport qs from "qs"; const query = qs.stringify({ filter: { active: true, tags: ["a", "b"] } }); // filter%5Bactive%5D=true&filter%5Btags%5D%5B0%5D=a...
4.6 You meant to submit it via FormData
Serialize explicitly:
jsfd.append("meta", JSON.stringify({ a: 1 }));
Or append individual fields:
jsfd.append("meta[a]", "1");
5) Debugging: finding where [object Object] is introduced
When you see [object Object], the important question is: where did the implicit coercion happen?
5.1 Search for concatenation or interpolation
Look for:
"..." + something`${something}`String(something)- DOM assignment:
textContent =,innerHTML =,setAttribute(..., value)
A quick grep/ripgrep helps:
bashrg "\+\s*[a-zA-Z_$][\w$]*" src rg "\$\{.*\}" src
5.2 Use stack traces by throwing
If the conversion is happening deep in a helper, you can temporarily replace the value with a getter that throws:
jsconst payload = { get toString() { throw new Error("toString accessed"); } };
Or wrap the suspect path with:
jsconsole.trace("Coercion happening here");
5.3 Instrument the UI boundary
If it appears in a specific DOM node, add a breakpoint where that node is updated.
- Chrome DevTools → Elements → right-click node → “Break on… subtree modifications”
- Or set a breakpoint in the rendering function.
5.4 Network tab: verify payload encoding
If [object Object] shows up server-side or in a request:
- Open DevTools → Network → click request
- Check “Request Payload” or “Form Data”
If you see meta: [object Object], you’re not sending JSON correctly.
5.5 Node.js / backend: inspect raw inputs
In Express, verify what you’re parsing:
express.json()parses JSON bodies.express.urlencoded({ extended: true })parses form bodies.
Log the headers and body type:
jsapp.post("/api", (req, res) => { console.log(req.headers["content-type"]); console.log(req.body); res.sendStatus(200); });
If content-type is wrong, your body parser might treat it as a plain string.
6) Framework-specific notes
6.1 React: rendering objects is not allowed (usually)
React will error if you try to render a plain object as a child:
jsxreturn <div>{user}</div>; // throws: Objects are not valid as a React child
So how do you still see [object Object] in React apps?
Common scenarios:
- Building strings:
jsxreturn <div>{"User: " + user}</div>; // coerces -> [object Object]
- Passing objects to components that expect strings (toast libraries, error banners):
jstoast.error(err); // library does String(err)
Fix:
jstoast.error(err.message ?? JSON.stringify(err));
6.2 Vue / Angular templating
Vue interpolation uses toString-like behavior:
html<div>{{ payload }}</div> <!-- [object Object] -->
Use:
html<pre>{{ JSON.stringify(payload, null, 2) }}</pre>
For production UIs, prefer selecting fields or using computed properties.
6.3 Server-side templating (Handlebars/EJS)
Many template engines stringify values when interpolating:
{{payload}}becomes[object Object].
Fix by formatting or iterating:
{{payload.name}}- loops like
{{#each payload}} ... {{/each}}
7) Avoiding JSON.stringify footguns (circular refs, BigInt, Dates)
7.1 Circular references
jsconst a = {}; a.self = a; JSON.stringify(a); // TypeError: Converting circular structure to JSON
Solutions:
- Use a safe stringifier like
fast-safe-stringify. - Use
util.inspectin Node. - Use structured logging rather than stringifying.
Example with fast-safe-stringify:
jsimport stringify from "fast-safe-stringify"; console.log(stringify(a));
7.2 BigInt
jsJSON.stringify({ id: 1n }); // TypeError
Workarounds:
jsJSON.stringify({ id: 1n.toString() });
Or provide a replacer:
jsconst json = JSON.stringify( { id: 1n }, (_, v) => (typeof v === "bigint" ? v.toString() : v) );
7.3 Dates
Dates stringify to ISO strings (usually OK), but be explicit about timezone expectations.
jsJSON.stringify({ when: new Date() });
8) Best practices to prevent [object Object]
8.1 Treat boundaries as typed contracts
Most [object Object] issues occur at boundaries:
- UI rendering
- logs
- HTTP serialization
- storage (localStorage)
Define what type crosses the boundary (string? JSON? structured object?), and enforce it.
TypeScript helps:
tstype ToastInput = string; function showError(message: ToastInput) { toast.error(message); } // showError(err); // type error if err is unknown/object
8.2 Prefer structured logging over string building
Instead of:
jslogger.info("User=" + user);
Do:
jslogger.info({ user }, "User loaded");
This works best with loggers like:
- pino (fast, JSON logs)
- winston (flexible transports)
- bunyan (older, JSON logs)
8.3 Validate and normalize errors
Errors are often objects with nested causes.
Create a helper:
jsfunction formatError(err) { if (!err) return "Unknown error"; if (typeof err === "string") return err; if (err instanceof Error) return err.message; if (typeof err === "object") return JSON.stringify(err); return String(err); }
Use it wherever you display error messages.
8.4 Use ESLint rules and code review heuristics
Heuristic: flag suspicious concatenations.
no-base-to-string(helps catch"" + objpatterns)@typescript-eslint/restrict-template-expressions(prevents unsafe${obj})
TypeScript config can prevent accidental string interpolation of any/unknown.
8.5 Make objects renderable intentionally (rarely)
Sometimes you want a domain object to have a meaningful string form.
Implement toString() intentionally:
jsclass User { constructor(id, name) { this.id = id; this.name = name; } toString() { return `User(${this.id}, ${this.name})`; } } const u = new User(1, "Ada"); String(u); // "User(1, Ada)"
Even better: implement Symbol.toPrimitive for more control:
jsclass Money { constructor(cents) { this.cents = cents; } [Symbol.toPrimitive](hint) { if (hint === "number") return this.cents / 100; return `$${(this.cents / 100).toFixed(2)}`; } } const m = new Money(1234); String(m); // "$12.34" Number(m); // 12.34
Use this sparingly; implicit coercion can also create confusing bugs.
9) Tooling comparisons: how different tools display objects
Browser console.log
- Pros: interactive inspection, expandable object trees.
- Cons: objects are shown by reference; later mutations can change what you see.
Tip: log snapshots using structured clones or stringify:
jsconsole.log(JSON.parse(JSON.stringify(obj))); // snapshot (lossy)
Node.js console.log
- Uses
util.inspectinternally. - Can be configured with
util.inspectfor depth/color.
Logger libraries
- pino: best for performance and JSON pipelines; pairs well with log aggregation.
- winston: many transports; more overhead.
For production systems, prefer emitting structured fields and let your log backend render them.
Network inspection
- DevTools Network tab is the ground truth for what you sent.
- Always confirm
Content-Type, payload shape, and encoding.
10) Practical “fix patterns” you can copy-paste
Pattern A: Safe display of unknown values
jsfunction safeDisplay(value) { if (value == null) return ""; if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return String(value); if (value instanceof Error) return value.message; try { return JSON.stringify(value, null, 2); } catch { return Object.prototype.toString.call(value); } }
Pattern B: Ensure URLSearchParams never gets an object
jsfunction setParam(params, key, value) { if (typeof value === "object" && value !== null) { params.set(key, JSON.stringify(value)); } else { params.set(key, String(value)); } } const url = new URL(location.href); setParam(url.searchParams, "filter", { active: true });
Pattern C: Express handler that rejects [object Object] fields
If a client mistakenly sends form data:
jsapp.post("/api", (req, res) => { if (typeof req.body.meta === "string" && req.body.meta === "[object Object]") { return res.status(400).json({ error: "Invalid meta: expected JSON string or object, got [object Object]" }); } res.sendStatus(200); });
This doesn’t fix the client, but it shortens the debugging loop.
11) A quick checklist for production debugging
- Where did it appear? (UI text, logs, server, DB, network)
- Is it user-facing or internal? (decide whether to stringify or select fields)
- Inspect the boundary:
- DOM assignment
- logging statement
- request encoding (
Content-Type) - serialization function
- Find implicit coercion:
- concatenation (
+) - template literal interpolation
String(value)- libraries that accept
anyand calltoString()
- concatenation (
- Fix at the source:
- render
obj.field JSON.stringifyintentionally- send JSON with correct headers
- flatten query params
- render
- Add guardrails:
- TypeScript types
- ESLint rules
- structured logging
12) Summary
"[object Object]" is JavaScript’s way of telling you: “I had an object, but you asked me for a string, and you didn’t tell me how to represent it.”
The fix is always to be explicit about intent:
- Display a specific field for end users.
- Use
JSON.stringify(or safe alternatives) for debugging views. - Log objects as objects (structured logging), not concatenated strings.
- Serialize HTTP payloads correctly (JSON vs form encoding).
- Flatten or properly encode query parameters.
Once you start treating serialization and rendering as explicit boundaries—with types, lint rules, and predictable helpers—[object Object] stops being a recurring mystery and becomes a quick, easily preventable signal.
![Diagnosing and Fixing “[object Object]” in JavaScript: A Practical Guide for Developers](https://trouvai-blog-media.s3.us-east-2.amazonaws.com/data_3f46a536b0.png)