Google Flights URL parameters, decoded
Copy a URL out of Google Flights after setting up a search and you get something like:
https://www.google.com/travel/flights?tfs=CBwQAhopEgoyMDI2LTEwLTE1...&curr=USD
Everything you clicked (origin, destination, dates, passengers, cabin, stops) is in there. It is just not in there in a form meant for you to read. This guide covers what each parameter does, how far you can get decoding the big one, and where the honest limits are.
The three parameters that matter
tfs= is the search itself. It is a protocol-buffer message, serialized and
base64url-encoded. One opaque blob encodes the full flight query: the legs (origin,
destination, date: one leg for a one-way, two for a round-trip), passenger counts,
cabin, and filters like stops and airlines. When Google Flights loads a URL with
tfs=, it reconstructs the entire search UI state from this blob.
q= is the informal cousin: a free-text query, the same thing you could type
into the search box. ?q=flights to tokyo opens Google Flights with the query parsed
out of natural language. It is convenient for hand-written links, and useless for
precision: you cannot pin dates, filters, or a specific itinerary with it the way
tfs= does.
curr= sets the display currency (curr=USD, curr=EUR). It rides alongside
tfs= rather than inside it, which is occasionally useful: you can take an existing
deep link and re-point the currency without touching the blob.
Decoding tfs= at the wire level
You do not need Google's schema to inspect a tfs= blob, because protobuf's wire
format is self-describing down to field numbers and wire types (though not field
names or meanings). Two steps:
1. Undo the base64url. The blob uses the URL-safe alphabet (- and _ instead
of + and /) and typically arrives without padding, so pad it back up before
decoding:
import base64, sys
from urllib.parse import urlparse, parse_qs
url = sys.argv[1]
tfs = parse_qs(urlparse(url).query)["tfs"][0]
raw = base64.urlsafe_b64decode(tfs + "=" * (-len(tfs) % 4))
print(raw.hex())
2. Walk the protobuf wire format. Every field in the byte stream is a varint tag
(field number shifted left three bits, ORed with a wire type) followed by a payload
whose shape the wire type determines: varint, 64-bit, length-delimited, or 32-bit.
Length-delimited fields are where the interesting things live: strings like airport
codes and dates are directly readable, and nested messages (the legs) decode
recursively. If you have protoc installed, it will do the walk for you:
python3 -c "
import base64, sys
tfs = sys.argv[1]
sys.stdout.buffer.write(base64.urlsafe_b64decode(tfs + '=' * (-len(tfs) % 4)))
" "$TFS_BLOB" | protoc --decode_raw
--decode_raw prints the field-number tree with strings legible. You will spot
IATA codes and YYYY-MM-DD dates immediately. What you will not get is names or
guarantees: field 2 is just "field 2," and nothing promises it means the same thing
next quarter.
If you would rather not install anything, the Google Flights URL parser on this site does the base64url + wire-format walk client-side in your browser: paste a URL, see the decoded tree.
The honest caveats
- The format is undocumented. Google publishes no schema for
tfs=and owes nobody stability. Everything decoded from it is inference from observed bytes. - Reading is safer than writing. Decoding a blob you already have degrades
gracefully: worst case, a field you relied on moves and your parser shows an
unknown. Constructing
tfs=blobs yourself is the fragile direction: an encoding quirk you did not reproduce, or a silent format revision, produces links that open a wrong or empty search, and nothing errors to tell you. - A decoded URL is not an API. Even a perfect decoder only tells you what a search asks; it gets you no prices. To act on the data you still need something that returns fares.
The shortcut: let the API build the deep link
The main practical reason people try to encode tfs= themselves is to build "book
this" links programmatically. That problem is already solved from the other
direction: every result returned by the
Google Flights Live API
carries a buy_link, a working deep link into Google Flights for that exact
itinerary, with the blob already encoded by the same pipeline that read the fare:
{
"price": "$56",
"price_as_number": 56,
"airline": "easyJet",
"buy_link": "https://www.google.com/travel/flights?tfs=...&curr=usd"
}
So the division of labour that actually works is: decode URLs when a user hands
you one (the parser tool or the snippet above),
and never encode them. Search via the API and pass along the buy_link it
returns per itinerary. You get the deep link without owning the format risk, and the
same response carries the price context (price_insights_low/high and Google's
low | typical | high verdict) that the URL alone never had. The
one-way endpoint documents the full response shape.
Related
- Google Flights URL parser: paste a URL, see the decoded tree, free
- How to get real-time Google Flights data: the full API walkthrough
- Price Insights API: the context a URL never carries
Skip the blob entirely
Search by route and date, get fares with a working buy_link on every result. The deep link is built for you.
Free tier: 10 requests/month. No card to try.