",
"expiresAt": "2026-08-26T18:00:00Z"
}
```
### Step 2 — Pass to the widget
```javascript theme={null}
const importer = new DromoUploader("YOUR_LICENSE_KEY", fields, {
backendSyncMode: "FULL_DATA",
savePartialSession: true,
importIdentifier: "contacts-import-v1",
hydrationId: freshHydrationId,
});
```
The widget uses the `hydrationId` to fetch the saved state and resume from where the user left off.
***
## Deleting a session
Sessions are automatically cleared when an import completes. To delete manually, send a `DELETE` request with the session's UUID and your **backend** license key:
```bash theme={null}
curl --request DELETE \
--url https://app.dromo.io/api/widget/session/v2/SESSION_ID/ \
--header 'X-DROMO-LICENSE-KEY: YOUR_BACKEND_LICENSE_KEY'
```
| Field | Required | Description |
| ------------------ | -------- | ------------------------------ |
| `SESSION_ID` (URL) | Yes | UUID of the session to delete. |
**Response:**
```json theme={null}
{ "success": true }
```
All hydration tokens for the session are immediately invalidated — any subsequent widget calls with the old `hydrationId` return 403. This call is idempotent and returns `success: true` even if the session was already deleted.
***
## Error reference
| Error | Meaning | Action |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `404` on resume or delete | Session not found or not saved within 7 days | Start a new session |
| `403` on widget call | `hydrationId` invalidated (session deleted or token expired) | Re-issue a `hydrationId` via the resume endpoint, or start a new session |
***
## Full endpoint reference
All endpoints require `X-DROMO-LICENSE-KEY` (backend license key).
| Method | Endpoint | Description |
| -------- | ------------------------------------------ | -------------------------------------------------------------------------- |
| `POST` | `/api/widget/session/v2/auth/` | Create a new session. Returns `sessionId`, `hydrationId`, and `expiresAt`. |
| `POST` | `/api/widget/session/v2/{sessionId}/auth/` | Re-issue a `hydrationId` for an existing session. |
| `DELETE` | `/api/widget/session/v2/{sessionId}/` | Delete a session and invalidate all its tokens. Idempotent. |
# Using Schema Studio
Source: https://developer.dromo.io/guides/schema-builder
Create, configure, test, and manage Dromo import schemas visually with Schema Studio in the Dromo dashboard.
Dromo's Schema Studio streamlines creating a schema, so you can start working with Dromo.
## Creating a Schema[](#creating-a-schema "Direct link to Creating a Schema")
[Click here to get started with Schema Studio now!](https://dashboard.dromo.io/public/schemas/create)
Once you've [logged into](https://dashboard.dromo.io/auth/login) your Dromo account, you can access Schema Studio from your [Dromo dashboard](https://dashboard.dromo.io/schemas). Here, you can create a new schema or update any schemas you've previously created. Click **Create Schema**:
Next, you can customize the importer your end-user interacts with:
You can optionally configure the importer's:
* title
* behavior for [invalid data](/reference/settings/settings#invaliddatabehavior)
* a [webhook URL](/reference/webhooks) where new submissions are sent
* ability to let users add [custom fields](/reference/settings/settings#allowcustomfields)
* private mode settings
Private mode lets you to control whether user data is sent or persisted through Dromo's servers. Private mode is only available on the [Dromo Pro plan](https://dromo.io/pricing).
You can also optionally customize your importer's appearance with custom colors. You can preview your Dromo importer's appearance by clicking **Test now** in the footer:
Next, you'll define your schema's fields. You can think of a schema's [fields](/reference/fields) like columns in a spreadsheet.
If you are importing your user-collected data into a database, you'll want your data to be in the shape your database table expects. This means your schema's fields usually match the columns in your database. For example, let's say you have a client database table with two columns for clients' names and emails. Your schema would have two fields to match your database table; one for our clients' names and a second for their emails.
You can add fields in two ways:
1. Importing a CSV and letting Schema Studio automatically detect the field and types.
2. Manually clicking the **Add field** button.
Once you've added a new field, clicking on the name of that field enables you to customize or delete it. You can customize a field's:
* Label: The column name your users see in the importer.
* Key: The field's unique identifier is this field's key in the JSON results.
* Description: A description for this field that your users will see in the importer.
* Type: The [type](/reference/fields/field-types) of data your user should input (e.g., email, date, text, etc.).
Additionally, you can use [**validators**](/reference/fields/field-validators) to enforce rules for the data a user is allowed to import. You can choose to make a field required, unique, follow a particular [Regex](https://en.wikipedia.org/wiki/Regular_expression) pattern, or any combination of the above:
You can try out your importer settings and fields in real-time using the **Test now** button on the footer on the screen:
Once you've customized your schema, click **Save schema**. You'll be redirected to the main Schema Studio page, where you can see your new schema:
## Using Schemas
To start using a schema in your application, click on the angled brackets icon in the Schemas Tab in the Dashboard:
Copy and paste the pop-up code block into your own website's code and you're done! 🎉 You should be able to test the flow now.
You can see your user's imports from the [**Imports** Tab](https://dashboard.dromo.io/imports) in your Dromo dashboard.
However, to integrate into your application, you will still need to handle the data, which you can do via the [`onResult` callback](/reference/results).
```javascript JavaScript theme={null}
dromo.onResults((response, metadata) => {
// Connect to our database
db.connect();
// create new entries in our database using the user's imported data
db.uploadData(response);
});
```
```jsx React theme={null}
// Connect to our database
db.connect();
// create new entries in our database using the user's imported data
db.uploadData(response);
}
>
```
## Advanced Usage
### Assigning User Metadata
Currently, your importer doesn't know *who* is using it to upload information. If a user uploads data, there is no associated metadata for that user, so all user-related fields are left blank:
However, you can add more context to an upload by setting user metadata about the end user to associate with an import:
```javascript theme={null}
const dromo = new DromoUploader("YOUR_LICENSE_KEY", "YOUR_SCHEMA_ID");
// Create a user object to associate imports to
const user = {
id: '1',
name: 'Jane Doe',
email: 'jane@dromo.io',
companyId: '12345',
companyName: 'Dromo'
};
// Associate the above user to this import
dromo.setUser(user);
```
### Development Mode[](#development-mode "Direct link to Development mode")
The `DromoUploader` offers a "development mode" for testing import flows or iterating on finishing touches. Dromo doesn't charge you for data you import in development mode, but you are restricted to only importing 100 rows for any given file.
Your importer runs in development mode by default if you do **not** have an active Dromo subscription. To run Dromo in production, upgrade [to one of our pricing plans](https://dromo.io/pricing).
You can switch your `DromoUploader` into development mode using the `setDevelopmentMode` method, like so:
```javascript theme={null}
```
The importer now shows a development mode banner, and all incoming imports are now flagged as development imports and you don't get charged for them!
# Concatenating and Splitting Fields via addField
Source: https://developer.dromo.io/guides/using-addField
Use step hooks, addField, removeField, and row hooks to dynamically concatenate, split, add, or remove fields during an import.
This guide demonstrates how to use step hooks to add or remove fields midway through an import.
For more information on the `instance.addField` method, see the [Reference](/reference/hooks#instanceaddfield) documentation. For more information on the `instance.removeField` method, see the [Reference](/reference/hooks#instanceremovefield) documentation.
```javascript JavaScript theme={null}
dromo.registerStepHook("REVIEW_STEP", function (instance, data) {
instance.addField({
label: "Full Name",
key: "fullName",
});
});
dromo.registerStepHook("REVIEW_STEP_POST_HOOKS", function (instance, data) {
instance.removeField("firstName");
instance.removeField("lastName");
});
dromo.registerRowHook(function (data, mode) {
let out: IRowHookOutput = { row: { ...data.row } };
if (data.row.firstName && data.row.lastName && mode === "init") {
out.row.fullName = {
value: data.row.firstName.value + " " + data.row.lastName.value,
info: [
{
message: "combined first and last name into full name",
level: "info",
},
],
};
}
return out;
});
```
```jsx React theme={null}
{
instance.addField({
label: "Full Name",
key: "fullName",
})
}
},
{
type: "REVIEW_STEP_POST_HOOKS",
callback: (instance, _data) => {
instance.removeField("firstName");
instance.removeField("lastName");
}
}
]}
rowHooks={[
(data: IRowHookInput, mode: string) => {
let out: IRowHookOutput = { row: { ...data.row } };
if (data.row.firstName && data.row.lastName && mode === 'init') {
out.row.fullName = {
value: data.row.firstName.value + " " + data.row.lastName.value,
info: [
{
message: "combined first and last name into full name",
level: "info"
}
]
}
}
return out
}
]}>
Launch Dromo
```
# Mapping Multiple Columns to a Single Field
Source: https://developer.dromo.io/guides/using-manyToOne
Configure manyToOne fields to map a variable number of uploaded spreadsheet columns into a single Dromo schema field.
This guide demonstrates how to use field setting to map multiple columns to a single field.
For more detail about the `manyToOne` setting, see the [field object reference](/reference/fields/fields#field-object-reference).
Suppose we're collecting tax information: `email`, `primary_earnings`, and optional `additional_earnings`. Users upload spreadsheets with these columns, though the number of `additional_earnings` columns may vary. We'll map them all to a single `additional_earnings` field and validate all earnings as USD currency.
## Field Configuration
Adding the property `manyToOne` with the value `true` enables many to one mapping.
```javascript JavaScript theme={null}
export const fields = [
{
label: 'Email',
key: 'email',
type: 'email'
},
{
label: 'Primary Earnings',
key: 'primary_earnings',
type: ['number', 'usd'],
},
{
label: 'Additional Earnings',
key: 'additional_earnings',
type: ['number', 'usd'],
manyToOne: true,
},
];
```
```jsx React theme={null}
Import Earnings
```
With this setting enabled, a user will be able to choose multiple columns and match them to the field without error.
## Data Review Screen
With multiple columns mapped to one field, all the columns will have the label with a number indicating their order.
Note that validations defined in a `manyToOne` field apply to all mapped columns.
## Results
Upon successful import, the [results and metadata](/reference/results#the-onresults-callback) include all the values from the mapped columns and metadata indicating how the columns were mapped to the target schema.
### Result Data
Each value from a `manyToOne` column appear in an array that is the value for the `manyToOne` field's key.
```json theme={null}
[
{
"email": "tumvu@comowa.fk",
"primary_earnings": 8954.08,
"additional_earnings": [
66.66,
88.88
]
},
{
"email": "hovawu@goiv.io",
"primary_earnings": 446.02,
"additional_earnings": [
6478.45,
7435.89
]
},
{
"email": "idmup@te.au",
"primary_earnings": 2175.84,
"additional_earnings": [
7949.13,
1998.52
]
}
...
]
```
### Result Metadata
The result metadata indicates which columns from the source file were mapped to which columns.
```json theme={null}
{
"id": null,
"filename": "earnings.csv",
"importIdentifier": "earnings",
"user": {
"id": "testUser"
},
"rowsWithError": [],
"rawHeaders": [
"email",
"salary",
"gifts",
"bonuses"
],
"fields": {
"email": {
"fileHeaderIndex": 0,
"fileHeader": "email",
"isCustom": false,
"manyToOne": false
},
"primary_earnings": {
"fileHeaderIndex": 1,
"fileHeader": "salary",
"isCustom": false,
"manyToOne": false
},
"additional_earnings": {
"fileHeader": null,
"fileHeaders": [
"gifts",
"bonuses"
],
"fileHeaderIndex": null,
"fileHeaderIndexes": [
2,
3
],
"isCustom": false,
"manyToOne": true
}
},
"errors": []
}
```
## Hooks
### Row Hook
`manyToOne` fields can have their values changed and their info messages and validations set in [row hooks](/reference/hooks#row-hooks).
```javascript JavaScript theme={null}
dromo.registerRowHook(function (record) {
if (record.index !== 2) return record;
record.row.additional_earnings.manyToOne![0].value = 42.0;
record.row.additional_earnings.manyToOne![1].info = [
{ level: "error", message: "Invalid additional earning" },
];
return record;
});
```
```jsx React theme={null}
{
if (record.index !== 2) return record;
record.row.additional_earnings.manyToOne![0].value = 42.0;
record.row.additional_earnings.manyToOne![1].info = [
{ level: "error", message: "Invalid additional earning" },
];
return record;
}
]}
...>
Import Earnings
```
### Column Hooks
For `manyToOne` fields, a [column hook](/reference/hooks#column-hooks) callback registered for a `manyToOne` field will be invoked once for every mapped column.
```javascript JavaScript theme={null}
dromo.registerColumnHook("additional_earnings", (values) => {
// Cap additional earnings at $5,000
return values.map((v) => {
if (v.value > 5000) {
return {
...v,
value: 5000,
info: [{ level: "info", message: "capped at $5000" }],
};
} else return v;
});
});
```
```jsx React theme={null}
{
// Cap additional earnings at $5,000
return values.map((v) => {
if (v.value > 5000) {
return {
...v,
value: 5000,
info: [{ level: "info", message: "capped at $5000" }],
};
} else return v;
});
}
}]}
...>
Import Earnings
```
### Step Hooks
[Step hooks](/reference/hooks#step-hooks) `REVIEW_STEP` and `REVIEW_STEP_POST_HOOKS` can be used to inspect column mapping at run time to see which columns were mapped to `manyToOne` fields.
The full data object supplied as an argument to the step hook will resemble this:
```json theme={null}
{
"fields": {
"email": {
"fileHeaderIndex": 0,
"fileHeader": "email",
"isCustom": false,
"manyToOne": false
},
"primary_earnings": {
"fileHeaderIndex": 1,
"fileHeader": "salary",
"isCustom": false,
"manyToOne": false
},
"additional_earnings": {
"fileHeader": null,
"fileHeaders": [
"gifts",
"bonuses"
],
"fileHeaderIndex": null,
"fileHeaderIndexes": [
2,
3
],
"isCustom": false,
"manyToOne": true
}
},
"rawHeaders": [
"email",
"salary",
"gifts",
"bonuses"
],
"headerMapping": {
"email": "email",
"salary": "primary_earnings",
"gifts": "additional_earnings",
"bonuses": "additional_earnings"
}
}
```
So in code:
```javascript JavaScript theme={null}
...
dromo.registerStepHook("REVIEW_STEP", (_uploader, data) => {
console.log("manyToOne file headers", data.fields.additional_earnings.fileHeaders);
// ["gifts", "bonuses"]
console.log("manyToOne file indexes", data.fields.additional_earnings.fileHeaderIndexes);
// [2, 3]
});
...
```
```jsx React theme={null}
{
console.log("manyToOne file headers", data.fields.additional_earnings.fileHeaders);
// ["gifts", "bonuses"]
console.log("manyToOne file indexes", data.fields.additional_earnings.fileHeaderIndexes);
// [2, 3]
}
}
]}
...>
Import Earnings
```
# AI Editing with User Transformations
Source: https://developer.dromo.io/guides/using-transform-data
Enable Transform Data so users can describe, preview, and apply AI-generated bulk data edits from Dromo's review step.
## Overview
Dromo's Transform Data feature allows your users to create bulk transformations from natural language and execute them on the dataset. The feature is available on the Pro plan.
Enable Transform Data with the [`reviewStep.enableUserTransformations` setting](/reference/settings/settings#enableusertransformations).
With `enableUserTransformations` enabled, you will see a "Transform Data" button on the Data Review step.
Click this button to open the Transform Data modal. You will be prompted for what you would like to do to your data.
Transform Data takes a user's request and turns it in to a transformation that can be applied to the data. If the request was successfully able to produce a transformation, the result is previewed to the user so they can optionally accept or reject it.
The feature can edit values and remove rows. It cannot add rows.
## Best Practices
### Include context about inputs
The AI building the transformation function never has access to the import data, so including context about both the input and output is often helpful.
For example, given a column containing dates formatted like "Jul-8-1999" where you wish to convert them to "07/08/1999," you should request "Change the date format from 'Jul-8-1999' to MM-DD-YYYY." A request unlikely to succeed would be "Change the dates to US format" because the AI does not have access to the current format.
### Only apply transformations to one row at a time
A transformation only has access to an individual row. If you need to perform an operation that requires knowledge of other rows or information outside of the table, the transformation will not succeed.
### Keep expectations realistic
Transform data performs best when it is given a simple, well-scoped task. Changing values based on simple criteria, adjusting date and time formats, and removing rows based on simple conditions are all great uses for the transform data feature.
## Privacy
Dromo sends the request to OpenAI along with some information about the schema, but absolutely no user data is transmitted as part of this process.
# Using Field Types
Source: https://developer.dromo.io/guides/using-types
Choose and configure Dromo field types to validate, transform, format, and consistently output imported spreadsheet data.
Dromo's field types define how a field accepts, transforms, validates, and outputs data. They ensure consistent data types across a field (e.g., `number` for numeric values, `string` for text). Field types cover categories like numbers, text, dates, and times.
### Selecting the Best Type
By default, if you don't specify a type for a field, it uses the [`string` type](/reference/fields/field-types#string%E2%80%8B). For example, below, we create a field for collecting email addresses:
```javascript JavaScript theme={null}
const fields = [
{
label: 'Email Address',
key: 'emailAddress'
}
];
const settings = {
importIdentifier: 'Field types example'
};
const user = {
id: '12345'
};
const dromo = new DromoUploader('FRONTEND_API_KEY', fields, settings, user);
```
```jsx React theme={null}
Import data
```
Because we didn't specify a *type* in the above "Email Address" field, it uses the default `string` type.
However, if we update the "Email address" field to use the [`email` type](/reference/fields/field-types#email%E2%80%8B), that field gains the advantage of Dromo's built-in email address validation:
```javascript JavaScript theme={null}
const fields = [
{
label: 'Email',
key: 'email',
type: 'email'
}
];
```
```jsx React theme={null}
```
Selecting the ideal type for each field is essential to honing your Dromo workflow. [See all field types.](/reference/fields/field-types)
### Configuring Field Types
Certain field types offer different configuration options, enabling you to customize how data is validated, displayed to the user, and output.
For example, we want a field that tracks prices in USD. We can add a "Price" field using the ["usd" preset](/reference/fields/field-types#number%E2%80%8B):
```javascript theme={null}
const fields = [
{
label: 'Price',
key: 'price',
type: ['number', { preset: 'usd' }]
}
];
```
Dromo provides many preset options for formatting standard `number`, `date`, `time`, and `datetime` formats. Presets work by automatically filling preset values into the configuration options for a field type.
You can manually configure the options for a number type:
```javascript theme={null}
type: [
"number",
{
round: ,
displayFormat: ,
outputFormat:
}
]
```
Or you can use a preset, which configures options for you. Under the hood, the "usd" preset inputs the following options:
```javascript theme={null}
{
round: 2,
displayFormat: {
mantissa: 2,
thousandSeparated: true,
output: "currency",
average: false,
spaceSeparatedCurrency: true,
currencyPosition: "prefix",
currencySymbol: "$"
}
}
```
To learn more about configuring your types without presets, see [customizing numbers](/reference/fields/field-types#number%E2%80%8B) or [customizing dates, times, and datetimes](/reference/fields/field-types#datetime%E2%80%8B).
The "usd" preset ensures that this `number` field parses, validates, displays, and outputs values in dollars. Therefore, if a user attempts to insert a value that *can't* be parsed into USD (i.e., another currency), Dromo throws a validation error:
For numbers (and a few other field types) the display format is not what the field outputs. If you want to change how a field is output, the `outputFormat` configuration option enables you to do so. For example, if you want to convert a number into USD, you can use the ["usd" preset](/reference/fields/field-types#number%E2%80%8B) alongside an `outputFormat` option.
```javascript theme={null}
{
label: 'Price',
key: 'price',
type: [
'number',
{
preset: 'usd',
outputFormat: {
mantissa: 2,
thousandSeparated: true,
output: 'currency',
average: false,
spaceSeparatedCurrency: true,
currencyPosition: 'prefix',
currencySymbol: '$'
}
}
]
}
```
# Validating with External Data
Source: https://developer.dromo.io/guides/validate-external-data
Use asynchronous Dromo row hooks to validate imported values against an external API or another remote data source.
The following example demonstrates how to use row hooks to validate data against an external API.
For more information on row hooks, see the [Row Hooks](/reference/hooks#row-hooks) documentation.
Although it's not required to use `async`/`await` with hooks, we recommend you use it with an external API call.
```javascript JavaScript theme={null}
export const fields = [
{
label: 'Email',
key: 'email'
}
];
let hasFetchedData = false;
let serverResults;
dromo.registerRowHook(async function (record) {
const newRecord = record;
if (!hasFetchedData) {
await fetch('https://my-json-server.typicode.com/dromoio/docs-email-api/leads', {
method: 'GET'
})
.then((response) => response.json())
.then(function (json) {
serverResults = json.map((record) => {
return record['email'];
});
hasFetchedData = true;
});
}
if (serverResults.includes(newRecord.row['email'].value)) {
newRecord.row['email'].info = [
{
message: 'Duplicate record. This user is already in the database.',
level: 'error'
}
];
}
return newRecord;
});
```
```jsx React theme={null}
let hasFetchedData = false;
let serverResults;
{
const newRecord = record;
if (!hasFetchedData) {
await fetch('https://my-json-server.typicode.com/dromoio/docs-email-api/leads', {
method: 'GET'
})
.then((response) => response.json())
.then(function (json) {
serverResults = json.map((record) => {
return record['email'];
});
hasFetchedData = true;
});
}
if (serverResults.includes(newRecord.row['email'].value)) {
newRecord.row['email'].info = [
{
message: 'Duplicate record. This user is already in the database.',
level: 'error'
}
];
}
return newRecord;
}
]}>
Import Contacts
```
# Validating Multiple Rows at Once
Source: https://developer.dromo.io/guides/validate-multiple-rows
Validate requirements across an entire Dromo import and block completion with the beforeFinish callback and table-level errors.
Row hooks are great for validating a single row, but what if you want to validate multiple rows at once? For example, you want to make sure that the user has imported at least 20 rows. In this case, you can use the beforeFinish callback.
This function will run every time the user clicks the "Finish" button. If it returns an object with the `cancel` property set to `true`, the user will be reverted to the data review screen, and the `message` will be displayed as a table-level error.
For more information on the beforeFinish callback, see the [Reference](/reference/results#the-beforefinish-callback) documentation.
```javascript JavaScript theme={null}
dromo.beforeFinish(function (data, metadata, instance) {
if (data.length < 20) {
return { cancel: true, message: "You must import at least 20 rows" }
}
});
```
```jsx React theme={null}
{
if (data.length < 20) {
return { cancel: true, message: "You must import at least 20 rows" }
}
}}>
Launch Dromo
```
# Zapier Integration
Source: https://developer.dromo.io/guides/zapier-integration
Connect Dromo imports to Zapier workflows using result-file and imported-row triggers for notifications, storage, and CRM automation.
Dromo's Zapier integration allows you to connect your imports with thousands of web services with a few mouse clicks. You can enable email or Slack notifications for new imports, copy import results to Google Drive, or automatically add imported records to your CRM, to name a few."
## How to Use Zapier with Dromo
You can create [Zaps](https://help.zapier.com/hc/en-us/articles/8496309697421-Create-Zaps) to perform actions based on Dromo triggers.
Dromo offers two event triggers: "New Results File" and "New Data Row Imported".
"New Results File" will trigger every time a file is imported for a selected schema. This trigger is useful when you want to notify a service when an import is completed, for example to send a Slack message or store the results file.
"New Data Row Imported" will trigger for every individual row in an import. This trigger is useful when adding data directly to an existing database like a spreadsheet or CRM.
## Dromo Zap Templates
You can manually [create a new Zap](https://zapier.com/resources/guides/quick-start/create-zap) or use one of Dromo's pre-configured Zap templates. These are available from the integrations menu in the Schema Editor.
If you manually create a Zap, you can add a Dromo trigger by searching for the "Dromo" app in the menu. Once you've selected the Dromo app, you can choose the event to subscribe to.
## Step by Step Example: Dromo Slack Notifications
In this example, we will configure Zapier to send us a Slack message each time a new import is completed.
To follow along, you will need a [Dromo account](https://dashboard.dromo.io/), a [Zapier account](https://zapier.com), and a [Slack account](https://slack.com/) with permissions to add new apps.
Zapier triggers only operate in Production Mode and require a paid Dromo subscription or active free trial.
The Dromo Zapier triggers are scoped to a specific saved schema. If you don't have one already, you will need to [create a schema](/guides/schema-builder) using the Schema Studio to try out your Zap with. It is also helpful to have at least one completed import using your schema so you can test that your Zap will work as expected.
Open the Schema Integrations Menu and click "Use this Zap" under the "Send Slack Messages for New Dromo Imports" template.
A new draft Zap will open in Zapier.
Expand the Dromo trigger, where you can configure how your Zap will run. First, you will need to connect your Dromo account. Then, select the schema you want notifications for in the "Trigger" section.
Under the "Test" section, click "Test Trigger". Zapier will retrieve a recent import from your schema. Click "Continue with selected record".
Next, we will configure the Slack action. For this example, we'll configure Zapier to send ourselves a DM, so we'll choose the "Send Direct Message" event. Click continue and connect your Slack account.
Then, set "To Username" to yourself. For the message text, we'll configure the Zap to send us a DM with the result filename and a link to download the results.
Test the action, you should receive a Slack message with the ID as text and a working link to download the file!
Click publish, and your Zap is now live! Try importing some files with Dromo in production mode. You should see a new Slack message for each import.
# Custom File Parsers
Source: https://developer.dromo.io/reference/custom-file-parsers
Custom file parsers allow you to parse any file format by giving you control of the parsing logic. Dromo natively supports common CSV and Excel formats, but with custom file parsers you can enable your users to import PDF, XML, HTML, or any other file type when you implement the file parsing.
For a PDF importing example, see our [guide on importing a PDF using the PDFTables API](/guides/importing-a-pdf).
At a high level, custom parsers are a function you define which is called with the file contents and returns the parsed file as arrays of strings.
You define which file extensions your parser should be used for, and if a user uploads a matching file, your file parsing function will be called instead of Dromo's native file parsers.
Custom parsers are invoked immediately when the user selects a file. Additionally, if a user goes to the Data Review step and then backtracks to a previous step, the file is re-parsed, so the custom parser will be called again to get a fresh, unedited copy of the file data.
Custom parser functions receive two arguments, the file buffer and the filename.
An ArrayBuffer containing the raw binary content of the uploaded file
The filename of the file selected by the user
File parsers must return a 2-dimensional array of strings.
File parsers may return the value async, wrapped in a promise.
Parsed data to be imported. The data must be an array with a length of at least 1, with each row being an array of strings. Each row must be the same length.
In this example we're parsing a fixed width file with stock prices. Each stock symbol is 4 characters and the price is the following 6 characters.
```
AAPL023367GOOG016968TSLA025952
```
```javascript JavaScript theme={null}
const fields = [
{
label: "Stock Symbol",
key: "stock_symbol",
},
{
label: "Stock Price",
key: "stock_price",
},
];
const settings = {
importIdentifier: "Custom Parser Example",
};
const user = {
id: "12345",
};
const dromo = new DromoUploader("FRONTEND_API_KEY", fields, settings, user);
dromo.registerFileParser({
extensions: ["txt"],
parseFile: async (buffer, fileName) => {
const stockData = new TextDecoder().decode(buffer);
const headers = ["Stock Symbol", "Stock Price"];
const data: string[][] = stockData
.split("\n")
.map((line) => [line.slice(0, 4), line.slice(4, -1)]);
return [headers, ...data];
},
});
```
```jsx React theme={null}
{
const stockData = new TextDecoder().decode(buffer);
const headers = ["Stock Symbol", "Stock Price"];
const data: string[][] = stockData
.split("\n")
.map((line) => [line.slice(0, 4), line.slice(4, -1)]);
return [headers, ...data];
},
},
]}>
Import Users
```
# Custom Steps
Source: https://developer.dromo.io/reference/custom-steps
Reference for the custom step configuration and the postMessage API for injecting custom UI into the Dromo import flow.
Custom steps allow you to insert your own UI between any two built-in steps in the Dromo import flow. See the [Custom Steps guide](/guides/custom-steps) for a walkthrough and examples.
## Configuration
Unique identifier for this step. Used as a fallback label in the stepper progress bar if `label` is omitted.
Which built-in step this custom step is inserted after.
| Value | Inserted after |
| ----------------- | ----------------------------------------- |
| `"HEADER_SELECT"` | Header row detection |
| `"COLUMN_MATCH"` | Column mapping |
| `"SELECT_MATCH"` | Select field matching, just before Review |
Text shown in the stepper progress bar. Defaults to `id` if omitted.
URL of the page to render inside an `
Called when the iframe posts `{ type: "DROMO_MESSAGE", payload }`. The `uploader` argument exposes the same mutation methods available in [step hooks](/reference/hooks#the-dromo-uploader-instance).
All uploader methods return `Promise` — await them when order matters.
***
## Registering custom steps
### JavaScript / TypeScript
```typescript theme={null}
dromo.registerCustomStep(config): void
```
Call `registerCustomStep` on the `DromoUploader` instance before `open()`. Multiple calls at the same `insertAfter` position are presented in registration order.
### React
Pass an array of config objects to the `customSteps` prop:
```tsx theme={null}
Import
```
### Angular
Bind a config array to the `[customSteps]` input on `lib-dromo-uploader`:
```typescript theme={null}
customSteps = [
{
id: "terms",
insertAfter: "SELECT_MATCH",
label: "Terms & Conditions",
url: "https://your-app.com/terms-step.html",
},
];
```
```html theme={null}
Import with Dromo!
```
***
## postMessage API
Your iframe page communicates with Dromo by posting messages to `window.parent`.
### Navigation messages
Advance to the next step.
Return to the previous step.
Trigger the `onMessage` callback with the uploader instance and `payload`. Does not automatically navigate — post `DROMO_NEXT` separately when ready.
***
## Uploader methods in `onMessage`
The `uploader` instance passed to `onMessage` exposes the following methods. These are the same mutation methods available in [step hooks](/reference/hooks#the-dromo-uploader-instance).
Dynamically add a field to the import schema. See [`instance.addField`](/reference/hooks#instanceaddfield).
Remove a field from the schema. See [`instance.removeField`](/reference/hooks#instanceremovefield).
Inject rows into the dataset. Returns the IDs of the added rows. See [`instance.addRows`](/reference/hooks#instanceaddrows).
Remove rows from the dataset by ID. See [`instance.removeRows`](/reference/hooks#instanceremoverows).
Update the current user associated with the import.
# DromoUploader SDK Reference
Source: https://developer.dromo.io/reference/dromo-uploader-sdk
Detailed reference for the DromoUploader JavaScript SDK instance, its constructor, and configuration methods.
## DromoUploader Instance
To begin an import, you first need to create an instance of the `DromoUploader`:
```javascript theme={null}
const dromo = new DromoUploader(licenseKey, fields, settings, user);
```
### Constructor
The `DromoUploader` constructor takes 4 parameters:
Your Dromo front-end license key, which can be found in [the Dromo dashboard](https://dashboard.dromo.io/).
An array of fields which comprise your import schema.
Settings to change the behavior of the Dromo importer.
Metadata about the end user to associate with the import.
### Configuration Methods
Once you have an instance, you can register callbacks and configure its behavior using the following methods. You can register callbacks for [running hooks](/reference/hooks) and [receiving results](/reference/results).
Used to register a [column hook](/reference/hooks#column-hooks) callback on the field with the given field key.
Used to register a standard [row hook](/reference/hooks#standard-row-hooks) callback.
Used to register a [bulk row hook](/reference/hooks#bulk-row-hooks) callback.
Registers an initialization-only [batch row hook](/reference/hooks#batch-row-hooks). The callback receives one bounded slice of rows and its one-based batch number. Configure the maximum slice size with [`batchRowHookBatchSize`](/reference/settings/settings#batchrowhookbatchsize).
Used to register a [step hook](/reference/hooks#column-hooks) callback of the given type.
Registers a [custom file parser](/reference/custom-file-parsers) for one or more file extensions.
Used to register a [row delete hook](/reference/hooks#row-delete-hooks) callback.
Registers a [custom step](/reference/custom-steps) to be inserted into the import flow. Call before `open()`. Multiple steps at the same `insertAfter` position are presented in registration order.
Used to register a [beforeFinish callback](/reference/results#the-beforefinish-callback).
Used to register an [onResults callback](/reference/results#the-onresults-callback).
Called when the user exits the Dromo Uploader without completing the import.
Registers a callback that fires whenever the user interacts with a tracked UI control inside the importer (e.g. button clicks). Useful for analytics, audit logging, and conditional UI reactions. Only one callback can be registered per instance — calling this a second time replaces the first. Not invoked in headless mode. See the [onUserEvent reference](/reference/on-user-event) for event types, payload shapes, and the full button name reference.
**Example of registering callbacks:**
```javascript theme={null}
dromo.registerColumnHook('zipCode', (colData) => {
return colData.map((zipCode) => {
return { ...zipCode, value: zipCode.value.padStart(5, '0') };
});
});
dromo.onResults((data, metadata) => {
console.log(data);
});
```
### Starting the Import
With Dromo fully configured, call `open()` on the uploader instance to start the import process.
Opens the Dromo Uploader interface to begin the import process.
```javascript theme={null}
dromo.open();
```
# Field Types
Source: https://developer.dromo.io/reference/fields/field-types
Dromo supports a variety of field types, each with their own unique properties and behaviors. This section describes the different types of fields available and their usage.
## Usage
The field type is set by passing a `type` parameter in the field object. This can be a string referring to one of the field types, explained below. Alternatively, some field types can take additional options controlling their behavior and format. In this case, you can provide the `type` parameter with a 2-element array, where the first element is the type name and the second element is any options that field type takes.
Here is an example of a few different valid field type configurations:
```json theme={null}
{
"fields": [
{
"label": "Email address",
"key": "email",
"type": "email"
},
{
"label": "Subscription fee",
"key": "subscriptionFee",
"type": ["number", "eur"]
},
{
"label": "Subscribed since",
"key": "subscriptionStart",
"type": ["date", { "locale": "de-DE" }]
}
]
}
```
## Types
### String[](#string "Direct link to String")
> `type: "string"`
By default (if no field type is specified), all fields in Dromo have the `string` type. This is the simplest field type, and does not do any validation or transformation of the data – the data is output as a string exactly as it was entered.
The string type does not take any options.
### Number[](#number "Direct link to Number")
> `type: "number"`
> `type: ["number",
]`
```json theme={null}
{
"type": ["number", {
"preset": "",
"round": "",
"displayFormat": "",
"outputFormat": "",
"min": "",
"max": ""
}]
}
```
#### For all kinds of numeric values[](#for-all-kinds-of-numeric-values "Direct link to For all kinds of numeric values")
The field type parses many common number formats and displays the number in a user-friendly format.
If the input data is unable to be parsed as a number, a validation error is added to the cell.
Unless an output format is specified, the number is output to the result JSON as a number.
#### Presets[](#presets "Direct link to Presets")
The number type has several presets available to handle the most common number formats. You can use a preset by passing the name of the preset with the field type, such as `type: ["number", "usd"]`.
Here are all of the presets that Dromo provides:
| Preset Name | Source value | Display value |
| ------------------------------------------------ | ---------------- | ---------------------- |
| `default`(used if no display settings are given) | 1234567.1234567 | 1,234,567.1234567 |
| `percent` | 0.12340.56 | 12.34%56% |
| `plain` | 1234567.123 | 1234567.123 |
| `usd` | 1234.567-3456.78 | $1,234.57-$3,456.78 |
| `usd_accounting` | 1234.567-3456.78 | $ 1,234.57$ (3,456.78) |
| `eur` | 1234.567-3456.78 | 1,234.57 €-3,456.78 € |
| `gbp` | 1234.567-3456.78 | £ 1,234.57- £3,456.78 |
| `decimal_0` *or*`integer` | 234.567 | 235 |
| `decimal_1` | 234.567123 | 234.6123.0 |
| `decimal_2` | 234.567123 | 234.57123.00 |
| `decimal_3` | 234.5678123 | 234.568123.000 |
| `decimal_4` | 234.56789123 | 234.5679123.0000 |
| `percent_0` | 0.1256 | 13% |
| `percent_1` | 0.12560.34 | 12.6%34.0% |
| `percent_2` | 0.125670.34 | 12.57%34.00% |
| `percent_3` | 0.1234560.34 | 12.346%34.000% |
| `percent_4` | 0.123456780.34 | 12.3457%34.0000% |
#### Custom configuration[](#custom-configuration "Direct link to Custom configuration")
If none of the presets meet your needs, you can define your own rounding, display format and output format behavior.
The `round` setting takes an integer which defines how many digits after the decimal point the value will be rounded to. Negative values will round to digits before the decimal.
The `displayFormat` and `outputFormat` settings can be used for creating your own custom formats. The `displayFormat` is used for displaying the number to the user on the review screen. The `outputFormat`, if set, will determine how the number is output in the result JSON, as a string.
For more information on using `displayFormat` and `outputFormat`, see the [number formatting section](/reference/fields/field-types#number%E2%80%8B).
#### Validating minimum and maximum[](#validating-minimum-and-maximum "Direct link to Validating minimum and maximum")
You can validate that the number falls within a given range using the `min` and `max` settings.
The `min` and `max` settings are optional and you can specify none, one or both. If a given number falls below the `min` or above the `max`, a validation error will be added to the field.
### Date[](#date "Direct link to Date")
> `type: "date"`
```json theme={null}
{
"type": ["date", {
"locale": "
",
"displayFormat": "",
"outputFormat": "",
"autofix": ""
}]
}
```
#### For handling date values (with no time)[](#for-handling-date-values-with-no-time "Direct link to For handling date values (with no time)")
The date type parses dates in several formats including:
* **ISO-8601** (e.g., `2024-03-27`, `2024-03-27T10:30`)
* **RFC-2822** (e.g., `27 Mar 2024`)
* **Common date formats** (e.g., `03/27/2024`, `27/03/2024`, `27.03.2024`)
* **Two-digit years** (e.g., `03/27/24` → `2024-03-27`)
* **Unpadded dates** (e.g., `3/7/2024` → `2024-03-07`)
* **Various separator styles** (dots, slashes, dashes)
If a time is also given, it is allowed but ignored. If an input date cannot be parsed, a validation error will be added to the cell.
When using a date field, Dromo's enhanced date detection will analyze the imported data and suggest appropriate formatting fixes during the Bulk Fix step. The system uses heuristic-based detection to identify various date patterns and recommend the most likely correct interpretation.
#### Automatic Date Fixing[](#automatic-date-fixing "Direct link to Automatic Date Fixing")
You can control whether date format fixes are applied automatically or require user approval using the `autofix` parameter:
* **`autofix: true`** (default): Date format fixes are applied automatically without requiring user interaction. If all date fields in your schema have autofix enabled, the Bulk Fix step will be skipped entirely.
* **`autofix: false`**: Date format fixes are suggested to the user during the Bulk Fix step, requiring manual approval before being applied.
The enhanced date detection system can automatically handle:
* **Ambiguous date formats** (e.g., distinguishing between MM/DD/YYYY and DD/MM/YYYY based on data patterns)
* **Mixed formats within the same column** (e.g., some rows using `03/27/2024` and others using `27-Mar-2024`)
* **Two-digit year conversion** with intelligent century detection
* **Locale-aware parsing** that respects the field's configured locale for format interpretation
This is particularly useful when you want to ensure users review date formatting changes, or when you want to streamline the import process for trusted data sources.
The `autofix` parameter is currently only available for date fields. Datetime and time fields do not support this feature.
By default, dates are output to the results in ISO-8601 format (e.g. `"2022-03-27"`).
You can specify a locale for the date field, which must be a valid [BCP 47 locale string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation). If not provided, the locale defaults to `"en-US"`.
The locale will change how the date is displayed to the user with the display format. The locale will also affect the output format if a custom format is used.
The default display format is the standard short form in the configured locale, for example:
| Locale | Default format example |
| ------ | ---------------------- |
| en-US | 3/27/2022 |
| ar-SY | ٢٧/٣/٢٠٢٢ |
| de-DE | 27.3.2022 |
| pt-BR | 27/03/2022 |
| th-TH | 27/3/2565 |
| zh-CN | 2022/3/27 |
You can define your own display and/or output formats with a format string. For more information, see the [datetime formatting section](/reference/fields/field-types#datetime%E2%80%8B).
### Datetime[](#datetime "Direct link to Datetime")
> `type: "datetime"`
```json theme={null}
{
"type": ["datetime", {
"locale": "",
"displayFormat": "",
"outputFormat": "",
"withSeconds": ""
}]
}
```
#### For handling a date and time together in one field[](#for-handling-a-date-and-time-together-in-one-field "Direct link to For handling a date and time together in one field")
The datetime type parses datetimes in several formats: ISO-8601, RFC-2822, and the configured display format in the configured locale.
By default, dates are output to the results in ISO-8601 format, with no time zone offset (e.g. `"2022-03-27T15:20"`).
By default, seconds are neither displayed nor output. You can change this by providing `withSeconds: true` in the field settings.
You can specify a locale for the datetime field, which must be a valid [BCP 47 locale string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation). If not provided, the locale defaults to `"en-US"`.
The locale will change how the datetime is displayed to the user with the display format. The locale will also affect the output format if a custom format is used.
The default display format is the standard short form in the configured locale, for example:
| Locale | Default format example |
| ------ | ---------------------- |
| en-US | 3/27/2022, 3:20 PM |
| ar-SY | ٢٧/٣/٢٠٢٢, ٣:٢٠ م |
| de-DE | 27.3.2022, 15:20 |
| pt-BR | 27/03/2022 15:20 |
| th-TH | 27/3/2565 15:20 |
| zh-CN | 2022/3/27 下午3:20 |
You can define your own display and/or output formats with a format string. For more information, see the [datetime formatting section](/reference/fields/field-types#datetime%E2%80%8B).
### Time[](#time "Direct link to Time")
> `type: "time"`
```json theme={null}
{
"type": ["time", {
"locale": "",
"displayFormat": "",
"outputFormat": "",
"withSeconds": ""
}]
}
```
#### For handling a time-of-day independent of date (i.e., a clock time)[](#for-handling-a-time-of-day-independent-of-date-ie-a-clock-time "Direct link to For handling a time-of-day independent of date (i.e., a clock time)")
The time type parses datetimes in several formats: ISO-8601, RFC-2822, and the configured display format in the configured locale.
By default, times are output to the results in ISO-8601 format (e.g. `"15:20"`).
By default, seconds are neither displayed nor output. You can change this by providing `withSeconds: true` in the field settings.
You can specify a locale for the datetime field, which must be a valid [BCP 47 locale string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation). If not provided, the locale defaults to `"en-US"`.
The locale will change how the time is displayed to the user with the display format. The locale will also affect the output format if a custom format is used.
The default display format is the standard time format in the specified locale, for example:
| Locale | Default format example |
| ------ | ---------------------- |
| en-US | 3:20 PM |
| ar-SY | ٣:٢٠ م |
| de-DE | 15:20 |
| zh-CN | 下午3:20 |
You can define your own display and/or output formats with a format string. For more information, see the [datetime formatting section](/reference/fields/field-types#datetime%E2%80%8B).
### Select[](#select "Direct link to Select")
> ```json theme={null}
> {
> "type": "select",
> "selectOptions": [
> {
> "label": "string",
> "value": "string",
> "alternateMatches": ["string"],
> "exactMatchOnly": true
> }
> ]
> }
> ```
>
> or `type: ["select", { "allowCustom": true }]`
Select fields let users choose from predefined options. You must provide an array of valid `selectOptions`, where the `label` is shown in the UI; the `value` is saved in the output.
Select fields are treated differently during the import process. Expand the section below to learn more.
After the column matching step, the user will be prompted to match the values in their file to the given select options.
On the review screen, the field will show as a dropdown menu that the user can pick an option from.
A validation error will be shown if a value was not matched and does not equal one of the option labels.
`selectOptions` can be overridden for individual cells using a row hook. See the [Row Hooks](/reference/hooks#row-hooks) documentation for details.
Each select option can optionally include `alternateMatche`s\`, an array of strings. If an input value matches any of these, it will be mapped to that option's value.
For a given select field, the labels, values, and alternate matches should be unique across all options. Each of these parameters is used to map imported data to select options, so conflicts will occur if multiple select options share the same matches.
If set to true, select fields will allow the user to enter their own value. With `allowCustom` set to `true`, the user can either choose from the valid select options, or enter their own value. This treats the select options as autocomplete suggestions. Any value is considered valid, although you can add additional validations like normal.
By default, Dromo will suggest matches to the user with fuzzy matching, and, if you have backend sync enabled, AI matching. If you would like to disable fuzzy and AI matching, and only suggest exact matches to the user, you can pass the setting `exactMatchOnly: true` with the field settings. Dromo will check the label, value, and alternateMatches for an exact match.
For example, this is how you would enable both custom values and exact matches:
```json theme={null}
{
"type": ["select", { "allowCustom": true, "exactMatchOnly": true }],
"selectOptions": [
{
"label": "string",
"value": "string",
"alternateMatches": ["string"]
}
]
}
```
### Multi-Select[](#multi-select "Direct link to Multi-Select")
> ```json theme={null}
> {
> "type": "multi-select",
> "selectOptions": [
> {
> "label": "string",
> "value": "string",
> "alternateMatches": ["string"]
> }
> ]
> }
> ```
>
> or
>
> ```json theme={null}
> {
> "type": ["multi-select", {
> "delimiter": ",",
> "trimValues": true
> }],
> "selectOptions": [],
> "manyToOne": true
> }
> ```
#### For fields that allow selecting multiple values from predefined options[](#for-fields-that-allow-selecting-multiple-values-from-predefined-options "Direct link to For fields that allow selecting multiple values from predefined options")
Multi-select fields let users choose multiple values from predefined options. You must provide an array of valid `selectOptions`, where the `label` is shown in the UI; the `value` is saved in the output. The field outputs as an array of strings (`string[]`) containing the selected option values.
Multi-select fields are treated differently during the import process. Expand the section below to learn more.
After the column matching step, the user will be prompted to match the values in their file to the given select options (similar to select fields).
On the review screen, the field will show as a multi-select dropdown or chip interface that the user can pick multiple options from.
A validation error will be shown if a value was not matched and does not equal one of the option labels.
`selectOptions` can be overridden for individual cells using a row hook. See the [Row Hooks](/reference/hooks#row-hooks) documentation for details.
Each select option can optionally include `alternateMatches`, an array of strings. If an input value matches any of these, it will be mapped to that option's value.
For a given multi-select field, the labels, values, and alternate matches should be unique across all options. Each of these parameters is used to map imported data to select options, so conflicts will occur if multiple select options share the same matches.
#### Delimiter Configuration[](#delimiter-configuration "Direct link to Delimiter Configuration")
By default, multi-select fields parse input data using a comma (`,`) as the delimiter. You can customize this behavior:
The character used to separate multiple values in the input data. Common delimiters include `,`, `;`, `|`, or any custom character.
Whether to trim whitespace from individual values after splitting. When `true`, values like `" apple , banana "` will be parsed as `["apple", "banana"]`.
Example configuration with custom delimiter:
```json theme={null}
{
"key": "skills",
"label": "Skills",
"type": ["multi-select", { "delimiter": ";", "trimValues": true }],
"selectOptions": [
{ "label": "JavaScript", "value": "js" },
{ "label": "Python", "value": "py" },
{ "label": "Java", "value": "java" }
]
}
```
#### Many-to-One Mapping[](#many-to-one-mapping "Direct link to Many-to-One Mapping")
Multi-select fields support many-to-one column mapping by setting `manyToOne: true` on the field definition. This allows multiple source columns to be combined into a single multi-select field. This is useful when your input data has multiple columns that should be merged into one multi-select output.
For example, if you have columns `"Interest1"`, `"Interest2"`, and `"Interest3"` in your CSV, you can map all of them to a single multi-select field with `manyToOne: true`, and the values will be combined into one array in the output.
Example:
```json theme={null}
{
"key": "interests",
"label": "Interests",
"type": "multi-select",
"selectOptions": [
{ "label": "Reading", "value": "reading" },
{ "label": "Gaming", "value": "gaming" },
{ "label": "Music", "value": "music" }
],
"manyToOne": true
}
```
#### Validation[](#validation "Direct link to Validation")
Multi-select fields automatically validate that all selected values match one of the provided select options. Invalid values will result in an error message on the review screen.
The field also checks for duplicate values within the same cell and displays a warning (not an error) if duplicates are found.
### Checkbox[](#checkbox "Direct link to Checkbox")
> `type: "checkbox"`
The checkbox field type is useful when you want to import a boolean (yes/no) field. The field will be displayed to the user as a checkbox and output as a JSON boolean (`true` or `false`).
When importing, the following case-insensitive values are considered false: `""` (empty cell), `"0"`, `"off"`, `"n"`, `"no"`, `"false"`, `"disabled"`. All other values are considered true.
### Email[](#email "Direct link to Email")
> `type: "email"`
Email fields are strings with built-in validation per the [HTML5 spec](https://html.spec.whatwg.org/multipage/input.html#email-state-\(type=email\)). There are some additional constraints: single domains (such as `contact@dromo`, with no TLD) and IP addresses are not allowed in the host portion.
### Country[](#country "Direct link to Country")
> `type: "country"`
>
> ```json theme={null}
> type: [ "country", { format: <"2-letter" or "3-letter"> }]
> ```
Country fields validate values against a list of valid country codes (either ISO 3166 alpha-2 or alpha-3) based on the `format` option. If not specified, "2-letter" is used.
### Phone number[](#phone-number "Direct link to Phone number")
> `type: "phone-number"`
>
> ```json theme={null}
> type: [ "phone-number", { country: <2 letter iso country code>, format: "national" | "international" | "both", outputFormatted: boolean }]
> ```
Phone number fields can be used to validate phone numbers in many different scenarios.
When format is `"international"` (default), the field validates full international numbers with country codes, displaying them with spaces (e.g., +1 212 867 5309) but outputting in E.164 ("+33123456789"), unless `outputFormatted: true`.
With format: `"national"` and a country (e.g., `"US"`), it validates local numbers and displays them in national format (e.g., (212) 867-5309), outputting digits only unless outputFormatted: true.
Use format: `"both"` with a country to accept both international and national formats. All values are displayed and output in international format.
### Social Security Number[](#social-security-number "Direct link to Social Security Number")
> `type: "ssn"`
>
> ```json theme={null}
> type: [ "ssn", { outputDash: }]
> ```
Social security number fields can be used to validate US SSNs. The input can be either 9 digits or 11 digits with dashes. The output is a string of digits, optionally including dashes if `outputDash` is set to `true`. This (obviously) does not validate the actual SSN.
### Domain[](#domain "Direct link to Domain")
> `type: "domain"`
>
> ```
> type: [ "domain", { allowSubdomains: }]
> ```
Domain fields validate input as a proper domain name (no paths, queries, or fragments), checking subdomains based on the `allowSubdomains` option, which defaults to true.
If you need a field that accepts paths and query params, consider using an URL field.
### URL[](#url "Direct link to URL")
> `type: "url"`
>
> ```json theme={null}
> type: ["url", { acceptedProtocols: array of accepted protocol strings. e.g: "https", "ftp">, acceptedDomains:
}]
> ```
URL fields can be used to validate and normalize all URLs. You can optionally specify a list of accepted protocols (like `https`) and/or domains (`like dromo.io`).
The value of the URL will be normalized to strip extra dashes and relative path references. For example, an input of https\:///dromo.io/foo/../bar is a valid URL, but will be normalized to the value `https://dromo.io/bar`.
### US State/Territory[](#us-stateterritory "Direct link to US State/Territory")
> `type: "us-state-territory"`
US State/Territory fields can be used to validate values against standard US state and territory postal codes. The input can be both 2-letter ISO state/territory codes or 4-letter codes. e.g. `AL` or `US-AL`. The output is a 2-letter code.
### US Zipcode[](#us-zipcode "Direct link to US Zipcode")
> `type: "us-zip-code"`
>
> ```json theme={null}
> type: ["us-zip-code", { outputDash: , format: <"5-digit"|"9-digit"> }]
> ```
US Zipcode fields can be used to validate values against US zip codes, either the 5-digit or 9-digit "ZIP+4" variant, specified by the `format` option.
* `5-digit` format accepts 5 or 9 digit values (with or without dashes), but truncates 9-digit inputs to the first 5 digits. Shorter values are left-padded with zeros (e.g., `342` → `00342`).
* `9-digit` format does not accept 5 digit values. It will accept only 9 digits, with or without dashes.
Any value that contains characters other than dashes or digits will be invalid.
### UUID[](#uuid "Direct link to UUID")
> `type: "uuid"`
```json theme={null}
type: ["uuid", { version: }]
```
The UUID field is a string field that validates UUID format and version number, if provided. The field uses the built-in `validate` function provided by `uuid` library. If `version` is provided, it will also check the UUID version using the `getVersion` from the `uuid` library.
# Field Validators
Source: https://developer.dromo.io/reference/fields/field-validators
Field validators allow you to enforce stricter rules about the data a user can import.
Indepedent of field types, Dromo provides a number of validators that you can use to ensure that get clean data in exactly the format you want them. You can use validators to highlight errors for the user and ensure that they fix the errors before submitting an import.
You can add validation to a field using the `validators` parameter—an array of objects, each with a `validate` rule. Optionally, include an `errorMessage` to show users a custom tooltip when validation fails.
## Validators
### Required
Specifies that the field cannot be empty.
Custom error message to display when validation fails.
### Unique
Specifies that all non-empty values in the column must be distinct (non-equal).
You can also validate uniqueness case-insensitively with `{ validate: "unique_case_insensitive" }`.
Custom error message to display when validation fails.
### Unique Across Multiple Fields
The `unique_with` validator can be used to validate uniqueness across multiple fields.
A unique identifier shared across all fields that should be validated together for uniqueness.
Custom error message to display when validation fails.
The `unique_with` validator can be used to validate uniqueness across multiple fields.
To use it, place a `unique_with` validator on each field that you want to be part of the uniqueness validation, all sharing the same `uniqueKey`.
Here is an example set of fields using `unique_with` validators used to validate that the combination of the first name and last name fields are unique.
```javascript theme={null}
[
{
label: "First Name",
key: "firstName",
validators: [
{
validate: "unique_with",
uniqueKey: "full name",
},
],
},
{
label: "Last Name",
key: "lastName",
validators: [
{
validate: "unique_with",
uniqueKey: "full name",
},
],
},
]
```
And here is an example of the validators in action:
### Match a Regular Expression
You can validate that a field matches the given regular expression with the `regex_match` validator.
Conversely, you can validate that a field does *not* match a regular expression with the `regex_exclude` validator.
The regular expression pattern to match against.
Optional regex configuration options.
Custom error message to display when validation fails.
The Dromo uploader uses standard JavaScript regular expressions. You can set the `regex` key as either a `RegExp` literal/object or a string.
If you choose to use a string, be careful to properly escape special characters in your regex. For example, the regex `/^"\d+"$/` should be passed in as `"^\"\\d+\"$"`. Additionally, if using a string, note that you cannot pass in regex flags directly in your regex. Instead, you may optionally provide a `regexOptions` parameter with the validator which takes an object with the following keys:
* `ignoreCase: boolean` - Case insensitive flag (regex flag `i`)
* `dotAll: boolean` - Matches all including any line breaks (regex flag `s`)
* `multiline: boolean` - Multiline flag (regex flag `m`)
* `unicode: boolean` - Unicode flag (regex flag `u`)
### Require with Other Fields
You can use these validators to require that a field be non-empty *only if* other fields are present or empty. There are four variations.
Array of field keys to check against.
Custom error message to display when validation fails.
You can use these validators to require that a field be non-empty *only if* other fields are present or empty. There are four variations:
> `{ validate: "require_with", fields: [, ...] }`
This will require that the field has a value if **any** of the fields listed in the `fields` array are **non-empty**.
> `{ validate: "require_without", fields: [, ...] }`
This will require that the field has a value if **any** of the fields listed in the `fields` array are **empty**.
> `{ validate: "require_with_all", fields: [, ...] }`
This will require that the field has a value if **all** of the fields listed in the `fields` array are **non-empty**.
> `{ validate: "require_without_all", fields: [, ...] }`
This will require that the field has a value if **all** of the fields listed in the `fields` array are **empty**.
### Require with Other Field Values
You can use these validators to require that a field be non-empty *only if* other fields have specific values. There are four variations.
Object mapping field keys to their required values.
Custom error message to display when validation fails.
You can use these validators to require that a field be non-empty *only if* other fields have specific values. There are four variations:
> `{ validate: "require_with_values", fieldValues: { : , ... } }`
This will require that the field has a value if **any** of the fields contain the corresponding value specified under `fieldValues`.
> `{ validate: "require_without_values", fieldValues: { : , ... } }`
This will require that the field has a value if **any** of the fields **do not** contain the corresponding value specified under `fieldValues`.
> `{ validate: "require_with_all_values", fieldValues: { : , ... } }`
This will require that the field has a value if **all** of the fields contain the corresponding value specified under `fieldValues`.
> `{ validate: "require_without_all_values", fieldValues: { : , ... } }`
This will require that the field has a value if **all** of the fields **do not** contain the corresponding value specified under `fieldValues`.
### Length
Validates that a field's value falls within a minimum or maximum number of characters.
Either min, max, or both must be defined. Values are inclusive.
The length validator cannot be used with non-string-based fields, such as number or date fields.
Minimum number of characters allowed.
Maximum number of characters allowed.
Custom error message to display when validation fails.
### Alphabetical
Validates that a field's value only contains alphabetical characters (a-z, lowercase and uppercase).
Custom error message to display when validation fails.
# Fields
Source: https://developer.dromo.io/reference/fields/fields
To implement Dromo in your application, you must first define a schema. A schema is an array of field objects. These fields will be matched to columns during the import process, and define the properties of the JSON payload passed back in the results.
## Basics[](#basics "Direct link to Basics")
The most basic field consists of only a `key` and `label`. The field's key is its unique identifier, and will be the key of that field in the result JSON object. The field's label is how it will be displayed to the user.
* Schema
* Review screen
* Results
A basic schema could look like this:
```javascript theme={null}
fields: [
{
label: "First name",
key: "firstName"
},
{
label: "Last name",
key: "lastName"
},
{
label: "Membership ID",
key: "memberId"
}
]
```
The user would then match these fields to the columns in their imported file, resulting in a review screen which looks like this:
And, when submitted, Dromo would provide you with a result data array like this:
```json theme={null}
[
{
"firstName": "Richard",
"lastName": "Wysocki",
"memberId": "38008"
},
{
"firstName": "Paige",
"lastName": "Pierce",
"memberId": "29190"
},
{
"firstName": "Paul",
"lastName": "McBeth",
"memberId": "27523"
},
{
"firstName": "Kristin",
"lastName": "Tattar",
"memberId": "73986"
},
{
"firstName": "Calvin",
"lastName": "Heimburg",
"memberId": "45971"
},
{
"firstName": "Catrina",
"lastName": "Allen",
"memberId": "44184"
},
{
"firstName": "Eagle",
"lastName": "McMahon",
"memberId": "37817"
}
]
```
## Field object reference[](#field-object-reference "Direct link to Field object reference")
The human-friendly name of the field, shown to the user in the Dromo Uploader interface
The field's unique identifier, used as the key in the JSON results
The field's type.
Can be supplied in two formats:
* As a string corresponding to a valid field type. If the field type accepts options, defaults will be used.
* For field types which accept options, as a two element array where the first element is the string corresponding to a valid field type, and the second element is an object with type options.
For more details, see [field types](/reference/fields/field-types).
An optional description of this field for the user. If you specify a value for this field, Dromo will show a ? icon in the importer that users can hover over to see this message.
An array of alternate matches that this field to map to. See [column matching](#column-matching).
An array of validator objects to validate this field. See [field validators](/reference/fields/field-validators).
For select fields only, an array of possible picklist values. See the [select field type](/reference/fields/field-types#select).
If `true`, the user will not be able to edit the data in this field.
This is useful if you want to exclusively populate a virtual field using hooks.
If `true`, the field will be hidden from the user at all times. A hidden field can be set only using hooks.
A user will not be able to match a column of data to the field. A user will not see the field in the review screen. The field will only be added to the submitted final result.
Since a user cannot change the value of a hidden field, no validations can be set on a hidden field.
If `true`, the field must be mapped when matching columns. The user will not be able to progress to the data review screen unless this field has been matched to a column in the input file.
Note: cells in this column aren't required to have any values; that can be validated using [the `required` validator](/reference/fields/field-validators#required).
If `true`, the field may have multiple data columns mapped to it.
The result payload will have an array of all mapped columns' values instead of just a single value. Row hooks will have an entry in a `manyToOne` array for each mapped column.
## Column matching[](#column-matching "Direct link to Column matching")
During the column matching step, Dromo will try to match columns headers in the user data to a corresponding `label` or `key` within your fields.
If you want to provide additional aliases that should automatically match to a given field, you can do so by setting `alternateMatches` for that field.
Column matching is case-sensitive.
Example:
```javascript theme={null}
{
label: "City",
key: "city",
alternateMatches: ["municipality", "town", "locality"]
}
```
# Hooks
Source: https://developer.dromo.io/reference/hooks
Reference for column, row, bulk row, and step hooks, including callback inputs and outputs, row-cell transformations, dynamic fields with addField, validation messages, and lifecycle timing.
Hooks are a powerful feature that allow you to programmatically validate, transform, and enhance data during the import process. They integrate seamlessly with [field validation](/reference/fields/field-validators) and [results handling](/reference/results) to give you complete control over your data pipeline.
Looking for simpler validation options? Check out [field validators](/reference/fields/field-validators) for built-in validation rules, or see [validating with external data](/guides/validate-external-data) for a row hook example.
The diagram below shows the general flow for Dromo. Column hooks run first and only once after the "column matching" stage. Batch row hooks run next during initialization, followed by standard and bulk row hooks. Standard and bulk row hooks also run after the user updates data; batch row hooks do not.
## Column hooks[](#column-hooks "Direct link to Column hooks")
Column hooks run on a specified column at the beginning of the data review step. They are run before the row hooks and will only run once during the import process. Column hooks are ideal for batch operations like API lookups or data enrichment for entire columns.
To register column hooks, use `registerColumnHook()` in the [JavaScript SDK](/reference/dromo-uploader-sdk) or the `columnHooks` prop in the [React component](/reference/react-dromo-uploader).
Column hooks are called with a single argument, `values`, with an array of objects. Each object corresponds to one value in the column, and has two parameters.
The row index of the column value, starting with 0
The raw value as imported
Column hooks must return an array of objects in a similar format.
Column hooks may return the value async, wrapped in a promise.
The row index of the column value. This should be unchanged from the input.
The new value for this cell. If omitted, the value is left unchanged.
An array of messages you would like to add to this cell.
For more details, see [Info Messages](#working-with-info-messages).
If omitted, no messages are added.
Given the following imported data:
| Name | Email |
| :-----------: | :---------------------------------------------------: |
| Michelle Doe | [michelledoe@gmail.com](mailto:michelledoe@gmail.com) |
| Jane Doe | [janedoe@gmail.com](mailto:janedoe@gmail.com) |
| Steve Jones | [steve@hotmail.com](mailto:steve@hotmail.com) |
| Wayne Gretzky | [wayne@nhl.com](mailto:wayne@nhl.com) |
A column hook registered on the `Email` field would be passed the following data:
```javascript Input theme={null}
[
{ value: "michelledoe@gmail.com", index: 0 },
{ value: "janedoe@gmail.com", index: 1 },
{ value: "steve@hotmail.com", index: 2 },
{ value: "wayne@nhl.com", index: 3 },
];
```
This column hook could return something like this:
```javascript Output theme={null}
[
{
index: 0,
value: "michelledoe@aol.com",
info: [
{
message: "Automatically corrected email domain",
level: "info", // should be "info", "warning" or "error"
},
],
},
// we can omit cells that don't need any changes or messages
{
index: 2,
// Don't need to supply value if we're not changing it
info: [
{
message: "Email not found",
level: "error",
},
],
},
];
```
```javascript JavaScript theme={null}
const fields = [
{
label: "Email",
key: "email",
},
];
const settings = {
importIdentifier: "Column Hooks Server Validation",
};
const user = {
id: "12345",
};
const dromo = new DromoUploader("FRONTEND_API_KEY", fields, settings, user);
dromo.registerColumnHook("email", function (values) {
return values.map((row) => {
const newRow = {
index: row.index,
value: "0" + row.value,
info: [
{
message: "Prepended 0 to value",
level: "info",
},
],
};
return newRow;
});
});
```
```jsx React theme={null}
{
return values.map((row) => {
const newRow = {
index: row.index,
value: "0" + row.value,
info: [
{
message: "Prepended 0 to value",
level: "info",
},
],
};
return newRow;
});
},
},
]}>
Import Emails
```
## Row hooks[](#row-hooks "Direct link to Row hooks")
Row hooks run validation on imported records and return updated data and/or user messages. They come in three variants: standard, batch, and bulk.
To register row hooks, use `registerRowHook()`, `registerBatchRowHook()`, or `registerBulkRowHook()` in the [JavaScript SDK](/reference/dromo-uploader-sdk). React and Angular integrations provide the corresponding `rowHooks`, `batchRowHooks`, and `bulkRowHooks` props or inputs.
Standard and bulk row hooks run once in `"init"` mode after the column matching step and before the user begins the review step. They run again in `"update"` mode after the user changes data. Batch row hooks run only during initialization and receive bounded slices of the dataset.
Row hooks are run again in `"update"` mode after each time the user changes any data, with only the changed rows.
### Standard row hooks[](#standard-row-hooks "Direct link to Standard row hooks")
Standard row hooks are invoked once for every row on `init`, and once for every row that changed on `update`.
Standard row hooks are called with two arguments.
The index of this row
The `row` object will have keys matching the field key of each mapped field. Each of these keys will contain an object with data and metadata of that field in the given row.
The value of this field at this row, as displayed to the user
The value of this field at this row, as it will appear in the result data
An array of any info messages you have previously added to this cell
Overridden selectOptions previously added to this cell
Mode will be `"init"` if running before the start of the review step, and `"update"` if running due to a user change.
If the field is [manyToOne](/reference/fields/field-types#manytoone), every mapped cell for this row appears as an array.
Row hooks should return the updated record in a similar format.
Row hooks may return the value async, wrapped in a promise.
The `row` object should contain keys matching the field key of each mapped field. If a mapped field is omitted, it will remain unchanged.
Each field key object may have the following entries:
The new value for this row
New messages for this row. Messages returned will replace any previously set messages. If `info` is omitted, messages will remain unchanged.
Select option overrides for this cell. If provided, the cell will use these select options for transforming values and validating instead of those set when the field was declared.
When fetching external data, we recommend that you make any API calls in a column hook and then validate that the data remains correct using a row hook in `update` mode.
```javascript JavaScript theme={null}
const fields = [
{
label: "Email",
key: "email",
},
{
label: "State",
key: "state",
selectOptions: [{ label: "Alabama", value: "AL" }, { label: "Alaska", value: "AK" }],
},
];
const settings = {
importIdentifier: "Row Hooks Example",
};
const user = {
id: "12345",
};
const dromo = new DromoUploader("FRONTEND_API_KEY", fields, settings, user);
dromo.registerRowHook(function (record, mode) {
const newRecord = record;
if (record.index < 10 && mode === "init") {
newRecord.row.email.value = "0" + newRecord.row.email.value;
newRecord.row.email.info = [
{
message: "Prepend 0 to value",
level: "info",
},
];
newRecord.row.state.selectOptions = [
{ label: "Alberta", value: "AB"},
{ label: "British Colombia", value: "BC"}
];
}
return newRecord;
});
```
```jsx React theme={null}
{
const newRecord = record;
if (record.index < 10) {
if (newRecord.row.email) {
newRecord.row.email.value = "0" + newRecord.row.email.value;
newRecord.row.email.info = [
{
message: "Prepend 0 to value",
level: "info",
},
];
}
newRecord.row.state.selectOptions = [
{ label: "Alberta", value: "AB"},
{ label: "British Colombia", value: "BC"}
];
}
return newRecord;
},
]}>
Import Contacts
```
### Batch row hooks[](#batch-row-hooks "Direct link to Batch row hooks")
Batch row hooks process large imports in bounded slices instead of passing the full dataset to one callback. They run only once during initialization, after column hooks and before standard and bulk row hooks.
The callback receives two arguments:
A slice of row hook record objects. The maximum number of records is controlled by [`batchRowHookBatchSize`](/reference/settings/settings#batchrowhookbatchsize), which defaults to `2000`.
The one-based number of the current batch. The first callback receives `1`, the second receives `2`, and so on.
Batch row hooks do not receive a mode argument because they never run in `"update"` mode. Return an array of row hook output objects with each record's original `index`. You may return only the fields that should change; omitted fields and rows remain unchanged. Async callbacks are supported.
```javascript JavaScript theme={null}
const settings = {
importIdentifier: "Large Contact Import",
batchRowHookBatchSize: 5000,
};
const dromo = new DromoUploader("FRONTEND_API_KEY", fields, settings, user);
dromo.registerBatchRowHook(async (records, batchNumber) => {
const enrichedEmails = await enrichEmailBatch(records, batchNumber);
return records.map((record) => ({
index: record.index,
row: {
email: { value: enrichedEmails[record.index] },
},
}));
});
```
```jsx React theme={null}
{
console.log(`Processing batch ${batchNumber}`);
return records;
},
]}
>
Import Contacts
```
```typescript Angular theme={null}
import { IBatchRowHook } from "dromo-uploader-angular";
batchRowHooks: IBatchRowHook[] = [
async (records, batchNumber) => {
console.log(`Processing batch ${batchNumber}`);
return records;
},
];
```
```html Angular template theme={null}
Import Contacts
```
### Bulk row hooks[](#bulk-row-hooks "Direct link to Bulk row hooks")
Bulk row hooks are invoked a single time on `init` with the full dataset, and a single time on `update` with all of the changed rows.
Bulk row hooks are a good fit if you need to perform potentially long-running operations that can be optimized by processing all changes in a single function call.
Bulk row hooks work exactly like standard row hooks, except the first parameter is an array of record objects instead of a single record object. Similarly, bulk row hooks must return an array of record objects, preserving the `index` parameter of each record.
```javascript JavaScript theme={null}
const fields = [
{
label: "Email",
key: "email",
},
];
const settings = {
importIdentifier: "Row Hooks Example",
};
const user = {
id: "12345",
};
const dromo = new DromoUploader("FRONTEND_API_KEY", fields, settings, user);
dromo.registerBulkRowHook(function (records, mode) {
return records.map((record) => {
const newRecord = record;
if (record.index < 10 && mode === "init") {
newRecord.row.email.value = "0" + newRecord.row.email.value;
newRecord.row.email.info = [
{
message: "Prepend 0 to value",
level: "info",
},
];
}
return newRecord;
});
});
```
```jsx React theme={null}
{
return records.map((record) => {
const newRecord = record;
if (record.index < 10) {
if (newRecord.row.email) {
newRecord.row.email.value = "0" + newRecord.row.email.value;
newRecord.row.email.info = [
{
message: "Prepend 0 to value",
level: "info",
},
];
}
}
return newRecord;
})
},
]}>
Import Contacts
```
## Row delete hooks[](#row-delete-hooks "Direct link to Row delete hooks")
Row delete hooks are called when a row (or rows) is removed from the table by the user.
Row delete hooks are called once for each row that was removed.
The index of this row
The `row` object will have keys matching the field key of each mapped field. Each of these keys will contain an object with data and metadata of that field in the given row.
The value of this field at this row, as displayed to the user
The value of this field at this row, as it will appear in the result data
An array of any info messages you have previously added to this cell
```javascript JavaScript theme={null}
dromo.registerRowDeleteHook(function (record) {
console.log("Deleted row index: " + record.index);
});
```
```jsx React theme={null}
console.log("Deleted row index: +" + record.index)]}
...>
Launch Dromo
```
## Step hooks[](#step-hooks "Direct link to Step hooks")
Step hooks are callbacks that are invoked at different parts of the import process. In addition to fetching metadata about the import at that time, you can use them to call mutation functions like [`addField`](#instanceaddfield).
To register step hooks, use `registerStepHook()` in the [JavaScript SDK](/reference/dromo-uploader-sdk) or the `stepHooks` prop in the [React component](/reference/react-dromo-uploader). See the [using addField guide](/guides/using-addField) for advanced use cases.
### `UPLOAD_STEP` hook[](#upload_step-hook "Direct link to upload_step-hook")
This hook is called when the user has completed uploading a file and provides you with a 20 row data preview of that data.
The function is called with two arguments.
The Dromo Uploader instance which you can call mutation methods on
An array of arrays containing the first 20 rows of raw uploaded data
```javascript JavaScript theme={null}
dromo.registerStepHook("UPLOAD_STEP", function (instance, data) {
instance.addField({
label: "Full Name",
key: "fullName",
});
console.log(data.filename, data.dataPreview);
});
```
```jsx React theme={null}
{
instance.addField({
label: "Full Name",
key: "fullName",
})
console.log(data.filename, data.dataPreview);
}
}
],
.../>
```
### `REVIEW_STEP` hook[](#review_step-hook "Direct link to review_step-hook")
This hook is triggered just before the final review screen loads (and before any of the row and column hooks).
The function is called with two arguments.
The Dromo Uploader instance which you can call mutation methods on
The raw headers from the uploaded file. If no file was uploaded (e.g. manual entry was used), this will be `null`.
An object whose keys are the file headers and values are the field keys of the field the header was mapped to
An object whose keys are the field keys and values are objects containing metadata about that field
The header this field was mapped to. `null` if no file was uploaded.
The column header index this field was mapped to. `null` if no file was uploaded.
Whether this field was added by the user as a custom field
Indicates if this field is [`manyToOne`](/reference/fields/field-types#manytoone).
The headers that were mapped to the field if it is [`manyToOne`](/reference/fields/field-types#manytoone).
The indexes of columns that were mapped to a [`manyToOne`](/reference/fields/field-types#manytoone) field.
```javascript JavaScript theme={null}
dromo.registerStepHook("REVIEW_STEP", function (instance, data) {
instance.addField({
label: "Full Name",
key: "fullName",
});
console.log(data.rawHeaders, data.headerMapping, data.fields);
});
```
```jsx React theme={null}
{
instance.addField({
label: "Full Name",
key: "fullName",
})
console.log(data.rawHeaders, data.headerMapping, data.fields);
}
},
],
.../>
```
### `REVIEW_STEP_POST_HOOKS` hook[](#review_step_post_hooks-hook "Direct link to review_step_post_hooks-hook")
This hook is triggered during the review step after all of the initial row and column hooks have run (and after any \`REVIEW\_STEP\` hooks have run). It can be handy for doing batched updates to the data based on aggregated results from row or column hooks.
The function is called with two arguments.
The Dromo Uploader instance which you can call mutation methods on
An object whose keys are the file headers and values are the field keys of the field the header was mapped to
An object whose keys are the field keys and values are objects containing metadata about that field
The header this field was mapped to. `null` if no file was uploaded.
The column header index this field was mapped to. `null` if no file was uploaded.
Whether this field was added by the user as a custom field
```javascript JavaScript theme={null}
dromo.registerStepHook("REVIEW_STEP_POST_HOOKS", function (instance, data) {
instance.updateInfoMessages([
{
rowIndex: 5,
fieldKey: "month",
messages: [
{ level: "info", message: "Sales exceed previous month" },
],
},
]);
console.log(data.headerMapping, data.fields);
});
```
```jsx React theme={null}
{
instance.updateInfoMessages([
{
rowIndex: 5,
fieldKey: "month",
messages: [
{ level: "info", message: "Sales exceed previous month" },
],
},
]);
console.log(data.headerMapping, data.fields);
}
},
],
.../>
```
### `REVIEW_STEP_PRE_SUBMIT` hook[](#review_step_pre_submit-hook "Direct link to review_step_pre_submit-hook")
This hook is triggered during the review step right after the user clicks 'Finish' but before [`beforeFinish`](/reference/results#the-beforefinish-callback) runs. It can be useful for customizing the 'Are you ready to submit?' dialog with info summarizing the changes and impact of the import data.
The function is called with two arguments.
The Dromo Uploader instance which you can call mutation methods on
An object whose keys are the file headers and values are the field keys of the field the header was mapped to
An object whose keys are the field keys and values are objects containing metadata about that field
The header this field was mapped to. `null` if no file was uploaded.
The column header index this field was mapped to. `null` if no file was uploaded.
Whether this field was added by the user as a custom field
```javascript JavaScript theme={null}
dromo.registerStepHook("REVIEW_STEP_PRE_SUBMIT", function (instance, data) {
instance.setConfirmationMessage(
"Adding 5 new contacts. Modifying 20 existing contacts.
",
{
submitButtonText: "Accept changes",
cancelButtonText: "Cancel",
}
);
console.log(data.headerMapping, data.fields);
});
```
```jsx React theme={null}
{
instance.setConfirmationMessage(
"Adding 5 new contacts. Modifying 20 existing contacts.
",
{
submitButtonText: "Accept changes",
cancelButtonText: "Cancel",
}
);
console.log(data.headerMapping, data.fields);
}
},
],
.../>
```
## Working with info messages[](#working-with-info-messages "Direct link to Working with info messages")
Dromo offers the ability to add info messages to data cells to enable custom validation and provide other feedback to the user. Info messages are a key part of the validation system and work alongside [field validators](/reference/fields/field-validators).
You can add and update info messages via [column hooks](#column-hooks), [row hooks](#row-hooks), and [the updateInfoMessages method](#instanceupdateinfomessages) in step hooks.
For simple validation scenarios, consider using [built-in field validators](/reference/fields/field-validators) before implementing custom validation with info messages.
Info messages are provided as objects with these parameters.
The message to be displayed to the user in the review screen when they hover over the corresponding cell
The nature of the message.
* A cell with an `"error"` message is highlighted in red. It is considered a validation error and impacts the user's submission options based on [`invalidDataBehavior`](/reference/settings/settings#invaliddatabehavior)
* A cell with a `"warning"` message is highlighted in yellow
* A cell with an `"info"` message is highlighted in blue
## The Dromo Uploader instance[](#the-dromo-uploader-instance "Direct link to The Dromo Uploader instance")
Step hooks provide access to the Dromo Uploader instance, which exposes methods to alter the import.
### `instance.addField`[](#instanceaddfield "Direct link to instanceaddfield")
Adds a field to the schema after a step hook has completed.
By default, the field will be added to the end of the fields.
You may optionally provide a position object specifying where in the list of fields to insert the new one.
A field spec for the new field to add
Specifies where in the list of fields to add the new one.
If provided, must be an object of the form `{ before: fieldKey }` or `{ after: fieldKey }` where `fieldKey` is the `key` of a mapped field. If no mapped field with the given key exists, the new field will be added to the end.
For an example of concatenating or splitting fields, see the [Guide](/guides/using-addField).
### `instance.removeField`[](#instanceremovefield "Direct link to instanceremovefield")
Removes a field after a step hook has completed.
The field will no longer be visible on the data review screen nor included in the result data. Any validation errors or info messages will be discarded.
Can be used in conjuction with [`addField`](/reference/hooks#instanceaddfield) to concatenate columns and remove the originals, i.e. combine `firstName` + `lastName` into `fullName` and remove `firstName` and `lastName` from the result data.
Key of the field to be deleted.
For an example of concatenating or splitting fields, see the [Guide](/guides/using-addField).
### `instance.updateInfoMessages`[](#instanceupdateinfomessages "Direct link to instanceupdateinfomessages")
Updates the messages for targeted cells, overwriting any previous messages.
An array of objects with an object for each cell to update the messages on
The 0-indexed position of the row you are targeting
The key of the [field](/reference/fields/fields) you are targeting
If `manyToOne` is enabled on a field, you can use this property to select which mapped column shall receive the updates.
An array of [infoMessages](#working-with-info-messages) which will be set for the cell at the specified row and field.
An empty array will clear existing messages from the cell.
In this case, if you had previously set a message on the email field of row 3 or the phone field of row 8, these messages would be replaced with the new ones.
```javascript theme={null}
instance.updateInfoMessages([
{
rowIndex: 3,
fieldKey: "email",
messages: [{ level: "warning", message: "Email has not been confirmed" }],
},
{
rowIndex: 8,
fieldKey: "phone",
messages: [{ level: "error", message: "Phone number not in database" }],
},
]);
```
### `instance.addRows`[](#instanceaddrows "Direct link to instanceaddrows")
Adds rows to the dataset. Returns an array of row IDs for the rows that were just added.
Rows to be added to the dataset.
Index where this row will be inserted. If omitted, row will be added to end of the dataset.
The `row` object should contain keys matching the field key of each mapped field. If a mapped field is omitted, it will remain unchanged.
Each field key object may have the following entries:
The new value for this row
Info messages for this row.
Select option overrides for this cell. If provided, the cell will use these select options for transforming values and validating instead of those set when the field was declared.
### `instance.removeRows`[](#instanceremoverows "Direct link to instanceremoverows")
Removes rows rows the dataset.
Rows to be removed. Row IDs can be read on any row or column hooks.
### `instance.setConfirmationMessage`[](#instancesetconfirmationmessage "Direct link to instancesetconfirmationmessage")
Allows customization of the messaged displayed to the user after they click 'Finish'. Content can be plain text or HTML.
Message for finish confirmation alert modal. Text or HTML for embedding links or media.
Optionally override text for submit and cancel buttons.
# onUserEvent
Source: https://developer.dromo.io/reference/on-user-event
Receive real-time notifications when a user interacts with specific UI controls inside the Dromo importer.
## Overview
`onUserEvent` is a callback you can register on a `DromoUploader` instance to receive real-time notifications when a user interacts with specific UI controls inside the importer. It gives your host application observability into what the user is doing — useful for analytics, audit logging, conditional UI reactions, or debugging.
## Registration
```javascript theme={null}
const importer = new DromoUploader(licenseKey, fields, settings, user);
importer.onUserEvent((event) => {
console.log(event.eventType, event.payload);
});
```
The method accepts a single callback function. It can be registered at any point before the user triggers the relevant interaction.
Only one callback can be registered per instance. Calling `onUserEvent` a second time replaces the first.
## Callback Signature
```typescript theme={null}
onUserEvent(callback: (event: IUserEvent) => void): void
```
The callback receives one argument, an `IUserEvent` object:
```typescript theme={null}
interface IUserEvent {
eventType: T;
payload: IUserEventPayloadMap[T];
}
```
## Event Types
Currently one event type is emitted:
| `eventType` | Description |
| ---------------- | --------------------------------------------------------------- |
| `"BUTTON_CLICK"` | Fired whenever the user clicks a tracked button in the importer |
### BUTTON\_CLICK Payload
When `eventType` is `"BUTTON_CLICK"`, the payload is:
```typescript theme={null}
interface IButtonClickPayload {
buttonName: string; // see button name reference below
step: string; // the active step when the click occurred
}
```
**`step` values**
| Value | Step |
| ----------------- | -------------------------------------- |
| `"UPLOAD"` | File upload / manual data entry screen |
| `"HEADER_SELECT"` | Header row selection screen |
| `"COLUMN_MATCH"` | Column mapping screen |
| `"SELECT_MATCH"` | Value / select matching screen |
| `"REVIEW"` | Data review and validation screen |
## Button Name Reference
The full set of `buttonName` values, grouped by step:
### Navigation (cross-step)
| Value | Description |
| ---------- | ----------------------- |
| `CONTINUE` | Primary continue action |
| `NEXT` | Next step |
| `GO_BACK` | Back navigation |
| `FINISH` | Final submit / finish |
### Upload step
| Value | Description |
| ------------------- | ------------------------------------------- |
| `UPLOAD_DATA` | Upload a file |
| `ENTER_DATA` | Open manual data entry |
| `DOWNLOAD_TEMPLATE` | Download the CSV template |
| `CONFIRM_SHEET` | Confirm sheet selection (multi-sheet files) |
### Header select step
| Value | Description |
| ------------------------ | --------------------------------- |
| `HEADER_SELECT_CONTINUE` | Continue with selected header row |
| `NO_HEADER_ROW` | Mark file as having no header row |
### Column match step
| Value | Description |
| --------------------- | -------------------------- |
| `COLUMN_MATCH_IGNORE` | Ignore an unmatched column |
| `COLUMN_MATCH_REMOVE` | Remove an unmatched column |
### Select / value match step
| Value | Description |
| -------------------------- | -------------------------------- |
| `DATE_FIX_ACCEPT` | Accept suggested date format fix |
| `DATE_FIX_UNDO` | Undo date format fix |
| `SELECT_MATCH_CLEAR` | Clear a single-select match |
| `MULTI_SELECT_MATCH_CLEAR` | Clear a multi-select match |
### Review step
| Value | Description |
| ------------------------------- | ---------------------------------- |
| `FIND_AND_REPLACE_OPEN` | Open find & replace panel |
| `FIND_AND_REPLACE_CONFIRM` | Confirm a find & replace operation |
| `FIND_AND_REPLACE_CANCEL` | Cancel find & replace |
| `SHOW_ROWS_WITH_ERRORS` | Filter to rows with errors |
| `CLEAR_ROWS_WITH_ERRORS_FILTER` | Clear the error filter |
| `TRANSFORM_DATA_OPEN` | Open AI transform panel |
| `TRANSFORM_DATA_PREVIEW` | Preview AI transform results |
| `TRANSFORM_DATA_REGENERATE` | Regenerate AI transform |
| `TRANSFORM_DATA_CONFIRM` | Confirm AI transform |
| `TRANSFORM_DATA_CANCEL` | Cancel AI transform |
| `EXPORT_ALL` | Export all rows |
| `EXPORT_ERRORS_ONLY` | Export only rows with errors |
| `DOWNLOAD_XLSX` | Download data as XLSX |
| `TOGGLE_SEARCH` | Toggle search bar |
### Exit confirmation modal
| Value | Description |
| -------------- | ---------------------------- |
| `EXIT_CONFIRM` | Confirm exit / cancel import |
| `EXIT_CANCEL` | Stay in the importer |
### Generic alert / modal actions
| Value | Description |
| ----------------- | ----------------------------------- |
| `ALERT_PRIMARY` | Primary action on a generic alert |
| `ALERT_SECONDARY` | Secondary action on a generic alert |
## Error Handling
Errors thrown inside your `onUserEvent` callback are caught by Dromo and logged as a warning — they will not crash the importer or interrupt the user's flow:
```
[Dromo-External-Error] There was an error in your onUserEvent callback.
```
## Example
```javascript theme={null}
importer.onUserEvent((event) => {
if (event.eventType === "BUTTON_CLICK") {
const { buttonName, step } = event.payload;
analytics.track("dromo_button_click", {
button: buttonName,
step: step,
userId: currentUser.id,
});
}
});
```
## Notes
* The callback is fire-and-forget from Dromo's perspective — the return value is ignored and the importer does not await it.
* The callback is registered on the importer instance, so each importer instance can have its own independent `onUserEvent` handler.
* The callback is not invoked in headless mode (server-side Lambda execution), only during interactive browser sessions.
# Component Props
Source: https://developer.dromo.io/reference/react-dromo-uploader
Reference for props accepted by the DromoUploader React component.
The `DromoUploader` component is the primary way to integrate Dromo into your React application. It accepts the following props:
Your Dromo front-end license key, which can be found in [the Dromo dashboard](https://dashboard.dromo.io/).
An array of [Field objects](/reference/fields/fields) which comprise your import schema.
[Settings object](/reference/settings/settings) to change the behavior of the Dromo importer.
[User metadata object](/reference/users) to associate with the import.
The ID of a pre-existing schema created in Schema Studio. If provided, the `fields`, `settings` (except `importIdentifier` and `developmentMode`), and `hooks` props will be ignored in favor of the saved schema's configuration. See [Usage with saved schemas](/getting-started/react#usage-with-saved-schemas).
An array of [row hook](/reference/hooks#row-hooks) functions.
An array of [bulk row hook](/reference/hooks#bulk-row-hooks) functions.
An array of initialization-only [batch row hook](/reference/hooks#batch-row-hooks) functions. Each function receives a bounded slice of rows and its one-based batch number.
An array of [row delete hook](/reference/hooks#row-delete-hooks) functions called when users remove rows.
An array of [column hook](/reference/hooks#column-hooks) objects. Each object has two entries:
The [field key](/reference/fields/fields) of the field this column hook should run on.
The [column hook](/reference/hooks#column-hooks) function.
An array of [step hook](/reference/hooks#step-hooks) objects. Each object has two entries:
The type of step hook.
A function matching the signature for the specified step hook type.
An array of [custom step](/reference/custom-steps) configurations inserted into the import flow.
An array of [custom file parser](/reference/custom-file-parsers) configurations.
A [beforeFinish callback function](/reference/results#the-beforefinish-callback) called immediately before completing the import to check results.
An [onResults callback function](/reference/results#the-onresults-callback) called with the results after the user finishes the import.
Called if the user leaves the Dromo Uploader before completing the import.
Called when the user interacts with a tracked control in the importer. See the [onUserEvent reference](/reference/on-user-event) for event payloads.
(Available in version >= 2.0.9)
If provided, determines whether the Dromo importer modal should open. See [Controlling the Modal Programmatically](/getting-started/react#open-prop-vs-children-as-open-button).
Overrides the `developmentMode` setting if a `schemaId` is also provided. Defaults to `false`.
Overrides the `importIdentifier` setting if a `schemaId` is also provided.
# Import Results Callbacks and Metadata
Source: https://developer.dromo.io/reference/results
Reference for Dromo's onResults and beforeFinish callbacks, returned row data, import metadata, and completion control.
## The `onResults` callback[](#the-onresults-callback "Direct link to the-onresults-callback")
This callback is called when the user completes the import flow. `onResults` is called with two arguments.
```js JavaScript theme={null}
dromo.onResults(function (response, metadata) { // Do something with the data here console.log(response, metadata);});
```
```jsx React theme={null}
// Do something with the data here console.log(response, metadata) }> Launch Dromo
```
An array of objects, one for each row in the final data.
The keys of each object will be the field keys of each mapped field, and the values will be the final value of that field, based on the field's type.
**Note: If a field is [`manyToOne`](/reference/fields/field-types#manytoone), the value will be an array.**
Dromo assigns a unique ID to each completed import that is synced to the backend. This ID can be used to retrieve the import from the [Dromo API](/api-reference).
If [`backendSyncMode`](/reference/settings/settings#backendsyncmode) is `"DISABLED"`, the import ID will be `null`.
The filename of the file that the user uploaded.
`null` if the user did not upload a file (i.e. used manual entry).
The [`importIdentifier`](/reference/settings/settings#importidentifier) of this import
The [user](/reference/users) associated with this import
Array of row indices where at least one error appears.
Array of objects detailing all the errors in the upload.
An object whose keys are the field keys and values are objects containing metadata about that field
The header this field was mapped to. `null` if no file was uploaded.
The column header index this field was mapped to. `null` if no file was uploaded.
Whether this field was added by the user as a custom field
Indicates if this field is [manyToOne](/reference/fields/field-types#manytoone).
The headers that were mapped to the field if it is [manyToOne](/reference/fields/field-types#manytoone).
The indexes of columns that were mapped to a [manyToOne](/reference/fields/field-types#manytoone) field.
## The `onCancel` callback[](#the-oncancel-callback "Direct link to the-oncancel-callback")
This callback is called if the user exits from the import without completing it.
`onCancel` does not receive any arguments.
## The `beforeFinish` callback[](#the-beforefinish-callback "Direct link to the-beforefinish-callback")
Sometimes, you may wish to perform validation on the final result data before the user leaves the import.
To accomplish this, you can define a `beforeFinish` callback. This callback will fire when the user finishes the import, and receives the same arguments as the `onResults` callback. The callback also receives a third argument, `instance`, with the instance of the uploader.
If you define a `beforeFinish` callback, you can cancel finishing the upload by returning an object in the format `{ cancel: true, message: "Message to user" }`. In this case, the user will remain on the Review Screen, with the given error message shown to the user. The `onResults` callback will not fire and the import will not complete. This process will repeat the next time the user submits the import.
Otherwise, if nothing is returned from the `beforeFinish` callback (or any other value is returned), the `onResults` callback will fire and the import will complete normally.
An array of objects, one for each row in the final data.
The keys of each object will be the field keys of each mapped field, and the values will be the final value of that field, based on the field's type.
The filename of the file that the user uploaded.
`null` if the user did not upload a file (i.e. used manual entry).
The [`importIdentifier`](/reference/settings/settings#importidentifier) of this import
The [user](/reference/users) associated with this import
Array of row indices where at least one error appears.
Array of objects detailing all the errors in the upload.
An object whose keys are the field keys and values are objects containing metadata about that field
The header this field was mapped to. `null` if no file was uploaded.
The column header index this field was mapped to. `null` if no file was uploaded.
Whether this field was added by the user as a custom field
Indicates if this field is [manyToOne](/reference/fields/field-types#manytoone).
The headers that were mapped to the field if it is [manyToOne](/reference/fields/field-types#manytoone).
The indexes of columns that were mapped to a [manyToOne](/reference/fields/field-types#manytoone) field.
The Dromo Uploader instance which you can call mutation methods on
```js JavaScript theme={null}
dromo.beforeFinish(function (data, metadata, instance) { if (data.length < 20) { return { cancel: true, message: "You must import at least 20 rows" } }});
```
```jsx React theme={null}
if (data.length < 20) { return { cancel: true, message: "You must import at least 20 rows" } } }> Launch Dromo
```
## Custom fields[](#custom-fields "Direct link to Custom fields")
If you have enabled [`allowCustomFields`](/reference/settings/settings#allowcustomfields), it will allow the user to add custom fields that are not part of the field schema. The name of the custom field will be added as a key to the result objects.
As an example, if the user defines a column name "fav\_color", the response would look like the following:
```
{ "name": "Jack", "email_address": "jack@thehill.com", "fav_color": "blue"}
```
# Deprecated Settings
Source: https://developer.dromo.io/reference/settings/deprecated
Deprecated settings are settings that are no longer supported and will be removed in a future version of Dromo. These settings are provided for backwards compatibility and will be removed in a future version of Dromo.
`allowInvalidSubmit` is deprecated in favor of the `invalidDataBehavior` setting.
`allowInvalidSubmit = false` is equivalent to `invalidDataBehavior = "REMOVE_INVALID_ROWS"`.
`allowInvalidSubmit = true` is equivalent to `invalidDataBehavior = "INCLUDE_INVALID_ROWS"`.
This option impacts the messaging on the final screen before submitting data. When this is set to `false`, Dromo will discard the records with errors and the user will see "Discard *X* rows with issues. Submit the rest." When this is set to `true`, Dromo will submit the records with errors and the user will see "Submit your data anyway (errors may occur).":
`backendSync` is deprecated in favor of `backendSyncMode`
`backendSync = false` is equivalent to `backendSyncMode = "DISABLED"`
`backendSync = true` is equivalent to `backendSyncMode = "FULL_DATA"`
Removed in v2
Allows the user to specify an encoding for any files they upload. Dromo will default to the encoding that it detects. If `displayEncoding` is set to `true` (default), the encoding dropdown is visible in the importer:
# Settings
Source: https://developer.dromo.io/reference/settings/settings
Settings are supplied as an object to the Dromo instance on initialization. For details on how to supply settings to Dromo, see the Getting Started guide for the SDK you are using.
This option identifies the data you are importing. If you have multiple imports in your application (i.e. *Customers* and *Sales*), you should specify different import identifiers for each type of import. The title of the importer modal will be "Add `importIdentifier`" unless you supply the [`title` option](#title).
Replaces the title of the importer modal. For instance, if we set `title` to "Enjoy Dromo!" you would see the title appear in the importer like this:
The default for this will display "Add `importIdentifier`". For instance, if the `importIdentifier` is `Products`, it will say "Add Products".
This option impacts the user's experience when invalid data is present. When this is set to `"BLOCK_SUBMIT"`, Dromo will disable the 'Finish' button preventing the user from submitting with errors. When this is set to `"REMOVE_INVALID_ROWS"`, Dromo will remove any rows with errors and the user will see a dialog indicating "Discard *X* rows with issues. Submit the rest." When this is set to `"INCLUDE_INVALID_ROWS"`, Dromo will submit the records with errors and the user will see "Submit your data anyway (errors may occur).".
| `REMOVE_INVALID_ROWS` | `INCLUDE_INVALID_ROWS` | `BLOCK_SUBMIT` |
| :--------------------------------------------: | :---------------------------------------------: | :---------------------------------------------------: |
|
|
|
|
Controls whether the user can complete an import even if 0 rows will be submitted. This can be because there are no rows in the import data, or if `invalidDataBehavior` is set to `REMOVE_INVALID_ROWS` and all rows have at least one error.
If set to `false`, the submit import button will be disabled when no rows would be submitted.
Show or hide an input table for the user to manually input data. If this is set to `true`, then there will only be a file dropzone on the page.
Show or hide the file dropzone that appears when the importer is started. If this is set to `true`, then there will only be a manual input table on the page and the dropzone will be hidden. The `manualInputDisabled` and `manualInputOnly` settings cannot both be set to `true`.
The `backendSyncMode` setting controls whether the upload data is sent to the Dromo server for storage, if only the column mapping is sent, or if all processing is completed in the browser.
Setting `backendSyncMode` to `"FULL_DATA"` enables several features:
* Viewing your uploads in the Dromo Dashboard
* Using webhooks and the Dromo API to retrieve results
* Using the `autoMapHeaders` setting
It sends all the data from the upload to the Dromo backend for storage.
Setting `backendSyncMode` to `"MAPPINGS_ONLY"` enables using the `autoMapHeaders` setting. In this mode, only the column mapping and unique values from `'select'` type fields will be stored in our backend.
You can set `backendSync` to `"DISABLED"` if you have data privacy requirements, and you don't need the additional functionality the other modes provide.
Dromo uses industry-standard best practices to secure your data on our servers.
Requires [backendSyncMode](#backendsyncmode) = "FULL\_DATA"
This is a URL that is called upon the completion of a successful upload through Dromo. Note: `backendSync` needs to be set to `true` for this to work. For more information on webhooks, see the [Webhooks](/reference/webhooks) documentation.
This settings allows users to create custom fields that are not included in the `fields` object. If set to `true`, a user will be able to type in a custom field name during the matching phase. These user-provided field names will be included as keys for the returned results.
This allows you to specify the data being uploaded, bypassing the CSV upload stage.
You can supply this data in one of two forms:
* As an array of objects, where each object represents a row. The object itself is a mapping of the `key` specified in [fields](/fields) and the value for that field in that row. As an example you could pass in:
```
[ { name: "Jack", email_address: "jack@thehill.com", }, { name: "Jill", email_address: "jill@thehill.com", },];
```
* As an array of arrays of strings, where each row is represented by an array, with the fields matching the order specified by [`fields`](/fields). For example:
```
[ ["Jack", "jack@thehill.com"], ["Jill", "jill@thehill.com"],];
```
All of the supplied data must be in one form or the other – they cannot be mixed.
The `initialFile` setting allows you to provide a [Web API File object](https://developer.mozilla.org/en-US/docs/Web/API/File), providing the Dromo importer with the raw data to parse and bypassing the user picking a file to import.
The File object can come from a file input within your application, or you can build the file object from any source data using the [File constructor](https://developer.mozilla.org/en-US/docs/Web/API/File/File). The filename extension of the File object should match the format of the file (e.g. if you are providing an Excel file, the filename should end in ".xlsx".)
Specify the maximum number of records that a single file can import. This value does not include the headers of the file.
If the number of rows in the file exceed this number, the user will be warned and the excess rows will be dropped.
Sets the maximum number of rows passed to each [batch row hook](/reference/hooks#batch-row-hooks) invocation. The value must be a positive integer.
This setting has no effect unless a batch row hook is registered. Batch row hooks run only once during import initialization and receive a one-based batch number as their second argument.
Specifies the maximum file size, in bytes, that can be imported. If the user selects a larger file, they will be shown an error message.
The maximum file size must be a positive integer and cannot exceed 1 GiB (the default value).
Set this value to `true` when testing or in development environments. The importer will show a "development mode" banner and the imports will be flagged as development imports. These imports will not be charged against your account.
Customizes the importer's colors, typography, controls, and other visual elements. See the complete [style overrides reference](/reference/settings/style-overrides).
Overrides supported error and interface text.
Maps supported error keys to replacement messages.
Overrides the subtitle shown on the Column Matching or Bulk Fix step.
```javascript theme={null}
settings: {
textOverrides: {
ui: {
COLUMN_MATCH_SUBTITLE: "Match each spreadsheet column to a destination field",
BULK_FIX_SUBTITLE: "Review and standardize unmatched values",
},
},
}
```
Requires [backendSyncMode](#backendsyncmode) = "MAPPINGS\_ONLY" or "FULL\_DATA"
With this setting on, Dromo will try to automatically map the CSV headers to fields based off of previous uploads with the same `importIdentifier`. The user can still click back and change the mapping if needed.
If `true`, Dromo always skips the Bulk Fix step, even when select, multi-select, or date fields would otherwise require bulk fixing. This setting takes precedence over the automatic conditions that normally show the step.
```javascript theme={null}
skipBulkFixStep: true
```
Allows you to explicitly define a field delimiter for your file (i.e. `|`). By default, Dromo will attempt auto-detect the delimiter.
Configures client-side parsing for XML uploads.
XML tag names that should always be treated as arrays, including when only one matching element exists.
Maps XML tag names to the column headers Dromo should display.
Automatically recognizes plural parent and singular child tags, such as ``, as arrays.
Treats a single object nested beneath a single root tag as a one-row dataset.
```javascript theme={null}
settings: {
xmlParsingOptions: {
forceArrayTags: ["contact"],
headerAlias: { email_address: "Email" },
enableAutoArrayHeuristics: true,
assumeSingleRootChildIsArray: true,
},
}
```
If set, Dromo will generate a template CSV file using the fields you have defined on the import. Users will see a download link for this template file on the Upload screen of the Dromo importer. The value of this setting will be used as the filename of the template, plus the `.csv` extension.
Activates the parsing of Excel files in the end user's browser. Either this setting or `backendSyncMode: "FULL_DATA"` must be active to enable Excel file parsing.
Defaults to `true` in SDK version >= 2.0.0
With this setting enabled, Dromo will include all columns not mapped or added as custom fields in the result payload.
The values for each row will be in an object with the key `$unmapped`. The key for each value will correspond to the column index in the original file.
Additionally, the `metadata` object passed to your `resultsCallback` will include a `rawHeaders` array containing the original column headers in order, allowing you to map column indexes to their actual header names.
In the example below, we only mapped two columns to `firstName` and `email`. The remaining unmapped columns were added to the `$unmapped` object.
```javascript theme={null}
// Example results
[
{
"$unmapped": {
"1": "Phillips",
"3": "(124) 246-3980",
"4": "United States",
"5": "93715",
"6": "",
"7": "proposed terms"
},
"firstName": "Michael",
"email": "jason@moxie.xyz"
},
{
"$unmapped": {
"1": "Kukreja",
"3": "+1 124 246 3981",
"4": "Canada",
"5": "J6R",
"6": "17-Dec-91",
"7": "qualifying lead"
},
"firstName": "Mohit",
"email": "info@hub.inc"
}
]
// metadata.rawHeaders
["Name", "Last Name", "Email", "Phone", "Country", "Zip", "Birthday", "Notes"]
```
For a complete guide on working with unmapped columns including how to use `rawHeaders` to convert column indexes to header names, see the [Accessing Unmapped Columns guide](/guides/accessing-unmapped-columns).
## Step settings
### `uploadStep`[](#uploadstep "Direct link to uploadstep")
The `settings.uploadStep` object contains settings for the Upload Step experience. It defaults to `{}`.
If set, the given string will be shown to the user on the Upload Step. This can be used to give the user additional context on what file they should upload.
Content can be plain text or HTML.
If set, the sheet selection modal will be skipped if a sheet matching the string is found in the workbook.
If the uploaded file does not have sheets (e.g. a CSV), or the uploaded file has only one sheet, this setting has no effect.
If set, the template download button will download from this URL instead of generating a CSV template from your schema fields.
This allows you to provide a custom template file that may include additional formatting, instructions, or example data beyond what can be captured in the basic CSV template.
The file will be downloaded with the same filename that would be used for the generated template.
```javascript theme={null}
uploadStep: {
templateDownloadOverrideURL: "https://your-domain.com/custom-template.xlsx"
}
```
### `matchingStep`[](#matchingstep "Direct link to matchingstep")
object
Default: `{}`
Settings for the Matching Step experience
Added in v2
By default, the user is asked to match or ignore all of the column names that exist in their file. If set to `true`, the user will instead be asked to match only the fields contained in the schema. This is particularly useful when a file may contain numerous irrelevant columns.
Determines whether Dromo will use fuzzy matching to suggest column mappings during the matching step. If set to `false`, Dromo will only suggest columns whose headers exactly match a field's `label`, `key`, or `alternateMatches`.
If set, the given string will be shown to the user on the Matching Step. This can be used to give the user additional context on how to match the columns for your import.
Content can be plain text or HTML.
If set, `matchingStep.headerRowOverride` will preset the header row to the 0-indexed row specified. If the header row is after the first row (i.e. `headerRowOverride` is set to a value greater than 0), rows before the header row will be discarded.
If set to true, matchingStep.suggestCustomFirst will prioritize the "Add custom field" option by displaying it at the top of the dropdown menu during column matching, rather than positioning it at the end.
### `matchValuesStep`[](#matchvaluesstep "Direct link to matchvaluesstep")
object
Default: `{}`
Settings for the Bulk Fix step experience (formerly known as the Match Values step)
If set, the given string will be shown to the user on the Match Values Step. This can be used to give the user additional context on how they should match values with their select fields.
Content can be plain text or HTML.
The maximum number of unique values that can be mapped using the Match Values screen per field. If more than `maxMappableSelectValues` are found in a column of data, Dromo will indicate to the user that there are too many unique values to map on the Match Values screen. The maximum value that can be set for `maxMappableSelectValues` is 1000.
### `reviewStep`[](#reviewstep "Direct link to reviewstep")
The `settings.reviewStep` object contains settings for the Review Step experience. It defaults to `{}`. The following settings are available:
If set, the given string will be shown to the user while the hooks are running at the beginning of the review step. This can be used to give the user additional context if you have any slow-running hooks.
If set, the given string will be shown to the user on the Review Step. This can be used to give the user additional context on what issues they should resolve on this step.
Content can be plain text or HTML.
If false, Dromo will prevent the user from adding any new rows to the data.
Dromo will disable manual input mode (user starts with an empty data table and adds data line by line instead of a file) if this setting is `false`. Manual input mode specifically requires adding rows.
If false, Dromo will prevent the user from removing any rows from the data.
If true, the 'Transform data' feature will be available on the review step.
See the [Transform data guide](/guides/using-transform-data) for more information.
An array of custom prompt strings displayed as clickable suggestions in the AI "Transform Data" modal. When set to a non-empty array, these strings replace the built-in localized prompts shown to the user. `null` or an empty array falls back to the default prompts.
Requires [`enableUserTransformations`](#enableusertransformations) to be `true`.
```javascript theme={null}
settings: {
reviewStep: {
enableUserTransformations: true,
transformDataPrompts: [
"Remove all rows where the email domain is not @company.com",
"Normalize all phone numbers to E.164 format",
"Fill blank 'country' cells with 'United States'",
],
}
}
```
If true, Dromo will highlight any cell that had substantive changes made by automatic transformations, such as:
* Rounded number values
* Modified datetime values
* Select values changed from alternate matches
* Left-padded zip codes or removed last 4 digits
* Modified URLs
* Adjusted phone numbers
This feature can help users quickly identify and verify automatic transformations made to their data during the import process.
When enabled, adds an error navigation panel to the Review Step that lets users move through validation errors one at a time and fix them inline.
**Row headers**: Any row with a validation error displays a red circular info icon next to its row number. Clicking the icon opens the navigator panel anchored to that row's first error.
**Navigator panel**: Slides in from the right of the table and lists all validation errors as cards — each showing the row number, column name, and error message.
* **Column filter**: A dropdown filters the list to a single column.
* **Prev/Next navigation**: Buttons and the `Alt+↑` / `Alt+↓` keyboard shortcuts step through errors in order, scrolling the table to each cell.
* **Jump to cell**: Clicking any error card scrolls directly to that cell.
* **Inline actions** (shown on the active error card):
* **Clear** — removes the invalid cell value. For multi-select fields, only the invalid tokens are removed.
* **Replace** — replaces the cell value with a new one. For select fields a valid-options dropdown is shown; for multi-select fields the chosen value is appended to the existing valid tokens; for all other fields a free-text input is shown.
* **Closing the panel** (X button) resets the panel state and column filter.
```javascript theme={null}
settings: {
reviewStep: {
enableNavigatingErrors: true
}
}
```
## Localization and sessions
The language to display within the Dromo importer. Dromo supports the following internationalization (i18n) options:
* **bn** (Bengali)
* **cs** (Czech)
* **da** (Danish)
* **de** (German)
* **el** (Greek)
* **en** (English)
* **es** (Spanish)
* **fi** (Finnish)
* **fr** (French)
* **hi** (Hindi)
* **hr** (Croatian)
* **hu** (Hungarian)
* **id** (Indonesian)
* **is** (Icelandic)
* **it** (Italian)
* **ja** (Japanese)
* **ko** (Korean)
* **lt** (Lithuanian)
* **lv** (Latvian)
* **ms** (Malay)
* **nl** (Dutch)
* **no** (Norwegian)
* **pl** (Polish)
* **pt** (Portuguese)
* **ro** (Romanian)
* **ru** (Russian)
* **sw** (Swahili)
* **sv** (Swedish)
* **th** (Thai)
* **tr** (Turkish)
* **uk** (Ukrainian)
* **vi** (Vietnamese)
* **zh\_CN** (Chinese - Simplified)
* **zh\_TW** (Chinese - Traditional)
Requires [backendSyncMode](#backendsyncmode) = "FULL\_DATA"
When enabled, Dromo will automatically save user progress throughout the import flow, allowing users to resume their session if they navigate away or close the browser.
**Auto-Save Behavior:**
* Progress is automatically saved to the backend with intelligent debouncing (\~700ms after changes)
* Captures the complete state at each stage: upload, header selection, column matching, and data review
* Includes column mappings, transformations, row data, and the uploaded file
* Sessions are stored server-side and linked to a unique session identifier
**Use Cases:**
* Long-running imports where users may need to step away
* Complex data mappings that take time to configure
* Preventing data loss from accidental browser closures
* Multi-session workflows where users return to complete imports later
**Note:** Completed imports automatically clear their saved session data. Use [`hydrationId`](#hydrationid) for new integrations, or [`sessionId`](#sessionid) for the legacy flow, to restore a saved session. See the [Session Saving & Restoration Guide](/guides/saving-progress) for the full security model and API reference.
```javascript theme={null}
settings: {
backendSyncMode: "FULL_DATA",
savePartialSession: true
}
```
Requires [backendSyncMode](#backendsyncmode) = "FULL\_DATA"
Restores a previously saved import using the legacy session identifier. Sessions remain available for seven days after the most recent save.
**Restoration Behavior:**
* Fetches saved state from the backend using the raw session identifier and the widget's frontend license key
* Validates that the session matches the current `importIdentifier`
* Downloads and restores the uploaded file data
* Rehydrates the UI to the exact stage where the user left off:
* **Sheet Selection** → Returns to upload step
* **Header Selection** → Restores headers and jumps to header selection
* **Column Matching** → Restores mappings and jumps to column matching
* **Data Review** → Restores full data grid and jumps to review step
* Preserves all user work including mappings, transformations, and data edits
For new integrations, prefer [`hydrationId`](#hydrationid), which avoids exposing the long-lived session identifier to the browser. Store `sessionId` on your backend so it can create a fresh hydration token when the user returns.
**Use Cases:**
* Resume workflows where users receive a session link to continue an import
* Multi-step approval processes where different users complete different stages
* Scheduled or delayed import completion
**Note:** Use with [`savePartialSession`](#savepartialsession) enabled to create sessions that can be restored. See the [Session Saving & Restoration Guide](/guides/saving-progress) for the full token lifecycle and API reference.
```javascript theme={null}
settings: {
backendSyncMode: "FULL_DATA",
sessionId: "1b6acf28-8748-4120-8d7a-4b7846d5e487"
}
```
Requires [backendSyncMode](#backendsyncmode) = "FULL\_DATA"
A short-lived signed access token used by the widget to create, read, update, and delete saved session state. The token identifies one session, so the widget does not need the raw `sessionId` when `hydrationId` is provided.
Create a session from your backend with `POST /api/widget/session/v2/auth/`. When a user returns, mint a fresh token with `POST /api/widget/session/v2/{sessionId}/auth/`. Delete a session with `DELETE /api/widget/session/v2/{sessionId}/`.
Hydration tokens default to a six-hour lifetime. Set `expires_in` in the create or resume API request to choose a different lifetime; this is an API parameter, not a widget setting. The underlying session expires seven days after its most recent save.
Call the session create, resume, and delete endpoints only from your backend with a backend license key. Pass the returned `hydrationId` to the widget, but never expose the backend key.
```javascript theme={null}
settings: {
backendSyncMode: "FULL_DATA",
savePartialSession: true,
hydrationId: ""
}
```
See the [Session Saving & Restoration Guide](/guides/saving-progress) for the complete token lifecycle and API examples.
# Style Overrides
Source: https://developer.dromo.io/reference/settings/style-overrides
Customize the Dromo Uploader with your own CSS styles.
The Dromo Uploader supports custom CSS styling. You can use the `styleOverrides` prop to customize the appearance of the uploader.
Specify custom CSS styling on any UI element to make it match your brand requirements.
For a visual overlay of the available styling options, see the [guide to customizing styles](/guides/custom-styling).
Style overrides that affect the general UI of the Dromo Uploader
The primary text color
The secondary text color
A URL to a custom font file
The name of the font family provided by the custom font file
Added in v2
The background color of the modal
Added in v2
The border radius of the modal
Added in v2
The border style of the modal
Added in v2
The border width of the modal
Added in v2
The border color of the modal
The general text color for UI elements
Color used for success states and messages
Color used for warning states and messages
Whether to apply a blur effect to the background when modals are open
Style overrides for the primary action button
The radius of the button
The background color of the button
The color of the text in the button
The `border` shorthand property to apply to the button
The background color of the button while hovering
The text color of the button while hovering
The `border` shorthand property to apply to the button while hovering
The background color of the button when it is disabled
The text color of the button when it is disabled
The padding of the button
Style overrides for the secondary action button
The radius of the button
The background color of the button
The color of the text in the button
The `border` shorthand property to apply to the button
The background color of the button while hovering
The text color of the button while hovering
The `border` shorthand property to apply to the button while hovering
The background color of the button when it is disabled
The text color of the button when it is disabled
The padding of the button
Style overrides for the tertiary action button
The radius of the button
The background color of the button
The color of the text in the button
The `border` shorthand property to apply to the button
The background color of the button while hovering
The text color of the button while hovering
The `border` shorthand property to apply to the button while hovering
The background color of the button when it is disabled
The text color of the button when it is disabled
The padding of the button
Style overrides for the file dropzone on the upload screen
The width of the dropzone border in pixels
The radius of the dropzone border in pixels
The color of the dropzone border
The style of the dropzone border
The background color of the dropzone
The text color of the dropzone message
The outline property of the dropzone
The border color while a file is being dragged over the dropzone
The border color when the dragged file can be accepted
The border color when the dragged file cannot be accepted
Style overrides for the help text boxes
The text color of the help text
The background color of the help text box
Style overrides for the progress bar in the header
The color of completed steps
The color of steps that have not been started yet
The color of the step currently in progress
Added in v2
The font size of the step text
Added in v2
The font weight of completed steps
Added in v2
The font weight of incomplete steps
Added in v2
The font weight of the current step
Added in v2
The background color of the stepper bar
Added in v2
The bottom border separating the stepper bar from the rest of the modal
Style overrides for the data table (Handsontable) component
The font weight of column headers
The background color of column headers
The text color of column headers
The background color of row headers (row numbers)
The text color of row headers (row numbers)
The background color of the corner header (top-left cell)
The border color of selected cells
The background color of selected cells
General accent color that affects various interactive elements in the table
The background color of active column headers
The text color of active column headers
The background color of highlighted column headers
The text color of highlighted column headers
The background color of body cells
The text color of body cells
The background color of a hovered cell
The text color of a hovered cell
The background color of alternating odd rows
The background color of a selected row
The border color of a selected row
The outer border color of the table
The color of grid lines between rows and columns
The background color of cells with error messages
The hover background color of cells with error messages
The background color of cells with warning messages
The hover background color of cells with warning messages
The background color of cells with informational messages
The hover background color of cells with informational messages
The background color of read-only cells
The text color of read-only cells
Style overrides for modal overlay backdrop
The background color of the modal overlay
The opacity of the modal overlay (0-100)
Style overrides for cards used in column matching and other importer screens.
The card background color
The background color of the area containing cards
The card border color
The card border width
The card border radius
The card text color
The background color of expanded cards
The color of icons in cards
Style overrides for select controls and dropdown menus.
The background color of the closed control
The control border color
The selected value text color
The control border color in an error state
The background color of the open menu
The background color of a hovered option
The background color of the selected option
Style overrides for the Error Navigator panel shown during the Review Step when [`enableNavigatingErrors`](/reference/settings/settings#enablenavigatingerrors) is `true`. All color values accept any valid CSS color string (hex, rgb, hsl, CSS variables, etc.).
Color applied to error-level indicator icons, error message text, and the error issue count badge.
Color applied to warning-level indicator icons and warning message text.
Color applied to the primary action buttons ("Replace", "Apply") and the active column filter chip border and text.
URL of a custom image to display in place of the default exclamation circle icon for error rows. The image renders at 12×12 px with `object-contain`. Set to `null` to use the default icon.
URL of a custom image to display in place of the default exclamation circle icon for warning rows. The image renders at 12×12 px with `object-contain`. Set to `null` to use the default icon.
```javascript theme={null}
styleOverrides: {
errorNavigator: {
errorColor: "#B91C1C",
warningColor: "#D97706",
accentColor: "#6366F1",
errorIconUrl: "https://cdn.example.com/error-icon.svg",
warningIconUrl: null,
}
}
```
# The User Object
Source: https://developer.dromo.io/reference/users
Attach metadata about the end user launching a Dromo import with the user object and receive it alongside completed upload data.
This data will be stored alongside finished uploads, and is provided as a convenience to more easily identify the end user behind each upload. The DromoUploader does not utilize this data in any way, so you may supply arbitrary values for any key.
Unique identifier for the user
Display name of the user
Email address of the user
Unique identifier for the user's company
Name of the user's company
# Webhooks
Source: https://developer.dromo.io/reference/webhooks
You can use webhooks to get notified when your users complete an import using Dromo.
Dromo provides two kinds of webhooks:
* **Managed Webhooks** are managed in the Dromo dashboard. They provide context about the completed import, and can be applied to multiple schemas.
* **Basic webhooks** are configured by setting the [webhookUrl setting](/reference/settings/settings#webhookurl) in your import settings. This webhook can be configured dynamically at the time of import.
## Managed webhooks
Managed webhooks are configured in the dashboard on the [webhooks page](https://dashboard.dromo.io/webhooks). Click on the "New webhook" button to create your first webhook.
There are a few things to specify:
* **URL**: Provide a publicly accessible URL where the webhook should be sent.
* **Development Mode**: Toggle this setting to control whether the webhook fires for development imports or production imports, based on the developmentMode flag. This is helpful for testing webhooks in staging environments.
* **Schemas to Trigger On**: Choose when the webhook should be triggered using either or both of the following:
* **Import Identifiers**: A list of importIdentifier values. The webhook will fire whenever an import with a matching identifier completes. Useful for dynamic imports.
* **Saved Schemas**: Select from your predefined schemas in Schema Studio.
You can mix import identifiers and saved schemas as needed.
The next time an import is completed for a matching schema, you will receive a POST request like the following:
```json theme={null}
{
"event": "import_completed",
"data": {
"id": "025f399f-4370-4064-aed5-b8d16b597183",
"filename": "contacts.csv",
"user": {
"id": "user-123",
"name": "Jasmine Clark",
"email": "jclark@acme.com",
"company_id": null,
"company_name": null
},
"created_date": "2024-04-24T17:20:14.659349Z",
"import_type": "EMBEDDED",
"status": "SUCCESSFUL",
"num_data_rows": 433,
"development_mode": false,
"has_data": true
}
}
```
To get more details about the parameters, expand the following section:
The event that triggered this webhook. Currently, this will always be `"import_completed"` unless your organization is using Dromo Headless (see below).
The id of the import. You can use this ID with the Dromo Imports API.
The filename of the file uploaded by the user. This will be `null` if manual input or [initialData](/reference/settings/settings#initialdata) was used.
The user metadata supplied with this import
The completion time of the import, in ISO-8601 format
`"HEADLESS"` if the import was a Headless import, otherwise `"EMBEDDED"`
For standard (embedded) imports, currently will always be `"SUCCESSFUL"`. See below for Headless.
The number of rows in the completed import
Whether this import was completed in development mode
Whether the results of this import were saved using [backend sync](/reference/settings/settings#backendsyncmode).
If `true`, you can fetch the import results using the [get import data endpoint](/api-reference/uploads/get-upload-metadata).
When using managed webhooks with headless imports, Dromo exposes two additional events and data in the webhook:
* A `"headless_import_needs_review"` event is triggered when a headless import enters the [needs review state](/getting-started/headless#import-needs-review).
* A `"headless_import_failed"` event is triggered when a headless import encounters an unrecoverable error and goes to the [failed state](/getting-started/headless#import-failed).
Completed headless imports, whether fully automated or completed using manual review, will trigger the standard `"import_completed"` event as detailed above.
All headless import webhooks include an additional `"headless"` key in the `data` object. This will include data about the headless import in a format matching the [retrieve headless import endpoint](/api-reference/headless/retrieve-headless-import), with the exception that `"review_url"` and `"original_file_download"` are not exposed in the webhook for security reasons.
## Basic webhooks
Basic webhooks are triggered when an import is completed, when the settings for that import included a [webhookUrl](/reference/settings/settings#webhookurl).
The webhooks are delivered to the configured URL via POST request in URL-encoded form format, and contain only a single key, `uploadId`.
```sh theme={null}
uploadId=7df3a66d-b7bb-4da6-9244-b4e22c7d7ab0
```
This key can be used to fetch import data and metadata using the [Dromo API](https://developer.dromo.io/api#tag/uploads).
# Welcome to Dromo
Source: https://developer.dromo.io/welcome
Learn how Dromo helps applications import spreadsheets with column matching, validation, transformations, and clean structured results.
Dromo is an easy and intuitive spreadsheet importer that integrates seamlessly with your product. Dromo helps your users match columns, correct errors, perform complex validations, transform values, and upload perfectly formatted data.
Experience Dromo with realistic data and see how easy data importing can be
## Quick Start
Get up and running with Dromo in minutes. Choose the integration that works best for your stack:
Universal JavaScript implementation for any web application
Pre-built React component with TypeScript support
Native Angular implementation with full type safety
No-code schema creation in the Dromo Dashboard
## See Dromo in Action
## How Dromo Works
Specify the fields you want to import using our [Schema Builder](/guides/schema-builder) or define them programmatically with our SDKs.
Configure [validation rules](/fields) and [transformation hooks](/reference/hooks) to ensure data quality and provide real-time feedback.
Receive clean, validated JSON data that's ready to use in your application or database.
Brand the importer to match your application with custom styling and user experience settings.
## Next Steps
Explore a fully customized Dromo implementation
Our team is here to help you succeed
**Questions?** Chat with us anytime using the chat bubble, or email us at [support@dromo.io](mailto:support@dromo.io).