← All posts

3 min read

Your data leaves with you

Site export and import bundles, the account data export, a nightly warehouse push, and why no-lock-in is a design constraint rather than a slogan.

When I introduced Tracely back in April, one of the three principles was "composable, not a walled garden: if you ever want to leave, your data comes with you." That's easy to write and easy to fake — plenty of products call a CSV of top pages an "export." So here's what the exit doors actually look like in Tracely today.

The site bundle

The unit of portability is a site. Two commands move one:

php artisan tracely:export-site {ulid} --path=...
php artisan tracely:import-site {path} --into={ulid} --fresh

Export writes a directory containing a manifest.json, small JSON files for configuration (goals, funnels, annotations, content groups, alert webhooks), and JSONL streams for the raw analytics: pageviews, events, performance metrics, and heatmap buckets. Import reads that directory into another site — on the same install or a completely different one. No HTTP between the two machines, so it works for air-gapped moves too.

The manifest carries a schema_version that import validates, plus a field dictionary documenting every export column with its type and description — so if you're loading pageviews into a warehouse instead of another Tracely, you don't have to reverse-engineer what visitor_id means (an opaque identifier, not PII, for what it's worth).

Import requires the --fresh flag explicitly, which wipes the target site's analytics and configuration first. That's deliberate friction: without it, re-running an import would silently duplicate every stream, and I'd rather make you type six characters than make you deduplicate a million rows.

If you don't want to touch a terminal, site settings has an "Email me an export" button that builds the same bundle in a queued job, zips it, and emails a time-limited download link.

The same data, three transports

The bundle isn't a special code path — the same export and import logic backs three transports. The HTTP export API at /api/v1/export/* streams the identical payload with a site Bearer token; tracely:sync-from-remote pulls those endpoints from a remote Tracely into a local site on a schedule (I use this myself to mirror a production site into my dev environment); and the file commands above write it to disk. One logical format, whichever pipe suits you.

There's also a fourth, push-shaped variant: when warehouse push is enabled (TRACELY_WAREHOUSE_PUSH_ENABLED), a nightly tracely:warehouse-push uploads a gzipped NDJSON slice of each configured site's pageviews to an S3-compatible disk, laid out as {prefix}/{site_ulid}/date=YYYY-MM-DD/pageviews.ndjson.gz — a partition scheme that Athena, DuckDB, or Spark will read without ceremony. A --date option backfills a specific day. If your analytics belongs in your warehouse next to everything else, it shouldn't need a middleware vendor to get there.

The account export

Separate from analytics, you can download an account JSON export: your profile, organizations, teams, and metadata for the sites you can access. Deliberately no visitor analytics in that one — it answers "what does Tracely know about me and my account structure," which is a different question from "give me my traffic data," and mixing them would make both worse.

What's honestly not covered

A no-lock-in claim needs its boundaries stated. There is no first-party tool that exports the entire application database — users, billing, permissions, sessions — and replays it into another install. For whole-server moves, that job belongs to mysqldump or pg_dump and a migration run, and pretending an app-level tool should own it would just be a worse backup system. The in-app tools cover the thing that's actually yours in a meaningful sense: the analytics and configuration of your sites.

Why bother

Nobody evaluates an analytics tool by its exit doors, and almost nobody uses them. But their existence changes the relationship: you're staying because it works, not because leaving is expensive. And the discipline compounds — a real export format forces a documented schema, versioning, and a second consumer of every table, which makes the whole system sturdier for the people who never export a byte. It's the same reasoning as the privacy posture: constraints you adopt on purpose tend to pay for themselves.

product data privacy

Try Tracely on your site

One snippet, cookieless, first-party. See your traffic in minutes without handing it to an ad network.

Start free

Get new posts by email

Short product notes twice a week plus the occasional deep dive. No tracking pixels in the emails, and you can unsubscribe anytime.

Your address is used only to send new posts. No open tracking.