
experience-lwc-typescript-migrate
Use when converting an existing JavaScript Lightning Web Component (.js, .html, .css) to TypeScript with full type annotations and a matching .d.ts file that exposes only the component's @api surface. TRIGGER when the user says "convert LWC to TypeScript", "migrate LWC to TS", "rename .js to .ts for this component", "add types to my LWC", "generate .d.ts for this LWC", "type-annotate @api properties", or "produce declare module 'c/componentName' definitions". DO NOT TRIGGER when the user is authoring a brand-new LWC from scratch (use experience-lwc-generate), generating Jest tests for an existing LWC (use experience-lwc-generate), or migrating an Aura component to LWC.
- Installs
- 2.3K
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/forcedotcom/sf-skills --skill experience-lwc-typescript-migrate- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads experience-lwc-typescript-migrate automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Converting LWC to TypeScript
Convert a Lightning Web Component bundle from JavaScript to TypeScript. The
deliverable is a fully-typed .ts implementation plus a .d.ts file
that only exposes @api members (the public surface other LWCs consume).
When to Use This Skill
- User wants to migrate a single component or a folder of components from
.jsto.ts. - User needs a
.d.tsfor an existing LWC so other components (or an external TypeScript host) can import it safely. - User is adding type annotations to an already-renamed
.tsLWC that hasn't been properly typed yet. - User wants JSDoc-style type hints upgraded to real TypeScript types.
Prerequisites
- The component builds and runs correctly in JavaScript today.
gitis available (the rename must preserve history viagit mv).- A TypeScript compiler is wired into the build (either the SFDX TS
pipeline or a standalone
tscstep).
Workflow
Step 1 — Read the component
Open every file in the bundle:
componentName/
├── componentName.js
├── componentName.html
├── componentName.css
└── (possibly) __tests__/, __utam__/, existing .d.ts
Understand:
- What extends
LightningElement? What is the class name? - Which fields and methods carry the
@apidecorator? - Which properties/methods have existing JSDoc (use as a type hint starting point, but validate against actual usage — JSDoc lies).
- Which parameters / return types can you infer from how the code is called internally?
Step 2 — Rename .js → .ts using git mv
git mv componentName/componentName.js componentName/componentName.ts
Repeat for any helper .js files in the bundle (unless they're already
.ts). Never plain mv — that loses the history link TypeScript
reviewers rely on.
Step 3 — Add type annotations in the .ts
Apply types in this priority order so you stop as soon as the public contract is solid:
@apiproperties and methods first. Generate JSDoc if it's missing, then translate JSDoc types to TS syntax (string,number,boolean,Promise<T>). Validate each JSDoc claim against the code before trusting it.- Complex shapes become
interfaceortypealiases — not inline shapes repeated everywhere. - Optional members use
?only when the value is genuinely allowed to beundefined. Do not sprinkle?defensively. - Private/internal state — still type it, but don't export the
types. Use
privatefor members that must never be touched by consumers. - Event handlers — prefer precise DOM event types:
MouseEventforonclick(and other click-like handlers).clickis dispatched as aMouseEvent— including keyboard-activated clicks — so typing it asPointerEventwould let handlers rely on pointer-only fields (pointerType,pressure, etc.) that are undefined in those cases.PointerEventforonpointerdown/onpointerup/onpointermoveand otherpointer*handlers where pointer-specific fields are actually meaningful.CustomEvent<{ detail: ... }>for LWC custom events.Eventis the last resort; document why when using it.
- Async methods always return
Promise<T>— never bareT. - Avoid
any. If you genuinely can't type something, useunknownand narrow with a type guard.
Reference patterns
Load [[assets/type-patterns.ts|assets/type-patterns.ts]] as an inline example covering property types, method types, and event handler types.
Step 4 — Generate the .d.ts
Create componentName.d.ts next to the .ts. It must:
- Contain only
@apimembers — no private state, no internal methods, no lifecycle hooks unless they are themselves@api. - Preserve
@apiJSDoc verbatim (including@type,@required,@default,@param,@returnstags) directly above each declaration. - Declare the LWC module namespace
c/componentName(or the org's namespace if different).
Template: load [[assets/dts-template.ts|assets/dts-template.ts]] as the
starting .d.ts shape.
If the component has no @api members, still produce the module
declaration with a comment explaining there's no public surface — don't
skip the file.
Step 5 — Compile and test
- Run the TypeScript compiler (
tsc --noEmitor the build's equivalent). Resolve every error before calling it done; no@ts-ignorepatches. - Run the component's existing Jest tests. The behavior should be identical.
- Run the bundled consumer-finder unconditionally — empty output is a
valid result, not a reason to skip. The script resolves the search
paths from
sfdx-project.json'spackageDirectories(or falls back to<project-root>), rejects any entry that escapes the project root, and performs the LWC-import search internally so the invocation is fully deterministic:
"<skill_dir>/scripts/find-consumers.sh" "<project-root>" "<componentName>"
For each match, confirm the consumer's expected types still align with
the new .d.ts public surface.
Step 6 — Expected final bundle shape
componentName/
├── componentName.ts # Main TypeScript implementation
├── componentName.html # Template (unchanged)
├── componentName.css # Styles (unchanged)
└── componentName.d.ts # Type definitions (new)
Verification Checklist
Before conversion:
- Component is valid JS and all tests pass.
- You've identified every
@apimember and its intended type.
After conversion:
-
git mvwas used so history is preserved. - Every variable and parameter in the
.tshas a concrete type (no implicitany). - Complex object shapes live in
interface/typealiases, not inline repeats. - Optional
?is only on genuinely optional fields. -
.d.tsexists, declaresc/componentName, extendsLightningElement, includes only@apimembers. - Every
@apiJSDoc is preserved verbatim in the.d.ts. -
tscpasses with zero errors; no@ts-ignoreoranyused as a workaround. - Jest tests still pass.
Common Pitfalls
- Using
anyto silence errors. Solve the actual type instead. If the value is truly unknown, useunknown+ a type guard. - Including private members in the
.d.ts. The.d.tsis the public contract. Internal lifecycle and helpers must not leak. - Losing JSDoc during the rename. Scan before and after — JSDoc
comments on
@apimembers must appear in both the.tsand.d.ts. - Skipping
git mv. Makes review miserable and confuses blame. - Forgetting async return types.
foo()with anasynckeyword always returns aPromise. Declare it. - Typing
onclickasPointerEvent.clickis aMouseEvent(keyboard-triggered clicks included), soPointerEventfields likepointerTypeare undefined for those events. TypeonclickasMouseEvent; reservePointerEventforonpointer*handlers. UseMouseEvent | TouchEventonly when the code branches onTouchEventdistinctly.
Support Resources
FAQ
What does the experience-lwc-typescript-migrate skill do?
Use when converting an existing JavaScript Lightning Web Component (.js, .html, .css) to TypeScript with full type annotations and a matching .d.ts file that exposes only the component's @api surface. TRIGGER when the user says "convert LWC to TypeScript", "migrate LWC to TS", "rename .js to .ts for this component", "add types to my LWC", "generate .d.ts for this LWC", "type-annotate @api properties", or "produce declare module 'c/componentName' definitions". DO NOT TRIGGER when the user is authoring a brand-new LWC from scratch (use experience-lwc-generate), generating Jest tests for an existing LWC (use experience-lwc-generate), or migrating an Aura component to LWC.
How do I install experience-lwc-typescript-migrate?
Run npx skills add https://github.com/forcedotcom/sf-skills --skill experience-lwc-typescript-migrate in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/experience-lwc-typescript-migrate/ (personal) or .claude/skills/experience-lwc-typescript-migrate/ (project).
Which AI agents can use experience-lwc-typescript-migrate?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is experience-lwc-typescript-migrate safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains experience-lwc-typescript-migrate?
It is published by forcedotcom in forcedotcom/sf-skills. It has 2,304 recorded installs.
More from forcedotcom/sf-skills
platform-apex-generate
forcedotcom/sf-skills
Primary Apex authoring skill for class generation, refactoring, and review. ALWAYS ACTIVATE when the user mentions Apex, .cls, triggers, or asks to create/refactor a class (service, selector, domain, batch, queueable, schedulable, invocable, DTO, utility, interface, abstract, exception, REST resource). Use this skill for requests involving SObject CRUD, mapping collections, fetching related records, scheduled jobs, batch jobs, trigger design, @AuraEnabled controllers, @RestResource endpoints, custom REST APIs, or code review of existing Apex.
platform-apex-test-generate
forcedotcom/sf-skills
Use to generate and validate Apex test classes with TestDataFactory patterns, bulk testing (251+ records), mocking, assertions, and disciplined test-fix loops. Use when creating Apex test classes (for triggers, services, controllers, batch jobs, queueables, and callouts), improving coverage, debugging or fixing failing Apex tests, or running test/coverage analysis. Triggers on Test.cls files, sf apex run test, coverage reports. Do NOT trigger for production Apex (use platform-apex-generate) or Jest/LWC tests.
platform-apex-test-run
forcedotcom/sf-skills
Apex test execution, coverage analysis, and test-fix loops with 120-point scoring. Use when the user runs Apex tests, checks code coverage, fixes failing tests, or touches Test.cls / Test.cls files. DO NOT TRIGGER when writing Apex production code (use platform-apex-generate), Agentforce agent testing (use agentforce-test), or Jest/LWC tests (use experience-lwc-generate).
platform-apex-logs-debug
forcedotcom/sf-skills
Salesforce debug log analysis and troubleshooting with 100-point scoring. TRIGGER when: user analyzes debug logs, hits governor limits, reads stack traces, or touches .log files from Salesforce orgs. DO NOT TRIGGER when: running Apex tests (use platform-apex-test-run), generating or fixing Apex code (use platform-apex-generate), or Agentforce session tracing (use agentforce-observe).
platform-soql-query
forcedotcom/sf-skills
Use when the user needs SOQL/SOSL authoring or optimization: natural-language-to-query generation, relationship queries, aggregates, query-plan/selectivity analysis, and performance or safety improvements. TRIGGER when the user writes, optimizes, or debugs SOQL/SOSL, touches .soql files, or asks about relationship queries, aggregates, or query performance. DO NOT TRIGGER for bulk data operations (use platform-data-manage), Apex DML logic (use platform-apex-generate), or report/dashboard queries.
platform-metadata-deploy
forcedotcom/sf-skills
Salesforce DevOps automation using sf CLI v2. TRIGGER when: user deploys metadata, validates a deploy or does a dry-run, deploys to a Production org, quick-deploys a validated job ID, ships/promotes to prod, creates/manages scratch orgs or sandboxes, sets up CI/CD pipelines, or troubleshoots deployment errors with sf project deploy. DO NOT TRIGGER when: writing Apex code, building LWC components (use experience-lwc-generate), creating metadata definitions (use platform-custom-object-generate), deleting metadata from an org (use platform-destructive-deploy), or querying org data (use platform-data-manage).
Popular code quality & workflow skills
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
improve-codebase-architecture
mattpocock/skills
Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
handoff
mattpocock/skills
Compact the current conversation into a handoff document for another agent to pick up.
domain-modeling
mattpocock/skills
Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.
codebase-design
mattpocock/skills
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
diagnosing-bugs
mattpocock/skills
Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.