Configuration
Options for access, cookies, duration, UI, and session seams.
| Option | Type | Default | Description |
|---|---|---|---|
access.impersonate | ({ req, target, targetCollection }) => true | false | required | Start gate. Only literal true allows. |
access.filterTargets | ({ req, targetCollection }) => Where | boolean | unset | Who the switcher lists. true = whole collection, false = hide it, Where = filter the list. Start re-checks this. Omit to show everyone the viewer can read. |
access.terminate | ({ req }) => true | false | deny | Remote end of an open row. Return true to allow. That principal can end any open row by id. In swap mode this signs the impersonator out of the admin. See Limits. |
access.readRecords | Payload Access | deny | Read the impersonation-sessions collection. Does not put the collection in the nav. Create, update, and delete stay denied. |
disabled | boolean | false | Return the incoming config unchanged. |
collectionSlug | string | impersonation-sessions | Record collection. Create, update, and delete are denied. Hidden until ui.sessionsCollection is true. |
collection.overrides | (collection) => collection | unset | Last pass over the built sessions collection. Whatever it returns is registered, including access. Do not rename fields the plugin reads, and do not change the slug here. |
apiPath | string | /impersonation | Root endpoint prefix. Must not collide with a collection slug. |
hintCookieName | string | impersonation-hint | Row id hint. Must not start with cookiePrefix. |
decorateRequests | boolean | true | Paint req.user._impersonation on the target session, including both emails. false skips that field only. maxDuration still closes the session on the next authenticated request. |
reason | 'off' | 'optional' | 'required' | 'off' | Note stored on the row. |
maxDuration | number (seconds) | unset | Absolute cap from startedAt. Unset means refresh can keep the session alive. |
retention | { deleteAfterDays, cron?, queue? } | off | Opt-in job: close stale open rows, then delete closed rows older than N days. Host must run the queue (jobs.autoRun). Open rows are never deleted. |
cookies.clearOnSwitch | string[] | ['payload-tenant'] | Extra cookies expired on swap start and exit. Parallel leaves them in place. See Multi-tenancy. |
security.trustedOrigins | string[] | [] | Extra Origins allowed for cookie POSTs. |
targets | string[] | every local-strategy auth collection | Collections that can be impersonated. |
session.issue / revoke / binding | functions | Payload local-auth | Custom auth seams. |
ui | false or per-surface flags | all true | See below. |
translations | per-locale map | built-ins | See Translations. |
UI flags
Each flag is independent. ui: false turns all of them off. The provider still mounts, so useImpersonation() works from your own components.
| Flag | Default | What it does |
|---|---|---|
ui.headerAction | true | Impersonate in the admin header. Opens the user drawer. |
ui.bar | true | Bottom bar while a session is open. It sits beside the nav, not over it. Collapse keeps an Impersonation chip that snaps to the edges of the content area. |
ui.documentAction | true | Impersonate next to Save on a target document (beforeDocumentControls, so it still shows when the dots menu is hidden). Hidden while a session is already open. |
ui.recordAction | true | End session beside Save on an impersonation-sessions document (beforeDocumentControls). Hidden when the row already has endedAt. Create, update, and delete stay denied. |
ui.sessionsCollection | false | Put impersonation-sessions in the admin nav. access.readRecords still decides who can read a row. |
ui.card | unset | Replacement for the default switcher row. Same props as ImpersonationUserCard: collectionSlug, doc, onSelect, onOpenDrawer, documentHref, title, email, openDocumentLabel, openDrawerLabel. Pass a component path, or { path, exportName } for a named export. Omit to keep the default card. |
ui.cardEmail | true | Show email under useAsTitle on default switcher cards. |
ui.userLabel | 'title' | How the bar, collapsed chip, and frontend banner name users. 'title' is the collection's useAsTitle, 'email' is the email. Each falls back to the other. |
Build your own control with useImpersonation from @10x-media/impersonation/client. It returns { status, setStatus, start, exit, end, reasonMode, targetFilters, apiPath, cardEmail, sessionCollection }. targetFilters is the access.filterTargets result (true or a Where per collection). status starts from the server render and updates after the bar checks the live session. StartConfirmModal is exported too.
collection.overrides receives the built sessions collection and whatever it returns is registered, including access. Do not rename fields the plugin reads, and do not change the slug here. Use collectionSlug for that.
getImpersonation
Server helper. Pass user when you already called payload.auth, so it does not authenticate again. Omit user to authenticate from headers. Pass user: null when the request is anonymous.
const { user } = await payload.auth({ headers })
const impersonation = await getImpersonation({ headers, payload, user })side is target when this request is the impersonated session, and impersonator when it is the admin side of a parallel session.
See Frontend for the shipped banner.
access.filterTargets
The switcher cannot call access.impersonate (that needs a concrete target). Use filterTargets so the list does not show people start would 403.
filterTargets: ({ req, targetCollection }) => {
if (req.user?.roles?.includes('admin')) {
return targetCollection === 'users' ? { roles: { not_in: ['admin'] } } : true
}
if (req.user?.roles?.includes('editor')) {
return targetCollection === 'members'
}
return false
}This is UX. access.impersonate is still the start check. Start also re-runs the filter so list and start cannot disagree.