--- title: "Build a Bulk Delete Module" slug: "build-a-bulk-delete-module" description: "Learn to build a module for bulk deletion of records, featuring a two-stage process for archiving and permanent removal with a user-friendly interface." status: "update" updated: 2026-10-13T15:33:33Z published: 2026-10-13T15:33:33Z canonical: "docs.unqork.io/build-a-bulk-delete-module" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.unqork.io/llms.txt > Use this file to discover all available pages before exploring further. # Build a Bulk Delete Module This guide walks you through building a UI module that deletes submission records in bulk. The module handles the two-stage delete process: archiving records first (soft delete), then permanently removing them if needed (hard delete). Creators set the date range in the UI, and the module displays job status after each stage. [Download the example module bundle](https://cdn.us.document360.io/15548f06-2fba-434c-b0c1-5df88317dbd7/Images/Setting/bulk-delete-poc.uqbundle?sv=2026-02-06&spr=https&st=2026-10-27T23%3A14%3A30Z&se=2026-10-28T00%3A14%3A30Z&sr=c&sp=r&sig=6Ywf37tiFk6jzlZYHaV6vk3TnQpvj9UowaaiugdmWME%3D) to use as a starting point. After importing, replace every placeholder before using the module: | Placeholder | Appears in | Replace with. | | --- | --- | --- | | `REPLACE_WITH_DATA_MODEL_ID` | `pluginDelete`, `pluginHardDelete` | The resource ID of the Data Model or module containing the records to delete. | > [!CAUTION] > BDO Delete is disabled by default. Contact an Unqork representative to enable it for your environment. Hard delete is permanent and cannot be reversed. ## Prerequisites - Creator or Express Administrator access to your workspace. - The BDO Delete endpoint enabled by an Unqork representative. - The resource ID of the Data Model or module containing the records to delete. - Familiarity with the **Plug-In**, **Decisions**, and **Button** components. ## Module Overview Organize the module into three field groups: one for initialization logic, one for the Creator-facing UI, and one for the backend Plug-In component sequence. | Name | Key | Component | Purpose | | --- | --- | --- | --- | | On-Load Initializer | `initOnload` | Initializer | Triggers on page load. | | Refresh Button | `btnRefresh` | Button | Lets the Creator check job status manually; enabled when a job is running. | | Delete Button | `btnDelete` | Button | Starts the soft-delete sequence. | | Hard Delete Button | `btnHardDelete` | Button | Starts the hard-delete sequence; enabled when soft delete completes. | | Start Date | `startDate` | Date | Filters records created after this date. | | End Date | `endDate` | Date Input | Filters records created on or before this date. | | Error Initializer | `initError` | Initializer | Sets the response field to indicate failure. | | Soft Delete | `pluginDelete` | Plug-In | Calls the Delete endpoint to archive records in the date range. | | Hard Delete | `pluginHardDelete` | Plug-In | Calls the Delete endpoint with `destroy=true` to permanently remove archived records. | | Get Job Status | `pluginGetJobDetails` | Plug-In | Checks delete progress using the job ID. | | Check Status Rule | `ruleCheckStatus` | Decisions | Shows a status message and enables the `btnHardDelete` Button component when soft delete is complete. | > [!NOTE] > The example Property IDs in this guide use a `plugin` prefix (for example, `pluginExport`) for clarity. The platform default for Plug-In components is `plug` — see [Property ID Naming Conventions](/unqorkai/docs/property-id-naming-conventions). > **How to read settings tables** > > > The tables below describe each configurable option as it shows in the platform. Values are formatted as follows: `inline code` — a value to type exactly as shown | **Bold** — a value selected from a drop-down, radio button, tab, or choice chip | **ON**/**OFF** — toggle state | ✓ — checkbox checked | ☐ — checkbox unchecked ## Set Up the Field Groups Create three field groups in the module to keep components organized. | Field Group | Contains | | --- | --- | | `fgOnLoad` | The on-load Initializer that fires when the page renders. | | `fgUI` | The buttons, date inputs, and HTML instructions the Creator interacts with. | | `fgConfig` | All Plug-In components, status logic, the error Initializer, and hidden fields. | ## Create the Supporting Fields Add the following fields to `fgConfig`. These fields carry data between components throughout the delete sequence. | Property ID | Component | Default | Purpose | | --- | --- | --- | --- | | `jobId` | Hidden | — | Stores the delete job ID returned by `pluginDelete` or `pluginHardDelete`. | | `status` | Hidden | — | Stores the current job status returned by `pluginGetJobDetails`. | | `response` | Hidden | — | Set to `Fail` by `initError` when any Plug-In encounters an error. | ## Before You Begin Unqork recommends exporting a copy of records before running a bulk delete. See [Build a Bulk Download Module](/unqorkai/docs/build-a-bulk-download-module) for those steps. BDO Delete removes a maximum of 500,000 records per operation. For larger collections, run the operation multiple times. ## Configure `initOnload` The `initOnload` Initializer component fires when the module first loads and serves as the module's entry point. It has no outputs and no connections to other components. 1. Add an **Initializer** component to `fgOnLoad`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `initOnload` | | Trigger Type | **New Submission** | ## Configure `btnRefresh` The `btnRefresh` Button component lets the Creator manually re-check job status. It triggers the `pluginGetJobDetails` Plug-In component on click, and the `ruleCheckStatus` Decisions component enables it once a job is running. It starts disabled. 1. Add a **Button** component to `fgUI`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `btnRefresh` | | Label | `Refresh` | | Action | **Event** | | Initial State | **Disabled** | 3. Under **Triggers**, set the **On Click** drop-down to `pluginGetJobDetails`. ## Configure `btnDelete` The `btnDelete` Button component starts the soft-delete sequence. After validation passes, it triggers the `pluginDelete` Plug-In component. 1. Add a **Button** component to `fgUI` after the Refresh button. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `btnDelete` | | Label | `Delete Records` | | Action | **Validate** | | Theme | **Danger** | 3. Under **Triggers**, set the **On Validation Success** drop-down to `pluginDelete`. ## Configure `btnHardDelete` The `btnHardDelete` Button component permanently removes the archived records. It starts disabled and is enabled by the `ruleCheckStatus` Decisions component only after the soft delete reaches `completed`. After validation passes, it triggers the `pluginHardDelete` Plug-In component. 1. Add a **Button** component to `fgUI` after `btnDelete`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `btnHardDelete` | | Label | `Permanently Delete` | | Action | **Validate** | | Theme | **Danger** | | Initial State | **Disabled** | 3. Under **Triggers**, set the **On Validation Success** drop-down to `pluginHardDelete`. ## Configure `startDate` The `startDate` Date Input lets the Creator specify the start of the date range. The `pluginDelete` Plug-In component and the `pluginHardDelete` Plug-In component read its value as `dateFieldStartToFilterOn`. 1. Add a **Date Input** component to `fgUI` after the buttons. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `startDate` | | Label | `Start Date (created after)` | | Required | **ON** | ## Configure `endDate` The `endDate` Date Input lets the Creator specify the end of the date range. The `pluginDelete` Plug-In component and the `pluginHardDelete` Plug-In component read its value as `dateFieldEndToFilterOn`. 1. Add a **Date Input** component to `fgUI` after `startDate`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `endDate` | | Label | `End Date (created on/before)` | | Required | **ON** | ## Configure `initError` The `initError` Initializer component sets the `response` field to `Fail` when something goes wrong. Both delete Plug-Ins reference it as their error handler. 1. Add an **Initializer** component to `fgConfig`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `initError` | | Trigger Type | **Manual** | 3. Configure the **Outputs**: | # | Property ID | Type | Value | | --- | --- | --- | --- | | 1 | `response` | Value | `Fail` | ## Configure `pluginDelete` The `pluginDelete` Plug-In component calls the Delete endpoint to archive records in the selected date range (soft delete). It is triggered by the `btnDelete` Button component. On success, it triggers the `pluginGetJobDetails` Plug-In component to begin polling. 1. Add a **Plug-In** component to `fgConfig`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginDelete` | | Trigger Type | **Manual** | | Endpoint URL | `https://{environment}.unqork.io/fbu/uapi/bulkOperations/delete` | | Method | **POST** | 3. Configure the **Inputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `resourceId` | `'REPLACE_WITH_DATA_MODEL_ID'` | | 2 | `name` | `'Bulk Delete'` | | 3 | `dateFieldToFilterOn` | `'created'` | | 4 | `dateFieldStartToFilterOn` | `startDate` | | 5 | `dateFieldEndToFilterOn` | `endDate` | 4. Configure the **Outputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `jobId` | `id` | 5. Under **Triggers**, set the **Post Trigger** drop-down to `pluginGetJobDetails` and the **Error Handler** drop-down to `initError`. > [!NOTE] > To delete records from a previous Export operation instead, set `dataSource` to `'EXPORT'` and include `exportOperationId` as an additional input. See [Bulk Data Operations](/unqorkai/docs/bulk-data-operations#delete) for all available parameters. ## Configure `pluginHardDelete` The `pluginHardDelete` Plug-In component permanently removes all archived records in the same date range. It is triggered by the `btnHardDelete` Button component after the soft delete completes, and targets only `ARCHIVED` records to avoid affecting active records. 1. Add a **Plug-In** component to `fgConfig` after `pluginDelete`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginHardDelete` | | Trigger Type | **Manual** | | Endpoint URL | `https://{environment}.unqork.io/fbu/uapi/bulkOperations/delete?destroy=true` | | Method | **POST** | 3. Configure the **Inputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `resourceId` | `'REPLACE_WITH_DATA_MODEL_ID'` | | 2 | `name` | `'Bulk Hard Delete'` | | 3 | `targetRecordType` | `'ARCHIVED'` | | 4 | `dateFieldToFilterOn` | `'created'` | | 5 | `dateFieldStartToFilterOn` | `startDate` | | 6 | `dateFieldEndToFilterOn` | `endDate` | 4. Configure the **Outputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `jobId` | `id` | 5. Under **Triggers**, set the **Post Trigger** drop-down to `pluginGetJobDetails` and the **Error Handler** drop-down to `initError`. > [!CAUTION] > Hard delete is permanent. Unqork cannot recover records after this step completes. ## Configure `pluginGetJobDetails` The `pluginGetJobDetails` Plug-In component checks the status of the current delete job. It is triggered by both the `pluginDelete` Plug-In component and the `pluginHardDelete` Plug-In component on success, and by the `btnRefresh` Button component when the Creator clicks it manually. On completion, it triggers the `ruleCheckStatus` Decisions component. 1. Add a **Plug-In** component to `fgConfig` after `pluginHardDelete`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginGetJobDetails` | | Trigger Type | **Manual** | | Endpoint URL | `https://{environment}.unqork.io/fbu/uapi/bulkOperations/jobs/{{data.jobId}}` | | Method | | 3. Configure the **Outputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `status` | `data.status` | 4. Under **Triggers**, set the **Post Trigger** drop-down to `ruleCheckStatus`. > [!NOTE] > All BDO endpoints in this module use the `bulkOperations` (camelCase, no hyphen). ## Configure `ruleCheckStatus` The `ruleCheckStatus` Decisions component reads the job status from the `pluginGetJobDetails` Plug-In component, triggers a pop-up message, and enables the `btnRefresh` Button component while a job is running. When the soft delete reaches `completed`, it also enables the `btnHardDelete` Button component so the Creator can proceed to the permanent delete. 1. Add a **Decisions** component to `fgConfig` after `pluginGetJobDetails`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `ruleCheckStatus` | | Trigger Type | **Manual** | 3. Configure the **Inputs**: | # | Property ID | Alias | Type | | --- | --- | --- | --- | | 1 | `jobId` | `A` | Exact | | 2 | `status` | | Exact | 4. Configure the **Outputs**: | # | Property ID | Type | | --- | --- | --- | | 1 | `btnRefresh` | Disabled | | 2 | `btnHardDelete` | Disabled | | 3 | `fgUI` | Pop Message | 5. Add one row per job status in the **Decision** grid: | Status | `btnRefresh_disabled` | `btnHardDelete_disabled` | `fgUI_popMessage` | | --- | --- | --- | --- | | `created` | `=IF(LEN(A)>0,'no','yes')` | `yes` | `Delete started.` | | `inProgress` | `=IF(LEN(A)>0,'no','yes')` | `yes` | `Delete in progress.` | | `completed` | `=IF(LEN(A)>0,'no','yes')` | `no` | `Records archived. Click Permanently Delete to remove them, or leave them archived.` | | `partiallyCompleted` | `=IF(LEN(A)>0,'no','yes')` | `yes` | `Delete partially complete.` | | `canceled` | `=IF(LEN(A)>0,'no','yes')` | `yes` | `Delete canceled.` | | `failed` | `=IF(LEN(A)>0,'no','yes')` | `yes` | `Delete failed.` | The formula in the disabled column uses `A` (the `jobId` alias). `btnHardDelete` is set to a literal `yes` (always disabled) for all rows except `completed`, where it is set to `no`. ## Test the Module 1. Open the module in **Preview**. 2. Enter a **Start Date** and **End Date** to define the range of records to delete. 3. Click **Delete Records** to begin the soft delete. 4. Click **Refresh** until the pop-up message reads `Records archived. Click Permanently Delete to remove them, or leave them archived.` 5. Open the submission using the platform's submission inspection method. See [Debugger](/unqorkai/docs/api-debugger) for how to access submission data from Express View. 6. Confirm the `jobId` and `status` fields contain the expected values. 7. To permanently remove the archived records, click **Permanently Delete**. 8. Click **Refresh** again until the pop-up message updates to confirm the hard delete is complete. > [!CAUTION] > Step 7 is irreversible. Only proceed if you are certain the archived records should be permanently removed. --- **Changelog** | Date | Change | | --- | --- | | 2026-10-06 | Set btnDelete and btnHardDelete Action to Validate; added Required ON to startDate and endDate; corrected startDate label from "created on/after" to "created after" to match exclusive API behavior; updated bundle to match. | | 2026-10-06 | Full restructure from tested POC: added startDate/endDate Date Input components, corrected pluginHardDelete to use targetRecordType ARCHIVED and dynamic date fields, fixed URLs to bulkOperations (camelCase), per-component Configure sections, Inputs/Outputs tables, bundle download link. | | 2026-09-30 | Initial publication. | The UI (user interface) is a device's end-user interaction and communication point. It can include display screens, keyboards, and mouse. It is also how a user interacts with your application. Also known as Unqork Users, or Designer Users; is anyone who is inside the Unqork platform. Also known as Unqork Users, or Designer Users; is anyone who is inside the Unqork platform. Includes the Short Answer component, Long Answer component, Select Boxes, Radio component, Dropdown, Number component and Single Checkbox. An API endpoint is the location where an API receives requests for a specific resource on its server. It's the point of contact between an API client and an API server, and is usually a uniform resource locator (URL) that provides the location of the resource. A Property ID is the unique field ID used by Unqork to track and link components in your module. Rendering is the process of loading components and displaying the final result on the front-end screen. HTML (Hyper Text Markup Language) is the standard markup language for for achieving font, color, graphic, and hyperlink effects when creating web pages. A Property ID is the unique field ID used by Unqork to track and link components in your module. Deteremines how the component triggers. The component fires when the page or called module loads without an existing submission ID. Dashboards and remote execute modules are great uses for this trigger. Label Text conveys what the input component is and what information it displays. Enter the purpose of the corresponding component or field. Enter the Property ID of the component that you want to trigger on each button-click. Enter outputs components and actions you want the component to perform. A definition A URL (uniform resource locator) is a unique identifier used to locate a resource on the internet. Also known as a web address. The HTTP POST method is used to send data to a server to create or update a resource, typically including the data in the body of the request. Enter inputs components and actions you want the component to perform.. The Post Trigger value fires after a component has completed its action. For example, post triggering a component to display the data received from a Plug-In component's API call. An API endpoint is the location where an API receives requests for a specific resource on its server. It's the point of contact between an API client and an API server, and is usually a uniform resource locator (URL) that provides the location of the resource. A naming convention for computer programming. Use camelCase for Property IDs, for example: newUser, lastName, & rdoButton. Express View is how your end-user views your application. Express View also lets you preview your applications to test your configuration and view the styling. This is also the view your end-users will see when interacting with your application. After configuring a module, click Preview in the Module Builder to interact with the module in Express View. APIs (application programming interfaces) are a set of protocols and definitions developers use to build and integrate application software. APIs act as the connective tissue between products and services. ## Related - [Build a Bulk Download Module](/build-a-bulk-download-module.md) - [Build a Bulk Upload Module](/build-a-bulk-upload-module.md) - [Query Component](/query-component.md)