---
title: "Authentication — Custom Strategies"
aliases: [custom-auth-strategy, payload-auth-strategies]
tags: [payloadcms, authentication, advanced]
sources: [raw/authentication__custom-strategies.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

Custom auth strategies let you replace or augment Payload's built-in auth with any logic you need — header-based tokens, API keys, OAuth callbacks, etc. Added in Payload 3.0 (dropped Passport.js dependency).

> **Warning:** Advanced feature. Full server restart required when changing strategies — they are not hot-reloaded.

---

## Strategy Shape

```ts
{
  name: string,          // unique identifier
  authenticate: async (args) => { user, responseHeaders? }
}
```

### `authenticate` Arguments

| Arg | Type | Description |
|-----|------|-------------|
| `headers` | `Headers` | Incoming request headers — extract tokens, codes, etc. |
| `payload` | `Payload` | Full Payload instance — use to query users |
| `canSetHeaders` | `boolean` | Whether response headers can be set (default `false`) |
| `isGraphQL` | `boolean` | Whether called from GraphQL endpoint (default `false`) |

### Return Value

```ts
{
  user: { collection: 'users', ...doc } | null,
  responseHeaders?: Headers   // optional — Payload will set these on the response
}
```

Return `null` for `user` if authentication fails — Payload treats this as unauthenticated.

---

## Registering a Strategy

Strategies are defined inside the collection's `auth` config:

```ts
export const Users: CollectionConfig = {
  slug: 'users',
  auth: {
    disableLocalStrategy: true,   // disable email/password if replacing entirely
    strategies: [
      {
        name: 'custom-strategy',
        authenticate: async ({ payload, headers }) => {
          const result = await payload.find({
            collection: 'users',
            where: {
              code: { equals: headers.get('code') },
              secret: { equals: headers.get('secret') },
            },
          })
          return {
            user: result.docs[0]
              ? { collection: 'users', ...result.docs[0] }
              : null,
            responseHeaders: new Headers({ 'some-header': 'value' }),
          }
        },
      },
    ],
  },
  fields: [
    { name: 'code', type: 'text', index: true, unique: true },
    { name: 'secret', type: 'text' },
  ],
}
```

---

## Common Patterns

| Use Case | Approach |
|----------|----------|
| Header token auth | Read `Authorization` header, verify JWT, query user |
| API key (custom) | Match header value against stored key field |
| Replace built-in entirely | Set `disableLocalStrategy: true` |
| Augment (add alongside) | Keep `disableLocalStrategy` unset, add strategy to array |
| Set auth cookie after custom login | Use `responseHeaders` to set `Set-Cookie` |

---

## Key Takeaways

- A strategy = `name` + `authenticate` function; no Passport.js required.
- `authenticate` receives `headers` + `payload` instance — query your DB directly.
- Return `{ user: { collection, ...doc } }` on success, `{ user: null }` on failure.
- `disableLocalStrategy: true` removes email/password; omit it to stack strategies.
- **Restart required** on strategy changes — not hot-reloaded in dev.
- `responseHeaders` in return lets you set cookies or custom headers transparently.

---

## Related

- [[wiki/payloadcms/authentication-api-keys|Authentication — API Keys]]
- [[wiki/payloadcms/authentication-cookies|Authentication — Cookie Strategy]]
- [[wiki/payloadcms/local-api|Local API]] — `payload.find` used inside authenticate
- [[wiki/payloadcms/rest-api|REST API]] — endpoints that trigger auth strategies

---

## Sources

- `raw/authentication__custom-strategies.md`
- https://payloadcms.com/docs/authentication/custom-strategies
