> ## Documentation Index
> Fetch the complete documentation index at: https://help.csvbox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Hasura

> Push uploaded spreadsheet data straight into a table exposed by your Hasura GraphQL API.

The Hasura destination inserts uploaded spreadsheet data from CSVbox into a table exposed by your Hasura GraphQL API. Rows are validated, batched, and sent as GraphQL mutations, with optional upsert support.

## Before you start

* A Hasura project with a GraphQL endpoint URL, for example `https://your-project.hasura.app/v1/graphql`
* The project's admin secret
* A table already tracked in Hasura, with insert permissions available to the admin role
* An endpoint reachable from the public internet

<Note>
  A sheet can have only one destination at a time. Selecting Hasura replaces whatever the sheet was writing to before.
</Note>

## Setup

In your **Sheet Settings**, open the **Destination** tab and choose **Hasura**. Then work through the fields in order. The column mapping unlocks only after the connection test passes.

<Steps>
  <Step title="Enter your GraphQL endpoint">
    Paste the full endpoint URL, including the `/v1/graphql` path.

    ```text theme={null}
       https://your-project.hasura.app/v1/graphql
    ```

    You'll find this on the Hasura Cloud project page, or it's the endpoint your own instance serves.
  </Step>

  <Step title="Enter your admin secret">
    Provide the value of your project's `x-hasura-admin-secret`. In Hasura Cloud, it lives under **Project** → **Env Vars** → `HASURA_GRAPHQL_ADMIN_SECRET`.

    <Note>
      CSVbox stores credentials in encrypted form and uses them only to insert rows into the table you specify.
    </Note>
  </Step>

  <Step title="Set the target table">
    Enter the table you want rows inserted into, for example `users`. Use the name exactly as Hasura exposes it in your GraphQL schema.

    If the table is tracked under a non-`public` schema or carries a custom GraphQL name, use that name: the one that appears as `insert_<name>` in your API.
  </Step>

  <Step title="Test the connection">
    Click **Test Connection**. CSVbox introspects your schema to confirm that the endpoint, admin secret, and table all check out, and loads the table's column list for mapping.

    A green check means everything is ready. If it fails, see [Troubleshooting](#troubleshooting).
  </Step>

  <Step title="Map columns to table fields">
    Click **Map Columns**. For each column in your importer, including any virtual columns, pick the Hasura column it should be written to. Each target's data type is shown next to its name, so you can confirm the destination is the one you meant.

    Leave a row set to `(empty)` to skip that column; it isn't sent.
  </Step>

  <Step title="Map custom attributes (optional)">
    In the same window, the **Custom Attributes** table writes values you pass into the importer at runtime into columns on the target table. This is how every imported row gets tagged with the tenant, account, or user it belongs to.

    `user_id` is available by default, and you can define up to five attributes in total. Values come from the user object you set on the importer:

    ```javascript theme={null}
       importer.setUser({
         user_id: "1234",
         team_id: "acme-42"
       });
    ```

    Map `team_id` to a `team_id` column, and every row from that import carries it.
  </Step>

  <Step title="Save">
    Save the mapping, then save the sheet. The next file submitted through this importer writes to Hasura.
  </Step>
</Steps>

## Insert vs. upsert

By default, every import performs a plain insert: each row becomes a new row in the table. To upsert instead, updating existing rows rather than duplicating them, fill in the two optional fields:

| Field | Description |
| - | - |
| **Upsert Constraint** | The Postgres unique constraint to match on, e.g. `users_email_key`. Constraint names are listed in the Hasura Console under **Data** → your table → **Modify**. |
| **Update Columns on Conflict** | Comma-separated columns to overwrite when a matching row is found, e.g. `name, email, updated_at`. |

Leaving **Upsert Constraint** blank disables upsert entirely; the update-columns value is then ignored.

<Warning>
  Be careful with large files. Plain inserts are not idempotent: if an import is retried after a partial failure, rows already written by a completed batch can be inserted a second time. An upsert on a unique constraint makes retries safe, which is why it's the better choice for big files, not just for deduplication.
</Warning>

## Data flow

Once setup is complete, every submitted file is processed without further intervention.

| Step | Description |
| - | - |
| **01 · Validate** | Your sheet's rules and transforms run on every row. |
| **02 · Batch** | Rows are grouped 500 at a time. |
| **03 · Mutate** | Each batch goes out as one `insert_<table>` mutation using GraphQL variables. |
| **04 · Report** | Progress streams back to the widget; `affected_rows` becomes the imported count. |

Each import ends in one of three states, visible on the **Imports** page in your dashboard:

| Status | Meaning |
| - | - |
| **Success** | Every batch was accepted by Hasura. |
| **Partial** | Some batches succeeded and others were rejected. The failing errors are recorded against the import. |
| **Failed** | No rows were written. |

## How values are converted

Spreadsheet cells are text. Before sending a batch, CSVbox converts each value to the JSON type your column expects, based on the schema read during **Test Connection**.

| Hasura column type | Sent as |
| - | - |
| `Int`, `smallint`, `bigint` | Number (integer) |
| `Float`, `numeric`, `money`, `float4`, `float8` | Number (decimal) |
| `Boolean` | `true` for `true`, `1`, `yes`, `on`; `false` for `false`, `0`, `no`, `off` |
| `timestamptz`, `timestamp`, `date`, `time`, `interval`, `uuid`, `json`, `jsonb`, `citext`, and others | A string, for Postgres to parse |
| Any type, empty cell | `null` |

<Tip>
  Because date, timestamp, UUID, and JSON values pass through as strings, the values in your file need to be in a form Postgres accepts, for instance `2026-03-14` or `2026-03-14T09:30:00Z` for timestamps. Use validation rules and data transform functions to normalize them before they're sent.
</Tip>

## Environment variables

The endpoint, admin secret, table name, and upsert settings all accept environment variables. Wrap the variable name in double curly braces, and CSVbox substitutes it at import time:

```text theme={null}
https://{{tenant}}.hasura.app/v1/graphql
```

Pass the value when you create the importer:

```javascript theme={null}
let importer = new CSVBoxImporter("YOUR_LICENSE_KEY_HERE", {}, callback, {
  lazy: true,
  environment: {
    tenant: "acme"
  }
});
```

One importer can then write to a different Hasura project, table, or upsert constraint per customer or per environment.

## Troubleshooting

| Message | What to check |
| - | - |
| **Invalid Admin Secret** | The secret doesn't match your project's `HASURA_GRAPHQL_ADMIN_SECRET`. Re-copy it, watching for trailing spaces. |
| **Could not reach the Hasura GraphQL Endpoint** | The URL is wrong, missing `/v1/graphql`, or the instance isn't publicly reachable. A self-hosted Hasura behind a VPN or firewall must allow inbound requests from CSVbox. |
| **Table not found in Hasura schema** | The table isn't tracked, the name is misspelled, or it's exposed under a different GraphQL name. Confirm `insert_<table>` appears in your Hasura API explorer. |
| **One or more mapped columns were not found on the table** | The schema changed after you mapped columns. Run **Test Connection** to reload the column list, then re-map. |
| **Constraint does not exist** | The upsert constraint name doesn't match a unique constraint on the table. Use the constraint name, not the column name. |

## Notes & limits

* Only insertable scalar columns appear in the mapping list. Columns backed by Postgres enums, arrays, or Hasura relationships aren't offered as targets, and nested or related inserts aren't supported.
* Authentication uses the admin secret only. JWT and webhook auth modes aren't supported for this destination.
* Column names are case-sensitive and must match your Hasura schema exactly.
* Generated columns and columns with database defaults can be left unmapped; Hasura and Postgres fill them in.
* Hasura can return errors inside an otherwise successful HTTP response. CSVbox inspects every response body, so a rejected batch is always reported as an import error rather than counted as a silent success.
* This destination composes with the rest of CSVbox: validation rules, virtual columns, data transform functions, and AI transforms all apply before rows are sent.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.