Type declarations (.d.ts files) allow TypeScript to understand JavaScript libraries and provide type checking and autocomplete. DefinitelyTyped is a massive repository of type declarations for thousands of JavaScript libraries. Let's master type declarations!
DefinitelyTyped and @types Packages
DefinitelyTyped hosts community-maintained type declarations for JavaScript libraries. Install them via npm with @types/ prefix.
# Install type declarations for popular libraries
npm install --save-dev @types/node
npm install --save-dev @types/react
npm install --save-dev @types/express
npm install --save-dev @types/lodash
npm install --save-dev @types/jest
# Or with a library that doesn't have built-in types
npm install lodash
npm install --save-dev @types/lodash
# TypeScript will automatically find these types
# No import needed for most cases// After installing @types/lodash
import _ from 'lodash';
// TypeScript now knows all lodash methods!
const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, (n) => n * 2);
// TypeScript knows: doubled is number[]
const sum = _.sum(numbers);
// TypeScript knows: sum is number
const chunked = _.chunk(numbers, 2);
// TypeScript knows: chunked is number[][]
// After installing @types/node
import * as fs from 'fs';
import * as path from 'path';
// TypeScript knows Node.js APIs
fs.readFile('file.txt', 'utf8', (err, data) => {
// err: NodeJS.ErrnoException | null
// data: string
if (err) {
console.error(err);
return;
}
console.log(data);
});
const filePath = path.join(__dirname, 'file.txt');
// filePath: string
// After installing @types/express
import express, { Request, Response, NextFunction } from 'express';
const app = express();
app.get('/users/:id', (req: Request, res: Response) => {
const userId = req.params.id; // string
res.json({ id: userId });
});
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});π‘ Finding Type Declarations
Search for @types/package-name on npm. If a package has built-in TypeScript support, you don't need @types. Check the package's package.json for a "types" or "typings" field.
Basic Declaration Files
// Global type declarations
// These are available everywhere without imports
// Declare global variables
declare const API_URL: string;
declare const VERSION: string;
declare const DEBUG: boolean;
// Declare global functions
declare function log(message: string): void;
declare function formatPrice(amount: number): string;
// Declare global classes
declare class Storage {
get(key: string): any;
set(key: string, value: any): void;
remove(key: string): void;
clear(): void;
}
// Declare global interfaces
interface Window {
gtag: (command: string, ...args: any[]) => void;
dataLayer: any[];
}
// Declare global types
type UUID = string;
type Timestamp = number;
type Currency = 'NGN' | 'USD' | 'EUR';
// Now you can use these anywhere in your project
const url = API_URL; // TypeScript knows this is string
const version = VERSION; // string
const isDebug = DEBUG; // boolean
log("Application started"); // OK
const price = formatPrice(50000); // Returns string
const storage = new Storage();
storage.set('user', { id: 1 });
// Using augmented Window interface
window.gtag('config', 'GA_MEASUREMENT_ID');
window.dataLayer.push({ event: 'page_view' });
const userId: UUID = "550e8400-e29b-41d4-a716-446655440000";
const now: Timestamp = Date.now();
const currency: Currency = 'NGN';Module Declarations
// Declare a module that doesn't have types
declare module 'my-js-library' {
// Export types for the module
export interface Config {
apiKey: string;
timeout: number;
}
export class Client {
constructor(config: Config);
request(endpoint: string): Promise<any>;
}
export function initialize(apiKey: string): void;
export function getVersion(): string;
// Default export
export default Client;
}
// Declare module for JSON files
declare module '*.json' {
const value: any;
export default value;
}
// Declare module for CSS files
declare module '*.css' {
const classes: { [key: string]: string };
export default classes;
}
// Declare module for image files
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}
// Declare module for environment variables
declare module 'process' {
global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_URL: string;
DATABASE_URL: string;
SECRET_KEY: string;
}
}
}
}
// Using the declarations
import Client, { Config, initialize } from 'my-js-library';
const config: Config = {
apiKey: 'abc123',
timeout: 5000
};
const client = new Client(config);
initialize('api-key');
// Using JSON import
import data from './data.json';
console.log(data);
// Using CSS import
import styles from './App.css';
const className = styles.container;
// Using image import
import logo from './logo.png';
console.log(logo); // string (image path)
// Using process.env
const apiUrl = process.env.API_URL; // string
const nodeEnv = process.env.NODE_ENV; // 'development' | 'production' | 'test'Namespace Declarations
// Declare namespace for libraries like jQuery
declare namespace $ {
// Static methods
function ajax(url: string, settings?: AjaxSettings): JqXHR;
function get(url: string, data?: any, success?: Function): JqXHR;
function post(url: string, data?: any, success?: Function): JqXHR;
// Nested namespace
namespace fn {
function extend(obj: any): void;
}
// Interfaces
interface AjaxSettings {
method?: string;
data?: any;
success?: (data: any) => void;
error?: (error: any) => void;
}
interface JqXHR {
done(callback: Function): JqXHR;
fail(callback: Function): JqXHR;
always(callback: Function): JqXHR;
}
}
// Using the jQuery declarations
$.ajax('/api/users', {
method: 'GET',
success: (data) => {
console.log(data);
},
error: (error) => {
console.error(error);
}
});
$.get('/api/products').done((data) => {
console.log(data);
});
// Declare namespace for your own library
declare namespace MyUtils {
function formatCurrency(amount: number): string;
function parseDate(date: string): Date;
namespace String {
function capitalize(str: string): string;
function truncate(str: string, length: number): string;
}
namespace Array {
function chunk<T>(arr: T[], size: number): T[][];
function unique<T>(arr: T[]): T[];
}
}
// Using custom namespace
const price = MyUtils.formatCurrency(50000);
const capitalized = MyUtils.String.capitalize("hello");
const chunks = MyUtils.Array.chunk([1, 2, 3, 4, 5], 2);Module Augmentation
// Extend existing module types
// Add custom properties to Express Request
import 'express';
declare module 'express' {
interface Request {
user?: {
id: number;
email: string;
role: string;
};
requestId?: string;
startTime?: number;
}
interface Response {
sendSuccess(data: any): Response;
sendError(message: string, statusCode?: number): Response;
}
}
// Now you can use the extended types
import { Request, Response, NextFunction } from 'express';
function authMiddleware(req: Request, res: Response, next: NextFunction) {
// TypeScript now knows about req.user
req.user = {
id: 1,
email: 'yusuf@example.com',
role: 'admin'
};
req.requestId = 'req-' + Date.now();
req.startTime = Date.now();
next();
}
function getUserHandler(req: Request, res: Response) {
if (!req.user) {
return res.sendError('Unauthorized', 401);
}
res.sendSuccess({
user: req.user,
requestId: req.requestId
});
}
// Augment array prototype
declare global {
interface Array<T> {
first(): T | undefined;
last(): T | undefined;
random(): T | undefined;
}
}
// Now arrays have these methods (in types)
const numbers = [1, 2, 3, 4, 5];
const first = numbers.first(); // number | undefined
const last = numbers.last(); // number | undefined
const random = numbers.random(); // number | undefined
// Augment String prototype
declare global {
interface String {
capitalize(): string;
truncate(length: number): string;
}
}
const text = "hello world";
const capitalized = text.capitalize(); // string
const truncated = text.truncate(5); // stringPractical Examples
Example 1: Creating Types for Legacy JavaScript Library
// Typing a legacy charting library without TypeScript support
declare module 'legacy-charts' {
// Chart configuration
export interface ChartConfig {
type: 'line' | 'bar' | 'pie' | 'doughnut';
data: ChartData;
options?: ChartOptions;
}
export interface ChartData {
labels: string[];
datasets: Dataset[];
}
export interface Dataset {
label: string;
data: number[];
backgroundColor?: string | string[];
borderColor?: string | string[];
borderWidth?: number;
}
export interface ChartOptions {
responsive?: boolean;
maintainAspectRatio?: boolean;
title?: {
display: boolean;
text: string;
};
legend?: {
display: boolean;
position?: 'top' | 'bottom' | 'left' | 'right';
};
scales?: {
y?: {
beginAtZero: boolean;
};
};
}
// Main Chart class
export class Chart {
constructor(canvas: HTMLCanvasElement, config: ChartConfig);
update(): void;
destroy(): void;
reset(): void;
data: ChartData;
options: ChartOptions;
}
// Utility functions
export function createChart(
canvas: HTMLCanvasElement,
config: ChartConfig
): Chart;
export function getDefaults(type: ChartConfig['type']): ChartOptions;
}
// Using the typed library
import { Chart, ChartConfig, createChart } from 'legacy-charts';
const config: ChartConfig = {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
datasets: [{
label: 'Sales (β¦)',
data: [50000, 75000, 60000, 90000, 85000],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
title: {
display: true,
text: 'Monthly Sales'
},
scales: {
y: {
beginAtZero: true
}
}
}
};
const canvas = document.getElementById('myChart') as HTMLCanvasElement;
const chart = new Chart(canvas, config);
// Update data
chart.data.datasets[0].data = [55000, 80000, 65000, 95000, 90000];
chart.update();Example 2: Global Script Variables
// Type definitions for scripts loaded via <script> tags
// Google Analytics
declare global {
interface Window {
gtag: (
command: 'config' | 'event' | 'set',
targetId: string,
params?: Record<string, any>
) => void;
dataLayer: any[];
}
}
// Facebook Pixel
declare global {
interface Window {
fbq: (
command: 'track' | 'trackCustom' | 'init',
event: string,
params?: Record<string, any>
) => void;
}
}
// Paystack
declare namespace Paystack {
interface PaymentData {
email: string;
amount: number;
currency?: string;
ref?: string;
metadata?: Record<string, any>;
}
interface PaymentResponse {
reference: string;
trans: string;
status: 'success' | 'failed';
message: string;
}
interface PaystackOptions {
key: string;
email: string;
amount: number;
currency?: string;
ref?: string;
metadata?: Record<string, any>;
onClose?: () => void;
callback?: (response: PaymentResponse) => void;
}
class PaystackPop {
constructor();
newTransaction(options: PaystackOptions): void;
}
}
declare global {
interface Window {
PaystackPop: typeof Paystack.PaystackPop;
}
}
// Using the global declarations
function trackPageView(page: string): void {
if (window.gtag) {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: page
});
}
}
function trackEvent(eventName: string, params: Record<string, any>): void {
if (window.gtag) {
window.gtag('event', eventName, params);
}
if (window.fbq) {
window.fbq('trackCustom', eventName, params);
}
}
function initiatePayment(): void {
const handler = new window.PaystackPop();
handler.newTransaction({
key: 'pk_test_xxxxx',
email: 'yusuf@example.com',
amount: 50000 * 100, // Amount in kobo
currency: 'NGN',
metadata: {
custom_fields: [
{
display_name: 'Customer Name',
variable_name: 'customer_name',
value: 'Yusuf Ibrahim'
}
]
},
callback: (response) => {
console.log('Payment successful:', response.reference);
trackEvent('purchase', {
value: 50000,
currency: 'NGN',
transaction_id: response.reference
});
},
onClose: () => {
console.log('Payment cancelled');
}
});
}
// Track page view
trackPageView('/products');
// Track custom event
trackEvent('add_to_cart', {
item_id: 'PROD-123',
item_name: 'Laptop',
value: 450000
});Example 3: Environment Variables
// Type-safe environment variables
declare global {
namespace NodeJS {
interface ProcessEnv {
// Required environment variables
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
API_URL: string;
SECRET_KEY: string;
// Optional environment variables
PORT?: string;
REDIS_URL?: string;
SMTP_HOST?: string;
SMTP_PORT?: string;
// Third-party API keys
PAYSTACK_PUBLIC_KEY: string;
PAYSTACK_SECRET_KEY: string;
GOOGLE_ANALYTICS_ID?: string;
FACEBOOK_PIXEL_ID?: string;
// Feature flags
ENABLE_REGISTRATION?: 'true' | 'false';
ENABLE_PAYMENTS?: 'true' | 'false';
DEBUG?: 'true' | 'false';
}
}
}
export {};
// Using typed environment variables
const config = {
nodeEnv: process.env.NODE_ENV,
database: process.env.DATABASE_URL,
api: process.env.API_URL,
port: parseInt(process.env.PORT || '3000'),
paystack: {
publicKey: process.env.PAYSTACK_PUBLIC_KEY,
secretKey: process.env.PAYSTACK_SECRET_KEY
},
features: {
registration: process.env.ENABLE_REGISTRATION === 'true',
payments: process.env.ENABLE_PAYMENTS === 'true',
debug: process.env.DEBUG === 'true'
}
};
// TypeScript validates all these
console.log(`Running in ${config.nodeEnv} mode`);
console.log(`Database: ${config.database}`);
console.log(`Port: ${config.port}`);
if (config.features.payments) {
console.log('Payments enabled');
}
// Helper function with type checking
function getEnvVar(key: keyof NodeJS.ProcessEnv): string {
const value = process.env[key];
if (!value) {
throw new Error(`Environment variable ${key} is not set`);
}
return value;
}
const apiUrl = getEnvVar('API_URL'); // Type-safe
// const invalid = getEnvVar('INVALID_KEY'); // β TypeScript error!Test Your Knowledge
Type Safety Check
Which declaration file pattern is correct?
// Option A - Global declaration
// myLibrary.d.ts
declare const VERSION: string;
declare function calculate(a: number, b: number): number;
declare class Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
}
// Option B - Module declaration
// lodash.d.ts
declare module 'lodash' {
export function map(arr: any[], fn: Function): any[];
export function filter(arr: any[], fn: Function): any[];
}
// Option C - Namespace declaration
// jQuery.d.ts
declare namespace $ {
function ajax(url: string): void;
function get(url: string): void;
}
// Option D - Missing declare keyword
// utils.d.ts
const PI = 3.14159; // β Missing 'declare'
function add(a: number, b: number): number; // β Missing 'declare'Common TypeScript Error
// File: utils.d.ts
// Trying to declare without 'declare' keyword
const API_URL = "https://api.example.com"; // β Error!
function fetchData(url: string): Promise<any>; // β Error!
class HttpClient { // β Error!
get(url: string): Promise<any>;
}
// Using in application
// app.ts
import { API_URL } from './utils'; // Can't find module!
const data = await fetchData('/users'); // fetchData not found!β Initializers are not allowed in ambient contexts. An implementation cannot be declared in ambient contexts.
What's Wrong?
Declaration files (.d.ts) describe types, not implementations. Use 'declare' keyword and don't include actual values or implementations.
// File: utils.d.ts
// β
Correct: Using 'declare' keyword
declare const API_URL: string;
declare function fetchData(url: string): Promise<any>;
declare class HttpClient {
get(url: string): Promise<any>;
post(url: string, data: any): Promise<any>;
}
// β
For module declarations
declare module 'my-utils' {
export const API_URL: string;
export function fetchData(url: string): Promise<any>;
export class HttpClient {
get(url: string): Promise<any>;
post(url: string, data: any): Promise<any>;
}
}
// Using in application
// app.ts
import { API_URL, fetchData, HttpClient } from 'my-utils';
const client = new HttpClient();
const data = await client.get('/users');
// β
For extending existing types
declare module 'express' {
interface Request {
user?: {
id: number;
email: string;
};
}
}
// Now you can use req.user in Express
import { Request, Response } from 'express';
function handler(req: Request, res: Response) {
if (req.user) {
console.log(`User: ${req.user.email}`);
}
}Solution: Add 'declare' keyword and remove initializers
Best Practices
- Use @types packages when available - don't recreate existing types
- Place .d.ts files in types/ folder - keep declarations organized
- Use declare keyword in ambient declarations
- Don't include implementations in .d.ts files - only types
- Use module augmentation to extend existing types
- Document complex types with JSDoc comments
- Keep declarations DRY - avoid duplication
- Version control .d.ts files - commit them to git
// β
Good: Organized type declarations
// types/global.d.ts
declare global {
interface Window {
customMethod(): void;
}
}
export {};
// β
Good: Module declaration
// types/custom-lib.d.ts
declare module 'custom-lib' {
export function doSomething(): void;
}
// β
Good: Module augmentation
// types/express.d.ts
import 'express';
declare module 'express' {
interface Request {
user?: { id: number };
}
}
// β
Good: Documented types
/**
* Configuration for the payment gateway
*/
declare module 'payment-lib' {
/**
* Initialize payment with user details
* @param email - User's email address
* @param amount - Amount in smallest currency unit (kobo for NGN)
*/
export function initPayment(email: string, amount: number): void;
}
// β Avoid: Including implementations
// types/utils.d.ts
declare function add(a: number, b: number): number {
return a + b; // β No implementations!
}
// β
Correct:
declare function add(a: number, b: number): number;
// β Avoid: Missing declare keyword
// types/globals.d.ts
const API_URL = "https://api.example.com"; // β Missing 'declare'
// β
Correct:
declare const API_URL: string;
// β Avoid: Duplicating existing types
// types/lodash.d.ts
declare module 'lodash' {
// Don't recreate - use @types/lodash instead!
}
// β
Correct:
// npm install --save-dev @types/lodashKey Takeaways
- Use
@types/package-namefor existing libraries declarekeyword creates ambient declarations- .d.ts files describe types without implementations
- Module declarations type external JavaScript libraries
- Module augmentation extends existing module types
- Global declarations work without imports
- Namespace declarations organize related types
- DefinitelyTyped has types for thousands of libraries
- Type environment variables for better safety
- Declaration files enable TypeScript for any JavaScript
What's Next?
Excellent work! You've mastered type declarations and can now work with any JavaScript library in TypeScript. Next, we'll explore TypeScript Best Practicesβcode organization, naming conventions, configuration, and patterns for writing production-ready TypeScript. This final tutorial brings everything together!
Get ready to master professional TypeScript development!