//
Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Create and manage .d.ts declaration files for global types, module augmentation, untyped third-party packages, and custom ambient declarations in a React/Next.js project.
// types/global.d.ts - Global type declarations
type User = {
id: string;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
};
type ApiResponse<T> = {
data: T;
meta: {
page: number;
totalPages: number;
};
};// types/environment.d.ts - Typed environment variables
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
DATABASE_URL: string;
NEXT_PUBLIC_API_URL: string;
NEXT_PUBLIC_SITE_URL: string;
AUTH_SECRET: string;
}
}// types/modules.d.ts - Declaring untyped modules
declare module "some-untyped-library" {
export function doSomething(input: string): Promise<string>;
export function configure(options: { verbose: boolean }): void;
}
// Asset imports
declare module "*.svg" {
const content: React.FC<React.SVGProps<SVGSVGElement>>;
export default content;
}
declare module "*.png" {
const src: string;
export default src;
}
declare module "*.css" {
const classes: Record<string, string>;
export default classes;
}.d.ts files are declaration files that contain only type information, no runtime code. They tell TypeScript about types that exist at runtime but are not expressed in TypeScript source.types/ directory are included automatically if your tsconfig.json has "include": ["**/*.ts", "**/*.tsx"] or if the path matches an include pattern.declare) tell TypeScript "this exists at runtime, trust me." Use them for global variables, untyped modules, and environment extensions.declare module "module-name". This is how you add fields to ProcessEnv, extend Next.js types, or patch third-party library types.paths mappings, node_modules/@types/*, then custom .d.ts files in include-matched directories.Augmenting Next.js types:
// types/next-auth.d.ts - Extending next-auth session types
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
role: "admin" | "editor" | "viewer";
} & DefaultSession["user"];
}
}Extending Window:
// types/window.d.ts
declare global {
interface Window {
analytics: {
track: (event: string, properties?: Record<string, unknown>) => void;
identify: (userId: string) => void;
};
__ENV__: Record<string, string>;
}
}
export {}; // Required to make this a moduleTyped CSS Modules:
// types/css-modules.d.ts
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module "*.module.scss" {
const classes: { readonly [key: string]: string };
export default classes;
}Creating a global utility type:
// types/utils.d.ts
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Nullable<T> = T | null;declare global { } modify the global scope. The file must have at least one import or export to be treated as a module; add export {} if needed.declare module "x" augments a module. If the module already has types, your declarations are merged. If it does not, your declaration replaces the implicit any..d.ts files. They are erased during compilation and serve only as type hints.typeRoots in tsconfig.json to control where TypeScript looks for declaration files: "typeRoots": ["./types", "./node_modules/@types"].export {} in a file with declare global makes the file a script (not a module), and its declarations may not merge correctly.declare module "x") replace the module's types entirely unless you import from the module first. To augment, add an import statement.@types/* packages from DefinitelyTyped can conflict with bundled types in newer package versions. Check if the library ships its own types before installing @types/.skipLibCheck: true skips type-checking all .d.ts files, including yours. Errors in your custom declarations will be silently ignored.ProcessEnv) provide compile-time confidence but no runtime guarantee. The variable could still be missing at runtime. Always validate at startup.| Approach | Pros | Cons |
|---|---|---|
Custom .d.ts files | Full control, project-specific | Must maintain manually |
@types/* packages | Community-maintained, well-tested | May lag behind library updates |
Inline declare in source files | Co-located with usage | Pollutes source files |
Zod + z.infer | Runtime validation + type inference | Not suitable for ambient/global types |
typeRoots config | Explicit type resolution order | Easy to misconfigure |
| Module augmentation | Extends existing types cleanly | Requires understanding of module system |
export {} (even with no actual exports) forces the file to be treated as a module.// types/environment.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
DATABASE_URL: string;
NEXT_PUBLIC_API_URL: string;
}
}declare) tell TypeScript something exists at runtime without providing implementation.declare module "x") extends or replaces types of an existing module.// types/modules.d.ts
declare module "some-untyped-library" {
export function doSomething(input: string): Promise<string>;
export function configure(options: { verbose: boolean }): void;
}.d.ts file included by your tsconfig.json.import statement from the module before the declare module block to merge instead of replace.declare module "*.svg" {
const content: React.FC<React.SVGProps<SVGSVGElement>>;
export default content;
}
declare module "*.png" {
const src: string;
export default src;
}"typeRoots": ["./types", "./node_modules/@types"].node_modules/@types explicitly or lose access to DefinitelyTyped packages.skipLibCheck skips type-checking all .d.ts files, including your custom declarations.types/ directory will be silently ignored.@types/* when the library does not ship its own types and a community package exists.@types/* exists, or when you need project-specific overrides.@types/ -- duplicates can cause conflicts.// types/window.d.ts
declare global {
interface Window {
analytics: {
track: (event: string) => void;
};
}
}
export {};declare global to modify the global scope.export {}).Reviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥