TypeScript strict mode configuration for 2025. Recommended tsconfig.json settings, strict flags explained, moduleResolution strategies (Bundler vs NodeNext), verbatimModuleSyntax, noUncheckedIndexedAccess. Use when setting up TypeScript projects or migrating to stricter type safety.
/plugin marketplace add laurigates/claude-plugins/plugin install typescript-plugin@lgates-claude-pluginsThis skill is limited to using the following tools:
Modern TypeScript configuration with strict type checking for maximum safety and developer experience. This guide focuses on TypeScript 5.x best practices for 2025.
What is Strict Mode?
Key Capabilities
{
"compilerOptions": {
// Type Checking
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// Modules
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": false,
"verbatimModuleSyntax": true,
// Emit
"target": "ES2022",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"noEmit": false,
// Interop
"isolatedModules": true,
"allowJs": false,
"checkJs": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "build"]
}
{
"compilerOptions": {
// Type Checking - Maximum strictness
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"exactOptionalPropertyTypes": true,
// Modules (Bundler for Vite/Bun)
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": false,
"verbatimModuleSyntax": true,
// Emit (Vite handles bundling)
"target": "ES2022",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"jsx": "preserve", // Vite handles JSX
"noEmit": true, // Vite handles emit
// Interop
"isolatedModules": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// Path Mapping
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
},
"include": ["src/**/*", "vite.config.ts"],
"exclude": ["node_modules", "dist"]
}
{
"compilerOptions": {
// Type Checking
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Modules (NodeNext for Node.js)
"module": "NodeNext",
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": false,
"esModuleInterop": true,
"verbatimModuleSyntax": true,
// Emit (Node.js library)
"target": "ES2022",
"lib": ["ES2023"],
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// Interop
"isolatedModules": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
strict: true (Umbrella Flag)Enables all strict type-checking options:
{
"strict": true
// Equivalent to:
// "noImplicitAny": true,
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitThis": true,
// "alwaysStrict": true
}
Always enable strict: true for new projects.
noImplicitAnyDisallows variables with implicit any type.
// ❌ Error with noImplicitAny
function add(a, b) {
// ^ Error: Parameter 'a' implicitly has an 'any' type
return a + b;
}
// ✅ Correct
function add(a: number, b: number): number {
return a + b;
}
strictNullChecksnull and undefined are distinct from other types.
// ❌ Error with strictNullChecks
const name: string = null;
// ^^^^ Error: Type 'null' is not assignable to type 'string'
// ✅ Correct
const name: string | null = null;
// ✅ Correct (handle null explicitly)
function greet(name: string | null): string {
if (name === null) {
return 'Hello, stranger!';
}
return `Hello, ${name}!`;
}
strictFunctionTypesFunction parameter types are checked contravariantly.
type Logger = (msg: string | number) => void;
// ❌ Error with strictFunctionTypes
const log: Logger = (msg: string) => console.log(msg);
// ^^^^^^^^^^^^^ Error: Type '(msg: string) => void' is not assignable
// ✅ Correct
const log: Logger = (msg: string | number) => console.log(msg);
strictBindCallApplyCheck that bind, call, and apply are invoked correctly.
function greet(name: string, age: number) {
console.log(`${name} is ${age} years old`);
}
// ❌ Error with strictBindCallApply
greet.call(undefined, 'John', '25');
// ^^^^ Error: Argument of type 'string' is not assignable to 'number'
// ✅ Correct
greet.call(undefined, 'John', 25);
strictPropertyInitializationClass properties must be initialized.
class User {
// ❌ Error with strictPropertyInitialization
name: string;
// ^^^^^^ Error: Property 'name' has no initializer
// ✅ Correct (initialize in constructor)
name: string;
constructor(name: string) {
this.name = name;
}
// ✅ Correct (default value)
age: number = 0;
// ✅ Correct (definitely assigned assertion)
id!: number;
}
noImplicitThisDisallow this with implicit any type.
// ❌ Error with noImplicitThis
function logName() {
console.log(this.name);
// ^^^^ Error: 'this' implicitly has type 'any'
}
// ✅ Correct (explicit this parameter)
function logName(this: { name: string }) {
console.log(this.name);
}
noUncheckedIndexedAccess (Essential)Index signatures return T | undefined instead of T.
// Without noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['john'];
// Type: User (wrong - might be undefined)
// ✅ With noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['john'];
// Type: User | undefined (correct)
if (user) {
console.log(user.name); // Type narrowed to User
}
Always enable this flag to prevent runtime errors.
noImplicitOverrideRequire override keyword for overridden methods.
class Base {
greet() {
console.log('Hello');
}
}
class Derived extends Base {
// ❌ Error with noImplicitOverride
greet() {
// ^^^^^ Error: This member must have an 'override' modifier
console.log('Hi');
}
// ✅ Correct
override greet() {
console.log('Hi');
}
}
noPropertyAccessFromIndexSignatureForce bracket notation for index signatures.
type User = {
name: string;
[key: string]: string;
};
const user: User = { name: 'John', email: 'john@example.com' };
// ❌ Error with noPropertyAccessFromIndexSignature
console.log(user.email);
// ^^^^^ Error: Property 'email' comes from index signature, use bracket notation
// ✅ Correct
console.log(user['email']);
// ✅ Also correct (explicit property)
console.log(user.name);
noFallthroughCasesInSwitchPrevent fallthrough in switch statements.
function getDiscount(role: string): number {
switch (role) {
case 'admin':
return 0.5;
// ❌ Error with noFallthroughCasesInSwitch
case 'user':
// ^^^^ Error: Fallthrough case in switch
console.log('User discount');
case 'guest':
return 0.1;
}
}
// ✅ Correct
function getDiscount(role: string): number {
switch (role) {
case 'admin':
return 0.5;
case 'user':
console.log('User discount');
return 0.2; // Explicit return
case 'guest':
return 0.1;
default:
return 0;
}
}
exactOptionalPropertyTypesOptional properties cannot be set to undefined explicitly.
type User = {
name: string;
age?: number; // Type: number | undefined (implicit)
};
// ❌ Error with exactOptionalPropertyTypes
const user: User = { name: 'John', age: undefined };
// ^^^^^^^^^ Error: Type 'undefined' is not assignable
// ✅ Correct (omit property)
const user: User = { name: 'John' };
// ✅ Correct (assign a value)
const user2: User = { name: 'Jane', age: 25 };
moduleResolution: "Bundler" (Vite/Bun)Use for projects with bundlers (Vite, Webpack, Bun).
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Features:
exports field support// ✅ Works with Bundler
import config from './config.json';
import { add } from './utils'; // No .ts extension
moduleResolution: "NodeNext" (Node.js)Use for Node.js libraries and servers.
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}
Features:
type: "module".js extensions (even for .ts files)// ✅ Works with NodeNext (note .js extension)
import { add } from './utils.js';
import config from './config.json' assert { type: 'json' };
package.json:
{
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}
Prevents TypeScript from rewriting imports/exports.
{
"compilerOptions": {
"verbatimModuleSyntax": true
}
}
Benefits:
type imports required// ❌ Error with verbatimModuleSyntax
import { User } from './types';
// ^^^^^^ Error: 'User' is a type and must be imported with 'import type'
// ✅ Correct
import type { User } from './types';
// ✅ Correct (value import)
import { fetchUser } from './api';
// ✅ Correct (mixed import)
import { fetchUser, type User } from './api';
Replaces:
importsNotUsedAsValues (deprecated)preserveValueImports (deprecated){
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
Usage:
// Instead of
import { Button } from '../../../components/Button';
// Use
import { Button } from '@components/Button';
Vite/Bun configuration:
// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
},
},
});
{
"compilerOptions": {
// Start with these
"noImplicitAny": true,
"strictNullChecks": false, // Enable later
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": false, // Enable later
"noImplicitThis": true,
"alwaysStrict": true
}
}
# Check errors without emitting
bunx tsc --noEmit
# Fix files one at a time
bunx tsc --noEmit src/utils.ts
{
"compilerOptions": {
"strict": true, // Enable all at once
"noUncheckedIndexedAccess": true
}
}
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true
}
}
// Non-null assertion (use sparingly)
const element = document.getElementById('app')!;
// Optional chaining
const name = user?.profile?.name;
// Nullish coalescing
const displayName = user?.name ?? 'Anonymous';
// Type guard
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'name' in value;
}
// ❌ Unsafe
const value = obj[key]; // Type: T (wrong)
// ✅ Safe with noUncheckedIndexedAccess
const value = obj[key]; // Type: T | undefined
if (value !== undefined) {
// Type: T
console.log(value);
}
// ✅ Safe with assertion
const value = obj[key];
if (value === undefined) throw new Error('Key not found');
// Type: T (narrowed)
# Enable flags gradually
# Start with noImplicitAny, then add others
# Use @ts-expect-error for temporary fixes
// @ts-expect-error - TODO: Fix this type
const value: string = null;
# Install type definitions
bun add --dev @types/node @types/react
# Skip type checking for libraries
{
"compilerOptions": {
"skipLibCheck": true
}
}
// Bundler: No extension needed
import { add } from './utils';
// NodeNext: Requires .js extension
import { add } from './utils.js';
// Check moduleResolution setting
bunx tsc --showConfig | grep moduleResolution
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.