Skip to content

Commit 38c1d4d

Browse files
docs: clarify unique field index behavior and add warnings for array/blocks nesting (#15969)
Fixes #12836 - updates description for unique in each field config - adds a Unique Fields section explaining the collection-wide index behavior - adds a warning banner after the config options table pointing to the new indexes section --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1213860518113240 --------- Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
1 parent d6862fe commit 38c1d4d

13 files changed

Lines changed: 77 additions & 34 deletions

File tree

docs/database/indexes.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@ export const MyCollection: CollectionConfig = {
6464
}
6565
```
6666

67+
## Unique Fields
68+
69+
Setting `unique: true` on a field creates a **collection-wide** database unique index on that field's path. This means no two documents in the collection can share the same value for that field.
70+
71+
<Banner type="warning">
72+
**Using `unique` on fields nested inside `array` or `blocks`** creates a
73+
collection-wide unique index on the dotted path (e.g. `items.key`). This is
74+
**not** the same as enforcing uniqueness within a single document's array rows
75+
— it prevents *any two documents* from having the same value at that path.
76+
77+
Additionally, on **MongoDB**, if the nested field also has `required: true`,
78+
Payload creates a non-sparse unique index. Documents that do not contain the
79+
array will share a `null` value at that path, causing a duplicate key error
80+
when saving a second document without the array. Removing `required: true`
81+
restores the sparse index and avoids this collision.
82+
83+
If you need to enforce uniqueness _within_ a document's array rows (e.g. no
84+
duplicate keys in the same document), use a custom
85+
[`validate`](/docs/fields/overview#validation) function on the array field
86+
instead.
87+
88+
</Banner>
89+
6790
## Localized fields and MongoDB indexes
6891

6992
When you set `index: true` or `unique: true` on a localized field, MongoDB creates one index **per locale path** (e.g., `slug.en`, `slug.da-dk`, etc.). With many locales and indexed fields, this can quickly approach MongoDB's per-collection index limit.

docs/fields/array.mdx

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,41 @@ export const MyArrayField: Field = {
3939

4040
## Config Options
4141

42-
| Option | Description |
43-
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44-
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
45-
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
46-
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
47-
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More details](/docs/fields/overview#validation). |
48-
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
49-
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
50-
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
51-
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
52-
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
53-
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
54-
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
55-
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
56-
| **`required`** | Require this field to have a value. |
57-
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
58-
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
59-
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
60-
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
61-
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
62-
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
42+
| Option | Description |
43+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
44+
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
45+
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
46+
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
47+
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More details](/docs/fields/overview#validation). |
48+
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
49+
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
50+
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
51+
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
52+
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
53+
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
54+
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
55+
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
56+
| **`required`** | Require this field to have a value. |
57+
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
58+
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
59+
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
60+
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
61+
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
62+
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
6363
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/overview#string-path-virtual-fields). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
6464

6565
_\* An asterisk denotes that a property is required._
6666

67+
<Banner type="warning">
68+
Setting `unique: true` on a field **inside** an Array creates a
69+
collection-wide database unique index — not a per-document one. No two
70+
documents in the collection can share the same value at that nested path, and
71+
on MongoDB, documents without the array will collide on `null`. To enforce
72+
uniqueness within a single document's rows, use a custom
73+
[`validate`](/docs/fields/overview#validation) function on this Array field.
74+
[More details](/docs/database/indexes#unique-fields).
75+
</Banner>
76+
6777
## Admin Options
6878

6979
To customize the appearance and behavior of the Array Field in the [Admin Panel](../admin/overview), you can use the `admin` option:

docs/fields/blocks.mdx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ This page is divided into two parts: first, the settings of the Blocks Field, an
5656
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. |
5757
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
5858
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
59-
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
59+
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. This creates a database-level unique index on the field's path. [More details](/docs/database/indexes#unique-fields). |
6060
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
6161
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
6262
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
@@ -65,6 +65,16 @@ This page is divided into two parts: first, the settings of the Blocks Field, an
6565

6666
_\* An asterisk denotes that a property is required._
6767

68+
<Banner type="warning">
69+
Setting `unique: true` on a field **inside** a Block creates a collection-wide
70+
database unique index — not a per-document one. No two documents in the
71+
collection can share the same value at that nested path, and on MongoDB,
72+
documents without the block will collide on `null`. To enforce uniqueness
73+
within a single document's blocks, use a custom
74+
[`validate`](/docs/fields/overview#validation) function on this Blocks field.
75+
[More details](/docs/database/indexes#unique-fields).
76+
</Banner>
77+
6878
### Block Admin Options
6979

7080
To customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
@@ -542,7 +552,7 @@ Blocks can be conditionally enabled using the `filterOptions` property on the bl
542552
- `filterOptions` is re-evaluated as part of the form state request, whenever the document data changes.
543553
- If a block is present in the field but no longer allowed by `filterOptions`, a validation error will occur when saving.
544554

545-
### Example
555+
### filterOptions Example
546556

547557
```ts
548558
{

0 commit comments

Comments
 (0)