feat: adds and exports reusable auth server functions (#11900)

### What
Adds exportable server functions for `login`, `logout` and `refresh`
that are fully typed and ready to use.

### Why
Creating server functions for these auth operations require the
developer to manually set and handle the cookies / auth JWT. This can be
a complex and involved process - instead we want to provide an option
that will handle the cookies internally and simplify the process for the
user.

### How
Three re-usable functions can be exported from
`@payload/next/server-functions`:
- login
- logout
- refresh

Examples of how to use these functions will be added to the docs
shortly, along with more in-depth info on server functions.
This commit is contained in:
Jessica Chowdhury
2025-04-14 09:47:08 +01:00
committed by GitHub
parent 39462bc6b9
commit 6b349378e0
21 changed files with 922 additions and 5 deletions

View File

@@ -158,7 +158,7 @@ mutation {
```ts
const result = await payload.login({
collection: '[collection-slug]',
collection: 'collection-slug',
data: {
email: 'dev@payloadcms.com',
password: 'get-out',
@@ -166,6 +166,13 @@ const result = await payload.login({
})
```
<Banner type="success">
**Server Functions:** Payload offers a ready-to-use `login` server function
that utilizes the Local API. For integration details and examples, check out
the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Logout
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
@@ -189,6 +196,13 @@ mutation {
}
```
<Banner type="success">
**Server Functions:** Payload provides a ready-to-use `logout` server function
that manages the user's cookie for a seamless logout. For integration details
and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Refresh
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.
@@ -240,6 +254,13 @@ mutation {
}
```
<Banner type="success">
**Server Functions:** Payload exports a ready-to-use `refresh` server function
that automatically renews the user's token and updates the associated cookie.
For integration details and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Verify by Email
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
@@ -270,7 +291,7 @@ mutation {
```ts
const result = await payload.verifyEmail({
collection: '[collection-slug]',
collection: 'collection-slug',
token: 'TOKEN_HERE',
})
```
@@ -308,7 +329,7 @@ mutation {
```ts
const result = await payload.unlock({
collection: '[collection-slug]',
collection: 'collection-slug',
})
```
@@ -349,7 +370,7 @@ mutation {
```ts
const token = await payload.forgotPassword({
collection: '[collection-slug]',
collection: 'collection-slug',
data: {
email: 'dev@payloadcms.com',
},

View File

@@ -310,7 +310,168 @@ export const PostForm: React.FC = () => {
## Reusable Payload Server Functions
Coming soon…
Managing authentication with the Local API can be tricky as you have to handle cookies and tokens yourself, and there aren't built-in logout or refresh functions since these only modify cookies. To make this easier, we provide `login`, `logout`, and `refresh` as ready-to-use server functions. They take care of the underlying complexity so you don't have to.
### `login`
Logs in a user by verifying credentials and setting the authentication cookie. This function allows login via username or email, depending on the collection auth configuration.
#### Importing the `login` function
```ts
import { login } from '@payloadcms/next/auth'
```
The login function needs your Payload config, which cannot be imported in a client component. To work around this, create a simple server function like the one below, and call it from your client.
```ts
'use server'
import { login } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function loginAction({
email,
password,
}: {
email: string
password: string
}) {
try {
const result = await login({
collection: 'users',
config,
email,
password,
})
return result
} catch (error) {
throw new Error(
`Login failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}
```
#### Login from the React Client Component
```tsx
'use client'
import { useState } from 'react'
import { loginAction } from '../loginAction'
export default function LoginForm() {
const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>('')
return (
<form onSubmit={() => loginAction({ email, password })}>
<label htmlFor="email">Email</label>
<input
id="email"
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setEmail(e.target.value)
}
type="email"
value={email}
/>
<label htmlFor="password">Password</label>
<input
id="password"
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setPassword(e.target.value)
}
type="password"
value={password}
/>
<button type="submit">Login</button>
</form>
)
}
```
### `logout`
Logs out the current user by clearing the authentication cookie.
#### Importing the `logout` function
```ts
import { logout } from '@payloadcms/next/auth'
```
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below.
```ts
'use server'
import { logout } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function logoutAction() {
try {
return await logout({ config })
} catch (error) {
throw new Error(
`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}
```
#### Logout from the React Client Component
```tsx
'use client'
import { logoutAction } from '../logoutAction'
export default function LogoutButton() {
return <button onClick={() => logoutFunction()}>Logout</button>
}
```
### `refresh`
Refreshes the authentication token for the logged-in user.
#### Importing the `refresh` function
```ts
import { refresh } from '@payloadcms/next/auth'
```
As with login and logout, you need to pass your Payload config to this function. Create a helper server function like the one below. Passing the config directly to the client is not possible and will throw errors.
```ts
'use server'
import { refresh } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function refreshAction() {
try {
return await refresh({
collection: 'users', // update this to your collection slug
config,
})
} catch (error) {
throw new Error(
`Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}
```
#### Using Refresh from the React Client Component
```tsx
'use client'
import { refreshAction } from '../actions/refreshAction'
export default function RefreshTokenButton() {
return <button onClick={() => refreshFunction()}>Refresh</button>
}
```
## Error Handling in Server Functions

View File

@@ -37,6 +37,11 @@
"types": "./src/exports/routes.ts",
"default": "./src/exports/routes.ts"
},
"./auth": {
"import": "./src/exports/auth.ts",
"types": "./src/exports/auth.ts",
"default": "./src/exports/auth.ts"
},
"./templates": {
"import": "./src/exports/templates.ts",
"types": "./src/exports/templates.ts",

View File

@@ -0,0 +1,87 @@
'use server'
import type { CollectionSlug } from 'payload'
import { cookies as getCookies } from 'next/headers.js'
import { generatePayloadCookie, getPayload } from 'payload'
import { setPayloadAuthCookie } from '../utilities/setPayloadAuthCookie.js'
type LoginWithEmail = {
collection: CollectionSlug
config: any
email: string
password: string
username?: never
}
type LoginWithUsername = {
collection: CollectionSlug
config: any
email?: never
password: string
username: string
}
type LoginArgs = LoginWithEmail | LoginWithUsername
export async function login({ collection, config, email, password, username }: LoginArgs): Promise<{
token?: string
user: any
}> {
const payload = await getPayload({ config })
const authConfig = payload.collections[collection]?.config.auth
if (!authConfig) {
throw new Error(`No auth config found for collection: ${collection}`)
}
const loginWithUsername = authConfig?.loginWithUsername ?? false
if (loginWithUsername) {
if (loginWithUsername.allowEmailLogin) {
if (!email && !username) {
throw new Error('Email or username is required.')
}
} else {
if (!username) {
throw new Error('Username is required.')
}
}
} else {
if (!email) {
throw new Error('Email is required.')
}
}
let loginData
if (loginWithUsername) {
loginData = username ? { password, username } : { email, password }
} else {
loginData = { email, password }
}
try {
const result = await payload.login({
collection,
data: loginData,
})
if (result.token) {
await setPayloadAuthCookie({
authConfig,
cookiePrefix: payload.config.cookiePrefix,
token: result.token,
})
}
if ('removeTokenFromResponses' in config && config.removeTokenFromResponses) {
delete result.token
}
return result
} catch (e) {
console.error('Login error:', e)
throw new Error(`${e}`)
}
}

View File

@@ -0,0 +1,29 @@
'use server'
import { cookies as getCookies, headers as nextHeaders } from 'next/headers.js'
import { getPayload } from 'payload'
import { getExistingAuthToken } from '../utilities/getExistingAuthToken.js'
export async function logout({ config }: { config: any }) {
try {
const payload = await getPayload({ config })
const headers = await nextHeaders()
const result = await payload.auth({ headers })
if (!result.user) {
return { message: 'User already logged out', success: true }
}
const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix)
if (existingCookie) {
const cookies = await getCookies()
cookies.delete(existingCookie.name)
return { message: 'User logged out successfully', success: true }
}
} catch (e) {
console.error('Logout error:', e)
throw new Error(`${e}`)
}
}

View File

@@ -0,0 +1,42 @@
'use server'
import type { CollectionSlug } from 'payload'
import { headers as nextHeaders } from 'next/headers.js'
import { getPayload } from 'payload'
import { getExistingAuthToken } from '../utilities/getExistingAuthToken.js'
import { setPayloadAuthCookie } from '../utilities/setPayloadAuthCookie.js'
export async function refresh({ collection, config }: { collection: CollectionSlug; config: any }) {
try {
const payload = await getPayload({ config })
const authConfig = payload.collections[collection]?.config.auth
if (!authConfig) {
throw new Error(`No auth config found for collection: ${collection}`)
}
const { user } = await payload.auth({ headers: await nextHeaders() })
if (!user) {
throw new Error('User not authenticated')
}
const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix)
if (!existingCookie) {
return { message: 'No valid token found', success: false }
}
await setPayloadAuthCookie({
authConfig,
cookiePrefix: payload.config.cookiePrefix,
token: existingCookie.value,
})
return { message: 'Token refreshed successfully', success: true }
} catch (e) {
console.error('Refresh error:', e)
throw new Error(`${e}`)
}
}

View File

@@ -0,0 +1,3 @@
export { login } from '../auth/login.js'
export { logout } from '../auth/logout.js'
export { refresh } from '../auth/refresh.js'

View File

@@ -0,0 +1,10 @@
import { cookies as getCookies } from 'next/headers.js'
type Cookie = {
name: string
value: string
}
export async function getExistingAuthToken(cookiePrefix: string): Promise<Cookie | undefined> {
const cookies = await getCookies()
return cookies.getAll().find((cookie) => cookie.name.startsWith(cookiePrefix))
}

View File

@@ -0,0 +1,42 @@
import type { Auth } from 'payload'
import { cookies as getCookies } from 'next/headers.js'
import { generatePayloadCookie } from 'payload'
type SetPayloadAuthCookieArgs = {
authConfig: Auth
cookiePrefix: string
token: string
}
export async function setPayloadAuthCookie({
authConfig,
cookiePrefix,
token,
}: SetPayloadAuthCookieArgs): Promise<void> {
const cookies = await getCookies()
const cookieExpiration = authConfig.tokenExpiration
? new Date(Date.now() + authConfig.tokenExpiration)
: undefined
const payloadCookie = generatePayloadCookie({
collectionAuthConfig: authConfig,
cookiePrefix,
expires: cookieExpiration,
returnCookieAsObject: true,
token,
})
if (payloadCookie.value) {
cookies.set(payloadCookie.name, payloadCookie.value, {
domain: authConfig.cookies.domain,
expires: payloadCookie.expires ? new Date(payloadCookie.expires) : undefined,
httpOnly: true,
sameSite: (typeof authConfig.cookies.sameSite === 'string'
? authConfig.cookies.sameSite.toLowerCase()
: 'lax') as 'lax' | 'none' | 'strict',
secure: authConfig.cookies.secure || false,
})
}
}

View File

@@ -0,0 +1,36 @@
'use client'
import { type ChangeEvent, type FormEvent, useState } from 'react'
import { loginFunction } from './loginFunction.js'
const LoginForm = () => {
const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>('')
return (
<form onSubmit={() => loginFunction({ email, password })}>
<label htmlFor="email">Email</label>
<input
id="email"
onChange={(e: ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)}
placeholder="Email"
required
type="email"
value={email}
/>
<label htmlFor="password">Password</label>
<input
id="password"
onChange={(e: ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)}
placeholder="Password"
required
type="password"
value={password}
/>
<button type="submit">Custom Login</button>
</form>
)
}
export default LoginForm

View File

@@ -0,0 +1,23 @@
'use server'
import { login } from '@payloadcms/next/auth'
import config from '../config.js'
type LoginArgs = {
email: string
password: string
}
export async function loginFunction({ email, password }: LoginArgs) {
try {
const result = await login({
collection: 'users',
config,
email,
password,
})
return result
} catch (error) {
throw new Error(`Login failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

View File

@@ -0,0 +1,8 @@
'use client'
import { logoutFunction } from './logoutFunction.js'
const LogoutButton = () => {
return <button onClick={() => logoutFunction()}>Custom Logout</button>
}
export default LogoutButton

View File

@@ -0,0 +1,14 @@
'use server'
import { logout } from '@payloadcms/next/auth'
import config from '../config.js'
export async function logoutFunction() {
try {
return await logout({
config,
})
} catch (error) {
throw new Error(`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

View File

@@ -0,0 +1,7 @@
'use client'
import { refreshFunction } from './refreshFunction.js'
const RefreshToken = () => {
return <button onClick={() => refreshFunction()}>Custom Refresh</button>
}
export default RefreshToken

View File

@@ -0,0 +1,16 @@
'use server'
import { refresh } from '@payloadcms/next/auth'
import config from '../config.js'
export async function refreshFunction() {
try {
return await refresh({
collection: 'users', // update this to your collection slug
config,
})
} catch (error) {
throw new Error(`Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

View File

@@ -0,0 +1,34 @@
import { fileURLToPath } from 'node:url'
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfigWithDefaults({
collections: [],
admin: {
autoLogin: false,
components: {
beforeLogin: ['/components/login.js'],
header: ['/components/refresh.js', '/components/logout.js'],
},
importMap: {
baseDir: path.resolve(dirname),
},
},
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
},
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})

View File

@@ -0,0 +1,97 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { devUser } from 'credentials.js'
import path from 'path'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
import type { Config } from './payload-types.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
let payload: PayloadTestSDK<Config>
const { beforeAll, describe } = test
describe('Server Functions', () => {
let page: Page
let url: AdminUrlUtil
let serverURL: string
beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
url = new AdminUrlUtil(serverURL, 'users')
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({
page,
serverURL,
noAutoLogin: true,
})
})
describe('Auth functions', () => {
test('should log user in from login server function', async () => {
await page.goto(`${serverURL}/admin`)
// Expect email and password fields to be visible
await expect(page.locator('#email')).toBeVisible()
await expect(page.locator('#password')).toBeVisible()
await page.fill('#email', devUser.email)
await page.fill('#password', devUser.password)
const loginButton = page.locator('text=Custom Login')
await expect(loginButton).toBeVisible()
await loginButton.click()
await page.waitForTimeout(1000)
await page.reload()
await page.goto(`${serverURL}/admin/account`)
await expect(page.locator('h1[title="dev@payloadcms.com"]')).toBeVisible()
})
test('should refresh user from refresh server function', async () => {
await page.goto(`${serverURL}/admin`)
const initialCookie = await page.context().cookies()
const payloadToken = initialCookie.find((cookie) => cookie.name === 'payload-token')
expect(payloadToken).toBeDefined()
const initialExpiry = payloadToken?.expires
const refreshButton = page.locator('text=Custom Refresh')
await expect(refreshButton).toBeVisible()
await refreshButton.click()
await page.waitForTimeout(1000)
const updatedCookie = await page.context().cookies()
const updatedPayloadToken = updatedCookie.find((cookie) => cookie.name === 'payload-token')
expect(updatedPayloadToken).toBeDefined()
expect(updatedPayloadToken?.expires).not.toBe(initialExpiry)
})
test('should log user out from logout server function', async () => {
await page.goto(`${serverURL}/admin`)
const logoutButton = page.locator('text=Custom Logout')
await expect(logoutButton).toBeVisible()
await logoutButton.click()
await page.waitForTimeout(1000)
await page.reload()
await page.goto(`${serverURL}/admin`)
await expect(page.locator('#email')).toBeVisible()
await expect(page.locator('#password')).toBeVisible()
})
})
})

View File

@@ -0,0 +1,19 @@
import { rootParserOptions } from '../../eslint.config.js'
import { testEslintConfig } from '../eslint.config.js'
/** @typedef {import('eslint').Linter.Config} Config */
/** @type {Config[]} */
export const index = [
...testEslintConfig,
{
languageOptions: {
parserOptions: {
...rootParserOptions,
tsconfigRootDir: import.meta.dirname,
},
},
},
]
export default index

View File

@@ -0,0 +1,242 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect: {};
locale: null;
user: User & {
collection: 'users';
};
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?: {
relationTo: 'users';
value: string | User;
} | null;
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}

View File

@@ -0,0 +1,13 @@
{
// extend your base config to share compilerOptions, etc
//"extends": "./tsconfig.json",
"compilerOptions": {
// ensure that nobody can accidentally use this config for a build
"noEmit": true
},
"include": [
// whatever paths you intend to lint
"./**/*.ts",
"./**/*.tsx"
]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"paths": {
"@payload-config": ["./config.js"]
}
}
}