---
url: https://simpledatalogs.com/docs/guide/exports.md
description: >-
  Download events and log lines as gzipped CSV or JSON lines, and on Scale write
  them every hour or day to your own S3, R2, B2 or MinIO bucket.
---

# Exports

Take your raw rows with you: download them, or have them written to your own bucket on a schedule.

| | Free | Starter | Pro | Scale |
|---|---|---|---|---|
| CSV and JSON lines downloads | — | ✓ | ✓ | ✓ |
| Scheduled exports to your bucket | — | — | — | ✓ |

Owners and members export on an environment's **Exports** tab. Viewers can't: exports move data out. Only owners set up a bucket.

## Downloads

Click **New export**, then pick:

* **Format:** CSV, or JSON lines (one JSON object per line, also called NDJSON).
* **Rows:** everything, log lines only, events only, or one event name.
* **From and To:** days in your time zone. Days past your plan's retention are already gone.
* **Level, deployment and message text**, as on the Logs page.

The **Export** button on the Logs page and on an event's page opens the same dialog with that page's filters.

The file is built in the background. We email you when it's ready, and the list shows **Ready** with a **Download** button. Files are gzipped (`.csv.gz`, `.ndjson.gz`) and kept for 3 days. Each download link works for 5 minutes, so it can't be passed around for long.

Every export is logged with who asked, the filters, the row count and each download.

## Columns

Both formats have the same columns, in this order. Times are UTC, ISO 8601 with milliseconds.

| Column | What it holds |
|---|---|
| `event_id` | The row's id; a re-sent row keeps it, so it's safe to dedupe on |
| `ts` | When it happened, as the sender said |
| `received_at` | When we received it |
| `name` | Event name; empty for a log line |
| `level` | Log level, or empty |
| `message` | The log message, or empty |
| `device` | The device or visitor id |
| `user_id` | The signed-in user, if sent |
| `country` | Two-letter country, from the sender's address |
| `deployment` | The deployment whose key sent it |
| `props` | Properties: a JSON object in JSON lines, JSON text in CSV |

In CSV, a cell that starts with `=`, `+`, `-` or `@` gets a leading `'`, so a spreadsheet never runs it as a formula. Use JSON lines when you need values byte for byte.

## Scheduled exports (Scale)

An owner clicks **Set up** under **Scheduled to your bucket** and fills in:

* **Endpoint:** your storage's `https://` URL, host only.
* **Bucket, region** and an optional **folder** (a key prefix).
* **Access key ID and secret key.** The secret is stored encrypted and never shown again; leave it blank when editing to keep it.
* **Every:** hour or day. **Format:** JSON lines or CSV.
* **Path-style URLs:** on for MinIO and some R2 or B2 setups.

**Test connection** writes a small file and deletes it, so you know the keys can write before you save.

### How files are cut

* Each file covers one hour, or one UTC day, of **when we received** the rows. Every row is in exactly one file, even when its `ts` is days old.
* A window is written an hour after it closes, so rows still on their way make it in.
* Copies of a re-sent row are written once when both arrive before their window is written. A later copy can land in a second file, so dedupe on `event_id`.
* Files land at `<folder>/<environment id>/<yyyy>/<mm>/<dd>/<start>-<end>-0001.ndjson.gz`, e.g. `sdl/0192…/2026/10/19/20261019T0000Z-20261020T0000Z-0001.ndjson.gz`. A window over 1,000,000 rows is split into `-0002`, `-0003` and so on.
* An empty window writes no file.
* The first file starts with the hour or day you set it up in.

### When something fails

The next run starts where the last good one ended, so nothing is skipped and a retried window overwrites the same files. The status shows **Failing** with the last error, and after 3 failures in a row the owners get one email. **Pause** stops the runs; **Resume** catches up from where it stopped, up to 48 windows per run.

Scheduled exports pause while the organization is over its monthly quota or off Scale, and pick up again from the same point.

A row that reaches us more than an hour after it was received (only during an outage on our side) is stored but not added to a file that's already written. Its `event_id` lets you find it in a download.

## Bucket setup

### AWS S3

1. Create a bucket, e.g. `acme-simpledatalogs` in `us-east-1`.
2. Create an IAM user with this policy, then an access key for it:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::acme-simpledatalogs/sdl/*"
    }
  ]
}
```

3. Endpoint `https://s3.us-east-1.amazonaws.com`, region `us-east-1`, folder `sdl`, path-style off.

`DeleteObject` is only used by **Test connection**, on its own test file.

### Cloudflare R2

1. Create a bucket, then an **R2 API token** with **Object Read & Write** on that bucket.
2. Endpoint `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`, region `auto`, path-style on.

### Backblaze B2

1. Create a private bucket and an application key with write access to it.
2. Endpoint `https://s3.<region>.backblazeb2.com` (e.g. `us-west-004`), the same region, path-style on.

### MinIO

1. Create a bucket and an access key whose policy allows `s3:PutObject` and `s3:DeleteObject` on it.
2. Endpoint: your server's public `https://` URL, any region (e.g. `us-east-1`), path-style on.

The endpoint must resolve only to public addresses; private, loopback and link-local addresses are refused, checked again before every run.

## Limits

| Limit | Value |
|---|---|
| Rows per download | 5,000,000 (more is refused; pick fewer days or add a filter) |
| Uncompressed size per download | 2 GB |
| Downloads queued or running per organization | 2 |
| Download files kept | 3 days |
| Download link lifetime | 5 minutes |
| Scheduled file part | 1,000,000 rows |
| Scheduled windows per run | 48 |
| Wait after a window closes | 1 hour |
