External Relationship Field
Relate to records in an external, non-Payload source
A relationship-style field for records that live in an external, non-Payload source (another headless CMS, a SaaS product, a partner API, …). Renders as a Payload-native async combobox with debounced search, infinite scroll, and label resolution for existing values.
Usage
1. Define a source
One source per file. Each exports a { handler, externalLink? } config.
import type { ExternalRelationshipSourceConfig, ExternalRelationshipSourceHandler } from '@adsign/payload-adsign-plugin';
import { stringify } from 'qs-esm';
const handler: ExternalRelationshipSourceHandler = async ({ search, values, page = 1 }) => {
const query = stringify(
{
where: {
...(search && { name: { like: search } }),
...(values?.length && { id: { in: values } }),
},
page,
limit: 20,
},
{ addQueryPrefix: true },
);
const res = await fetch(`${process.env.MASTER_URL}/api/tenants${query}`, {
headers: { Authorization: `Bearer ${process.env.MASTER_TOKEN}` },
});
const { docs, hasNextPage } = await res.json();
return {
options: docs.map((d: { id: string; name: string }) => ({ label: d.name, value: d.id })),
hasMore: hasNextPage,
};
};
export const Tenants: ExternalRelationshipSourceConfig = {
slug: 'tenants',
handler,
externalLink: {
label: 'Open in Master',
url: 'https://master.example.com/admin/collections/tenants',
},
};2. Register it in plugin config
import { adsignPlugin } from '@adsign/payload-adsign-plugin';
import { Tenants } from './externalRelationshipSources/tenants';
adsignPlugin({
siteName: 'My Site',
domain: 'example.com',
externalRelationshipSources: [Tenants],
})3. Run type generation
pnpm payload generate:types4. Use it in any collection
import { externalRelationshipField } from '@adsign/payload-adsign-plugin';
export const Pages: CollectionConfig = {
fields: [
externalRelationshipField({
name: 'tenants',
hasMany: true,
source: 'tenants',
}),
],
};Handler contract
| Arg | Type | Purpose |
|---|---|---|
search | string? | Debounced (300ms) user input for filtering |
values | string[]? | Resolve labels for existing values on mount |
page | number? | 1-based page for infinite scroll |
req | PayloadRequest | Access to req.user, req.payload, etc. |
Returns:
{
options: { label: string; value: string }[];
hasMore?: boolean;
}Field options
| Option | Type | Description |
|---|---|---|
name | string | Field name (required) |
source | ExternalRelationshipSourceSlug | Slug of a registered source |
hasMany | boolean | Store an array of values instead of a single one |
... | TextField | All other text-field options are passed through |
Source options
| Option | Type | Description |
|---|---|---|
slug | string | Unique slug — referenced by externalRelationshipField({ source }) |
handler | ExternalRelationshipSourceHandler | Server-side function that returns options |
externalLink | { label, url } | Optional link rendered beside every field bound to this source |