Skip to content

Property Converters

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.

Custom Converter Shape

converter: {
  fromAttribute: (value: string | null) => YourType,
  toAttribute: (value: YourType) => string | null,
}
  • 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.