This TypeScript error occurs when you try to declare global types, interfaces, or modules in a regular .ts file instead of a .d.ts declaration file. Global declarations must be placed in declaration files to avoid polluting the global namespace in implementation files.
The "Global declaration in non-declaration file" error (TS2669) appears when you attempt to declare global types, interfaces, modules, or namespaces in a regular TypeScript implementation file (.ts or .tsx) rather than a declaration file (.d.ts). TypeScript enforces a clear separation between: 1. **Declaration files (.d.ts)**: Contain type information only, no runtime code 2. **Implementation files (.ts/.tsx)**: Contain both type information and runtime code Global declarations (using `declare global`, `declare module`, or ambient declarations) should only appear in declaration files because they affect the global namespace. When placed in implementation files, they can cause unexpected side effects and namespace pollution. The error prevents you from accidentally creating global types that could conflict with other parts of your codebase or third-party libraries. TypeScript wants you to be explicit about where global declarations live to maintain code clarity and prevent hard-to-debug type conflicts.
Create a separate declaration file (e.g., global.d.ts, types.d.ts, or declarations.d.ts) and move all global declarations there:
// WRONG - In a regular .ts file (src/utils.ts):
declare global {
interface Window {
myCustomProperty: string;
}
}
// Error: Global declaration in non-declaration file
// CORRECT - Create a declaration file (src/global.d.ts):
declare global {
interface Window {
myCustomProperty: string;
}
}
// Also correct - In a declaration file for module augmentations (src/module-augmentations.d.ts):
declare module "some-module" {
export interface SomeInterface {
extraField: boolean;
}
}Declaration files should:
- Have the .d.ts extension
- Contain only type declarations (no import statements unless they're type-only)
- Be included in your tsconfig.json include or files array
Make sure your declaration files are included in TypeScript's compilation:
// tsconfig.json
{
"compilerOptions": {
// ... other options
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.d.ts", // Include declaration files
"types/**/*.d.ts" // If you have a types folder
],
"exclude": ["node_modules"]
}If you have a dedicated types folder:
{
"include": [
"src/**/*",
"types/**/*.d.ts"
]
}For project references or monorepos, ensure declaration files are in the referenced project's include list.
Declaration files should not contain runtime code. If you need to reference types from other modules, use type-only imports:
// WRONG - Regular import in .d.ts file:
import { SomeType } from "./some-module";
// Error: Import declarations in a namespace cannot reference a module
// CORRECT - Type-only import:
import type { SomeType } from "./some-module";
// Also correct - Using import() type syntax:
declare global {
type MyType = import("./some-module").SomeType;
}
// For third-party modules:
import type { ReactNode } from "react";
declare global {
type CustomComponentProps = {
children: ReactNode;
};
}Type-only imports (import type) ensure no runtime code is included in declaration files.
Here are correct patterns for common global augmentations:
// 1. Extending global interfaces (Window, Document, etc.)
// In src/global.d.ts:
declare global {
interface Window {
myApp: {
version: string;
init(): void;
};
}
interface Document {
customMethod(): void;
}
}
// 2. Augmenting Node.js global
// In src/node-global.d.ts:
declare global {
namespace NodeJS {
interface Global {
myGlobalVar: string;
}
interface ProcessEnv {
CUSTOM_ENV_VAR: string;
}
}
}
// 3. Module augmentations for third-party libraries
// In src/module-augmentations.d.ts:
declare module "express" {
interface Request {
user?: {
id: string;
name: string;
};
}
}
// 4. Adding types to built-in objects
// In src/builtin-augmentations.d.ts:
interface Array<T> {
customMethod(): T[];
}
interface String {
toTitleCase(): string;
}All these should be in .d.ts files, not .ts files.
If you have a file that contains only types, convert it to a .d.ts file:
// BEFORE: src/types.ts (implementation file with only types)
export interface User {
id: string;
name: string;
}
export type Status = "active" | "inactive" | "pending";
// Global declaration causing error:
declare module "*.svg" {
const content: string;
export default content;
}
// AFTER: src/types.d.ts (declaration file)
export interface User {
id: string;
name: string;
}
export type Status = "active" | "inactive" | "pending";
// Global declaration now allowed:
declare module "*.svg" {
const content: string;
export default content;
}To convert:
1. Change file extension from .ts to .d.ts
2. Remove any runtime code (functions, classes with implementations, etc.)
3. Keep only type declarations, interfaces, and type aliases
4. Update imports to use import type if needed
5. Update any import statements in other files to reference the .d.ts file
Organize your declaration files logically:
# Recommended structure
src/
├── index.ts # Main entry point
├── global.d.ts # Global augmentations
├── types/
│ ├── index.d.ts # Re-export all types
│ ├── user.d.ts # User-related types
│ ├── api.d.ts # API types
│ └── module-augmentations.d.ts # Third-party module types
├── declarations/
│ ├── images.d.ts # Image module declarations
│ ├── css-modules.d.ts # CSS module declarations
│ └── env.d.ts # Environment variable types
└── lib/
└── types.d.ts # Library-specific types
# Alternative flat structure
src/
├── types.d.ts # All type declarations
├── global-augmentations.d.ts
└── module-declarations.d.tsUpdate tsconfig.json to include all declaration locations:
{
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.d.ts",
"src/types/**/*.d.ts",
"src/declarations/**/*.d.ts"
]
}This keeps global declarations separate from implementation logic.
### Understanding Declaration Files vs Implementation Files
Declaration Files (.d.ts):
- Contain only type information
- No runtime code allowed
- Can use declare keyword for ambient declarations
- Support global augmentations (declare global)
- Can augment modules (declare module)
- Compiled to empty .js files (or no output)
Implementation Files (.ts/.tsx):
- Contain both types and runtime code
- Cannot have global declarations
- Produce .js output when compiled
- Should import types from declaration files
### Ambient Declarations
Ambient declarations tell TypeScript about types that exist elsewhere (in JavaScript files, runtime environments, or third-party libraries):
// In a .d.ts file
declare const VERSION: string; // Global constant
declare function log(message: string): void; // Global function
declare class MyClass { // Global class
constructor(value: string);
method(): void;
}
declare namespace MyNamespace { // Global namespace
export interface Config {
enabled: boolean;
}
}These should always be in .d.ts files.
### Module Augmentation vs Declaration Merging
Module Augmentation (for third-party modules):
// In a .d.ts file
declare module "express" {
interface Request {
user?: User;
}
}Declaration Merging (for your own code):
// In user.ts (implementation file)
export interface User {
id: string;
name: string;
}
// In user-roles.ts (another implementation file)
export interface User {
roles: string[]; // Merges with the User interface above
}Declaration merging works in .ts files because you're merging within the same module scope, not globally.
### Global vs Module Scope
Global scope (affects entire project):
// In a .d.ts file
declare global {
interface Window {
myGlobal: string;
}
}
// Can be used anywhere without importModule scope (needs import/export):
// In a .ts file
export interface MyInterface {
value: string;
}
// Must be imported to use### Checking Declaration File Output
Verify your declaration files produce correct output:
# Generate declaration files
npx tsc --declaration --emitDeclarationOnly
# Check what's emitted
ls dist/*.d.ts
# Test if a file is valid declaration-only
npx tsc --noEmit --isolatedModules your-file.d.ts### Common Pitfalls
1. Mixing imports: Regular import statements in .d.ts files (use import type)
2. Export assignments: export = something in .d.ts files (use export default or named exports)
3. Runtime code: Any function/class implementations in .d.ts files
4. Missing includes: Declaration files not included in tsconfig.json
5. Circular references: Type-only imports that create circular dependencies
### TypeScript Configuration for Declarations
Recommended tsconfig.json settings:
{
"compilerOptions": {
"declaration": true, // Generate .d.ts files
"declarationMap": true, // Generate source maps for declarations
"emitDeclarationOnly": false, // Set to true for declaration-only projects
"skipLibCheck": true, // Skip checking library declaration files
"types": [], // Explicitly include global types
"typeRoots": ["./node_modules/@types", "./types"] // Where to find declaration files
}
}For library projects that only export types:
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true, // Don't emit .js files
"outDir": "dist"
}
}Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript