#18163·payload

plugin-multi-tenant: an async `validate` override on the tenant field skips the plugin's required check (override not awaited)

Author: katlianikCreated Sep 11, 2026Updated Sep 11, 2026
LabelsBug

Describe the Bug

@payloadcms/plugin-multi-tenant wraps the tenant field's validate (the overrides.validate a project may pass) in fieldValidation, which calls the override without awaiting it and then runs its own required check:

typescript
// packages/plugin-multi-tenant/src/fields/tenantField/index.ts (v3.88.0, lines 10–29)
const fieldValidation =
  (validateFunction?: RelationshipFieldValidation): RelationshipFieldValidation =>
  (value, options) => {
    if (validateFunction) {
      const result = validateFunction(value, options)
      if (result !== true) {
        return result
      }
    }
    if (options.hasMany) { … } else {
      if (!value) {
        return options.req.t('validation:required')
      }
    }
    return true
  }

When the override is async (any validate that looks something up, e.g. a membership check), validateFunction(...) returns a Promise. A Promise is !== true, so the wrapper returns the Promise itself. Payload awaits it; when it resolves true, the field is treated as valid — and the wrapper's own required check (the lines after) never runs.

Result: with an async override, an empty tenant value passes validation. On Postgres the insert then fails on the NOT NULL column with a 500 (null value in column "tenant_id" violates not-null constraint) instead of the expected 400 This field is required.; where the column is nullable, a row without a tenant is written.

Expected: the override is awaited and the plugin's required check still applies after it — i.e.

typescript
const fieldValidation =
  (validateFunction?: RelationshipFieldValidation): RelationshipFieldValidation =>
  async (value, options) => {
    if (validateFunction) {
      const result = await validateFunction(value, options)
      if (result !== true) return result
    }
    …
  }

(Payload's validate already accepts a Promise, so awaiting is safe.)

Link to the code that reproduces this issue

https://github.com/payloadcms/payload/blob/v3.88.0/packages/plugin-multi-tenant/src/fields/tenantField/index.ts#L10-L29 — the wrapper itself; a config excerpt that triggers it is below (no repository needed: the defect is in the wrapper, any async override reaches it).

Reproduction Steps

  1. Configure the plugin with an async validate on the tenant field:
typescript
multiTenantPlugin({
  tenantsSlug: 'tenants',
  collections: { pages: {} },
  tenantField: {
    name: 'tenant',
    validate: async (value, { req }) => {
      // any async check — this one is a no-op
      await Promise.resolve()
      return true
    },
  },
})
  1. As a user who is a member of two tenants (so the plugin's cookie-less default resolves to nothing) send POST /api/pages with a body that omits tenant.

  2. Observe: no This field is required. error. On Postgres the request answers 500 with null value in column "tenant_id" ... violates not-null constraint; with a nullable column the page is created with tenant: null.

  3. Replace the override with a synchronous validate: () => true (or remove it): the same request answers 400 The following field is invalid: Tenant — the required check runs.

Which area(s) are affected? (Select all that apply)

plugin: multi-tenant

Environment Info

  • Payload 3.88.0, @payloadcms/plugin-multi-tenant 3.88.0, @payloadcms/db-postgres 3.88.0
  • Node 22.23.1
  • macOS (reproduced locally against Postgres 17)