# ImportButton

**Category:** Features/Import

## Design

### Description

The `ImportButton` is the platformized solution for importing a CSV file into a collection.

It renders an import action in the collection toolbar. On the default toolbar it shows a `ResponsiveButton`; on a bulk-action toolbar (`toolbarType="bulkActionToolbar"`) it shows a small secondary `Button`. Clicking it opens a multi-step modal (a `Drawer` on mobile) that walks the user through:

1. **Upload** — pick a `.csv` file.
2. **Map columns** — match each CSV column to an existing collection field, create a new field for it, or skip it.
3. **Import** — the rows are sent to the server and progress is shown.
4. **Summary** — created / updated / failed counts, with a "Download CSV" of the failed rows when some rows were rejected.

Like `ExportTo`, the feature has two parts:

- **Client** — the button and modal, shipped in `@wix/patterns`. Place it with the `` `importButton` prop; it reads what it needs off the table's source when the modal opens, and an optional `config` object carries only what the source can't supply (see [Choosing a source](#choosing-a-source)).
- **Server** — the actual write is done by a Wix Hub **Data Movement** job (see the [data-hub infrastructure](#data-hub-infrastructure) section). The client uploads the file, submits the job, and polls it to completion.

#### Configuring it

`` reads what it needs off the table's source when the modal opens; the optional `config` (`ImportConfig`) fills in what the source can't supply. Two things vary — **where the rows are written** (a Wix Data collection, or a platformized `destination`) and **where the mappable fields come from** (a declared schema source, or a `fields` list you pass) — giving three setups:

- **CMS (Wix Data) — schema-based, no `config`.** `useCmsSchemaSource` → `useTableCollection(source)`; the source supplies the collection, its fields, and create-new-field, and the CMS create/edit-field modal loads on demand. `` takes no props.

  ```jsx
  // `state` comes from `useTableCollection(source)` (@wix/patterns/schema).
  } />;
  ```

- **Platformized, from a source — schema-based, `config` names the destination.** A non–Wix-Data entity backed by `usePlatformizedSource`: the source supplies the fields to map onto, and you name the `destination` (a platformized target has no Wix Data collection to derive it from).

  ```jsx
  const source = usePlatformizedSource({ fqdn, backend, itemKey, itemName });
  const state = useTableCollection(source);
  }
  />;
  ```

- **Platformized, fields listed inline — non-schema, `config` names the destination and the fields.** No source; you list the `fields` to map onto (see `ProductsTable` in `example-bm`).

  ```jsx
  
    }
  />;
  ```

`ImportConfig`: `destination` (the platformized target; omit for CMS), `fields` (list to map onto; unneeded when a source supplies them), plus optional `backup`, `idFieldKey`, and `jobApplicationErrorMap` — see the examples below. **Create-new-field is a CMS (Wix Data) capability**; platformized imports map onto existing fields only.

#### data-hub infrastructure

The import runs **entirely server-side** in [Wix Data Hub](https://github.com/wix-private/wix-data-hub) — a generic data-movement job executor. The client drives it through the `@wix/ambassador-hub-v1-job` API ([job submission & monitoring](https://github.com/wix-private/cloud/blob/master/hub/wix-data-hub-fs2)); the actual reading/writing is done by the [Data Hub executor](https://github.com/wix-private/wix-data-hub/blob/master/packages/wix-data-hub-executor-c/README.md), whose model is: a **source** reads batches, **transformations** alter them, and a **destination** writes them. The client only:

1. Reserves an upload URL and PUTs the (rewritten) CSV, receiving a `fileId`.
2. Submits a job (`source: { file }`, `destination`) and receives a `jobId`.
3. Polls the job every 2 seconds until it reaches `COMPLETED`, `PARTIALLY_SUCCESSFUL`, or `FAILED`.
4. Lists the job's per-row movement logs to build the failed-rows CSV, and terminates the job if the user cancels. Terminating does **not** roll back rows already written — that is what an optional `backup` (in the `config`) is for.

The **`destination`** you pass identifies which executor [destination](https://github.com/wix-private/wix-data-hub/blob/master/packages/wix-data-hub-executor-c/src/api/destination.ts) writes the rows — e.g. `{ wixDataCollection: … }` ([wix-data-collection](https://github.com/wix-private/wix-data-hub/tree/master/packages/wix-data-hub-executor-c/src/plugins/wix-data-collection) plugin) or `{ storesCatalogProducts: {} }` ([online-stores](https://github.com/wix-private/wix-data-hub/tree/master/packages/wix-data-hub-executor-c/src/plugins/online-stores) plugin). The CMS case builds the `wixDataCollection` destination for you; a platformized `config` passes its `destination` through verbatim.

Because a failed import comes back on a `200` (the failure is in the job status, not an HTTP error), the optional `jobApplicationErrorMap` (in the `config`) lets you map the job's application-error codes to messages. You supply only the code → message map; the import resolves it through the error handler internally.


```tsx
import { ImportButton } from '@wix/patterns';
```

---

### Examples

### CMS import

A Wix Data (CMS) collection. `` needs no config — it reads the import inputs live from the table’s schema source, and the CMS create/edit-field modal is loaded on demand.

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import { CustomColumns, ImportButton, Table } from '@wix/patterns';
import { useTableCollection } from '@wix/patterns/schema';
import { useCmsSchemaSource } from '@wix/patterns-cms';

type CmsItem = {
  _id: string;
  name: string;
  price: string;
};

function CmsImport() {
  // The CMS (Wix Data) schema source: `useTableCollection` derives the query and
  // identity from it and attaches its fields + field management to the table, so
  // `<ImportButton />` reads everything it needs off the table when it opens — no
  // config. (In these docs `useCmsSchemaSource` is backed by in-memory data so it
  // runs with no backend.)
  const source = useCmsSchemaSource<CmsItem>({ collectionId: 'Products' });
  const state = useTableCollection<CmsItem>(source);

  return (
    <CollectionPage>
      <CollectionPage.Header title={{ text: 'Products' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          horizontalScroll
          customColumns={<CustomColumns />}
          columns={[]}
          importButton={<ImportButton />}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}

export default CmsImport;
```

---

### Platformized import (fields inline)

A non–Wix-Data destination (here the stores catalog) with no declared schema: pass a `config` naming the `destination` and listing the `fields` to map onto.

```tsx
import { Page } from '@wix/design-system';
import React from 'react';
import {
  ImportButton,
  PageWrapper,
  Table,
  ToolbarTitle,
  useTableCollection,
  type ImportConfig,
} from '@wix/patterns';
import { queryStoreVariants } from '@wix/ambassador-stores-catalog-v1-product/http';
import { StoreVariant } from '@wix/ambassador-stores-catalog-v1-product/types';
import { useHttpClient } from '@wix/yoshi-flow-bm';

function PlatformizedImport() {
  const httpClient = useHttpClient();

  const state = useTableCollection<StoreVariant>({
    queryName: 'ImportButton-platformized',
    fetchData: (query) => {
      const { limit } = query;
      return httpClient
        .request(queryStoreVariants({ query: { cursorPaging: { limit } } }))
        .then(({ data }) => ({
          items: data.variants ?? [],
          total: data.metadata?.total,
        }));
    },
    itemKey: (variant) => variant.id ?? '',
    itemName: (variant) => variant.productName ?? '',
    fetchErrorMessage: () => 'Error fetching products',
  });

  // A platformized (non–Wix-Data) destination — here the stores catalog — has no
  // declared schema, so the `config` names the write target and lists the fields
  // to map onto (plus any live fields the table's source exposes).
  const importConfig: ImportConfig = {
    destination: { storesCatalogProducts: {} },
    fields: [
      { id: 'name', header: 'Name' },
      { id: 'sku', header: 'SKU' },
    ],
  };

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Products" />
        <Page.Content>
          <Table
            state={state}
            horizontalScroll
            title={<ToolbarTitle title="Products" showTotal />}
            columns={[
              { title: 'Name', render: (variant) => variant.productName },
            ]}
            importButton={<ImportButton config={importConfig} />}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}

export default PlatformizedImport;
```

---

### Platformized import (from a source)

A non–Wix-Data destination whose fields come from a source (`usePlatformizedSource`) instead of an inline list — like the CMS case, but you still name the `destination` in `config`.

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import { CustomColumns, ImportButton, Table } from '@wix/patterns';
import { usePlatformizedSource, useTableCollection } from '@wix/patterns/schema';

type Product = {
  id: string;
  name: string;
  sku: string;
};

// A platformized entity driven by a source (`usePlatformizedSource`). Like the
// CMS case, the source supplies the columns and the fields to map onto — you
// don't hand-list `fields`. Unlike CMS you still name the `destination`: a
// platformized target has no Wix Data collection to derive it from.
// (In these docs `usePlatformizedSource` is backed by in-memory data so it runs
// with no backend.)
function PlatformizedSourceImport() {
  const source = usePlatformizedSource<Product>({
    fqdn: 'stores.catalog.product',
    backend: { find: async () => ({ items: [], total: 0 }) },
    itemKey: (product) => product.id,
    itemName: (product) => product.name,
  });
  const state = useTableCollection<Product>(source);

  return (
    <CollectionPage>
      <CollectionPage.Header title={{ text: 'Products' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          horizontalScroll
          customColumns={<CustomColumns />}
          columns={[]}
          importButton={
            <ImportButton
              config={{ destination: { storesCatalogProducts: {} } }}
            />
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}

export default PlatformizedSourceImport;
```

---

### Required fields

Mark fields with `required: true`. The import is blocked while any required field is still unmapped, with a footnote listing the missing ones. (Shown with a platformized `config`.)

```tsx
import { Page } from '@wix/design-system';
import React from 'react';
import {
  ImportButton,
  PageWrapper,
  Table,
  ToolbarTitle,
  useTableCollection,
  type ImportConfig,
} from '@wix/patterns';
import { queryStoreVariants } from '@wix/ambassador-stores-catalog-v1-product/http';
import { StoreVariant } from '@wix/ambassador-stores-catalog-v1-product/types';
import { useHttpClient } from '@wix/yoshi-flow-bm';

function RequiredFields() {
  const httpClient = useHttpClient();

  const state = useTableCollection<StoreVariant>({
    queryName: 'ImportButton-required',
    fetchData: (query) => {
      const { limit } = query;
      return httpClient
        .request(queryStoreVariants({ query: { cursorPaging: { limit } } }))
        .then(({ data }) => ({
          items: data.variants ?? [],
          total: data.metadata?.total,
        }));
    },
    itemKey: (variant) => variant.id ?? '',
    itemName: (variant) => variant.productName ?? '',
    fetchErrorMessage: () => 'Error fetching products',
  });

  // Mark fields `required: true`. The import is blocked while any required field
  // is still unmapped, with a footnote listing the missing ones.
  const importConfig: ImportConfig = {
    destination: { storesCatalogProducts: {} },
    fields: [
      { id: 'name', header: 'Name', required: true },
      { id: 'price', header: 'Price', required: true },
      { id: 'sku', header: 'SKU' },
    ],
  };

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Products" />
        <Page.Content>
          <Table
            state={state}
            horizontalScroll
            title={<ToolbarTitle title="Products" showTotal />}
            columns={[
              { title: 'Name', render: (variant) => variant.productName },
            ]}
            importButton={<ImportButton config={importConfig} />}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}

export default RequiredFields;
```

---

### Create new field

A CMS capability: when the collection’s schema permits adding fields, unmapped CSV columns default to creating a new field (with an "Edit field" affordance) instead of being skipped. `` wires it from the schema automatically.

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import { CustomColumns, ImportButton, Table } from '@wix/patterns';
import { useTableCollection } from '@wix/patterns/schema';
import { useCmsSchemaSource } from '@wix/patterns-cms';

type CmsItem = {
  _id: string;
  name: string;
  price: string;
};

// "Create new field" is a CMS (Wix Data) capability, wired automatically: when
// the collection's schema permits adding fields (`schemaPermissions.createField`),
// CSV columns that don't match an existing field default to creating a new field
// (with an "Edit field" affordance) instead of being skipped. It's read off the
// table's schema source, so `<ImportButton />` takes no config.
function CreateNewField() {
  // (In these docs `useCmsSchemaSource` is backed by in-memory data — with
  // add-field allowed — so it runs with no backend.)
  const source = useCmsSchemaSource<CmsItem>({ collectionId: 'Products' });
  const state = useTableCollection<CmsItem>(source);

  return (
    <CollectionPage>
      <CollectionPage.Header title={{ text: 'Products' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          horizontalScroll
          customColumns={<CustomColumns />}
          columns={[]}
          importButton={<ImportButton />}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}

export default CreateNewField;
```

---

### Update or skip by ID

A CMS behavior: when a CSV column maps to the collection’s id field (`_id`), that row floats to the top and the modal offers an "update or skip" write policy for rows that collide on ID.

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import { CustomColumns, ImportButton, Table } from '@wix/patterns';
import { useTableCollection } from '@wix/patterns/schema';
import { useCmsSchemaSource } from '@wix/patterns-cms';

type CmsItem = {
  _id: string;
  name: string;
  price: string;
};

// Update-or-skip-by-ID is a CMS (Wix Data) behavior. When a CSV column maps to
// the collection's id field (`_id`), that row floats to the top and the modal
// offers an "update or skip" write policy for rows that collide on ID. The id
// field comes from the schema source, so it's mappable and the import derives it
// — `<ImportButton />` takes no config.
function IdCollisionWritePolicy() {
  // (In these docs `useCmsSchemaSource` is backed by in-memory data — including
  // the `_id` field — so it runs with no backend.)
  const source = useCmsSchemaSource<CmsItem>({ collectionId: 'Products' });
  const state = useTableCollection<CmsItem>(source);

  return (
    <CollectionPage>
      <CollectionPage.Header title={{ text: 'Products' }} />
      <CollectionPage.Content>
        <Table
          state={state}
          horizontalScroll
          customColumns={<CustomColumns />}
          columns={[]}
          importButton={<ImportButton />}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}

export default IdCollisionWritePolicy;
```

---

### Backup & error handling

Pass a `backup` to snapshot the collection before importing (undone on cancel) and a `jobApplicationErrorMap` to map a failed job’s application-error codes to messages. (Shown with a platformized `config`.)

```tsx
import { Page } from '@wix/design-system';
import React from 'react';
import {
  ImportButton,
  PageWrapper,
  Table,
  ToolbarTitle,
  useTableCollection,
  type ImportConfig,
} from '@wix/patterns';
import { queryStoreVariants } from '@wix/ambassador-stores-catalog-v1-product/http';
import { StoreVariant } from '@wix/ambassador-stores-catalog-v1-product/types';
import { useHttpClient } from '@wix/yoshi-flow-bm';

function BackupAndErrorHandling() {
  const httpClient = useHttpClient();

  const state = useTableCollection<StoreVariant>({
    queryName: 'ImportButton-backup',
    fetchData: (query) => {
      const { limit } = query;
      return httpClient
        .request(queryStoreVariants({ query: { cursorPaging: { limit } } }))
        .then(({ data }) => ({
          items: data.variants ?? [],
          total: data.metadata?.total,
        }));
    },
    itemKey: (variant) => variant.id ?? '',
    itemName: (variant) => variant.productName ?? '',
    fetchErrorMessage: () => 'Error fetching products',
  });

  const importConfig: ImportConfig = {
    destination: { storesCatalogProducts: {} },
    fields: [{ id: 'name', header: 'Name', required: true }],

    // Optional: snapshot the collection before importing. The returned `restore`
    // runs only if the user cancels mid-import (a job failure leaves partial
    // rows and offers "Try again" instead).
    backup: async () => {
      // ...take a snapshot...
      return {
        restore: async () => {
          // ...restore the snapshot...
        },
      };
    },

    // Optional: map the failed job's application-error codes to messages. You
    // supply only the code → message map; the import resolves it through the
    // error handler internally.
    jobApplicationErrorMap: {
      MISSING_REQUIRED_FIELDS: ({ data }) => ({
        message: `Missing required fields: ${data?.missingFields?.join(', ')}`,
      }),
    },
  };

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Products" />
        <Page.Content>
          <Table
            state={state}
            horizontalScroll
            title={<ToolbarTitle title="Products" showTotal />}
            columns={[
              { title: 'Name', render: (variant) => variant.productName },
            ]}
            importButton={<ImportButton config={importConfig} />}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}

export default BackupAndErrorHandling;
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `config` | `ImportConfig` | No | - | The import inputs, as a plain object. Omit it entirely to import into the table's own collection (Wix Data) — the collection, its fields, and create-new-field are read from the table's source. Set `destination` (and any `fields`) to write to a platformized target instead. |
| `renderFieldEditor` | `RenderImportFieldEditor` | No | - | Overrides the create/edit-field modal (see {@link RenderImportFieldEditor}). By default the editor is chosen from the destination (CMS vs DataExtension) and loaded on demand, so this need not be passed. |
| `toolbarType` | `"bulkActionToolbar" \| "default"` | No | - | Which button variant to render. `'default'` (the default) shows a `ResponsiveButton`; `'bulkActionToolbar'` shows a compact secondary `Button`. |

