Step Handler
Concept
The Step Handler provides powerful hooks that execute after each step of the import process, giving you complete control over the data flow and user experience. Unlike its predecessor (Data Handler) which runs before the header and the review steps, the Step Handler runs after each step is completed, allowing you to validate, modify, or block progression based on your requirements.

Description | The Step Handler is an advanced feature that provides hooks after each step of the import process. It enables you to access the raw uploaded and parsed files, validate data, add cell-specific errors/warnings/infos, and control the import flow with customizable alert and block modals. With the Step Handler , you can implement complex validation logic, throw pre-submission errors, access column and option mappings, and provide custom user feedback at any stage of the import process. Step Handler replaces the Data Handler and provides more granular control with hooks for upload, header selection, mapping, and review steps. |
Available hooks |
|
Control functions |
|
Basic syntax
Here is an example of the Step Handler's syntax:
stepHandler={{
uploadStep: async ({parsedData, rawData, updateData, alert, block}) => {
// Hook runs after file upload and parsing
},
headerStep: async ({data, updateData, alert, block}) => {
// Hook runs after header selection confirmation
},
mappingStep: async ({data, tdm, updateData, alert, block, logs}) => {
// Hook runs after mapping confirmation
},
reviewStep: async ({data, updateData, block, logs}) => {
// Hook runs when user clicks "Complete import" in the review step
}
}}
uploadStep()
Description | This hook runs after the user has uploaded one or multiple files and the files have been parsed. It provides access to both the raw uploaded files and their parsed data, allowing validation and transformation before proceeding to the next step. |
Parameters |
|
Run event | The uploadStep function is executed immediately after file upload and parsing is complete |
Parameters
parsedData
Array of sheet objects containing the parsed data and metadata for each uploaded file/sheet.
Sheet object properties:
- data: A 2D array representing the content of the sheet
- fileName: The name of the uploaded file
- fileSize: The size of the file in megabytes (MB)
- fileType: The file extension (e.g., "xlsx", "csv", "xls")
- sheetName: The name of the sheet (for Excel files) or defaults to the
fileName
for single-sheet files
Example:
[
{
"data": [
[
"company",
"order_id",
"customer_email",
"order_instructions",
], ...
],
"fileName": "test-file.xlsx",
"fileSize": 0.0067,
"fileType": "xlsx",
"sheetName": "test-sheet-1"
}, ...
]
rawData
Array of raw file objects containing the unprocessed files exactly as they were uploaded. This gives you access to the original files before any parsing or processing.
updateData
Function to modify the data structure before proceeding to the next step.
Return formats:
-
Single sheet: Return data as a 2D array or array of objects
updateData([
["id", "name"],
["12345", "Jason"],
]); -
Multiple sheets: Return an array of sheet objects
updateData([
{
fileName: "Customers",
sheetName: "Internal", // optional
data: [
// supports 2D array and array of row objects
["id", "name"],
["12345", "Jason"],
],
},
{
fileName: "Products",
data: [
// supports 2D array and array of row objects
{ id: "12", quantity: 24 },
{ id: "13", quantity: 88 },
],
},
]);
alert
Function to show a warning modal with proceed/cancel options.
Parameters:
- title: Title of the warning modal
- description: Description text explaining the warning
- cancelButton: Text for the cancel button
- proceedButton: Text for the proceed button
block
Function to show an error modal that blocks progression.
Parameters:
- title: Text for the modal title
- description: Text explaining the error
- closeButton: Text for the close button
Implementation Example
stepHandler={{
uploadStep: async ({parsedData, rawData, updateData, alert, block}) => {
// Access raw uploaded files
console.log('Raw files:', rawData);
// Process parsed data
const processedData = parsedData.map(file => ({
fileName: file.fileName,
sheetName: file.sheetName,
data: file.data.filter(row => row.length > 0) // Remove empty rows
}));
// Check for issues and potentially block
if (processedData.some(file => file.data.length === 0)) {
block({
title: "Empty File Detected",
description: "One or more files contain no data. Please upload valid files.",
closeButton: "Close"
});
return;
}
// Update data for next step
updateData(processedData);
}
}}
Interaction with other features
- allowManualInput: When the user presses the "Manual Entry" button,
uploadStep()
is not triggered, and the user is forwarded to the review step. - automaticHeaderDetection: When
automaticHeaderDetection === true
, it does not affectuploadStep()
.uploadStep()
is triggered after parsing. If it finishes withoutalert()
orblock()
being called, the user is forwarded to the next step (sheet or header selection). - Contextual Engine:
uploadStep()
runs after file upload, before the Contextual Engine is applied or the modal to use the Contextual Engine is displayed.
headerStep()
Description | This hook runs after the user has confirmed their header selection. It allows validation of the selected headers and provides the ability to modify the data structure before proceeding to either the mapping step or join step. |
Parameters |
|
Run event | The headerStep function is executed when the user confirms their header selection |
Parameters
data
Array of sheet objects containing the data after header selection. It can contain one or multiple sheet objects depending on how many sheets have been selected inside the sheet selection. Each sheet object has the same structure as uploadStep
's parsedData
.
Sheet object properties:
- data: A 2D array representing the content of the sheet, where the first row contains the headers
- fileName: The name of the uploaded file
- fileSize: The size of the file in megabytes (MB)
- fileType: The file extension (e.g., "xlsx", "csv", "xls")
- sheetName: The name of the sheet (for Excel files) or defaults to the
fileName
for single-sheet files
Example:
[
{
"data": [
[
"company",
"order_id",
"customer_email",
"order_instructions",
], ...
],
"fileName": "test-file.xlsx",
"fileSize": 0.0067,
"fileType": "xlsx",
"sheetName": "test-sheet-1"
}, ...
]
updateData
Function to modify the data structure before proceeding to the next step.
Return formats:
- Single 2D array: Forwards the user to the mapping step
- Single array of row objects: Forwards the user to the mapping step
- Array with single sheet object: Forwards the user to the mapping step
- Array with multiple sheet objects: Forwards the user to the join step when multipleFileUpload is activated
Example:
// Single 2D array
updateData([
["id", "name"],
["12345", "Jason"],
]);
// Single array of row objects
updateData([
{ id: "12", quantity: 24 },
{ id: "13", quantity: 88 },
]);
// Array with single sheet object
updateData([
{
fileName: "Sheet1",
data: [
["header1", "header2"],
["value1", "value2"],
],
},
]);
// Array with multiple sheet objects
updateData([
{
fileName: "Sheet1",
data: [
["header1", "header2"],
["value1", "value2"],
],
},
{
fileName: "Sheet2",
data: [
["header3", "header4"],
["value3", "value4"],
],
},
]);
alert
Function to show a warning modal with proceed/cancel options.
Parameters:
- title: Title of the warning modal
- description: Description text explaining the warning
- cancelButton: Text for the cancel button
- proceedButton: Text for the proceed button
block
Function to show an error modal that blocks progression.
Parameters:
- title: Text for the modal title
- description: Text explaining the error
- closeButton: Text for the close button
Implementation example
headerStep: async ({ data, updateData, alert, block }) => {
// Validate required headers
const requiredHeaders = ["id", "email", "name"];
const headers = data[0].data[0];
const missingHeaders = requiredHeaders.filter((h) => !headers.includes(h));
if (missingHeaders.length > 0) {
alert({
title: "Missing required headers",
description: `The following headers are required: ${missingHeaders.join(", ")}`,
cancelButton: "Fix headers",
proceedButton: "Continue anyway",
});
}
// Remove empty columns
const cleanData = data.map((sheet) => ({
...sheet,
data: sheet.data.map((row) => row.filter((_, colIndex) => sheet.data.some((r) => r[colIndex]?.trim()))),
}));
updateData(cleanData);
};
Interaction with other features
- automaticHeaderDetection: When the user presses the "Manual Entry" button,
headerStep()
is not triggered, and the user is forwarded to the review step. - multipleFileUpload: If multiple sheets are returned via
updateData()
but multiple file upload is not activated, only the first sheet object is used for the mapping step. - allowManualInput: When
automaticHeaderDetection === true
,headerStep()
is triggered at the end of the automated header selection. Ifblock()
oralert()
is called, the corresponding modal is shown. Closing the modal returns the user to the previous step. Continuing forwards the user to the next step. - Contextual Engine: If the Contextual Engine is triggered in the header selection step (single sheet import),
headerStep()
does not run. If the Contextual Engine is triggered in the join step (multiple sheets import),headerStep()
runs at the end of header selection/before going to the join step.
mappingStep()
Description | This hook runs when the user confirms their column mappings and all required mappings are complete. It provides access to the mapped data and the Target Data Model (TDM), allowing validation and modification of both the schema and data. |
Parameters |
|
Run event | The mappingStep function is executed after the user confirms their column mappings and all required mappings are complete |
Parameters
data
Array of row objects containing the mapped values using the Target Data Model (TDM) keys.
Example:
[
{
id: "12345",
name: "Jason Miller",
email: "[email protected]"
}, ...
]
tdm
Target Data Model object for modifying the schema.
Available methods:
- addColumn(): Add a new column to the schema
tdm.addColumn({
key: "column_key",
label: "column_label",
columnType: "string",
validations: [{ validate: "required" }],
hidden: false,
disabled: false,
}); - removeColumn(): Remove a column from the schema via its
key
tdm.removeColumn("column_key");
updateData
Function to update data and adding cell-specific error/warning/info messages.
Data formats:
- Simple data: Array of row objects without validation messages
updateData([
{ id: "12345", name: "Jason" },
{ id: "67890", name: "Max" },
]); - Data with validation: Array of row objects with value and info properties
updateData([
{
id: {
value: "12345",
info: [
{
message: "Invalid ID format",
level: "error",
},
],
},
},
]);
alert
Function to show a warning modal with proceed/cancel options.
Parameters:
- title: Title of the warning modal
- description: Description text explaining the warning
- cancelButton: Text for the cancel button
- proceedButton: Text for the proceed button
block
Function to show an error modal that blocks progression.
Parameters:
- title: Text for the modal title
- description: Text explaining the error
- closeButton: Text for the close button
logs
Object containing details about the column and option mappings, the custom-added columns and options during the mapping step.
Properties:
- mappings: Array of mapping objects containing relation between input and target columns, including option mappings for category columns
- columns: Object containing added columns and options via
allowCustomColumns
andallowCustomOptions
Advanced
Example:
{
"mappings": [
{
"sourceColumn": "Company Identification Number", // input column
"targetColumn": "company_code" // matched Target Data Model column
},
{
"sourceColumn": "Status",
"targetColumn": "deal_status",
"options": [ // when this is a category column (columnType === "category", "currency_code", "country_code_alpha_2" or "country_code_alpha_3"
{
"sourceValue": "In progress", // input value
"targetOptions": ["Ongoing", ...] // matched Target Data Model dropdown option (can contain multiple strings when isMultiSelect === true)
},
{
"sourceValue": "TBD",
"targetOptions": [] // when being unmatched
}
]
},
{
"sourceColumn": "",
"targetColumn": "address"
}, ...
],
"columns": {
"addedColumns": [
{
"label": "Revenue",
"key": "revenue",
"columnType": "currency_eur"
}
],
"addedOptions": [
{
"columnKey": "deal_status",
"dropdownOptions": [
{
"label": "Done",
"value": "done",
"type": "string"
}
]
}
]
}
}
Implementation example
mappingStep: async ({ data, tdm, updateData, alert, block, logs }) => {
// Check mapping completeness
const unmappedRequired = logs.mappings.filter((m) => !m.sourceColumn).map((m) => m.targetColumn);
if (unmappedRequired.length > 0) {
block({
title: "Unmapped Columns",
description: `Every column needs to be mapped. Please map the following columns: ${unmappedRequired.join(", ")}`,
closeButton: "Fix mappings",
});
return;
}
// Add custom validation column
tdm.addColumn({
key: "status",
label: "Validation status",
columnType: "string",
hidden: true,
});
// Validate email format
const validatedData = data.map((row) => {
const validatedRow = { ...row };
if (row.email && !row.email.includes("@")) {
validatedRow.email = {
value: row.email,
info: [
{
message: "Invalid email format",
level: "error",
},
],
};
validatedRow.status = "Invalid";
}
return validatedRow;
});
updateData(validatedData);
};
Interaction with other features
- Cleaning Functions:
mappingStep()
is executed before column hooks andonEntryInit()
.columnHooks()
receives the data returned bymappingStep()
(without errors, warnings, infos). Ifalert()
orblock()
is called,columnHooks()
andonEntryInit()
are not triggered. - automaticMapping:
mappingStep()
is called even if the mapping step is skipped via automatic mapping. Ifblock()
oralert()
are used, the mapping step is not skipped and the corresponding alert/block modal is shown. - onlyMappedColumns: Normally, row objects in
data
contain all TDM keys. IfonlyMappedColumns === true
, only mapped TDM columns are included. - Contextual Engine: If the Contextual Engine runs and is successful,
mappingStep()
is triggered with the output of the Contextual Engine. - Hidden & disabled: Hidden and disabled columns are accessible and updatable via
data
andupdateData
inmappingStep()
. Errors on hidden columns are ignored.
reviewStep()
Description | This hook runs when the user attempts to complete their import from the review step. It serves as the final validation point before submission, allowing pre-submission checks and data enrichment. |
Parameters |
|
Run event | The reviewStep function is executed when the user clicks "Complete import" in the review step |
Parameters
data
Array of row objects containing the data, shown in the review step, including all error/warning/info messages. If the cell has no extra message, info
is undefined
.
Example:
[
{
id: {
value: "12345",
info: [{
message: "ID verified",
level: "info"
}]
},
email: {
value: "@getnuvo.com",
info: [{
message: "Invalid email format",
level: "error"
}]
},
amount: {
value: 36000,
info: [{
message: "This value seems to extraordinary high. Please check again.",
level: "warning"
}]
},
name: {
value: "Jason Miller",
info: undefined
},
...
}, ...
]
updateData
Function to update the final data before submission. You can modify the values and add error/warning/info messages cell-specific.
Example:
// Add an error message to every name cell
updateData(
data.map((row) => ({
...row,
name: {
value: row.name,
info: [
{
message: "Invalid name",
level: "error",
},
],
},
})),
);
block
Function to show an error modal that blocks progression.
Parameters:
- title: Text for the modal title
- description: Text explaining the error
- closeButton: Text for the close button
Note that alert
is not available in reviewStep()
logs
Object containing details about the column and option mappings, the custom-added columns and options during the entire import process.
Properties:
- mappings: Array of mapping objects containing relation between input and target columns, including option mappings for category columns
- columns: Object containing added columns and options via
allowCustomColumns
andallowCustomOptions
Advanced
Example:
{
"mappings": [
{
"sourceColumn": "Company Identification Number", // input column
"targetColumn": "company_code" // matched Target Data Model column
},
{
"sourceColumn": "Status",
"targetColumn": "deal_status",
"options": [ // when this is a category column (columnType === "category", "currency_code", "country_code_alpha_2" or "country_code_alpha_3"
{
"sourceValue": "In progress", // input value
"targetOptions": ["Ongoing", ...] // matched Target Data Model dropdown option (can contain multiple strings when isMultiSelect === true)
},
{
"sourceValue": "TBD",
"targetOptions": [] // when being unmatched
}
]
},
{
"sourceColumn": "",
"targetColumn": "address"
}, ...
],
"columns": {
"addedColumns": [
{
"label": "Revenue",
"key": "revenue",
"columnType": "currency_eur"
}
],
"addedOptions": [
{
"columnKey": "deal_status",
"dropdownOptions": [
{
"label": "Done",
"value": "done",
"type": "string"
}
]
}
]
}
}
Implementation example
stepHandler={{
reviewStep: async ({data, updateData, block, logs}) => {
// Pre-submission validation
const errorRows = data.filter(row =>
Object.values(row).some(cell =>
cell?.info?.some(info => info.level === 'error')
)
);
if (errorRows.length > 0) {
block({
title: "Data Validation Failed",
description: `${errorRows.length} rows contain errors that must be fixed before submission.`,
closeButton: "Fix Errors"
});
return;
}
// Add submission timestamp
const timestampedData = data.map(row => ({
...row,
submitted_at: new Date().toISOString()
}));
updateData(timestampedData);
}
}}
Interaction with other features
- onlyMappedColumns: Normally,
data
contains all TDM keys, even if empty or null. IfonlyMappedColumns === true
, only mapped TDM columns are included. - Hidden & disabled: Hidden and disabled columns are accessible and updatable via
data
andupdateData
inreviewStep()
. Errors on hidden columns are ignored.
Control functions
alert()
Description:
Shows a warning modal with "Continue" and "Cancel" options. Clicking "Cancel" or the "X" button closes the modal and keeps the user on the current step. Clicking "Continue" allows the user to proceed to the next step.
Alert modal:

Parameters:
- title: Title of the modal (default: "Are you sure you want to continue?")
- description: Description text (default: "Some required steps may not be completed yet.")
- cancelButton: Text for cancel button (default: "Cancel")
- proceedButton: Text for proceed button (default: "Continue")
Example:
alert({
title: "Warning Title",
description: "Warning description text",
cancelButton: "Cancel",
proceedButton: "Continue",
});
Callback availability:
Callback | Availablility |
---|---|
Callback | Availablility |
uploadStep() | Yes |
headerStep() | Yes |
mappingStep() | Yes |
reviewStep() | No |
block()
Description:
Shows an error modal that prevents the user from proceeding. Clicking the "Close" or "X" button closes the modal and returns the user to the current step.
Block modal:

Parameters:
- title: Title of the modal (default: "Unable to continue")
- description: Description text (default: "Something went wrong. Please try again.")
- closeButton: Text for close button (default: "Close")
Example:
block({
title: "Error Title",
description: "Error description text",
closeButton: "Close",
});
Callback availability:
Callback | Availablility |
---|---|
Callback | Availablility |
uploadStep() | Yes |
headerStep() | Yes |
mappingStep() | Yes |
reviewStep() | Yes |
Implementation examples
- React
- Angular
- Vue
- JavaScript
<NuvoImporter
licenseKey="Your License Key"
settings={{
developerMode: true,
identifier: "product_data",
columns: [
{
key: "id",
label: "Product ID",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "name",
label: "Product Name",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "price",
label: "Price",
columnType: "number",
},
{
key: "email",
label: "Contact Email",
columnType: "string",
},
],
}}
stepHandler={{
uploadStep: async ({ parsedData, rawData, updateData, alert, block }) => {
// Hook runs after file upload and parsing
},
headerStep: async ({ data, updateData, alert, block }) => {
// Hook runs after header selection confirmation
},
mappingStep: async ({ data, tdm, updateData, alert, block, logs }) => {
// Hook runs after mapping confirmation
},
reviewStep: async ({ data, updateData, block, logs }) => {
// Hook runs when user clicks "Complete import" in the review step
},
}}
onResults={(results, errors, complete, logs, block) => {
console.log("Import completed:", results);
complete();
}}
/>
- Add the NuvoImporter module to
app.module.ts
:
import { NuvoImporterModule } from "@getnuvo/importer-angular";
@NgModule({
declarations: [AppComponent],
imports: [NuvoImporterModule, BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
- Add the component to
app.component.html
:
<nuvo-importer [licenseKey]="licenseKey" [settings]="settings" [stepHandler]="stepHandler" [onResults]="onResults.bind(this)" />
- Define properties in
app.component.ts
:
export class AppComponent implements OnInit {
settings!: SettingsAPI;
licenseKey!: string;
stepHandler!: StepHandler;
ngOnInit(): void {
this.licenseKey = "Your license key";
this.settings = {
identifier: "product_data",
developerMode: true,
columns: [
{
key: "id",
label: "Product ID",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "name",
label: "Product Name",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "email",
label: "Contact Email",
columnType: "string",
},
],
};
this.stepHandler = {
uploadStep: async ({ parsedData, rawData, updateData, alert, block }) => {
// Hook runs after file upload and parsing
},
headerStep: async ({ data, updateData, alert, block }) => {
// Hook runs after header selection confirmation
},
mappingStep: async ({ data, tdm, updateData, alert, block, logs }) => {
// Hook runs after mapping confirmation
},
reviewStep: async ({ data, updateData, block, logs }) => {
// Hook runs when user clicks "Complete import" in the review step
},
};
onResults = (result, errors, complete, logs, block) => {
console.log("Result:", result);
console.log("Error rows:", errors);
console.log("Logs:", logs);
complete();
};
}
}
- Add the component to
App.vue
:
<template>
<div id="app">
<NuvoImporter :settings="settings" :licenseKey="licenseKey" :stepHandler="stepHandler" :onResults="onResults" />
</div>
</template>
- Define the component script:
export default {
name: "App",
components: {
NuvoImporter,
},
setup() {
const settings = {
developerMode: true,
identifier: "product_data",
columns: [
{
key: "id",
label: "Product ID",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "name",
label: "Product Name",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "email",
label: "Contact Email",
columnType: "string",
},
],
};
return { settings };
},
data: () => {
return {
licenseKey: "Your License Key",
stepHandler: {
uploadStep: async ({ parsedData, rawData, updateData, alert, block }) => {
// Hook runs after file upload and parsing
},
headerStep: async ({ data, updateData, alert, block }) => {
// Hook runs after header selection confirmation
},
mappingStep: async ({ data, tdm, updateData, alert, block, logs }) => {
// Hook runs after mapping confirmation
},
reviewStep: async ({ data, updateData, block, logs }) => {
// Hook runs when user clicks "Complete import" in the review step
},
},
};
},
methods: {
onResults: (result, errors, complete, logs, block) => {
console.log("Result:", result);
console.log("Error rows:", errors);
console.log("Logs:", logs);
complete();
},
},
};
<div class="nuvo-container" />
<script type="module">
import { launchNuvoImporter } from "@getnuvo/importer-vanilla-js";
launchNuvoImporter(".nuvo-container", {
licenseKey: "Your License Key",
settings: {
developerMode: true,
identifier: "product_data",
columns: [
{
key: "id",
label: "Product ID",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "name",
label: "Product Name",
columnType: "string",
validations: [{ validate: "required" }],
},
{
key: "email",
label: "Contact Email",
columnType: "string",
},
],
},
stepHandler: {
uploadStep: async ({ parsedData, rawData, updateData, alert, block }) => {
// Hook runs after file upload and parsing
},
headerStep: async ({ data, updateData, alert, block }) => {
// Hook runs after header selection confirmation
},
mappingStep: async ({ data, tdm, updateData, alert, block, logs }) => {
// Hook runs after mapping confirmation
},
reviewStep: async ({ data, updateData, block, logs }) => {
// Hook runs when user clicks "Complete import" in the review step
},
},
onResults: (result, errors, complete, logs, block) => {
console.log("Import completed:", result);
complete();
},
});
</script>
Interaction with Cleaning Functions
The Step Handler can be used together with our Cleaning Functions to validate and transform data after the mapping step. mappingStep()
runs first, followed by columnHooks()
, which operates on the data returned from mappingStep()
. Finally, onEntryInit()
executes using the data as modified by both of the preceding functions.
Here is an overview of all import steps and hooks, including those from the Step Handler, the Cleaning Functions, and onResults()
.

Migration from Data Handler
The Step Handler replaces the Data Handler with improved functionality.
Here is a quick overview of the main differences and similarities:
Data Handler | Step Handler | Description |
headerStep() | uploadStep() | Runs after file upload |
reviewStep() | mappingStep() | Runs after mapping confirmation |
N/A | headerStep() | New hook for accessing/modifying data after header selection |
N/A | reviewStep() | New hook for pre-submission validation |
N/A | block() | Blocks user from progression under certain conditions |
N/A | alert() | Shows custom alert modal under certain conditions |
N/A | Raw file access | Available in uploadStep() |
N/A | Blocking user during pre-submission | Available in reviewStep() via block() |
Migration steps:
- Replace
dataHandler
withstepHandler
- Move
dataHandler.headerStep()
logic tostepHandler.uploadStep()
- Move
dataHandler.reviewStep()
logic tostepHandler.mappingStep()
- Update data access patterns for new parameter structure & return mechanism
- Optionally: Add new
stepHandler.headerStep()
to access and modify data after the header selection andstepHandler.reviewStep()
for pre-submission validation
We offer two options to help with your migration:
- Use our Migration GPT for automated guidance
- Contact our support team at [email protected]
For more examples and advanced use cases, visit our knowledge base in our User Platform.