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.
Importers (since v1.5.0)
Section titled “Importers (since v1.5.0)”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.
| Tile | Source | When to use |
|---|---|---|
| From Flightradar24 | unmodified my.flightradar24.com CSV export | Migrating from FR24 (Silver/Gold or free tier) |
| From any logbook (CSV) | any flat per-flight CSV | OpenFlights, App in the Air, FlightAware “My Flights”, arbitrary spreadsheets |
| Re-import TravStats Excel | XLSX / CSV / JSON exported from TravStats | Round-tripping mass edits through Excel, restoring to another instance |
From Flightradar24
Section titled “From Flightradar24”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 reason→category(Business →business, Leisure →vacation)Seat number→seatNumberNote→notesRegistration→aircraftRegistration- Status auto-defaults from
dep_utcvs. 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.
From any logbook (CSV)
Section titled “From any logbook (CSV)”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
idcolumn are matched against existing flights and updated. - Rows without
idare 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, tagsTimes 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.
# FR24 source — body is the raw my.flightradar24.com CSV exportcurl -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 toocurl -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.
Personal export
Section titled “Personal export”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.
Database backups
Section titled “Database backups”Personal exports are convenient; full Postgres dumps are durable. TravStats has automated backups built in.
Built-in scheduler
Section titled “Built-in scheduler”Admin → Settings → Backups:
- Schedule — cron expression (default: daily at 02:00 UTC)
- Retention — how many backup files to keep on disk
- Location —
/app/data/backupsinside 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.
Manual pg_dump
Section titled “Manual pg_dump”For a fresh dump on demand, no scheduler involved:
docker exec travstats-db pg_dump -U flights flights | gzip > flights-$(date +%F).sql.gzTo restore into a fresh container:
# 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 dumpgunzip -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 DBdocker compose -f docker-compose.prod.yml restart appThe app’s first boot after restore re-applies any migrations that the dump pre-dates. Idempotent.
Migrating between instances
Section titled “Migrating between instances”Same-version migration (TravStats 1.3.0 → TravStats 1.3.0):
pg_dumpthe source instance (above).- Stand up the target instance with the same major.minor version.
- 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.