← All docs

S3 / R2 destinations

A destination with "type": "s3" delivers directly to a bucket your tenant owns. DataEgress supports real AWS S3 and any S3-compatible provider (Cloudflare R2, MinIO, etc.) via an optional endpoint override.

Creating an S3 destination

await dataEgress.createDestination({
  tenantId: "acme",
  id: "acme-s3",
  type: "s3",
  s3: {
    bucket: "acme-data-lake",
    region: "us-east-1",     // use "auto" for Cloudflare R2
    accessKeyId: "...",
    secretAccessKey: "...",
    prefix: "dataegress/",    // optional
    endpoint: undefined,     // set this for R2 (see below) — omit for real AWS S3
  },
});

Credentials are encrypted at rest (AES-256-GCM) and never echoed back by the API — GET/POST responses always mask secretAccessKey.

Using Cloudflare R2 instead of AWS S3

R2 speaks the S3 API. Set region: "auto" and endpoint to your account's R2 endpoint:

s3: {
  bucket: "acme-data-lake",
  region: "auto",
  endpoint: "https://<account_id>.r2.cloudflarestorage.com",
  accessKeyId: "...",
  secretAccessKey: "...",
}

Multipart uploads

Files are uploaded via @aws-sdk/lib-storage's Upload class, which automatically switches to multipart above a configured part size (8MB here). This is a deliberate correctness fix, not a nice-to-have: a naive single-PUT upload silently fails past S3's 5GB single-object limit — see GitLab #430277 for a real-world example of exactly this bug shipping to production elsewhere.

Verifying against a real bucket

pnpm verify:s3 (run from apps/web) exercises the full destination path — create destination, run export, poll to completion — against a bucket you control. It never prints secret values, only non-sensitive config (bucket, region, prefix) and the resulting manifest.

Add these to apps/web/.env.local (never commit this file — it's gitignored):

DATARELAY_VERIFY_S3_BUCKET=your-test-bucket
DATARELAY_VERIFY_S3_REGION=us-east-1        # "auto" for R2
DATARELAY_VERIFY_S3_ACCESS_KEY_ID=...
DATARELAY_VERIFY_S3_SECRET_ACCESS_KEY=...
DATARELAY_VERIFY_S3_PREFIX=dataegress-test/  # optional
DATARELAY_VERIFY_S3_ENDPOINT=               # only for R2/S3-compatible; omit for AWS S3

Then, with the dev server and Inngest dev server both running:

pnpm verify:s3

Use a dedicated test bucket (or a disposable prefix within one) — the script writes a real object at exports/acme/events/<run-id>.csv.

Not supported yet

See limitations.md — no GCS, no Azure Blob, no SFTP, no warehouse-native destinations (Snowflake/BigQuery/Redshift/Databricks) in V1. GCS is the most likely near-term addition since it shares most of the S3 destination's code path.