JavaScript to TypeScript Migration Best Practices
Guide for taking a JavaScript codebase to strict, modern TypeScript without a big-bang rewrite. Contains 42 rules across 7 categories, prioritized by impact to drive an incremental, file-by-file migration that keeps the build compiling at every step.
When to Apply
Reference these guidelines when:
- Converting a
.jscodebase to.ts(whole project or one module at a time) - Adding types to existing JavaScript via JSDoc or annotations
- Choosing a
tsconfigandallowJsstrategy for a mixed JS/TS repo - Turning on
strictmode or individual strict flags on a large codebase - Replacing
any,ascasts, and!assertions left over from a quick conversion - Validating external data (JSON, env, API responses) so the types you wrote are true at runtime
- Converting CommonJS to ESM, prototypes to classes, and other JS idioms to TS
- Updating the build, runner, and CI to type-check and publish TypeScript
How the Migration Flows
tsconfig & strategy → strictness ratchet → type the surfaces → kill any/casts
→ validate runtime boundaries → convert JS idioms → tooling/build/CI
Decisions at the front cascade: a wrong tsconfig or a top-down conversion order forces you to re-type modules twice, and an early any flood poisons everything downstream. Work from the front of this pipeline and from the leaves of the dependency graph inward.
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Migration Setup & tsconfig | CRITICAL | setup- |
| 2 | Strictness Ratcheting | CRITICAL | strict- |
| 3 | Typing Public Surfaces | HIGH | surface- |
| 4 | Replacing any & Unsafe Casts |
HIGH | unsafe- |
| 5 | Runtime Data Validation | MEDIUM-HIGH | runtime- |
| 6 | JS-to-TS Idiom Conversion | MEDIUM | idiom- |
| 7 | Tooling & Build Migration | LOW-MEDIUM | tooling- |
Quick Reference
1. Migration Setup & tsconfig (CRITICAL)
setup-allowjs-checkjs-bridge— Enable allowJs and checkJs for incremental migrationsetup-migrate-leaves-first— Convert dependency leaves before their dependentssetup-jsdoc-before-rename— Type JS with JSDoc and @ts-check before renamingsetup-prefer-ts-expect-error— Prefer @ts-expect-error over @ts-ignore for suppressionssetup-skiplibcheck-during-migration— Set skipLibCheck to silence third-party type noisesetup-modern-module-resolution— Set module and moduleResolution to a modern pairsetup-noemitonerror-isolatedmodules— Enable isolatedModules and noEmitOnError for safe output
2. Strictness Ratcheting (CRITICAL)
strict-enable-flags-incrementally— Enable strict flags one at a time, not all at oncestrict-prioritize-null-checks— Prioritize strictNullChecks for the highest bug yieldstrict-no-implicit-any— Enable noImplicitAny to surface every untyped valuestrict-no-unchecked-indexed-access— Enable noUncheckedIndexedAccess for index safetystrict-use-unknown-in-catch— Type caught errors as unknown, not anystrict-exact-optional-property-types— Separate missing from undefined with exactOptionalPropertyTypes
3. Typing Public Surfaces (HIGH)
surface-annotate-exported-signatures— Annotate exported function signatures explicitlysurface-replace-jsdoc-with-types— Replace JSDoc type tags with real annotationssurface-type-default-params— Type default and optional parameters preciselysurface-interface-for-object-args— Convert loose object arguments to named interfacessurface-type-callbacks— Type callback and higher-order parameterssurface-type-class-fields— Declare class field types instead of relying on assignment
4. Replacing any & Unsafe Casts (HIGH)
unsafe-prefer-unknown-over-any— Replace any with unknown at untrusted boundariesunsafe-eliminate-as-casts— Replace as casts with narrowing or validationunsafe-avoid-double-assertion— Avoid double assertions that force unrelated typesunsafe-type-dynamic-property-access— Type dynamic property access with Records or index signaturesunsafe-replace-function-type— Replace the Function type with specific call signaturesunsafe-narrow-instead-of-nonnull— Narrow values instead of using the non-null assertion
5. Runtime Data Validation (MEDIUM-HIGH)
runtime-validate-external-data— Validate external data at the boundaryruntime-type-environment-variables— Parse and type environment variables onceruntime-derive-types-from-schemas— Derive static types from runtime schemasruntime-type-guards-at-boundaries— Write type guards for untyped library returnsruntime-type-json-parse— Type JSON.parse results through validation
6. JS-to-TS Idiom Conversion (MEDIUM)
idiom-require-to-import— Convert require and module.exports to ESM syntaxidiom-prototype-to-class— Convert prototype constructors to class syntaxidiom-type-only-imports— Use import type for type-only importsidiom-replace-arguments-object— Replace the arguments object with rest parametersidiom-enum-to-union-or-const— Convert frozen-object enums to const objects or unionsidiom-default-export-to-named— Prefer named exports over default exportsidiom-optional-chaining-over-guards— Replace manual existence guards with optional chaining
7. Tooling & Build Migration (LOW-MEDIUM)
tooling-declare-untyped-modules— Provide ambient declarations for untyped dependenciestooling-install-types-packages— Install @types packages before casting library returnstooling-use-tsx-over-ts-node— Run TypeScript directly with tsx instead of ts-node flagstooling-emit-declaration-files— Emit declaration files for migrated librariestooling-typecheck-in-ci— Add a type-check step to CI separate from the build
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions — Category structure and impact levels
- Rule template — Template for adding new rules
Related Skills
typescript-refactor— Refactoring and modernizing code that is already TypeScripttypescript-advanced-patterns— Advanced type-level patterns once the migration is done
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |