← All docs
Customer data feeds (multi-tenant)
Problem: you're not exporting for one customer — you're offering "data feeds" as a product feature to every enterprise customer, each with their own bucket, their own schedule, their own format preference.
When to use DataEgress for this
- Multiple tenants, each needing independent destinations and schedules against the same dataset definition.
- You want your own dashboard (or your customers') to show per-tenant run history, not just "did the export cron job run today."
- You want to add this as a sellable feature without your team owning a queue, a cron scheduler, and a multipart-S3-upload implementation.
When NOT to use DataEgress for this
- You have exactly one customer who wants exactly one export, once. That's a script, not a feed — see export-millions-of-rows.md if it's still large, or just query and download it directly if it's small.
Architecture
One defineDataset call per logical dataset (e.g. "transactions",
"events"), reused across every tenant. Tenant isolation happens inside
your fetchPage — DataEgress passes tenantId through on every call and
never mixes data across tenants because it never aggregates across them
itself.
dataset "events" (one fetchPage, shared)
├─ tenant "acme" -> destination "acme-s3" -> schedule "0 2 * * *"
├─ tenant "globex" -> destination "globex-gcs?" -> not supported yet (S3/R2 only, see limitations.md)
└─ tenant "initech" -> destination "initech-url" -> destination type "signed_url", no bucket needed
Full implementation
// One dataset definition, reused by every tenant
const events = defineDataset({
id: "events",
fetchPage: async ({ tenantId, cursor, limit }) => {
// tenantId scoping happens here — the only place it needs to happen
return db.event.findManyPaginated({ tenantId, cursor, limit });
},
});
export const POST = events.handler();
// Per-tenant setup, e.g. triggered from your own "Enable data feed" UI
async function enableDataFeed(tenantId: string, bucketConfig: S3Config) {
await dataEgress.createDestination({
tenantId,
id: `${tenantId}-s3`,
type: "s3",
s3: bucketConfig,
});
await dataEgress.createSchedule({
tenantId,
dataset: "events",
destination: `${tenantId}-s3`,
format: "parquet",
cron: "0 3 * * *",
});
}
To show a tenant their own run history, query GET /api/exports and filter
by tenantId client-side (there's no server-side ?tenantId= filter yet —
see limitations.md).
Common errors
- Reusing a destination
idacross tenants — destination ids must be unique; namespace them by tenant (${tenantId}-s3) as shown above. - Forgetting a tenant has no destination yet —
runExport/createSchedulereturndestination_not_foundwith the missing id named explicitly.