Skip to content

Import & Export

TravStats keeps your data portable on purpose — list imports to seed a fresh instance, a spreadsheet round trip for mass edits, and a Postgres-level backup when you want a complete snapshot.

Settings → Import shows one tile per enabled domain. Everything here funnels through the same preview-and-commit pipeline: after picking a file you see every row with its validation badges, the questionable rows ordered first, a mapping for columns the import could not place — and only what you confirm is written.

TileSourceDomain
From Flightradar24unmodified my.flightradar24.com CSV exportFlights
From any logbook (CSV)any flat per-flight CSV — column-mapping wizardFlights
Spreadsheetthe workbook TravStats itself exportedEvery domain — see Spreadsheet
Hotel list (CSV)a flat per-stay CSVLodging
Google Maps saved places (beta)a Google Takeout listPlaces — see Places

A switch on the hub decides whether flights sharing a booking reference silently become a trip. Switched off, the booking reference is kept so Detect trips can group them later. Auto-created trips carry the date range of their flights.

Every import is a batch, and can be taken back

Section titled “Every import is a batch, and can be taken back”

Flights, cruises, stays, places and visits record where each row came from and which run created it. The import log below the tiles lists every run — file name, date, what it created — and a button reverts the whole run as one batch. Email imports are logged with a file name too, so several on the same day can be told apart.

Because rows remember their source, the same document read twice adds nothing the second time, and re-importing a file ends with a report — imported, partly imported, all duplicates — rather than in silence.

Drop the unmodified CSV. The column map for FR24’s schema is built in (it handles the leading blank line and the (IATA/ICAO) annotations). Times are converted server-side using the airport catalogue, no UTC maths on your side. Field mapping:

  • Flight reason → category (Business → business, Leisure → vacation)
  • Seat number, Note, Registration → the matching flight fields
  • Status defaults from the departure time vs. now (past → flown, future → scheduled)

Each row is tagged with its source so the provenance stays visible on the flight.

For non-FR24 CSVs a column-mapping wizard opens automatically. Each CSV column gets a dropdown for the matching TravStats field; known header names are suggested. Same timezone maths, same preview, same commit path. A mapping that produces no readable row keeps its continue button disabled rather than promising work it will not do.

The FlightDiary comparison has a column-by-column mapping for migrating in.

A flat CSV of stays. The preview names every row it considers questionable and orders those first; a chain the catalogue does not know is offered, never created behind your back; two names are offered as one house when the town agrees and they share an identifying word (“Hotel Meteora” beside “Hotel Restaurant Meteora”), and a hit is a guess you confirm, never a silent merge. Houses without coordinates are geocoded afterwards in the background, so a slow lookup never blocks the import. See Lodging.

Programmatic import: POST /api/v1/import/parse

Section titled “Programmatic import: POST /api/v1/import/parse”

Scripts can run the FR24 / generic-CSV parser → preview flow over the API without reimplementing the column map. Authenticate with a Personal Access Token — a read-scope token is enough, because the parser writes nothing.

Terminal window
curl -X POST https://travstats.example.com/api/v1/import/parse \
-H "Authorization: Bearer ts_pat_…" \
-H "Content-Type: application/json" \
-d '{"source":"fr24","csv":"<csv contents>"}'

Returns the same preview rows the browser shows. Commit them through POST /flights/batch (write scope). The request and response shapes are in the OpenAPI spec.

The whole logbook exports to one workbook — a sheet per table, column A the row’s id — and comes back through the hub in Add only, Merge or Replace contents mode. Flights (18 fields, airports as IATA codes), cruises and their stops, houses and their stays, places and their visits are all in it. The rules, including which sheets are export-only, are on Spreadsheet.

Settings → Backup → Export all data downloads everything you own from the running instance as one JSON file — twelve domains: flights, cruises, stays, places, trips, tags, achievements, your settings. It does not carry stored API keys (they are encrypted at rest, but nothing in an export needs them), instance-level admin settings, or other users’ data.

Personal exports are convenient; full Postgres dumps are durable. TravStats has automated backups built in.

Admin → Backups:

  • Schedule — cron expression (default: daily at 02:00 UTC)
  • Retention — how many backup files to keep
  • Location — /app/data/backups inside the container, on the data volume
  • Optional WebDAV sync — Nextcloud, HiDrive, or any WebDAV target

A backup is a gzipped pg_dump plus the upload directories — trip photos, place photos, lodging photos, profile pictures. Backups taken before 2.6.0 carried only three of the six upload directories and cannot be repaired after the fact; take a fresh one after upgrading.

Terminal window
docker exec travstats-db pg_dump -U flights flights | gzip > flights-$(date +%F).sql.gz

To restore into a fresh container:

Terminal window
# Wipe the existing public schema (DESTRUCTIVE — have a backup ready)
docker exec -i travstats-db psql -U flights -d flights -c \
"DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
# Replay the dump
gunzip -c flights-2026-09-01.sql.gz | docker exec -i travstats-db psql -U flights flights
# Restart the app to re-run migrations against the restored DB
docker compose -f docker-compose.prod.yml restart app

The app’s first boot after a restore re-applies any migrations the dump pre-dates. Idempotent. Full drill on Backups & Restore.

Same-version migration:

  1. pg_dump the source instance (above), and copy /app/data/uploads.
  2. Stand up the target instance with the same version.
  3. Restore the dump on the target via the steps above.

The JWT secret, encryption keys and instance settings are part of the dump and travel with the data.

Cross-version migration: restore the older dump into the older version first, upgrade that instance (which runs migrations against real data), and only then dump again to move hardware. Do not restore an old dump straight into a fresh newer instance and let the migrations run on top of it — the order matters.

The CHANGELOG flags any release that changes the export format.