Go To Definition to Fix Type Errors

Used in this guide:
Next.js 15.3.4
Payload CMS

Introduction

This technique will help you resolve type errors in TypeScript, especially when working with third-party libraries. Instead of guessing or endlessly searching online, you'll learn how to investigate the exact types expected using Go To Definition in Visual Studio Code.


Scenario: Type errors in a Payload CMS user collection

Imagine you're working with a user collection in PayloadCMS with auth enabled. You have the following code in payload/collections/users.ts:

    forgotPassword: {
        generateEmailHTML: ({ req, token, user }) => {
            const resetPasswordURL = `http://localhost:4011/reset-password?token=${token}`
            return `
                <div>
                    <h1>Reset your password</h1>
                    <p>Hi, ${user.email}</p>
                    <p>Click the link below to reset your password.</p>
                    <p>
                        <a href="${resetPasswordURL}">Reset Password</a>
                    <p>
                </div>
            `
        }
    }

You may get type erros on the three arguments inside generateEmailHTML key such as:

Property 'req' does not exist on type '{ req?: PayloadRequest | undefined; token?: string | undefined; user?: any; } | undefined'.ts(2339)
Property 'token' does not exist on type '{ req?: PayloadRequest | undefined; token?: string | undefined; user?: any; } | undefined'.ts(2339)
Property 'user' does not exist on type '{ req?: PayloadRequest | undefined; token?: string | undefined; user?: any; } | undefined'.ts(2339)

These errors mean that the argument you're destructuring may be undefined, or its properties are optional. Rather than fixing each error manually, there's a better approach:


Use Go To Definition in VSCode

You can perform "Go To Definition" by holding down ctrl left right cick on the key. In our case, we will click on the key generateEmailHTML.

VS Code will take you to where generateEmailHTML is defined. There, you might see:

generateEmailHTML?: GenerateForgotPasswordEmailHTML;

This reveals that the value must conform to the type GenerateForgotPasswordEmailHTML. Next, Ctrl+Click on GenerateForgotPasswordEmailHTML to reveal its full definition:

type GenerateForgotPasswordEmailHTML<TUser = any> = (args?: {
    req?: PayloadRequest;
    token?: string;
    user?: TUser;
}) => Promise<string> | string;

This tells us everything about GenerateForgotPasswordEmailHTML:

  • The function takes a single optional args object
  • Each property on args is optional
  • The type of each property
  • The return type

With this information, we have two slutions we can use.


1. Destructure an Optional Argument (with Interface and Default)

Use this when you prefer destructuring but want to avoid TypeScript errors by providing a default fallback:

interface ForgotPasswordEmailArgs {
    req?: PayloadRequest
    token?: string
    user?: {
        email: string
        [key: string]: unknown
    }
}
 
forgotPassword: {
    generateEmailHTML: ({ token, user }: ForgotPasswordEmailArgs = {}) => {
        const resetPasswordURL = `http://localhost:4011/reset-password?token=${token ?? ''}`
        return `
            <div>
                <h1>Reset your password</h1>
                <p>Hi, ${user?.email ?? ''}</p>
                <p>Click the link below to reset your password.</p>
                <p>
                <a href="${resetPasswordURL}">Reset Password</a>
                </p>
            </div>
        `
    }
}

2. Use Dot Notation with Optional Chaining

Use this if you prefer simplicity and want to avoid destructuring entirely:
 
    forgotPassword: {
        generateEmailHTML: (args) => {
            const token = args?.token ?? ''
            const userEmail = args?.user?.email ?? ''
            const resetPasswordURL = `http://localhost:4011/reset-password?token=${token}`
            return `
                <div>
                    <h1>Reset your password</h1>
                    <p>Hi, ${userEmail}</p>
                    <p>Click the link below to reset your password.</p>
                    <p>
                        <a href="${resetPasswordURL}">Reset Password</a>
                    <p>
                </div>
            `
        }
    }

TypeScript is able to infer the type of args automatically from the PayloadCMS definition for generateEmailHTML. So even though you're not explicitly typing args, PayloadCMS already typed generateEmailHTML for you, and TypeScript applies that type as the default.

JKT

Stay focused, and the rest will follow

©Jakkrit Turner. All rights reserved