--- title: "Build a Bulk Upload Module" slug: "build-a-bulk-upload-module" description: "Build a module to upload CSV files and import records into a Data Model seamlessly with a single click. Streamline your data management today!" status: "update" updated: 2026-10-13T15:33:48Z published: 2026-10-13T15:33:48Z canonical: "docs.unqork.io/build-a-bulk-upload-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 Upload Module This guide walks you through building a UI module that lets Creators upload a CSV file and import its records into a Data Model in bulk. The module handles the full sequence: generating an upload URL, uploading the file, running the import, and checking job status, all from a single button click. [Download the example module bundle](https://cdn.us.document360.io/15548f06-2fba-434c-b0c1-5df88317dbd7/Images/Setting/bulk-upload-poc.uqbundle?sv=2026-02-06&spr=https&st=2026-10-27T23%3A23%3A36Z&se=2026-10-28T00%3A23%3A36Z&sr=c&sp=r&sig=Z1cEJr25sWwX%2BGvMlZWtYZv7r5%2F9BmR%2BIk%2B5fTuebAw%3D) to use as a starting point. After importing, replace every placeholder before using the module: | Placeholder | Appears in | Replace with. | | --- | --- | --- | | `REPLACE_WITH_MODULE_ID` | `pluginListSubmission`, `pluginBulkImportRecord` | The ID of the target Data Model or module. | | `REPLACE_WITH_FIELDS` | `pluginListSubmission` | A comma-separated list of field names to display from existing records (for example, `firstName,lastName,email`). | ## Prerequisites - Creator or Express Administrator access to your workspace. - A target Data Model to receive the imported records. - A CSV file with data matching your Data Model's field structure. - Familiarity with the **Plug-In**, **Decisions**, and **File** 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 `pluginListSubmission` on page load. | | List Submissions | `pluginListSubmission` | Plug-In | Loads existing records from the target module on page load. | | Refresh Button | `btnRefresh` | Button | Lets the Creator check import status manually; enabled when a job is running. | | File Upload | `fileUpload` | File | Accepts the CSV the Creator wants to import. | | Enable Button Rule | `ruleFileUpload` | Decisions | Enables `btnBulkImport` when a file is selected. | | Import Button | `btnBulkImport` | Button | Starts the upload and import sequence. | | Success Initializer | `initSuccess` | Initializer | Sets the response field to indicate success. | | Error Initializer | `initError` | Initializer | Sets the response field to indicate failure. | | Generate Upload Link | `pluginGenerateUploadLink` | Plug-In | Calls the Upload-Link endpoint to get a pre-signed URL. | | Upload File | `pluginUploadFile` | Plug-In | Posts the file bytes to the upload URL. | | Import Records | `pluginBulkImportRecord` | Plug-In | Calls the Import endpoint with the returned file key. | | Get Job Status | `pluginGetJobDetails` | Plug-In | Checks import progress using the job ID. | | Check Status Rule | `ruleCheckStatus` | Decisions | Shows a status message and resets the form when 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 and the submissions list Plug-In. | | `fgUI` | The buttons, file component, and enable rule the Creator interacts with. | | `fgConfig` | All import Plug-In components, status logic, Initializers, and hidden fields. | ## Create the Supporting Fields Add the following fields to `fgConfig`. These fields carry data between components throughout the upload and import sequence. | Property ID | Component | Default | Purpose | | --- | --- | --- | --- | | `uploadLink` | Hidden | — | Stores the pre-signed upload URL returned by `pluginGenerateUploadLink`. | | `fileKey` | Hidden | — | Stores the Unqork file key returned after the file upload. | | `jobId` | Hidden | — | Stores the import job ID returned by `pluginBulkImportRecord`. | | `status` | Hidden | — | Stores the current job status returned by `pluginGetJobDetails`. | | `response` | Hidden | — | Set to `Success` by `initSuccess` or `Fail` by `initError`. | ## Configure `initOnload` The `initOnload` Initializer component fires when the module first loads and triggers the `pluginListSubmission` Plug-In component to populate the existing records view. 1. Add an **Initializer** component to `fgOnLoad`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `initOnload` | | Trigger Type | **New Submission** | 3. Configure the **Outputs**: | # | Property ID | Type | Value | | --- | --- | --- | --- | | 1 | `pluginListSubmission` | Trigger | `GO` | ## Configure `pluginListSubmission` The `pluginListSubmission` Plug-In component loads existing records from the target module when the page renders. It is triggered by the `initOnload` Initializer component and has no outputs. The platform writes the response data directly to any connected display components. 1. Add a **Plug-In** component to `fgOnLoad` after `initOnload`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginListSubmission` | | Trigger Type | **Manual** | | Endpoint URL | `https://{environment}.unqork.io/fbu/uapi/system/getSubmissions` | | Method | | 3. Configure the **Inputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `moduleId` | `'REPLACE_WITH_MODULE_ID'` | | 2 | `fields` | `'REPLACE_WITH_FIELDS'` (comma-separated field names to return, for example `'firstName,lastName,email'`) | ## Configure `fileUpload` The `fileUpload` File component captures the CSV the Creator selects. After a file is chosen, it triggers the `ruleFileUpload` Decisions component to enable the import button. 1. Add a **File** component to `fgUI`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `fileUpload` | | Label | `Upload File` | | Accepted File Types | `.csv` | 3. Under **Triggers**, set the **Post Upload** drop-down to `ruleFileUpload`. ## Configure `ruleFileUpload` The `ruleFileUpload` Decisions component checks whether a file has been selected and enables the `btnBulkImport` Button component when one is present. It is triggered by the `fileUpload` File component after each file selection. 1. Add a **Decisions** component to `fgUI` after `fileUpload`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `ruleFileUpload` | | Trigger Type | **Manual** | 3. Configure the **Inputs**: | # | Property ID | Alias | Type | | --- | --- | --- | --- | | 1 | `fileUpload` | `A` | Exact | 4. Configure the **Outputs**: | # | Property ID | Type | | --- | --- | --- | | 1 | `btnBulkImport` | Disabled | 5. Add one row in the **Decision** grid: | `fileUpload` | `btnBulkImport_disabled` | | --- | --- | | (wildcard) | `=IF(LEN(A)>0,'no','yes')` | ## Configure `btnBulkImport` The `btnBulkImport` Button component starts the upload and import sequence. It starts disabled and is enabled by the `ruleFileUpload` Decisions component once a file is selected. On successful validation, it triggers the `pluginGenerateUploadLink` Plug-In component. 1. Add a **Button** component to `fgUI` after `ruleFileUpload`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `btnBulkImport` | | Label | `Bulk Import` | | Action | **Validate** | | Initial State | **Disabled** | 3. Under **Triggers**, set the **On Validation Success** drop-down to `pluginGenerateUploadLink`. ## Configure `btnRefresh` The `btnRefresh` Button component lets the Creator manually re-check import 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` above `fileUpload`. 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 `initSuccess` The `initSuccess` Initializer component sets the `response` field to `Success` when the import completes. It is triggered by components that confirm a successful outcome. 1. Add an **Initializer** component to `fgConfig`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `initSuccess` | | Trigger Type | **Manual** | 3. Configure the **Outputs**: | # | Property ID | Type | Value | | --- | --- | --- | --- | | 1 | `response` | Value | `Success` | ## Configure `initError` The `initError` Initializer component sets the `response` field to `Fail` when something goes wrong. All Plug-In components in `fgConfig` reference it as their error handler. 1. Add an **Initializer** component to `fgConfig` after `initSuccess`. 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 `pluginGenerateUploadLink` The `pluginGenerateUploadLink` Plug-In component gets a pre-signed URL for uploading the file to the Unqork Cloud. It is triggered by the `btnBulkImport` Button component. On success, it triggers the `pluginUploadFile` Plug-In component. 1. Add a **Plug-In** component to `fgConfig`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginGenerateUploadLink` | | Trigger Type | **Manual** | | Endpoint URL | `https://{environment}.unqork.io/fbu/uapi/bulkOperations/uploadLink` | | Method | **POST** | 3. No Inputs are required for this endpoint. 4. Configure the **Outputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `uploadLink` | `uploadLink` | 5. Under **Triggers**, set the **Post Trigger** drop-down to `pluginUploadFile` and the **Error Handler** drop-down to `initError`. ## Configure `pluginUploadFile` The `pluginUploadFile` Plug-In component posts the file bytes to the pre-signed URL using multipart/form-data. It is triggered by the `pluginGenerateUploadLink` Plug-In component. On success, it triggers the `pluginBulkImportRecord` Plug-In component. The endpoint URL is built dynamically from the `uploadLink` field. 1. Add a **Plug-In** component to `fgConfig` after `pluginGenerateUploadLink`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginUploadFile` | | Trigger Type | **Manual** | | Endpoint URL | `/api{{data.uploadLink}}` | | Method | **POST** | | Multipart | **ON** | 3. Configure the **Headers**: | # | Property ID | Value | | --- | --- | --- | | 1 | `Connect-Type` | `multipart/form-data` | 4. Configure the **Inputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `file` | `fileUpload[0]['url']` | > [!NOTE] > Enable **Resolve Base64** on the `file` input so the platform sends the file bytes rather than the base64 string. 5. Configure the **Outputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `fileKey` | `fileKey` | 6. Under **Triggers**, set the **Post Trigger** drop-down to `pluginBulkImportRecord` and the **Error Handler** drop-down to `initError`. ## Configure `pluginBulkImportRecord` The `pluginBulkImportRecord` Plug-In component calls the Import endpoint using the `fileKey` from the upload step. It is triggered by the `pluginUploadFile` Plug-In component. On success, it triggers the `pluginGetJobDetails` Plug-In component to begin polling. 1. Add a **Plug-In** component to `fgConfig` after `pluginUploadFile`. 2. Configure the following settings: | Setting | Value | | --- | --- | | Property ID | `pluginBulkImportRecord` | | Trigger Type | **Manual** | | Endpoint URL | `https://{environment}.unqork.io/fbu/uapi/bulkOperations/import` | | Method | **POST** | 3. Configure the **Inputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `dataModelOrModuleId` | `'REPLACE_WITH_MODULE_ID'` | | 2 | `fallbackRecordOwner` | `currentUser['email']` | | 3 | `fileFormat` | `'CSV'` | | 4 | `fileLocation` | `fileKey` | | 5 | `name` | `fileUpload[0]['name']` | | 6 | `storeInData` | `'true'` | 4. Configure the **Outputs**: | # | Property ID | Value | | --- | --- | --- | | 1 | `status` | `data.status` | | 2 | `jobId` | `id` | 5. Under **Triggers**, set the **Post Trigger** drop-down to `pluginGetJobDetails` and the **Error Handler** drop-down to `initError`. ## Configure `pluginGetJobDetails` The `pluginGetJobDetails` Plug-In component checks the status of the import job. It is triggered by the `pluginBulkImportRecord` 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 `pluginBulkImportRecord`. 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 | **GET** | 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 written by the `pluginGetJobDetails` Plug-In component, triggers a pop-up message, and enables the `btnRefresh` Button component while the job is running. When the job reaches `completed`, it clears and resets `fgUI` so the Creator can start another upload. 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 | `fgUI` | Pop Message | | 3 | `fgUI` | Multi | 5. Add one row per job status in the **Decision** grid: | Status | `btnRefresh_disabled` | `fgUI_popMessage` | `fgUI_multi` | | --- | --- | --- | --- | | `created` | `=IF(LEN(A)>0,'no','yes')` | `Import started.` | | | `inProgress` | `=IF(LEN(A)>0,'no','yes')` | `Import in progress.` | | | `completed` | `=IF(LEN(A)>0,'no','yes')` | `Import complete.` | `clear.reset` | | `partiallyCompleted` | `=IF(LEN(A)>0,'no','yes')` | `Import partially complete.` | | | `canceled` | `=IF(LEN(A)>0,'no','yes')` | `Import canceled.` | | | `failed` | `=IF(LEN(A)>0,'no','yes')` | `Import failed.` | | The formula in the disabled column uses `A` (the `jobId` alias). The `clear.reset` multi-action on `completed` clears `fgUI` and resets the form for the next upload. ## Test the Module 1. Open the module in **Preview**. 2. Select a CSV file using the **Upload File** component. The **Bulk Import** button enables when a file is present. 3. Click **Bulk Import**. 4. Click **Refresh** until the pop-up message reads `Import complete.` 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. Navigate to the target Data Model's Record Collection to verify the imported records appear. --- **Changelog** | Date | Change. | | --- | --- | | 2026-10-01 | Full restructure: field groups table, per-component Configure sections, Inputs/Outputs as UI-matching tables, Test the Module section, 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. A URL (uniform resource locator) is a unique identifier used to locate a resource on the internet. Also known as a web address. 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 key-value pair is a fundamental data structure where a unique "key" is associated with a specific "value," used for organizing and retrieving data efficiently, like in dictionaries or databases. A Property ID is the unique field ID used by Unqork to track and link components in your module. 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. Enter outputs components and actions you want the component to perform. Rendering is the process of loading components and displaying the final result on the front-end screen. Enter inputs components and actions you want the component to perform.. 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. 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. 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. Base64 is an encoding and decoding technique used to convert binary data to an ASCII (American Standard for Information Interchange) text format. A string is an object that represents a sequence of characters. Strings typically hold data represented in text form. A definition 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. ## Related - [Build a Bulk Download Module](/build-a-bulk-download-module.md) - [Bulk Operations: Utilities](/bulk-operations-utilities.md) - [Property ID Naming Conventions](/property-id-naming-conventions.md)