✈️ Flightwatch – a free flight price tracker
Open the dashboard ↗
Flightwatch watches flight prices for flexible travel dates, keeps a real price
history and tells you on Discord or Telegram when a price is worth a look. It runs
entirely on GitHub: no server, no paid flight API, no account to sign up for.
Right now it tracks flights from Düsseldorf, Frankfurt and Amsterdam to Bangkok
in October 2026. The search can be changed on the website at any time: up to five
trips, each with up to five destinations, for 1–9 travellers, as round trips or open
jaw (back from another airport).
A personal travel project. Prices come from Google Flights' public (unofficial)
search, which can change without notice. Always check the fare before booking.
What you get
- A dashboard with the cheapest checked flights (at the same price, the one with
the shorter journey): departure and arrival times, connections and waiting times,
a 7-day trend per flight, the cheapest price per airport, a price calendar, a
price-history chart and filters for everything that matters.
- Price alerts when a fare drops, rises, reaches your price target or hits a new
low, with flight times and connections, plus a short "no change" note after searches
without news.
- Flexible trips: every departure day in your window is combined with every trip
length you allow (for example 14–21 days). A trip can compare several destinations
(e.g. Bangkok, Phuket and Chiang Mai), fly back from another airport, limit flight
times and count several travellers.
- Your currency and country: each trip can be priced in its own currency (e.g.
Thai baht) and searched as from another country (e.g. Thailand), and can send its
alerts to its own Discord channel or Telegram chat, for example a friend's.
- Fair comparisons: non-stop and connecting flights are kept apart, overly long
journeys are filtered out, and only actually checked trips count.
- Baggage views that show fares whose booking offer includes a cabin bag, a
checked bag or both.
- Favorites saved in your browser, light/dark mode, and a layout that works on
phones.
How it works
- About every 6 hours a GitHub Actions run searches every date combination of every
trip and destination.
- The most promising flights are checked in detail (real itinerary, dates, stops,
flight times, travel time).
- Prices are stored and compared with the last 30 days.
- If something changed meaningfully, you get a message; the dashboard updates a few
minutes later.
- An hourly check makes sure no search is skipped, even when GitHub starts scheduled
runs late; an optional external timer gives exact search times.
The dashboard
- Trips: with several trips, a bar at the top switches between them. Each trip has
its own link, e.g.
…/flight-tracker/?trip=ams.
- Offers: each checked flight shows when the outbound and return flights leave and
land (local time at each airport, +1 means the next day), travel time and
connections, e.g. "via DOH 1 h 55". Waits under 1 hour or of 6 hours and more are
marked. With several travellers, prices are per person and the total is shown too.
- 7-day trend: each offer says how its price moved against the earliest check of
the last week. It describes the past; it is not a forecast.
- Cheapest per airport: a chip per departure airport shows its cheapest offer;
click one to see only that airport.
- Filters: departure airport, destination (for trips with several), connection
type, outbound day, trip length, departure and arrival time, and airlines (show only
or hide selected airlines). The outbound list has every day of the window; days
marked calendar only have no checked offer, because each search checks only the
cheapest date combinations.
- Price calendar: the cheapest price the date search found for every outbound day
and trip length, coloured in five steps from cheaper to pricier. These prices are
indicative because the return flight is not chosen yet. A price with a dot has a
checked offer and shows it in the list; any other price opens that date combination
on Google Flights.
- Price history: pick a flight to see every checked price, your price target and
the lowest price of the comparison period.
- Baggage: choose Base price, 1 cabin bag, 1 checked bag or both. Baggage views
only list offers that state the bags are included for the whole trip; they never
guess a fee.
- Favorites: save a flight with the star. Favorites stay in this browser only.
- Theme: the selector in the header switches between Auto (follows your device),
Light and Dark.
The page clearly says when a search was incomplete or the data is older than 12 hours.
Price alerts
Alerts go to a Discord channel (or a Telegram chat), or to the trip's own one, and name
their destination in the title. Each flight in an alert lists its dates, price (per
person in the trip's currency, plus the total with several travellers), flight times,
connections and a link to search it on Google Flights. The amounts below are the
defaults for a trip in euros.
| Message | Meaning |
|---|
| PRICE DROPPED / PRICE ROSE | The same flight dates changed by at least €25 since the last alert. |
| CHECK TO BUY | At or below your price target and close to the lowest price seen. |
| WITHIN BUDGET, but … | Within your target, but it has been at least €25 cheaper. |
| WATCH | Still above your target. |
| STRONG DEAL | At least 10% and €50 below the lowest price of the last 30 days. |
| CHEAPER ALTERNATIVE | Other dates are clearly cheaper than the ones you were alerted about. |
Searches without a new alert send a short "No price change" or "Only small price
changes" note, linked to the last alert. When a trip's travel window has passed, one
message says so and links to the settings page. A missing search result is never
treated as a price change.
Search settings (website)
Open Change search on the dashboard to change what is tracked. Airports can be
searched by city, airport name or code (German names such as "München" work too).
- Several trips: use + Add trip for up to five trips, e.g. Bangkok in October
and a weekend in Amsterdam. Each trip has its own price targets, alerts and history;
choose which one the website shows first.
- Route: departure airports and up to five destinations per trip. Each destination
is searched on its own, with its own price history and alerts, and costs its own
requests; the dashboard shows them together with a destination filter.
- Open jaw (return flight from): fly out to the destination and back from another
airport, e.g. to Bangkok and back from Phuket. See the note below.
- Dates: an earliest and latest departure day and the trip length, or a
Return date by which you must be back. With a return date, each departure day is
only combined with the lengths that return in time (from 20 Oct up to 19 days, from
23 Oct up to 16 days for a return by 8 Nov).
- Flights: travellers (1–9 adults), cabin, maximum stops, maximum travel time per
direction, airlines to include or exclude, separate tickets and bags in the base
search.
- Currency and search country: Google prices a trip in its currency (21 to choose
from, e.g. euro, Thai baht, US dollar); the tracker never converts a price. Search
from is the country Google assumes you search from (Germany unless you choose
another); offers and prices can differ, for example for someone living in Thailand.
Choosing another currency converts the trip's price targets roughly, to be checked.
- Flight times: limit when the outbound and return flights may leave and land
(local time), e.g. not before 08:00 or no landing at night. Google then searches only
such flights, so alerts and history follow them.
- Price targets per airport: prices can differ a lot between departure airports
(for Bangkok, Amsterdam is often well over €100 cheaper than Düsseldorf). Optionally
give an airport its own target; alerts and the website then judge each flight by
the target of its airport.
- Suggested prices: once a trip has checked prices, the form suggests targets per
airport from them (the price a quarter of them reached) and fills them in with one
click. A new trip only gets a rough guide from the flight distance, which knows
nothing about differences between airports.
- Request estimate: the form shows how many searches a run needs. All trips and
destinations share one budget, so very wide date ranges or too many destinations are
rejected before anything is sent.
- Applying: the form opens a prefilled GitHub issue. Once you create it, a workflow
checks the settings, saves them, replies in the issue and starts a new search of the
trips you changed; the other trips keep their prices until the next regular search.
When that search has finished, the website shows the new settings with their first
prices, usually after 5–15 minutes.
Changing a destination, the return airport, the currency, the search country,
travellers, cabin, bags, separate tickets, the travel time limit, flight times,
airlines or stops starts a new price history for that trip; the old one stays stored. Adding or reordering destinations keeps the
existing histories.
Open jaw is experimental. Google searches these trips as multi-city flights. The
search library warns that Google can answer such searches slowly or not at all, and
this could not be tested live yet. If it fails, the dashboard reports an incomplete
search and you get a "needs attention" message (at most once a day); no made-up
prices are shown. The "Find flight" link asks Google for the multi-city trip in words
and may open a plain search instead. Check the first results of such a trip, and use
round trips if they keep failing.
Only issues from the repository owner are applied; anyone else's are closed
automatically. Settings are stored on the search-config branch; deleting that
branch returns to config.json on main.
Get notifications
Discord (recommended)
- In a Discord server text channel, open Edit Channel → Integrations → Webhooks →
New Webhook and copy the webhook URL.
- Add it as the repository secret
DISCORD_WEBHOOK_URL
(Settings → Secrets and variables → Actions). Treat it like a password.
- Run the Test Discord workflow to receive a test message.
Telegram
- Create a bot with @BotFather (
/newbot) and add its token
as the secret TELEGRAM_BOT_TOKEN.
- Send
/start to your bot, then run the Telegram setup workflow; it replies with
your chat ID.
- Save that as the secret
TELEGRAM_CHAT_ID and run Test Telegram.
If both are set up, Discord is used. The repository variable NOTIFICATION_CHANNEL
(auto, discord or telegram) picks one explicitly.
A trip's own message channel
A trip can send its price alerts, check notes and "travel window ended" message to
its own Discord channel or Telegram chat, for example a friend's who is tracking
flights home. "Needs attention" messages about the tracker itself always go to the
main channel above. Every trip has a trip ID, shown on Change search below the
trips (the first trip is main).
- Discord: create a webhook in the other channel (as above) and add the secret
DISCORD_WEBHOOKS with one entry per trip ID:
{"ber": "https://discord.com/api/webhooks/…"}. Run Test Discord; it greets
every channel.
- Telegram (same bot): the other person sends
/start to your bot; within 30
minutes run Telegram setup, which replies to them with their chat ID. Add the
secret TELEGRAM_CHAT_IDS, e.g. {"ber": "123456789"}, and run Test
Telegram.
A trip listed in both gets Discord. Trips without an entry use the main channel, and
if one channel fails, the others still get their messages. Without a main channel,
only trips with their own channel get messages.
Run your own copy
You need a free GitHub account; no programming and no code changes. All links,
the website address and the settings form adjust to your copy automatically.
- Fork this repository (button at the top right on GitHub). Keep "Copy the
main
branch only" ticked, so you start with your own empty price history.
- In your copy, open the Actions tab and click "I understand my workflows, go
ahead and enable them".
- Settings → Pages: under Build and deployment, choose GitHub Actions as the
source.
- Settings → General → Features: tick Issues (the settings form uses them).
- Actions → Check setup → Run workflow. The run shows a checklist of what is
done and what is still missing, with links to the right settings pages.
- Actions → Track flights → Run workflow for the first search. Your website is
at
https://<your-user-name>.github.io/<repository-name>/.
- Open Change search on your website and set your own trip. Optional: set up
Discord or Telegram (above) for price alerts; without them only the website
updates.
Good to know:
- To search only one trip now, run Actions → Track flights → Run workflow and
enter its trip ID (shown on Change search, e.g.
ber; several with commas) under
Only these trips. Empty searches all trips. The regular searches still cover
every trip.
- Searches then run by themselves about every 6 hours. To pause them, add the
repository variable
TRACKER_ENABLED with the value false
(Settings → Secrets and variables → Actions → Variables).
- GitHub starts scheduled runs on a best-effort basis, often late. An hourly check
covers that; for searches at fixed times, set up the free external timer described in
DEPLOYMENT.md (about
10 minutes, needs a GitHub token that can only start workflows).
- Using your own domain for GitHub Pages? Add the variable
DASHBOARD_URL (for example
https://flights.example.org/) so links in messages point there.
- Only you, the owner of the copy, can change its search through the form.
More on operation and troubleshooting: DEPLOYMENT.md.
Costs and privacy
- Free: GitHub-hosted runners cost nothing for public repositories
(GitHub Actions billing).
No paid API, proxy or CAPTCHA service is used.
- Public data: the repository, the website and the
tracker-state branch show the
tracked routes, dates and prices. Don't use it for trips you want to keep private.
- Secrets stay secret: webhook URLs, bot tokens and chat IDs exist only as GitHub
secrets; they are never written to the repository, the state or the website.
- No tracking: the website uses no cookies or analytics. Favorites and the theme
choice are stored only in your browser.
Limitations
- GitHub can start scheduled runs late or skip them. The hourly check starts a search
when none was saved for 6 h 30 min, so without an external timer searches run about
every 7 hours at drifting times.
- Google may limit or change its search, and at times answers many requests with an
internal error. The tracker keeps every answer it gets, leaves out an airport that
keeps failing for the rest of the run and searches an incomplete trip again an hour
later (at most twice); it reports gaps instead of guessing. Open-jaw (multi-city) searches are the most likely to fail (see
the note under Search settings).
- Only a shortlist of flights is checked in detail, so a cheaper itinerary can be
missed. Calendar prices are indicative and can differ from checked prices.
- With several travellers, Google prices the whole party; the tracker divides that
price by the number of travellers. Compare the total with the "Find flight" link
before booking.
- Every extra destination multiplies the requests of its trip, so the request budget
limits how many dates and destinations fit into one run.
- Prices are search results, not reservations, and can change before you book.
For developers
python -m venv .venv && . .venv/bin/activate
python -m pip install --require-hashes -r requirements.txt
python -m unittest discover -s tests -v
node --test tests/site_model.test.mjs tests/search_model.test.mjs
python -m tracker demo --as-of 2026-09-18 # offline demo with synthetic prices
Other commands: python -m tracker plan, report, export --output history.csv,
discord-test, telegram-test. Don't run a live scan locally while the GitHub
workflow is running.
| Workflow | Purpose |
|---|
Track flights | Searches, checks flights, saves the history and sends messages |
Publish dashboard | Builds the website from the saved data |
Search schedule | Starts a search about every 6 hours (hourly check, optional external timer) |
Apply search settings | Applies settings issues from the owner |
Tests | Offline tests; no secrets, no live searches |
Check setup | Checklist of what a copy still needs |
Test Discord / Test Telegram / Telegram setup | Notification setup and tests |
History lives in a SQLite database on the tracker-state branch. Old observations are
cleaned up automatically, so it stays small. The search uses the open-source
fli client. Details on the data model, alerts,
trips, destinations and open-jaw searches, and the website:
docs/ARCHITECTURE.md.
Contributing: CONTRIBUTING.md.