Converters are the exact mechanism Lit uses to translate between attribute strings and typed JavaScript values. This lesson covers the built-in converters and how to write your own.
How Built-In Type Converters Work
When you set type: Number (or String, Boolean, Object, Array), Lit doesn't just note the type for documentation — it selects a matching built-in converter object with fromAttribute and toAttribute functions that actually perform the conversion whenever the attribute or property changes.
Understanding these built-in converters as functions, rather than magic, makes it much easier to reason about edge cases (like what happens with a malformed number attribute) and to write your own converter when a built-in one doesn't fit.
// Roughly what Lit's built-in Number converter does internally:
const numberConverter = {
fromAttribute: (value) => (value === null ? null : Number(value)),
toAttribute: (value) => (value == null ? value : String(value)),
};
@property({ type: Number }) quantity = 1;
// Setting the "quantity" attribute to "3" runs fromAttribute -> this.quantity === 3 (a Number)
This is a simplified illustration — the real implementation lives inside Lit's @lit/reactive-element package, but the shape is exactly this.
fromAttribute receives the raw attribute string (or null if removed) and must return the JS-typed value.
toAttribute receives the JS value and must return a string (or null/undefined to remove the attribute) — only called when reflect: true.
You can also pass a single function instead of an object; it's used as fromAttribute only, useful when you never need reflection.
Malformed input should be handled defensively inside fromAttribute — a bad attribute string should not throw and break rendering.
Built-In Converters Reference
Exactly how each built-in type converts values in both directions.
type
fromAttribute
toAttribute
String
Used as-is
Used as-is
Number
Number(value)
String(value)
Boolean
Attribute present -> true
Truthy -> attribute added, falsy -> removed
Object
JSON.parse(value)
JSON.stringify(value)
Array
JSON.parse(value)
JSON.stringify(value)
Writing a Defensive Custom Converter
Real-world attribute values can be missing, empty, or malformed — especially when set by hand-written HTML or a CMS. A good custom converter fails gracefully rather than throwing an exception that would break the whole component's render cycle.
const safeDateConverter = {
fromAttribute(value) {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
},
toAttribute(value) {
return value instanceof Date ? value.toISOString() : null;
},
};
@property({ converter: safeDateConverter })
publishedAt = null;
Returning null for invalid input, rather than throwing, keeps the component resilient to bad markup.
When to Avoid JSON-Encoded Attributes
While Object/Array converters make JSON-in-an-attribute technically possible, it's rarely a good idea for data set from JavaScript — it forces an unnecessary serialize/parse round trip and is fragile to hand-written HTML typos. Prefer plain property bindings (.data=${value}) for structured data set programmatically, and reserve attribute-based JSON for cases where the value genuinely needs to be authorable in static HTML.
Common Mistakes
Letting a custom fromAttribute throw on malformed input instead of returning a safe fallback value.
Using JSON-encoded object/array attributes for data that's always set from JavaScript, adding unnecessary serialization overhead.
Forgetting that toAttribute is only invoked when reflect: true — a custom converter without reflection only needs fromAttribute.
Assuming the Boolean converter checks the attribute's string value rather than its mere presence.
Key Takeaways
Converters are the functions Lit actually calls to translate between attribute strings and JS values.
Built-in converters exist for String, Number, Boolean, Object, and Array.
Custom converters take a { fromAttribute, toAttribute } object and should handle malformed input defensively.
Prefer property bindings over JSON-encoded attributes for structured data set from JavaScript.
Pro Tip
Write a quick unit test that sets a genuinely malformed value for any property using a custom converter (an invalid date string, broken JSON) — it's the fastest way to catch a converter that throws instead of degrading gracefully.
You now understand property converters in depth. Next, move into the Lit component lifecycle as a whole.