68

Is there a way to make a file in your typescript file that defines globally accessible types?

I like typescript but find that when I want to be truly type-safe I have to explicitly import types from all over the system. It's rather annoying.

AmerllicA
  • 29,059
  • 15
  • 130
  • 154
Tal
  • 1,298
  • 2
  • 10
  • 20

7 Answers7

69

Yes this is possible. You can find all information here: https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html

The important part is this:

declare global {
    /*~ Here, declare things that go in the global namespace, or augment
     *~ existing declarations in the global namespace
     */
    interface String {
        fancyFormat(opts: StringFormatOptions): string;
    }
}
Sebastian Sebald
  • 16,130
  • 5
  • 62
  • 66
  • 2
    Thanks, I tried this and put it in a typings file but couldn't get it to load. Is there a name to use or should I just include in my entry point? – Tal Mar 25 '17 at 01:57
  • 16
    I guess maybe my problem is more with knowing how imports of .d.ts files are. because i cant even tell if these files are getting imported or not. There is no error message other than unknown type. – Tal Apr 03 '17 at 18:40
  • @Tal, no. I'm having the same issue. Check my answer to how I got it to work. – Elias Jul 28 '20 at 12:50
  • 2
    I'm getting `ts(2669)` when trying this. – wegry Oct 28 '21 at 15:14
  • 2
    @wegry looks like an formatting error: https://stackoverflow.com/questions/57132428/augmentations-for-the-global-scope-can-only-be-directly-nested-in-external-modul – Sebastian Sebald Oct 29 '21 at 05:30
  • 1
    typescript global object key's have any type...anyone knows why? – Rupak Jan 25 '22 at 12:46
  • It works! There is a precondition: you need to make sure typescript compiler will read this file. For instance: edit `include` filed in `tsconfig.json` – fadeaway May 12 '23 at 14:32
50

I found the accepted answer is not working (maybe it is some configuration that needs to be done?). So with some tinkering, I got it to work for me (maybe I also have some weird configuration option? Let me know if it doesn't work and I will remove this answer).

  1. Create a definition file in a suitable folder. I will be using types/global.d.ts
  2. Check your tsconfig.json and include "*": ["types/*.d.ts"] under paths. (You should also be able to directly address the created file if you care to).
  3. Put your global definitions directly into the root of the file NO declare global or similar.

Now you should be good to go to use the types declared in this file (tested with typescript 3.9.6 and 3.7.5).

Example file:

// global.d.ts
declare interface Foo {
    bar: string;
    fooBar: string;
}

What your tsconfig should look like:

[...]
"paths": {
    "*": ["types/*.d.ts"]
},
[...]
Elias
  • 3,592
  • 2
  • 19
  • 42
  • 4
    If someone cares to explain `declare global` that would be cool. As it is in the official documentation. – Elias Jul 28 '20 at 12:49
  • 2
    This solution worked for me. The 'accepted' answer did not. Thanks you! – Joseph Dailey Nov 26 '20 at 10:20
  • 5
    This worked for me as well. Accepted answer didn't. – tumelo Jan 05 '21 at 22:51
  • This is a much cleaner solution, ty. – Gandalf The Grey Nov 05 '21 at 07:32
  • This did not work for me in typescript 4.5.2. `paths` need a baseUrl too, and even then it there seems to be no way to make typescript pick up `d.ts` files. It works for intellisense in vscode, but not with the tsc compiler. – Karlsson Nov 23 '21 at 11:29
  • @Karlsson I'm using this in a project with ts version `4.4.3` and it is working. My `baseUrl` is set to `.`. – Elias Nov 23 '21 at 11:45
  • 2
    and if you have an eslint issue after adding solution above, you can add a comment with global and name at the top - more details: https://eslint.org/docs/rules/no-undef – Mieszczańczyk S. Dec 07 '21 at 14:47
27

A little late but, you can add a file.d.ts anywhere in your project as I've noticed, and it will be picked up.

For example, in my project I wanted a:

Optional<T> = T | null;

And I didn't know where to add it, so I added a common.d.ts in a folder, and added:

declare type Optional<T> = T | null;

Now it's being picked up and no errors. Didn't even need to import it. This of course being tested in vscode, not sure if it will work in your editor.

(Depending on your file include/exclude rules of course, but most projects include all *.ts)

Raid
  • 747
  • 8
  • 17
15

All the answers above can actually coexist and work together. The key is understanding that declaration files (non-modules) must be "somehow" discoverable by the project, since they don't contain exports. And that there is a syntax that allows any module file (any file that uses imports/exports, basically anything in your src folder) to contribute ambient declarations as well.

Ambient Declarations files (non-modules)

  • Must be included in the project (i.e. tsconfig.json "include")
  • Must not contain any import or export statements
  • To import external types, there is a special syntax
tsconfig.json
{
  "include": ["./src/global.d.ts"],
  // or wildcard
  "include": ["**/*.d.ts"],
}
src/globals.d.ts
// Global types

type AnyObject<T = any> = Record<string, T>
type AnyFunction = (...args: any[]) => any

// Contributing to existing interfaces (interface merging)

interface Window {
  console: AnyObject
}

// Importing

declare type BaseContract = import('ethers').BaseContract
declare type _MockContract = import('ethereum-waffle').MockContract
declare type Stub = import('ethereum-waffle').Stub
// Here we are re-defining an existing interface to provide better typings.
interface MockContract<T extends BaseContract = BaseContract> extends _MockContract {
  mock: {
    [key in keyof T['functions']]: Stub
  }
}

JS Modules

  • Modules are those files that contain import or export statements,
    so basically every file within your src folder.
  • Any js (module) file can provide ambient declarations.
src/app.ts
import React from 'react'

export default MyApp(props: AppProps) {
  return <div>Hi</div>
}

// Ambient declarations

declare global {
  interface Window {
    console: AnyObject
  }
}

Put this at the top of your Ambient Declarations file to avoid coworker confusion

////// -----------------------------------------------------------------------------------------------------------------
/*//// -----------------------------------------------------------------------------------------------------------------

This file is an "Ambient declarations file". The types defined here are available globally.
More info here: https://stackoverflow.com/a/73389225/985454

Don't use `import` and `export` in this file directly! It breaks ambience.
To import external types in an ambient declarations file (this file) use the following:

*//**
* @example
* declare type React = import('react')
*//*

To contribute ambient declarations from any file, even non-ambient ones, use this:

*//**
* @example
* declare global {
*   interface Window {
*     ethereum: any
*   }
* }
*//*

/*//// -----------------------------------------------------------------------------------------------------------------
////// -----------------------------------------------------------------------------------------------------------------

// Your type definitions here ...
Qwerty
  • 29,062
  • 22
  • 108
  • 136
12

In addition to Sebastian Sebald's Answer

don't forget to

export {} 

which makes it actual module.

so like this.

this is working.

declare global {
    /*~ Here, declare things that go in the global namespace, or augment
     *~ existing declarations in the global namespace
     */
    interface String {
        fancyFormat(opts: StringFormatOptions): string;
    }
}
export {}
byyoung
  • 327
  • 3
  • 10
0

For a lot of people the detail I got myself tripped up on might be obvious. I spent 2 hours trying to figure out why my *.d.ts files were not getting picked up, and it turns out it was simply because in my tsconfig.json I had an include property like this (it was the default that came with a project template I started with):

{
  ...
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ]
}

But to keep my src/ directory clean I had put my whatever.d.ts file was under types/. ‍♂️ So in my case the fix was simply one of the following:

  1. Add **/*.d.ts or types/*.d.ts or something like that to my include property.
  2. Remove the include property entirely and let it use the default.
Aaron Beaudoin
  • 461
  • 1
  • 5
  • 14
0

When using global variables in TypeScript, two problems are often encountered:

  • Cannot find name 'variable-name', which means that the variable has not been properly exposed to the global scope.
  • The global variable type is any, which means that the type has not been properly imported.

Solution

  1. First, the type of the global variable needs to be declared:
// global-types.d.ts
import type { Foo } from "package-with-types";

declare global {
  type Bar = Foo;
}
export {};

A declaration file is not a module by default, so you cannot import types in a declaration file. Therefore, use export {} to make the declaration file a module, import types with import type, and then use declare global to define globally accessible types.

  1. Then, declare the global variable:
// global.d.ts
// eslint-disable-next-line  @typescript-eslint/no-explicit-any
declare const eruda: any;
declare const bar: Bar;

  1. This allows access to the global variable in the project with type hints.

References

James Lam
  • 145
  • 2
  • 8