> ## Documentation Index
> Fetch the complete documentation index at: https://docs.folksbase.joselito.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Exports

> How to export contacts as CSV — with optional tag filtering and streaming for large datasets.

## Overview

Exports let you download your contacts as a CSV file. You can export all contacts or filter by tags. The export runs as a background job so it doesn't block the UI, and uses streaming to handle large datasets without running out of memory.

## Starting an Export

Create an export via the UI or the API:

```json theme={"dark"}
// POST /api/exports
{
  "tagIds": ["uuid-1", "uuid-2"],
  "filename": "vip-contacts"
}
```

* `tagIds` is optional — omit it to export all contacts
* `filename` is optional — a default name with a timestamp is generated if not provided
* The `.csv` extension is appended automatically if missing

The API creates an export record with status `pending` and fires an Inngest background job to handle the actual processing.

## How the Export Job Works

The export job (`process-export`) runs through these steps:

1. **resolve-tag-filter** — if tag IDs were provided, fetches the matching contact IDs. If zero contacts match, short-circuits with an empty CSV.

2. **resolve-export-metadata** — resolves custom field keys (via `SELECT DISTINCT jsonb_object_keys`) and total row count in parallel. This avoids a two-pass scan over the data.

3. **stream-export** — fetches contacts in cursor-based batches of 1,000 and pipes them through a streaming CSV generator directly to Vercel Blob. Only one batch is in memory at a time.

4. **send-notification** — sends an email notification if the workspace has notifications enabled.

Each step is wrapped in `step.run()` for retry isolation — if the blob upload fails, only that step retries.

## CSV Format

The exported CSV includes these columns:

| Column            | Source                                                              |
| ----------------- | ------------------------------------------------------------------- |
| `email`           | Contact email                                                       |
| `first_name`      | Contact first name                                                  |
| `last_name`       | Contact last name                                                   |
| `phone`           | Contact phone                                                       |
| `company`         | Contact company                                                     |
| `notes`           | Contact notes                                                       |
| `is_unsubscribed` | `true` or `false`                                                   |
| `created_at`      | ISO 8601 timestamp                                                  |
| *custom fields*   | One column per unique custom field key across all exported contacts |

Custom field columns are discovered dynamically — the system queries for all distinct `jsonb_object_keys` across the exported contacts and adds them as additional columns.

## Downloading

Once the export completes, the CSV is stored in Vercel Blob. The download endpoint streams the blob directly to the client without buffering the entire file in memory:

```
GET /api/exports/:id/download
```

The response includes a `Content-Disposition` header with the sanitized filename.

## Tracking Status

The frontend polls the export status endpoint to show progress. Export records include:

* `status` — `pending`, `processing`, `completed`, or `failed`
* `total_rows` / `processed_rows` — for progress calculation
* `error_message` — populated if the export fails

## Error Handling

| Scenario                         | Behavior                                        |
| -------------------------------- | ----------------------------------------------- |
| Tag filter matches zero contacts | Empty CSV generated, export marked as completed |
| Blob upload fails                | Step retries (up to 2 retries)                  |
| Entire job fails                 | Export marked as `failed` with error message    |
| User email not found             | Notification email skipped silently             |
