Skip to content

Import & Export

TravStats keeps your data portable on purpose — bulk import to seed a fresh instance, round-trip CSV/XLSX for quick mass-edits in Excel, and a Postgres-level backup when you want a complete snapshot.

Settings → Import hosts three importer tiles that all funnel through the same preview-and-commit pipeline. After picking a file, you get a preview screen with per-row checkboxes and validation badges (duplicate, duration_mismatch, unresolvable_airport, date_invalid); only the rows you tick land in the database. Commits go through /flights/batch in 20-row chunks.

TileSourceWhen to use
From Flightradar24unmodified my.flightradar24.com CSV exportMigrating from FR24 (Silver/Gold or free tier)
From any logbook (CSV)any flat per-flight CSVOpenFlights, App in the Air, FlightAware “My Flights”, arbitrary spreadsheets
Re-import TravStats ExcelXLSX / CSV / JSON exported from TravStatsRound-tripping mass edits through Excel, restoring to another instance

Drop the unmodified my.flightradar24.com CSV. Hardcoded column map for FR24’s known schema (it handles the leading blank line and embedded (IATA/ICAO) annotations). Server-side timezone conversion via the existing airport lookup, no manual UTC math required. Field mapping:

  • Flight reasoncategory (Business → business, Leisure → vacation)
  • Seat numberseatNumber
  • Notenotes
  • RegistrationaircraftRegistration
  • Status auto-defaults from dep_utc vs. now (past → flown, future → scheduled)

Each row tagged dataSource: imported_fr24 so the import provenance is visible on the orange 📊 imported_fr24 badge in flight cards.

For non-FR24 CSVs, drop the file and a column-mapping wizard opens automatically. Each CSV column gets a dropdown to pick the matching TravStats field (Departure IATA, Departure Time, Arrival IATA, Status, Airline, Aircraft, …). Auto-suggestions for known field names cover the common cases. Same TZ math, same preview, same commit path. Tagged dataSource: imported_generic_csv.

Re-import TravStats Excel (round-trip, since v1.3.0)

Section titled “Re-import TravStats Excel (round-trip, since v1.3.0)”

Export the same flight list to .xlsx, edit in Excel, re-import the same file:

  • Rows with an id column are matched against existing flights and updated.
  • Rows without id are created as new flights.
  • Empty cells leave the existing value alone — they don’t blank fields.

Useful for one-off corrections like fixing a misspelled aircraft type on 200 flights or applying a tag to a year of trips. The export side (Settings → Data → Export) carries every editable field (43 columns) with real numeric and date cells, so the round-trip is loss-free across boarding-pass and email-import metadata. Tagged dataSource: imported_roundtrip.

CSV column layout (round-trip + manual import)

Section titled “CSV column layout (round-trip + manual import)”

The same columns regardless of source:

date, departureIata, arrivalIata, flightNumber, airline, aircraft,
departureLocal, arrivalLocal, depTimezone, arrTimezone, seat, class,
bookingReference, tags

Times are local in the depTimezone / arrTimezone IANA zone (e.g. Europe/Berlin, America/New_York). The server converts them to canonical UTC on save — the v1.2.0 contract change means raw departureTime strings are no longer accepted on writes.

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

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

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

External tools and scripts can run the FR24 / Generic-CSV parser → preview → commit flow over the API without reimplementing FR24’s column map or the RFC-4180 lexer. Authenticate with a Personal Access Token — a read-scope token is enough, because the parser is a pure function and writes nothing.

Terminal window
# FR24 source — body is the raw my.flightradar24.com CSV export
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>"}'
# Generic CSV — body carries the wizard's column mapping too
curl -X POST https://travstats.example.com/api/v1/import/parse \
-H "Authorization: Bearer ts_pat_…" \
-H "Content-Type: application/json" \
-d '{
"source":"generic_csv",
"csv":"<csv contents>",
"mapping":{"Date":"date","Departure IATA":"fromIata", "...": "..."}
}'

Returns the same PreviewRowInput[] shape the browser preview shows. Pipe it into POST /flights/batch (write scope required there) to commit, or run further client-side validation first.

Settings → Data → Export downloads everything you own from the running instance:

  • All flights with full metadata (and any cruise voyages once v2 lands)
  • Tags, trips, achievements
  • Personal settings (UI language, units, default tags) — but not instance-level admin settings or other users’ data

The export is a single JSON file that can be re-imported into another TravStats instance via the bulk-import endpoint.

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

Admin → Settings → Backups:

  • Schedule — cron expression (default: daily at 02:00 UTC)
  • Retention — how many backup files to keep on disk
  • Location/app/data/backups inside the container, persisted via the named volume
  • Optional WebDAV sync — Nextcloud, HiDrive, or any WebDAV-compatible target receives a copy off-host

Backups are gzipped pg_dump outputs (full schema + data) named with the timestamp. Restore them with the dump→psql workflow below.

For a fresh dump on demand, no scheduler involved:

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 — has 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-05-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 restore re-applies any migrations that the dump pre-dates. Idempotent.

Same-version migration (TravStats 1.3.0 → TravStats 1.3.0):

  1. pg_dump the source instance (above).
  2. Stand up the target instance with the same major.minor version.
  3. Restore the dump on the target via the steps above.

That’s it — the JWT secret, encryption keys, and instance settings are part of the dump and travel with the data.

Cross-version migration (TravStats 1.2.x → TravStats 1.3.0):

  • Restore the older dump into the older version first, upgrade that instance to the new version (which runs migrations against your real data), and only then dump again to migrate hardware.
  • Don’t restore a 1.2.x dump directly into a 1.3.0 fresh instance and let migrations run on top of it — the migrations expect to evolve a real-data DB, not import legacy schema. The order matters.

The CHANGELOG flags any release that changes the export format. Cross-version imports between versions where the format hasn’t changed are schema-stable and require no special steps.