Description
🔍 Search Terms
TypeScript Extensions
Declaration merging
✅ Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
A proposal to add first-class extensions to TypeScript, enabling developers to extend existing classes in a type-safe and modular way.
Well, right now you could:
- Mess with prototypes (yuck!)
- Write utility functions (and lose that nice method chaining)
- Use mixins (and deal with all that composition complexity)
- Try declaration merging (and give up on proper encapsulation)
TypeScript extensions would elegantly bridge the gap between JavaScript's prototype-based inheritance and TypeScript's type system by providing a safe, controlled way to extend objects that aligns with both languages' core principles:
1. Alignment with JavaScript's Prototype Nature
- JavaScript's prototype system already allows runtime extension of objects
- Extensions would provide a type-safe wrapper around this fundamental JavaScript capability
- The compilation output would leverage the existing prototype mechanism rather than introducing new runtime concepts
2. Type-Safe Prototype Enhancement
Instead of unsafe and discouraging modifications like:
declare global {
interface Array<T> {
first(): T | undefined;
}
}
Array.prototype.first = function () {
const [first] = this;
return first;
};
We get something clean and simple like this:
extension Array<T> {
first(): T | undefined {
const [first] = this;
return first;
}
}
3. Zero Runtime Overhead
- Extensions compile directly to prototype methods
- No additional wrapper objects or proxies needed
- No runtime type checking required
- Tree-shakeable by design
The original request was made in 2014, but with TypeScript's current maturity and the proven success of extensions in other languages, it might be time to revisit this feature. The modern TypeScript team might have a different perspective now, especially given:
- Growing complexity of TypeScript applications
- Need for better code organization patterns
- Success of similar features in other languages
- TypeScript's enhanced transformation capabilities
📃 Motivating Example
Swift, Kotlin, and C# — these languages have proven that extensions aren’t just a nice-to-have feature — they’re a game-changer for code organization and maintainability. What’s exciting is that we can learn from all of these implementations, taking the best parts of each while adapting them to TypeScript’s unique characteristics and use cases:
Swift Extensions
Kotlin Extensions
C# Extension Methods
💻 Use Cases
// Example 1: Array Extensions
// Shows how to add common utility methods to arrays with proper TypeScript generics.
extension Array<T> {
first(): T | undefined {
const [first] = this;
return first;
}
isEmpty(): boolean {
return this.length === 0;
}
}
['A', 'B', 'C'].first(); // returns 'A'
[].isEmpty(); // returns `true`
// Example 2: Date Extensions with Private State
// Demonstrates how extensions can use private fields (#today) for encapsulation.
extension Date {
#today = new Date();
isToday(): boolean {
return this.#today.toDateString() === this.toDateString();
}
}
const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));
yesterday.isToday(); // returns `false`
// Example 3: Number Extensions with Method Chaining
// Shows how to extend primitives with mathematical utilities.
// The clamp method constrains a number between min and max values,
// demonstrating how extensions can make mathematical operations more readable.
// Note the double dot (..) syntax required for number methods.
extension Number {
clamp(min: number, max: number): number {
return Math.min(Math.max(this, min), max);
}
}
10..clamp(0, 5); // returns 5
// Example 4: Class Layer Organization with Extensions
// Shows how extensions enable logical grouping of class functionality
// while maintaining clean separation of concerns and type safety.
// service.ts
class Service {
// Implement core state management
}
// actions.ts
import { Service } from './service';
extension Service {
add(...): ... {
// Implement
}
remove(...): ... {
// Implement
}
update(...): ... {
// Implement
}
complete(...): ... {
// Implement
}
}
// queries.ts
import { Service } from './service';
extension Service {
getAll(): ... {
// Implement
}
getById(...): ... {
// Implement
}
search(...): ... {
// Implement
}
}