SfSkills Domains Docs GitHub

Home / lwc / lwc-typescript-migration

lwc-typescript-migration

Plan, execute, and verify a Lightning Web Components JavaScript-to-TypeScript migration with an explicit Salesforce toolchain strategy, bundle-by-bundle sequencing, Lightning base-component types, generated-output ownership, tests, and rollback. Trigger keywords: migrate LWC to TypeScript, LWC .ts, defaultLwcLanguage, lightning-types, TypeScript LWC build. General TypeScript tutorials and non-Salesforce web applications are outside this package. NOT for performance refactoring unrelated to the language migration — use lwc/lwc-performance.

Open the skill folder on GitHub


LWC TypeScript Migration

Migrate Lightning Web Components one bundle at a time while preserving behavior and making the deployment contract explicit. Salesforce's TypeScript tooling changed materially during the Spring '26 rollout: earlier guidance compiled TypeScript locally and deployed JavaScript, while current Salesforce Extensions guidance describes first-class TypeScript projects and server-side type stripping. Pin the installed CLI/extensions/project template and validate the selected path rather than mixing both models.


Start With a Toolchain Decision

Record one strategy for the project or package directory.

StrategyUse whenSource of truthDeployment artifactRequired proof
Native TypeScript / type strippingThe installed Salesforce project tooling creates TypeScript projects, defaultLwcLanguage: "typescript" is supported, and the target validation environment accepts .ts bundles.ts source; generated local output is disposableRaw LWC bundle containing .tsType check, project deploy preview/validate, scratch or approved non-production deployment
Local compile to JavaScriptThe project or target toolchain follows the Spring '26 compile-before-deploy path, or CI deliberately owns transpilation.ts source plus deterministic build configGenerated .js bundleClean build from a fresh checkout, diff/manifest of generated output, deploy validation
JavaScript remains canonicalTooling, package, test, or delivery constraints are not ready.js source.jsDocument why migration is deferred and what unlocks it

Do not infer strategy from file extensions alone. Check the installed CLI command help, Salesforce Extensions version, generated project template, target API/release support, and an actual deployment validation. Record contradictions between current official pages rather than hiding them.


Project Preconditions

Before renaming a component:

  1. Identify all package directories and every LWC bundle with .js, .ts, templates, styles, metadata, tests, mocks, and shared modules.
  2. Capture a passing baseline: Jest, accessibility checks, ESLint/Code Analyzer, deployment validation, and representative behavior.
  3. Inspect sfdx-project.json, package.json, lockfile, tsconfig.json, .forceignore, .gitignore, .vscode/settings.json, and CI workflows.
  4. Confirm whether generated JavaScript is committed, ignored, or created only in CI. One owner must be explicit.
  5. Verify the installed Salesforce project template rather than copying a stale tsconfig.json from a blog.
  6. Create a migration branch and rollback point. Do not combine language migration with state-management, API, styling, or behavior redesign.

Configuration Contract

Salesforce DX project

For current first-class tooling, sfdx-project.json can declare the default language used by component generation:

{
  "packageDirectories": [
    { "path": "force-app", "default": true }
  ],
  "defaultLwcLanguage": "typescript",
  "sourceApiVersion": "67.0"
}

Treat the shown API version as an example, not a universal minimum. Preserve the project's supported version and validate feature behavior against the target org and installed tools.

For a new project, prefer the installed template when the CLI exposes the option:

sf project generate --name sample-typescript-project --lwc-language typescript

Run sf project generate --help first in automation because option availability is tied to the installed CLI/plugin version.

TypeScript and Lightning types

Current official Spring '26 guidance identifies TypeScript and @salesforce/lightning-types as development dependencies for Lightning base-component typing:

npm install --save-dev typescript @salesforce/lightning-types

Use the generated Salesforce tsconfig.json as the base. Current Salesforce Extensions guidance uses Salesforce-specific configuration, including erasableSyntaxOnly, standard decorators, ES module output, and a generated .sfdx base config. Do not hand-copy those settings across tool versions without regenerating or verifying them.

Generated output policy

Choose exactly one:

PolicySource controlCI responsibilityGuardrail
Raw TypeScript deploymentCommit .ts; ignore disposable dist or generated JSType-check and deploy/validate .tsFail if same-stem .js shadows .ts
Compile before deploymentCommit .ts; generated .js either reproducibly committed or generated in CIClean compile before every deployFail if generated JS differs from clean build
Transitional mixed projectBundle-level canonical source is declaredRun both JS and TS validationFail when one bundle contains competing same-stem .js and .ts

Never allow developers to edit generated JavaScript by hand.


For each bundle:

  1. Record current public API (@api properties/methods), events, wire adapters, Apex calls, navigation, message channels, DOM queries, and tests.
  2. Rename only the implementation file from .js to .ts; retain the bundle's .js-meta.xml metadata filename.
  3. Add types at boundaries first: public properties, method parameters/returns, custom-event detail, wire/Apex data, and DOM/base-component targets.
  4. Keep runtime behavior unchanged. Do not add optional chaining, refactor state, change event payloads, or rewrite error handling unless a separate change owns it.
  5. Replace broad any with interfaces, discriminated unions, unknown plus narrowing, or generated/declared data shapes.
  6. Type-check, run unit/accessibility tests, run Code Analyzer/ESLint, and validate deployment before moving to the next bundle.
  7. Compare public API, emitted events, rendered states, and error paths to the baseline.

Salesforce Boundary Types

TypeScript proves compile-time shape; Salesforce data and DOM values still require correct boundary handling.

Lightning base components

Use @salesforce/lightning-types for supported base-component element types instead of maintaining local approximations.

import { LightningElement } from 'lwc';
import type { LightningInput } from '@salesforce/lightning-types';

export default class ContactEditor extends LightningElement {
  handleChange(event: Event): void {
    const input = event.target as LightningInput;
    const value: string = input.value;
    // Preserve the component's existing behavior here.
    void value;
  }
}

A cast asserts a relationship; it does not validate runtime data. Cast only at a boundary you control and keep the target narrow.

Custom events

type SaveDetail = {
  recordId: string;
  source: 'manual' | 'automation';
};

const event = new CustomEvent<SaveDetail>('save', {
  detail: { recordId: this.recordId, source: 'manual' },
  bubbles: true,
  composed: false
});
this.dispatchEvent(event);

Preserve existing event names, detail shape, bubbling, and composition during migration. Changing them is an API change.

Apex and wire data

A TypeScript interface does not force Apex or UI API data to match it. Define only fields actually selected/returned, handle nullable values, and add runtime narrowing when the source is not guaranteed.

type AccountSummary = {
  Id: string;
  Name: string;
  AnnualRevenue?: number | null;
};

function isAccountSummary(value: unknown): value is AccountSummary {
  if (typeof value !== 'object' || value === null) return false;
  const candidate = value as Record<string, unknown>;
  return typeof candidate.Id === 'string' && typeof candidate.Name === 'string';
}

Do not use as AccountSummary to conceal an unverified server payload.


Testing and Static Analysis

A migration is complete only when all existing behavior remains covered.

GateWhat to prove
Type checkNo TypeScript errors under the committed/generated Salesforce config
JestExisting tests pass; tests and mocks resolve the TypeScript source strategy
AccessibilityExisting axe/accessibility assertions remain active
ESLint / Code AnalyzerTypeScript rules run for .ts; LWC rules still cover applicable sources
Clean buildFresh checkout plus lockfile produces the same deployable artifact
Deploy validationSelected strategy is accepted by a disposable or approved non-production target
Behavioral smokePublic properties/methods, events, wire/Apex states, and errors match baseline

Salesforce Code Analyzer v5's ESLint engine supports TypeScript analysis. ApexGuru is unrelated; it scans Apex .cls and .trigger files only.


Migration Review Checklist


Audit Command

Run the bundled project checker:

python3 skills/lwc/lwc-typescript-migration/scripts/check_lwc_typescript_migration.py --project path/to/sfdx-project

Use --strict to treat unsafe typing warnings as failures. The checker does not deploy; it inspects configuration, bundle collisions, deprecated preview flags, dependencies, scripts, and high-confidence source anti-patterns.


See the references for migration examples, contradictory toolchain guidance, and common TypeScript-specific failures.

Dependencies

Package files