Search Google Flights for one-way or round-trip itineraries between two airports on given dates and return the cheapest options with airline, flight numbers, total duration, stops, depart/arrive times, and a booking link. Read-only — never books.
Automate repetitive workflows and reduce manual effort
Example: Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Learn new skills, understand complex topics, get expert guidance
Example: Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Enhance output quality through reviews, suggestions, and refinements
Example: Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
Time estimate: 15-45 minutes depending on use case complexity
Prerequisites
Submit your Claude Code skill and start earning
browse install google.com/search-flights-ts4g1fCompatible with
Supported agents
| name | search-flights |
| title | Google Flights Search |
| description | >- Search Google Flights for one-way or round-trip itineraries between two airports on given dates and return the cheapest options with airline, flight numbers, total duration, stops, depart/arrive times, and a booking link. Read-only — never books. |
| website | google.com |
| category | travel |
| tags | - travel - flights - search - google - read-only - anti-bot |
| source | 'browserbase: agent-runtime 2026-05-18' |
| updated | '2026-05-18' |
| recommended_method | browser |
| alternative_methods | - method: url-param rationale: >- The `tfs=<base64-protobuf>` deep-link pre-fills origin/destination/dates/cabin/passengers in the URL itself, so the workflow needs zero form interaction. This is a navigation shortcut layered on top of the browser path — it does not replace browser rendering (the results page is JS-driven and the booking funnel is JS-only). - method: api rationale: >- Confirmed unavailable for consumer search. `developers.google.com/travel/flights` is a Travel Partner API for airlines/OTAs to push inventory TO Google — it does not return search results. Don't waste time searching for a JSON endpoint. - method: hybrid rationale: >- Direct HTTP `curl` of `https://www.google.com/travel/flights?tfs=...` with Chrome-UA + consent cookies sometimes returns parseable HTML (cheerio selectors documented in SKILL) and an embedded `script.ds:1` JS data blob with full itinerary detail. Reliable from clean residential IPs; expect `/sorry/index` 429 from most cloud / data-center IPs. Use as opportunistic optimization with browser fallback on any non-200. |
| verified | true |
| proxies | true |
Given an origin, a destination, one date (one-way) or two dates (round-trip), and a passenger / cabin config, return the cheapest itineraries with airline, flight numbers, total duration, stops, depart/arrive times, and a booking link. Read-only — never click Select flight, Book, or any continuation that leaves the results screen.
tfs= URL day-over-day).Google does not publish a consumer flight search API. The official Travel Partner API (developers.google.com/travel/flights) is for airlines/OTAs to push inventory to Google — it does not return search results. The reliable path is to drive the consumer site https://www.google.com/travel/flights with a deep-link URL that pre-applies all search parameters via the tfs= (base64-protobuf) query string, so no form interaction is required.
A residential-proxy + Browserbase-Verified session is mandatory. From data-center IPs Google serves the /sorry/index 429 interstitial ("Our systems have detected unusual traffic from your computer network") within 1–2 requests on /travel/flights* — verified at runtime (HTTP 429, Content-Type: text/html, captcha widget served inline).
tfs deep-linkConstruct the URL:
https://www.google.com/travel/flights?tfs=<BASE64_PROTOBUF>&hl=en&curr=USD&gl=us&tfu=EgQIABABIgA
The tfs value is a base64-URL-encoded protobuf with this schema (verified against open-source decoders):
message Airport { string airport = 2; } // 3-letter IATA, uppercase
message FlightData { string date = 2; // YYYY-MM-DD
Airport from_flight = 13;
Airport to_flight = 14;
optional int32 max_stops = 5; // 0 = nonstop
repeated string airlines = 6; } // 2-letter IATA or alliance
enum Seat { ECONOMY=1; PREMIUM_ECONOMY=2; BUSINESS=3; FIRST=4; }
enum Trip { ROUND_TRIP=1; ONE_WAY=2; MULTI_CITY=3; }
enum Passenger { ADULT=1; CHILD=2; INFANT_IN_SEAT=3; INFANT_ON_LAP=4; }
message Info { repeated FlightData data = 3; // 1 leg for one-way, 2 for round-trip
Seat seat = 9;
repeated Passenger passengers = 8; // one enum entry per passenger
Trip trip = 19; }
Wire-format rule: passengers is repeated and unpacked — three adults + one infant-on-lap = four entries [ADULT, ADULT, ADULT, INFANT_ON_LAP], not a single scalar count. Total passengers must be ≤ 9. infants_on_lap must be ≤ adults.
The trailing constants — hl=en, curr=USD, gl=us, tfu=EgQIABABIgA — are required. Drop any of them and Google often redirects to a "popular destinations" landing page that ignores the tfs payload.
Easier fallback when you don't want to encode protobuf: use the natural-language q= deep-link. The intent-parser handles ~95% of routes cleanly:
https://www.google.com/travel/flights?q=Flights+from+SFO+to+JFK+on+2026-06-15+through+2026-06-22&hl=en&curr=USD&gl=us
Verify after navigation that the resolved URL contains tfs= (intent-parser succeeded) — if it lands on /explore instead, the parser couldn't disambiguate and you must fall back to building the tfs directly.
sid=$(browse cloud sessions create --keep-alive --verified --proxies | jq -r .id)
export BROWSE_SESSION="$sid"
browse open "$URL" --remote
browse wait load --remote
browse wait timeout 3500 --remote # itinerary cards stream in 2–4s after `load`
Anything short of --verified --proxies gets either the /sorry/index 429 or an empty itinerary list with a "Something went wrong" toast.
On a fresh session (no SOCS cookie) Google overlays a consent dialog that swallows clicks on result rows.
browse snapshot --remote
# Look for a `button: "Reject all"` or `button: "Accept all"` ref — click it.
browse click "<ref>" --remote
browse wait timeout 1000 --remote
If you reuse a persisted Browserbase context, you can pre-seed the consent state once (CONSENT=PENDING+987; SOCS=<base64>) and skip this step on every subsequent run.
The default landing tab is "Best", which is Google's price-vs-duration heuristic, not strictly the cheapest. Click the "Cheapest" tab:
browse snapshot --remote
# Find: tab "Cheapest" → click its ref
browse click "<ref>" --remote
browse wait timeout 2500 --remote # the list re-sorts in place; no navigation
Equivalent URL-only path: append &sort=2 (SerpAPI-style sort codes — 1=top, 2=price, 3=depart, 4=arrive, 5=duration, 6=emissions). Google honors this param on the consumer page.
browse get html body --remote > /tmp/flights.html
The result list is a sequence of <li> items inside two parent containers (one per tab/section):
div[jsname="IWWDBc"] — "Best flights" / top sectiondiv[jsname="YdtKid"] — "Other flights" sectionWithin each ul.Rk10dc li row:
| Field | Selector |
|---|---|
| Airline name | div.sSHqwe.tPgKwe.ogfYpf span (first) |
| Depart / arrive times | span.mv1WYe div (nodes 0 and 1) |
Next-day arrival flag (+1, +2) | span.bOzv6.bOzv6 next to arrival time |
| Total duration | div.gvkrdb.AdWm1c.tPgKwe.ogfYpf ("5 hr 32 min") |
| Stops | div.EfT7Ae.AdWm1c.tPgKwe span.ogfYpf ("Nonstop" / "1 stop") |
| Layover detail | div.c8rWCd.tPgKwe.ogfYpf ("1 hr 45 min LAX") |
| Price | div.YMlIz.FpEdX span ("$412") |
| CO₂e | div.O7CXue.tPgKwe ("312 kg CO2e") |
| Card "details" expander | button[aria-label*="more details"] |
Flight numbers (e.g. DL 412) are not in the card-collapsed view. You have to expand each card — click button[aria-label*="more details"] — which renders an inner detail panel containing each segment's Airline + flight number + aircraft + seat pitch + on-time stats. Read span.sSHqwe span:nth-of-type(2) inside each segment block to get the AA 1234-style identifier. Budget 1.5–3 seconds per card for the detail-render to settle before the next snapshot.
There is no clean per-itinerary "deep link to airline" available before the user picks a card — Google routes booking through /travel/flights/booking which is Disallow: in robots.txt and serves Akamai-blocked responses to non-browser HTTP. The two practical options:
itinerary-key parameter from data-iid attribute on the row (<li data-iid="...">). Format: https://www.google.com/travel/flights/booking?tfs=<original>&iid=<row data-iid>. Reaches the booking-redirect page when followed in-browser. Use this when you need a single URL the user can paste.button[aria-label*="Select flight"] on the card, then browse get url immediately after navigation. Google bounces to either an airline-direct URL or a Google-hosted broker page. Carries an ?gclid= tracking param. Higher-fidelity but costs one navigation per card.Default to option (1) for read-only price-comparison flows. Use option (2) only when the consumer explicitly needs the merchant-direct URL.
The full structured itinerary list (including flight numbers, aircraft types, layover cities, codeshares) is embedded in the initial HTML as a nested-list blob inside <script class="ds:1">…AF_initDataCallback({…, data: [ …deeply nested arrays… ], …})…</script>. Regex: /^.*?\{.*?data:(\[.*\]).*}/. Parse the captured group as JSON, then index by the positional paths below (verified against open-source decoders):
data[0] is "best", data[1] is "other"flights[], layovers[], travel_time (minutes), departure_airport, arrival_airport, itinerary_summary.flights (e.g. "DL 412, DL 1735"), itinerary_summary.price (dollars, integer)itinerary.flights[i]): operator [2], departure_airport [3], arrival_airport [5], departure_time [8] ([hour, minute]), arrival_time [10] ([hour, minute]), travel_time [11] (minutes), seat_pitch_short [14], codeshares [15], aircraft [17], departure_date [20], arrival_date [21], airline_code [22][0], airline_name [22][3], flight_number [22][1]Reading the blob is one extraction per page vs. N expand-clicks in the DOM path, but the indices are positional and Google has rotated them at least once historically — always pin the parser against the latest version of wooldox/google-flights-ts/src/decoder.ts (or the upstream Python AWeirdDev/flights) before trusting them blindly.
browse cloud sessions update "$sid" --status REQUEST_RELEASE
If you have a clean residential IP and just need a quick price snapshot, you can sometimes skip the browser entirely:
curl -sS "https://www.google.com/travel/flights?tfs=<b64>&hl=en&curr=USD&tfu=EgQIABABIgA" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..." \
-H "Cookie: CONSENT=PENDING+987; SOCS=CAESHAgBEhJnd3NfMjAyMzA4MTAtMF9SQzIaAmRlIAEaBgiAo_CmBg" \
-H "Accept-Language: en-US,en;q=0.5"
The 200-OK HTML body contains both the cheerio-parsable cards (selectors above) and the script.ds:1 JS blob. Expect this path to fail most of the time from cloud IPs — Google returns /sorry/index 429 or a redirect to a generic landing page. Use only as an opportunistic optimization with a graceful fallback to the browser path on any non-200 response or any response containing <title>Sorry...</title>.
Select flight, Book, or any time-slot ref on the result card. The skill terminates at the results screen.--verified --proxies is mandatory. Bare and --proxies-only sessions get the /sorry/index 429 captcha interstitial within 1–2 requests on /travel/flights*. Verified at iteration time via direct browse cloud fetch of /sorry/index returning HTTP 429 with the canonical "Our systems have detected unusual traffic" body.browse cloud fetch https://www.google.com/travel/flights returns 502 The response body exceeded the maximum allowed size of 1MB. Use a browser session to handle large responses. Don't try to use Browserbase's Fetch API for the search page; you must drive a session.robots.txt explicitly disallows the booking funnel — Disallow: /travel/flights/booking, /travel/flights/search, /travel/flights/s/, /travel/search, /travel/clk. The main /travel/flights landing is the only allowed entry point. Skill stays on /travel/flights?tfs=... and never navigates into a Disallow-ed subpath.developers.google.com/travel/flights is a partner API for airlines/OTAs to push inventory to Google. It does not return search results. Don't waste time looking for a JSON endpoint — the consumer site is the only surface.tfs is base64-URL-encoded protobuf, not JSON. Decoders that try to JSON-parse it fail silently and produce blank pages. Verified protobuf schema in step 1; reference encoders: wooldox/google-flights-ts (TS, hand-rolled), AWeirdDev/flights (Python, upstream).passengers is repeated-unpacked, not a count. Two adults = [ADULT, ADULT], not 2. Mis-encoding shows up as "1 traveler" on the rendered page regardless of intent.&sort=2 to the URL) before reading rows, otherwise the top results are Google's "best value" heuristic, which can be $50–$200 above true cheapest on a US transcon.script.ds:1 JS blob (one parse per page). The blob path is ~10× faster but positionally indexed — see step 7 caveat.11:50 PM with a tiny +1 superscript when the flight lands the next day. Selector: span.bOzv6.bOzv6. Drop this and you'll mis-compute total_duration_minutes by 1440.q= intent-parser can rewrite the URL. Useful (you can skip protobuf encoding) but unpredictable — ambiguous airport names or odd date phrasings can land on /explore instead of running a search. Always inspect the post-navigation URL for tfs=; fall back to manually building the protobuf when it's missing.tfs payload can return a stale price for hours. For high-fidelity pricing, append &curr=USD&hl=en (forces currency / locale) and treat the result as "Google's last-seen price", not "current airline price". Surface the page's <title> "Updated …" timestamp when emitting output.jsname="IWWDBc" and ul.Rk10dc are stable across at least 18 months of open-source community usage but have changed in the past. When a parse drops to zero rows, refresh selectors from wooldox/google-flights-ts/src/core.ts (parseResponse) or the upstream AWeirdDev/flights library — both track Google's churn./travel/flights/booking URLs out-of-band — they only render in-session after a Select flight click. For consumer-facing booking URLs, prefer attaching the canonical Google Flights URL with the iid (see step 6 option 1).REFUSED for connect.usw2.browserbase.com and all other connect.*.browserbase.com hosts (verified across us-west-2, us-east-1, eu-central-1, ap-southeast-1). The Browserbase Fetch API at api.browserbase.com works, but CDP/Playwright sessions cannot be driven from the sandbox itself. The skill is designed to run from a Browserbase-connected client (laptop, CI runner, or a host whose egress is unfiltered) — this caveat is environment-specific, not a property of Google Flights.Two outcome shapes:
// Success
{
"success": true,
"origin": "SFO",
"destination": "JFK",
"depart_date": "2026-06-15",
"return_date": "2026-06-22",
"trip_type": "round-trip",
"passengers": { "adults": 1, "children": 0, "infants_in_seat": 0, "infants_on_lap": 0 },
"cabin": "economy",
"sort": "cheapest",
"price_currency": "USD",
"results": [
{
"rank": 1,
"price": 412,
"airlines": ["Delta"],
"flight_numbers": ["DL 412", "DL 1735"],
"stops": 0,
"total_duration_minutes": 332,
"outbound": {
"depart_time_local": "2026-06-15T07:30-07:00",
"arrive_time_local": "2026-06-15T15:50-04:00",
"depart_airport": "SFO",
"arrive_airport": "JFK",
"duration_minutes": 320,
"segments": [
{ "flight_number": "DL 412", "depart": "07:30", "arrive": "15:50",
"depart_airport": "SFO", "arrive_airport": "JFK",
"aircraft": "Boeing 757", "operator": "Delta Air Lines", "duration_minutes": 320, "layover_minutes": 0 }
]
},
"return": {
"depart_time_local": "2026-06-22T18:10-04:00",
"arrive_time_local": "2026-06-22T21:35-07:00",
"depart_airport": "JFK",
"arrive_airport": "SFO",
"duration_minutes": 385,
"segments": [ /* same shape */ ]
},
"co2e_kg": 312,
"co2e_vs_typical_pct": -8,
"booking_url": "https://www.google.com/travel/flights/booking?tfs=<original>&iid=<row data-iid>",
"last_updated": "2026-05-18T18:42:00Z"
}
]
}
// Anti-bot wall (Sorry page) — emit when the post-navigation URL contains /sorry/ or the page title starts with "Sorry"
{
"success": false,
"error_reasoning": "Google served the /sorry/index 429 anti-bot interstitial. Retry with a fresh --verified --proxies session, or rotate the proxy pool. The IP currently in use is on Google's traffic-anomaly list."
}
If the route has no flights on the requested date — valid empty result, not a failure: { "success": true, ..., "results": [] } plus the page's empty-state text in an info field ("No flights available for these dates. Try nearby dates.").
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionsearch-flightsExecute the skills CLI command in your project's root directory to begin installation:
browse install google.com/search-flights-ts4g1fFetches search-flights from google.com/search-flights-ts4g1f and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
.cursor/skills/search-flights
Restart Cursor to activate search-flights. Access via /search-flightsin your agent's command palette.
Learn more:View source on GitHubSkills CLI docsAbout CursorWhat are agent skills?
Steps
Common Pitfalls
✓ Do
✗ Don't
💡 Pro Tips
✓ Use when
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid when
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
search-hotels
booking.com/search-hotels-asq6cc · travel · 2 shared tags
google-flights
skillhq/flight-search · Backend · 2 shared tags
cn-ecommerce-search
shopmeskills/mcp · Productivity · tag: search
add-to-cart
kmart.com.au/kmart-irwsr8 · shopping · tag: read-only
cnki-search
cookjohn/cnki-skills · Productivity · tag: search
wechat-article-search
wuchubuzai2018/expert-skills-hub · Productivity · tag: search
Solid pick for teams standardizing on skills: search-flights is focused, and the summary matches what you get after install.
We added search-flights from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
Registry listing for search-flights matched our evaluation — installs cleanly and behaves as described in the markdown.
search-flights has been reliable in day-to-day use. Documentation quality is above average for community skills.
Keeps context tight: search-flights is the kind of skill you can hand to a new teammate without a long onboarding doc.
Registry listing for search-flights matched our evaluation — installs cleanly and behaves as described in the markdown.
search-flights is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
search-flights fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
search-flights reduced setup friction for our internal harness; good balance of opinion and flexibility.
We added search-flights from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
showing 1-10 of 56