Symbol.prototype.toString()
Formats a symbol as the string "Symbol(description)" for logging and debugging.
toString()- Added in JavaScript ES2015
Return value
stringA string of the form "Symbol(description)", where description is the symbol's description (empty when it has none).
Errors
Throws a TypeError if this is not a symbol primitive or Symbol wrapper object.
Symbol.prototype.toString() overrides Object.prototype.toString() and returns the familiar "Symbol(id)" form. It is one of the two supported ways to turn a symbol into text; the other is String(sym), which special-cases symbols and delegates here.
When to reach for it
Call it explicitly any time a symbol has to appear in text — a log line, an error message, a key in a debug dump. Symbols deliberately refuse implicit string conversion: "key: " + sym and `${sym}` both throw a TypeError. That rule exists so a symbol never silently degrades into a string property key. Making the conversion explicit is the point, not an inconvenience.
What trips people up
String(sym) works but sym + "" throws, and the difference catches people out inside template literals and array join() calls. Beware too that String(sym) is safe while new String(sym) is not. And console.log(sym) may print a symbol without complaint because the host formats it — that is not string coercion, so code that works in the console can still throw once the value flows into real string concatenation.
Example
When to use it
- Include a symbol key in an error message or log line
- Build a debug dump of Reflect.ownKeys() where symbols and strings must all render
- Compare the printed form of a well-known symbol while diagnosing protocol lookups
Frequently asked questions
Why do I get "Cannot convert a Symbol value to a string"?
Because you used implicit coercion such as `${sym}`, sym + '', or join(). Call sym.toString() or String(sym) instead, which are the explicit conversions the language allows.
Does String(symbol) work?
Yes. String() special-cases symbols and returns "Symbol(description)" without throwing, unlike concatenation or template literals.
What does toString() return for a symbol with no description?
It returns the string "Symbol()" with nothing between the parentheses, while the description property for that same symbol is undefined.
More examples
Rendering mixed string and symbol keys
String(k) handles both the string key and the symbol key, so the map never throws. Interpolating k directly would fail on the symbol.
const TAG = Symbol("tag");
const obj = { a: 1, [TAG]: 2 };
const rendered = Reflect.ownKeys(obj).map(
(k) => `${String(k)}=${obj[k]}`,
);
console.log(rendered);
console.log(rendered.join(", "));Template literals still throw
The first three all perform implicit string conversion and throw TypeError — including Array.prototype.join() — while only the explicit String(s) call succeeds.
const s = Symbol("boom");
const attempts = [
() => `${s}`,
() => s + "",
() => [s].join(","),
() => String(s),
];
for (const fn of attempts) {
try {
console.log("ok:", fn());
} catch (err) {
console.log("threw:", err.constructor.name);
}
}
Discussion