104

I have a node.js app that attaches some config information to the global object:

global.myConfig = {
    a: 1,
    b: 2
}

The TypeScript compiler doesn't like this because the Global type has no object named myConfig:

TS2339: Property 'myConfig' does not exist on type 'Global'.

I don't want to do this:

global['myConfig'] = { ... }

How do I either extend the Global type to contain myConfig or just tell TypeScript to shut up and trust me? I'd prefer the first one.

I don't want to change the declarations inside node.d.ts. I saw this SO post and tried this:

declare module NodeJS  {
    interface Global {
        myConfig: any
    }
}

as a way to extend the existing Global interface, but it doesn't seem to have any effect.

Community
  • 1
  • 1
d512
  • 32,267
  • 28
  • 81
  • 107

10 Answers10

126

As of node@16 the NodeJS.Global interface has been removed in favor of globalThis.

You can declare new global variable in a module file as:

declare global {
  var NEW_GLOBAL: string;
}

And in a non-module file (no top-level import/export) as:

declare var NEW_GLOBAL: string;

Important note: variable must be declared as var. let and const variables don't show up on globalThis.

thorn0
  • 9,362
  • 3
  • 68
  • 96
aleksxor
  • 7,535
  • 1
  • 22
  • 27
  • 10
    This is the only answer that works as of Sep 2021! (Node v14 LTS) – codejedi365 Sep 05 '21 at 03:39
  • 3
    That's a very good point regarding var in the context of TS declarations. – Estus Flask Sep 20 '21 at 09:22
  • Saved my bacon. Thank you! – CatDadCode Sep 23 '21 at 17:51
  • 2
    Not working for me, the compiler still complains with the following message: 'Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature'. – Anthony Luzquiños Oct 19 '21 at 00:01
  • 2
    and how to use this NEW_GLOBAL which is declared and set in Server.ts in eg. UserController? Do I need to somehow import it? How Typescript knows that it was already declared in Servert.s? – Suisse Nov 16 '21 at 12:57
  • 1
    I'm not sure how to do it with Node18, I ended up using @ts-ignore, this stuff is unnecessarily complicated. – Vadorequest Jan 09 '23 at 18:03
  • To get `declare global { ... }` to work in a module file, with `"isolatedModules": true` in the `tsconfig.json`, I needed to add an empty export object `export {}` to the declaration file. – lookyhooky Jan 12 '23 at 20:36
  • Thank you for this important note: variable must be declared as var. let and const variables don't show up on globalThis. I suggest you highlight it even more by raising its font size and putting it at the top. – Stephane L Jun 05 '23 at 11:39
  • @Suisse you can refer to it as `global.NEW_GLOBAL` or `globalThis.NEW_GLOBAL` – Daniel Kaplan Aug 05 '23 at 22:31
94

I saw this SO post and tried this:

You probably have something like vendor.d.ts:

// some import 
// AND/OR some export

declare module NodeJS  {
    interface Global {
        spotConfig: any
    }
}

Your file needs to be clean of any root level import or exports. That would turn the file into a module and disconnect it from the global type declaration namespace.

More : https://basarat.gitbooks.io/typescript/content/docs/project/modules.html

thisgeek
  • 3,396
  • 4
  • 24
  • 26
basarat
  • 261,912
  • 58
  • 460
  • 511
  • 1
    I can't quite figure this out. I don't have a vendor.d.ts, so I made one, but it seems to have no effect. Should I be importing it? – Justin Oct 31 '16 at 01:25
  • 2
    `NodeJS` is declared as a namespace, not as a module – marvinhagemeister Dec 06 '16 at 09:15
  • 4
    @basarat - RE: "Your file needs to be clean of any root level import or exports" would it not be possible to apply the "global modifying module" here as per https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html ? If I understand that example correctly, adding something along the lines of `declare global { namespace NodeJS { ... }}` should allow you to have both the global namespace augmentation and your module exports. – Jan Molak Dec 25 '16 at 22:46
  • I have the same problem, but I don't have a `vendor.d.ts` file in my project. The typings are loading from `./node_modules/@types` in my case. How do I make it work? – Slava Fomin II Mar 30 '17 at 20:29
  • 1
    How do I use interfaces from external modules as types? I want spotConfig of 'Sequelize.Sequelize' type. Now how do I do that? – Sid Verma Jun 20 '17 at 03:14
  • @d512: Unfortunately, this won't work with ES2017 anymore. Sigh... (And as a note, I'm pretty sure I'm working on the exact same code base you were working on originally.) – David Morton Dec 02 '17 at 19:57
  • Hi Basarat, your answer looks nice, but doesn't work for me, the only thing that works is what I have here: https://stackoverflow.com/a/49021561/1223975 – Alexander Mills Feb 28 '18 at 03:34
  • Basarat, can you help me figure out how to avoid declaring `global: CDTGlobal` in all my files? – Alexander Mills Feb 28 '18 at 03:35
  • 1
    This won't help for implicit global references, e.g. `spotConfig` instead of `global.spotConfig`. To fix this, add `declare const spotConfig: any`;. – Oliver Joseph Ash Nov 08 '18 at 10:25
  • @OliverJosephAsh Interesting, neither VS Code, nor WebStorm can autocomplete `spotConfig.` in TS files. Only WebStorm can autocomplete `spotConfig.` in JS files. And only WebStorm can autocomplete `global.spotConfig.` in TS files. Did you make VS Code work with globals properties autocompletion ? – Dzenly Jan 24 '19 at 04:59
  • The link seem to be broken: https://basarat.gitbook.io/typescript/project/modules – Benjamin Vincent Sep 15 '22 at 22:44
  • outdated - as of node 16 this should no longer be the accepted answer or it should get updated. – Jey DWork Apr 09 '23 at 14:13
83

To avoid Typescript claim something like this:

TS2339: Property 'myConfig' does not exist on type 'Global'.

I suggest to define custom types. I do it under src/types/custom.d.ts file in my project:

declare global {
  namespace NodeJS {
    interface Global {
        myConfig: {
          a: number;
          b: number;
        }
    }
  }
}

Then I ensure these are considered by Typescript in tsconfig.json file:

{
  ...
  "files": [
    ...
    "src/types/custom.d.ts"
  ]
}

Now you're safe to use your custom property:

console.log(global.myConfig.a);
Nik Sumeiko
  • 8,263
  • 8
  • 50
  • 53
29

Putting the following file into our project's root directory worked.

global.d.ts

declare namespace NodeJS {
  export interface Global {
    myConfig: any
  }
}

We're using "@types/node": "^7.0.18" and TypeScript Version 2.3.4. Our tsconfig.json file looks like this:

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es6"  
    },
    "exclude": [
        "node_modules"
    ]
}
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
15

Use 'namespace' instead of 'module' to declare custom TypeScript

If you use any of the above answers and are using a newer version of Typescript you'll get a nag about using "module". You should consider namespace instead.

In order to satisfy the requirement here you'll actually need more than extending the Global interface. You'll also need to create a constant with the type as well if you want it to be accessible directly from the "globalThis" context.

NOTE: while the OP asked about an object literal the process is the same as you see here below. Rather than the "Debug" type being a function you would simply define the interface as need, then change "debug:" to myConfig or whatever you wish.

// typically I'll store the below in something like "typings.d.ts"
// this is because, at least typically, these overrides tend to
// be minimal in nature. You could break them up and Typescript
// will pick them up if you wish.

// Augmentations for the global scope can only be directly nested 
// in external modules or ambient module declarations.
export {}

declare global {
  // Definition or type for the function.
  type Debug = (label: string) => (message: any, ...args: any[]) => void

  // If defining an object you might do something like this
  // interface IConfig { a: number, b: number }

  // Extend the Global interface for the NodeJS namespace.
  namespace NodeJS {
    interface Global {
      // Reference our above type, 
      // this allows global.debug to be used anywhere in our code.
      debug: Debug
    }
  }
  
  // This allows us to simply call debug('some_label')('some debug message')
  // from anywhere in our code.
  const debug: Debug
}

How the above might be used

For completeness in this example all we did was define a global so we can log a simple debug message. Here's how we'd bind the method to our global context.

global.debug = (label: string) => (message: any, ...args: any[]) => console.log(message, ...args)

We can also call our global debug method directly:

debug('express')(`${req.method}: ${req.url}`)
DarkLite1
  • 13,637
  • 40
  • 117
  • 214
Blujedis
  • 346
  • 4
  • 5
9

The only thing that works for me is this:

// lib/my.d.ts

import Global = NodeJS.Global;
export interface CDTGlobal extends Global {
  cdtProjectRoot: string
}

and then using it in other files like so

import {CDTGlobal} from "../lib/my.d.ts";
declare const global: CDTGlobal;

const cwd = global.cdtProjectRoot; // works
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • I am attempting something similar, but in regards to reading a configuration JSON file and then having it globally accessible by all my modules. I'm attempting to figure out a solution where I do not have to "re-read" the json file in each module but can't seem to pull it off. – Chason Arthur Mar 07 '19 at 18:21
  • then it is not Global as we have to import in all the files. – TechTurtle Oct 01 '19 at 16:25
7

What worked for me is:

declare global {
  module NodeJS {
    interface Global {
      myConfig: any;
    }
  }
}

global.myConfig = 'it works!';

Only downside is when using it you will have to turn off the ESLint rule @typescript-eslint/no-namespace.

For completeness here is my tsconfig.json:

{
  "compilerOptions": {
    "declaration": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "jsx": "react",
    "lib": ["dom", "es2017"],
    "module": "commonjs",
    "moduleResolution": "node",
    "noEmitOnError": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "outDir": "dist",
    "removeComments": true,
    "resolveJsonModule": true,
    "rootDir": "src",
    "sourceMap": true,
    "strict": true,
    "target": "es6"
  },
  "exclude": ["dist", "node_modules"]
}
Benny Code
  • 51,456
  • 28
  • 233
  • 198
0

Copy my answer of another post:

globalThis is the future.

// Way 1
var abc: number
globalThis.abc = 200 // no error

// Way2
declare var age: number
globalThis.age = 18 // no error
edvard chen
  • 2,320
  • 1
  • 12
  • 10
0

I can get both type check and code intelligence.

declare namespace NodeJS {
  interface Global {
    property: string
  }
}

But interface Global is point to global.GLOBAL.

You can get correct type check, is because:

declare var global: NodeJS.Global & typeof globalThis;

But you can not get better code intelligence by useing global.property, unless use global.GLOBAL.property.

So, you need to define global var global and extend interface Global both:

// myglobal.d.ts
declare namespace NodeJS {
  interface Global {
    property: string
  }
}
declare var global: NodeJS.Global & typeof globalThis

And now you can get property intelligence when you type global.

Izhaki
  • 23,372
  • 9
  • 69
  • 107
aolyang
  • 11
  • 2
0

I believe this should work

declare global {
  var user: string;
  function greeting(user: string, message: string): string;
}
    
export {};

Make sure you are using the var keyword, and you are setting this up in a d.ts file. e.g global.d.ts.

Don't forget to add the export statement

You can now access this anywhere in your codebase. e.g global.person

Tyler2P
  • 2,324
  • 26
  • 22
  • 31