Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/configuration/collections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ The following options are available:
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `disableBulkEdit` | Disable the bulk edit operation for the collection in the admin panel and the REST API |

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

Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export const renderListView = async (
collectionSlug,
columnState,
disableBulkDelete,
disableBulkEdit,
disableBulkEdit: collectionConfig.disableBulkEdit ?? disableBulkEdit,
disableQueryPresets,
enableRowSelections,
hasCreatePermission,
Expand Down
4 changes: 4 additions & 0 deletions packages/payload/src/collections/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,10 @@ export type CollectionConfig<TSlug extends CollectionSlug = any> = {
* Default field to sort by in collection list view
*/
defaultSort?: Sort
/**
* Disable the bulk edit operation for the collection in the admin panel and the API
*/
disableBulkEdit?: boolean
/**
* When true, do not show the "Duplicate" button while editing documents within this collection and prevent `duplicate` from all APIs
*/
Expand Down
6 changes: 5 additions & 1 deletion packages/payload/src/collections/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
import { APIError } from '../../errors/index.js'
import { APIError, Forbidden } from '../../errors/index.js'
import { type CollectionSlug, deepCopyObjectSimple } from '../../index.js'
import { generateFileData } from '../../uploads/generateFileData.js'
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles.js'
Expand Down Expand Up @@ -63,6 +63,10 @@ export const updateOperation = async <
): Promise<BulkOperationResult<TSlug, TSelect>> => {
let args = incomingArgs

if (args.collection.config.disableBulkEdit && !args.overrideAccess) {
throw new APIError(`Collection ${args.collection.config.slug} has disabled bulk edit`, 403)
}

try {
const shouldCommit = !args.disableTransaction && (await initTransaction(args.req))

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/views/CollectionFolder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ function CollectionFolderViewInContext(props: CollectionFolderViewInContextProps
!smallBreak && (
<ListSelection
disableBulkDelete={disableBulkDelete}
disableBulkEdit={disableBulkEdit}
disableBulkEdit={collectionConfig.disableBulkEdit ?? disableBulkEdit}
key="list-selection"
/>
),
Expand Down
7 changes: 7 additions & 0 deletions test/admin/collections/DisableBulkEdit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { CollectionConfig } from 'payload'

export const DisableBulkEdit: CollectionConfig = {
slug: 'disable-bulk-edit',
fields: [],
disableBulkEdit: true,
}
3 changes: 2 additions & 1 deletion test/admin/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable no-restricted-exports */
import { fileURLToPath } from 'node:url'
import path from 'path'

Expand All @@ -8,6 +7,7 @@ import { BaseListFilter } from './collections/BaseListFilter.js'
import { CustomFields } from './collections/CustomFields/index.js'
import { CustomViews1 } from './collections/CustomViews1.js'
import { CustomViews2 } from './collections/CustomViews2.js'
import { DisableBulkEdit } from './collections/DisableBulkEdit.js'
import { DisableCopyToLocale } from './collections/DisableCopyToLocale.js'
import { DisableDuplicate } from './collections/DisableDuplicate.js'
import { EditMenuItems } from './collections/editMenuItems.js'
Expand Down Expand Up @@ -180,6 +180,7 @@ export default buildConfigWithDefaults({
ListDrawer,
Placeholder,
UseAsTitleGroupField,
DisableBulkEdit,
],
globals: [
GlobalHidden,
Expand Down
12 changes: 12 additions & 0 deletions test/admin/e2e/list-view/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ describe('List View', () => {
let with300DocumentsUrl: AdminUrlUtil
let withListViewUrl: AdminUrlUtil
let placeholderUrl: AdminUrlUtil
let disableBulkEditUrl: AdminUrlUtil
let user: any

let serverURL: string
Expand All @@ -90,6 +91,7 @@ describe('List View', () => {
customViewsUrl = new AdminUrlUtil(serverURL, customViews1CollectionSlug)
withListViewUrl = new AdminUrlUtil(serverURL, listDrawerSlug)
placeholderUrl = new AdminUrlUtil(serverURL, placeholderCollectionSlug)
disableBulkEditUrl = new AdminUrlUtil(serverURL, 'disable-bulk-edit')
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
Expand Down Expand Up @@ -1302,6 +1304,16 @@ describe('List View', () => {
await page.locator('#confirm-delete-many-docs #confirm-action').click()
await expect(page.locator('.cell-_select')).toHaveCount(1)
})

test('should hide edit many from collection with disableBulkEdit: true', async () => {
await payload.create({ collection: 'disable-bulk-edit', data: {} })
await page.goto(disableBulkEditUrl.list)

// select one row
await page.locator('.row-1 .cell-_select input').check()
// ensure the edit many button is hidden
await expect(page.locator('.edit-many button')).toBeHidden()
})
})

describe('pagination', () => {
Expand Down
23 changes: 23 additions & 0 deletions test/admin/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface Config {
'with-list-drawer': WithListDrawer;
placeholder: Placeholder;
'use-as-title-group-field': UseAsTitleGroupField;
'disable-bulk-edit': DisableBulkEdit;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
Expand Down Expand Up @@ -123,6 +124,7 @@ export interface Config {
'with-list-drawer': WithListDrawerSelect<false> | WithListDrawerSelect<true>;
placeholder: PlaceholderSelect<false> | PlaceholderSelect<true>;
'use-as-title-group-field': UseAsTitleGroupFieldSelect<false> | UseAsTitleGroupFieldSelect<true>;
'disable-bulk-edit': DisableBulkEditSelect<false> | DisableBulkEditSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
Expand Down Expand Up @@ -547,6 +549,15 @@ export interface UseAsTitleGroupField {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disable-bulk-edit".
*/
export interface DisableBulkEdit {
id: string;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
Expand Down Expand Up @@ -653,6 +664,10 @@ export interface PayloadLockedDocument {
| ({
relationTo: 'use-as-title-group-field';
value: string | UseAsTitleGroupField;
} | null)
| ({
relationTo: 'disable-bulk-edit';
value: string | DisableBulkEdit;
} | null);
globalSlug?: string | null;
user: {
Expand Down Expand Up @@ -1037,6 +1052,14 @@ export interface UseAsTitleGroupFieldSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disable-bulk-edit_select".
*/
export interface DisableBulkEditSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
Expand Down
10 changes: 10 additions & 0 deletions test/collections-rest/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ export default buildConfigWithDefaults({
path: `/${method}-test`,
})),
},
{
slug: 'disabled-bulk-edit-docs',
fields: [
{
name: 'text',
type: 'text',
},
],
disableBulkEdit: true,
},
],
endpoints: [
{
Expand Down
22 changes: 22 additions & 0 deletions test/collections-rest/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,28 @@ describe('collections-rest', () => {
expect((await result.json()).message.startsWith('Route not found')).toBeTruthy()
}
})

it('should disable bulk edit for the collection with disableBulkEdit: true', async () => {
const res = await restClient.PATCH('/disabled-bulk-edit-docs?where[id][equals]=0', {})
expect(res.status).toBe(403)

await expect(
payload.update({
collection: 'disabled-bulk-edit-docs',
where: {},
data: {},
overrideAccess: false,
}),
).rejects.toBeInstanceOf(APIError)

await expect(
payload.update({
collection: 'disabled-bulk-edit-docs',
where: {},
data: {},
}),
).resolves.toBeTruthy()
})
})

async function createPost(overrides?: Partial<Post>) {
Expand Down
25 changes: 25 additions & 0 deletions test/collections-rest/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export interface Config {
'custom-id-number': CustomIdNumber;
'error-on-hooks': ErrorOnHook;
endpoints: Endpoint;
'disabled-bulk-edit-docs': DisabledBulkEditDoc;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
Expand All @@ -90,6 +91,7 @@ export interface Config {
'custom-id-number': CustomIdNumberSelect<false> | CustomIdNumberSelect<true>;
'error-on-hooks': ErrorOnHooksSelect<false> | ErrorOnHooksSelect<true>;
endpoints: EndpointsSelect<false> | EndpointsSelect<true>;
'disabled-bulk-edit-docs': DisabledBulkEditDocsSelect<false> | DisabledBulkEditDocsSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
Expand Down Expand Up @@ -247,6 +249,16 @@ export interface Endpoint {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disabled-bulk-edit-docs".
*/
export interface DisabledBulkEditDoc {
id: string;
text?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
Expand Down Expand Up @@ -303,6 +315,10 @@ export interface PayloadLockedDocument {
relationTo: 'endpoints';
value: string | Endpoint;
} | null)
| ({
relationTo: 'disabled-bulk-edit-docs';
value: string | DisabledBulkEditDoc;
} | null)
| ({
relationTo: 'users';
value: string | User;
Expand Down Expand Up @@ -446,6 +462,15 @@ export interface EndpointsSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "disabled-bulk-edit-docs_select".
*/
export interface DisabledBulkEditDocsSelect<T extends boolean = true> {
text?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
Expand Down
Loading