How to Write TypeScript Interfaces in JSDoc Comments

Just because you are using vanilla .js files doesn't mean you can't use TypeScript interfaces.

Interfaces defined in jsdoc comment

Updated 2023-07-23 to clarify that jsdoc comments support object types, not interfaces. Thanks to Matt Pocock for the pointer.

I like writing web apps without any build step, just vanilla .js files. But I still like the type-checking that TypeScript provides. Thankfully TypeScript supports type-checking .js files via JSDoc comments.

But how do you write out interfaces without a .ts file? The tl;dr is you can write an object type in a JSDoc comment, or you can write an interface in a .d.ts file and import that into your .js file. Let's dig into each option.

Interfaces in a .ts file

First, let's look at an example in a .ts file.

interface Address {
  street: string;
  city: string;
  zip: number;
}

interface Customer {
  name: sting;
  email: string;
  address: Address;
}

Option 1: Object Types in a JSDoc comment

An object type isn't exactly an interface, but for most use cases it behaves the same way.

The main difference is interfaces support declaration merging but object types do not.

Writing those same interfaces in a .js file would look like:

/**
 * @typedef Address
 * @prop {string} street The street
 * @prop {string} city The City
 * @prop {number} zip The zip code
 *
 * @typedef Customer
 * @prop {string} name The Customer's name
 * @prop {string} email The Customer's email
 * @prop {Address} address The Customer's address
 */

And then you can apply that interface to an object using the @type annotation:

/** @type {Customer} */
const theCustomer = { ... }

Boom 💥 now you've got type-checking and intellisense on your data model right in a vanilla .js file.

Option 2: import interfaces from a .d.ts file

The other option to define interfaces is to put them in a .d.ts file and import that into you .js file.

// models.d.ts

export interface Address {
  street: string;
  city: string;
  zip: number;
}

export interface Customer {
  name: sting;
  email: string;
  address: Address;
}

And then in your .js file you can import those types:

/**
 * @typedef { import("./models").Customer } Customer
 */

 /** @type {Customer} */
const theCustomer = { ... }

Importing from .d.ts file

This is my preferred approach as I find writing types in .d.ts files to be more terse than writing types out in jsdoc comments. But I like having both options available to use interchangeably.

Related Posts

Hopefully you found this post helpful, if you have any questions you can find me on Twitter.

theme-color meta tag vs manifest theme_color
Scoped CSS Styles with Declarative Shadow DOM