SfSkills Domains Docs GitHub

Home / lwc / lwc-custom-datatable-types

lwc-custom-datatable-types

Use when you need to extend `lightning-datatable` with custom cell renderings: status pills, progress bars, image thumbnails, action cells, editable pickliststo, rich-text, or any column that `lightning-datatable` does not ship out of the box. Triggers: 'custom cell type lightning datatable', 'progress bar column', 'image column', 'inline edit picklist in datatable', 'rich text column'. NOT for stock column types, key-field, or basic row actions — use lwc/lwc-data-table. NOT for 1,000-row virtualization — use lwc/lwc-virtualized-lists.

Open the skill folder on GitHub


LWC Custom Datatable Types

Use this skill when lightning-datatable is the right shell for your grid but the built-in cell types (text, number, url, email, button, boolean, date, currency, percent, phone) cannot render or edit the column the way the feature demands. This skill covers subclassing the base datatable, registering customTypes, writing display and edit templates, integrating with sorting and inline edit, and avoiding the traps that silently break the extension.


Before Starting

Gather this context before cutting code:


Core Concepts

Custom cell types in lightning-datatable are a subclassing mechanism, not a slot API. The subclass registers types by name and points each one at a template .html file that sits next to the component. The base datatable then uses those templates when a column's type matches a registered custom type.

Subclass LightningDatatable And Declare customTypes

The extension pattern is fixed by the platform: import the base class, extend it, and declare a static customTypes object whose keys are the new type names.

import LightningDatatable from 'lightning/datatable';
import customPill from './customPill.html';
import customPillEdit from './customPillEdit.html';

export default class CustomDatatable extends LightningDatatable {
    static customTypes = {
        statusPill: {
            template: customPill,
            editTemplate: customPillEdit,        // optional — only if inline-editable
            standardCellLayout: true,            // reuse built-in padding, focus, edit icon
            typeAttributes: ['variant', 'label'] // MUST list every key you want to read
        }
    };
}

The typeAttributes array is the most important and the most missed piece. It names the keys the template is allowed to read out of the column's typeAttributes binding. If a key is not in that array, the datatable silently strips it, the template sees undefined, and nothing visibly fails — the cell just renders blank or default.

Templates Are Data-Bound, Not Component Instances

The template and editTemplate HTML files are ordinary LWC template fragments, but they are evaluated against a fixed binding surface — {value, typeAttributes, editable, context, keyField} and the edit-template's editing equivalents. They have no this, no lifecycle hooks, no imperative handlers. A template cannot call connectedCallback, dispatch events from JavaScript, or hold state. If the cell needs interactive state (a multi-step toggle, debounced search, rich validation), the template must render a child LWC and delegate.

Edit Templates Must Wire To value For Draft-Values

lightning-datatable emits draft-values when an inline edit is saved, and it discovers the new cell value by reading the input element's value property inside the edit template. An editTemplate that uses, for example, lightning-combobox must bind value={typeAttributes.options} for choices and set value={value} for the current cell value so the datatable's harness can capture the draft. Miss this wiring and the cell appears editable, but the Save button never lights up.

Sorting Uses value, Not Rendered Output

The base datatable sorts by the column's primitive value, not by what the template paints. A status pill rendered from {color: 'red', label: 'Hot'} needs either a flat scalar in value or an explicit sortBy on the column so sort order matches the displayed order. Filtering through lightning-datatable itself does not ship — filtering is the consumer's job — but the same principle applies: whatever you filter against should be a stable scalar on the row.

Custom Templates Compete For The Virtualization Budget

lightning-datatable virtualizes rows so that only visible rows are in the DOM. That budget is small. A lightweight template (a span with an SLDS class) is cheap; a lightning-record-edit-form or a nested datatable inside a cell multiplies per-row cost and breaks smooth scrolling at a few hundred rows. For any cell beyond a few SLDS-decorated elements, extract it into a child LWC and keep the template itself trivial.


Common Patterns

Status Pill / Badge Column

When to use: A column displays a categorical state (open/closed, healthy/warning/error) and needs color coding.

How it works: Register a statusPill custom type whose template renders <span class={pillClass}>{typeAttributes.label}</span>, driving pillClass from typeAttributes.variant. Keep the primitive value on the column as the sortable string so the built-in sort behaves sensibly.

Why not the alternative: Using type: 'text' and dumping a status word into the cell loses the visual affordance; wrapping every row in a child LWC that ingests the full record is heavier than the SLDS badge markup needs.

Progress Bar Column

When to use: A numeric cell benefits from a bar (percent complete, score, SLA remaining).

How it works: Register a progress custom type whose template renders <lightning-progress-bar value={value} variant={typeAttributes.variant}>. Keep value as the numeric percent so sorting works without extra config.

Why not the alternative: Writing the percent as text hides scale; computing the bar width by hand in the template without lightning-progress-bar skips the accessibility attributes that SLDS ships.

Editable Picklist Column

When to use: A column needs a dropdown edit without opening a row-level modal.

How it works: Register editPicklist with both template (read-only text span) and editTemplate (lightning-combobox whose options come from typeAttributes.options and whose value is bound to value). The datatable harness picks up the draft on blur or change and emits draft-values; the consumer saves them.

Why not the alternative: Navigating to a separate edit screen for a single-field change is slow and breaks the datatable's bulk-edit story.

Action Cell With Multiple Buttons

When to use: Each row needs more than one action and the built-in type: 'action' menu is too limiting.

How it works: Register an actionRow type with a template that renders a horizontal stack of lightning-button elements. Dispatch a row event via a small child LWC if state (e.g., loading spinner) matters.

Why not the alternative: Embedding a full lightning-button-group with dynamic children inline in the template is fine; embedding a full form is not — that pattern belongs in a row-detail sub-LWC.


Decision Guidance

SituationRecommended ApproachReason
Categorical status badge or pillCustom type with SLDS badge classes, driven by typeAttributes.variantCheap template, preserves virtualization budget
Numeric progress indicatorCustom type wrapping lightning-progress-barAccessible and consistent with platform UI
Per-row action buttonscustomTypes with an actions array surfaced through typeAttributesKeeps action logic row-scoped and event-driven
Full row-level edit formExtract to a sub-LWC detail view, not a custom cellA cell that hosts a form kills the virtualization budget
Editable picklist with known optionseditTemplate using lightning-combobox wired to valueIntegrates with built-in draft-values save flow
Column needs to sort on a computed keyKeep value as the comparable scalar; put display data in typeAttributesDatatable sorts on value, not rendered DOM

  1. Confirm the column cannot be expressed as a built-in datatable type (re-read the base lightning-datatable type list).
  2. Design the row shape: what scalar goes on value, what goes on typeAttributes, and whether the column is editable.
  3. Scaffold the subclass: extends LightningDatatable, declare static customTypes, import each template, and list every key in typeAttributes.
  4. Build the display template first, verify rendering on a small dataset, then add the editTemplate and wire value so draft-values flows correctly.
  5. Profile with 200 and 1,000 rows; if scrolling lags, move interactive content into a child LWC and keep the template trivial.
  6. Add sort and filter behavior by keeping value comparable and documenting any sortBy override.
  7. Run scripts/check_lwc_custom_datatable_types.py against the component source to catch missing typeAttributes arrays and orphaned template filenames.

Review Checklist


Salesforce-Specific Gotchas

  1. typeAttributes names not listed are silently dropped — a typo in the array or forgetting the array entirely makes the binding undefined with no console warning.
  2. Templates have no this and no lifecycle — imperative handlers, renderedCallback, and LightningElement lifecycle APIs do not exist in the binding surface.
  3. Sorting ignores rendered DOM — the column's primitive value drives sort order, so two rows with different pills but the same value appear equal.
  4. draft-values requires the value binding on the edit control — using selected-value on a combobox or writing to a local proxy breaks the save path.
  5. Custom types are per-subclass — two separate extensions cannot be mixed in one datatable instance; you must build a merged subclass that registers both.
  6. Overriding base datatable CSS is brittle — prefer SLDS Styling Hooks where available; direct .slds-table overrides can break on minor-release DOM changes.
  7. Managed-package compatibility — a subclass declared in a managed namespace renders fine, but consumers cannot add new custom types on top without a new subclass in their own namespace.

Output Artifacts

ArtifactDescription
Custom datatable subclass (.js + .js-meta.xml)The extends LightningDatatable component with static customTypes and template imports
Display / edit template .html filesOne per custom type for render; one more per editable type for inline edit
Column configuration guidanceSample column array showing type, typeAttributes, and editable wiring for the host component
Checker outputFindings from check_lwc_custom_datatable_types.py: missing typeAttributes, orphaned templates, non-subclass usage

Package files