The "breadcrumbs" element is always present, on top of the page.
It shows you where you are on the lab.
It enables you to reach any page on the lab.
Every option below is also a live page of the demo app: the same native widgets, one settings screen shared by all of them, real searches on the Belgian network.
The settings screen, not the planner form. Two sub-screens, both compatible with every option below: the planner form still carries one of the inline components (presets, listbox, toggles, switch). User preferences are per-request parameters that ride along every query of a search, whatever the mode option, so they never add a query. Routing settings restrict the modes: they trim what the inline components offer, and the transit checkboxes drive the transportModes column of every query list on this page. Done well, with the right defaults, most travellers never open this screen: the defaults below are the OTP 2 ones (or the recipe's, for the live rental availability), which send nothing at all, with one exception: the app caps walk-only trips at 20 min.
The things a traveller sets once: pace, how far they accept to walk or cycle to a stop, transfers, accessibility. Each control maps to one OTP 2 parameter of both recipes, written straight into the snippet; defaults send nothing, but for one app default: walk-only trips are capped at 20 min (maxDirectDurationFoot=PT20M, seeded in the snippet with the trip), since OTP 2's 4 h cap would list hour-long walks next to the transit itineraries; the traveller can pick another cap or lift it. The "at most" caps are what OTP calls performance parameters: they can only lower the server caps (45 min to or from a stop, 4 h door to door; above that OTP answers with an error) and they are hard cut-offs (verified live: capping the walk to the stop at 5 min on a suburban origin left no itinerary at all, noTransitConnection). For a soft preference the lever is walkReluctance, not exposed here on purpose. Public transit and rental list the operators and the GBFS networks OTP 2 knows, all on by default; unticking one bans it (bannedAgencies, bannedRentalNetworks). OTP 2's trip query knows no "preferred" operator, so a subscription badge stays a client side concern.
Try the settings screen in the demo app
| control | parameters | OTP 2 default (nothing sent) |
|---|---|---|
| Wheelchair accessible trips only | wheelchairAccessible | wheelchairAccessible = false (ui: off) |
| Walking pace | walkSpeed | walkSpeed = 1.33 m/s (ui: normal) |
| Walk to and from the stop, at most | maxAccessEgressDurationFoot | maxAccessEgressDurationFoot = 45 min (server cap, can only be lowered) (ui: no limit) |
| Walk-only trips, at most | maxDirectDurationFoot | maxDirectDurationFoot = 4 h (server cap, can only be lowered) (ui: 20 min, app default) |
| Cycling pace | bikeSpeed | bikeSpeed = 5 m/s (ui: normal) |
| Bike routes | bicycleOptimisationMethod | bicycleOptimisationMethod = safe (ui: safest) |
| Cycle to and from the stop, at most | maxAccessEgressDurationBicycle | maxAccessEgressDurationBicycle = 45 min (server cap, can only be lowered) (ui: no limit) |
| Bike-only trips, at most | maxDirectDurationBicycle | maxDirectDurationBicycle = 4 h (server cap, can only be lowered) (ui: no limit) |
| Only rental bikes available right now | useBikeRentalAvailabilityInformation | useBikeRentalAvailabilityInformation = true (recipe default, OTP 2 itself: false) (ui: on) |
| Transfers | transferPenalty, maximumTransfers | transferPenalty = 0 s; maximumTransfers = 12 (ui: fastest trip) |
| Time to change vehicles, at least | transferSlack | transferSlack = 120 s (ui: 2 min) |
| Be at the platform 5 min before the train | boardSlackRail | boardSlackRail = 0 s (boardSlackDefault) (ui: off) |
| Use real-time information | ignoreRealtimeUpdates | ignoreRealtimeUpdates = false (ui: on) |
| Itinerary texts | locale | locale = en (ui: english) |
| Public transit operators | bannedAgencies | bannedAgencies = none sent, every operator (ui: all ticked) |
| Rental networks | bannedRentalNetworks | bannedRentalNetworks = none sent, every network (ui: all ticked) |
Every operator is used by default. Untick one to ban it from the results (bannedAgencies), for a traveller without a pass for it.
Every rental network OTP 2 reads (GBFS manifest) is used by default. You can ban a rental agency by disabling it here (bannedRentalNetworks): a traveller keeps the networks they have a subscription for.
One global setting to allow options. Default is walk & transit only. Every extra street mode allowed is another vehicle kind the app has to query for, since this control alone carries no intent (the allowed list is ui state, not a parameter). Unchecked transit modes go to transportModes: try unticking one and look at the other views. Used as the only control it is a fan out, one query per allowed kind, with the paging caveat that comes with it.ℹ see: one cursor for many queries, how the preset recipe pages
| allowed street mode | queries | accessMode / directMode / egressMode ⇒ result shapes |
|---|---|---|
| walk & transit (always) | 1 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] |
| bike | 1 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] |
| bike, park | 1 | bike_park / bicycle / foot ⇒ [bike, park → transit → walk] + [bike] |
| rental bike | 1 | bike_rental / bike_rental / bike_rental ⇒ [rental bike → transit → rental bike] + [rental bike] |
| rental scooter | 1 | scooter_rental / scooter_rental / scooter_rental ⇒ [rental scooter → transit → rental scooter] + [rental scooter] |
| car | 1 | foot / car / foot · no transit ⇒ [car] |
| car, park | 1 | car_park / foot / foot ⇒ [car, park → transit → walk] + [walk] |
| taxi | 1 | car_pickup / foot / foot ⇒ [taxi → transit → walk] + [walk] |
| rental car | 1 | car_rental / car_rental / car_rental ⇒ [rental car → transit → rental car] + [rental car] |
| on-demand | 1 | flexible / flexible / flexible ⇒ [on-demand → transit → on-demand] + [on-demand] |
One triple per preset, custom is one triple too, and every preset here is monomodal: one way of moving and nothing else. Walk is walk-only (foot / foot / foot, transportModes empty), public transit is transit-only (the direct slot left unset, so no door to door walk next to the transit itineraries) and the vehicle presets are the -only variants (transportModes empty), so an itinerary either carries the mode asked for or does not come back at all. OTP has no switch for that: with a rental mode next to transit it also returns transit itineraries reached on foot, since renting is optional in a rental search; the -only presets are the way to keep the promise (rental still walks when no scooter is around). Level 1: the five main presets. Level 2: on-demand behind "more options". Level 3: custom access / direct / egress (DIY), where asymmetric trips and vehicle + transit combinations belong (or option 2). A preset is one modePreset id of the preset recipe; custom is the three slots of planner-v2-1. No preset here runs more than one query.
| preset | modePreset | queries | accessMode / directMode / egressMode ⇒ result shapes |
|---|---|---|---|
| Public transit | transit-only | 1 | foot / (unset) / foot ⇒ [walk → transit → walk] |
| Bike | bike-only | 1 | foot / bicycle / foot · no transit ⇒ [bike] |
| Car | car-only | 1 | foot / car / foot · no transit ⇒ [car] |
| Scooter | scooter-only | 1 | foot / scooter_rental / foot · no transit ⇒ [rental scooter] |
| Walk | walk-only | 1 | foot / foot / foot · no transit ⇒ [walk] |
| On-demand transport | on-demand | 1 | flexible / flexible / flexible ⇒ [on-demand → transit → on-demand] + [on-demand] |
Same widget as option 1, other defaults. Here the bare vehicle name means "every way to combine that vehicle with transit": Bike runs own bike, bike Park&Ride and rental bike; Car runs drive, Park&Ride and rental car. The narrow, single-query variants keep a qualified name (own, rental, Park&Ride, just) and sit behind "more options": one list, the name says how wide the preset is, the table says what it costs. Same ids as option 1 where the preset is the same, so the two options stay in sync. Bike and Car are multi query presets: one cursor pages all their queries at once, so the cost of this option is the query count (latency), not the paging.ℹ see: one cursor for many queries, how the preset recipe pages The rental queries also return transit itineraries reached on foot (renting is optional in an OTP 2 rental search): both recipes mark every itinerary with matchesRequestedModes (at least one leg uses a vehicle of the preset, the root requestedModes lists them) and the demo greys the others out; hiding or sorting them last is the client's call, the paging is untouched.
| preset | modePreset | queries | accessMode / directMode / egressMode ⇒ result shapes |
|---|---|---|---|
| Best | best | 1 | foot / car / foot ⇒ [walk → transit → walk] + [car] |
| Public transit | public-transit | 1 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] |
| Bike | bike-multimodal | 3 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] bike_park / bicycle / foot ⇒ [bike, park → transit → walk] + [bike] bike_rental / bike_rental / bike_rental ⇒ [rental bike → transit → rental bike] + [rental bike] |
| Car | car-multimodal | 3 | foot / car / foot · no transit ⇒ [car] car_park / foot / foot ⇒ [car, park → transit → walk] + [walk] car_rental / car_rental / car_rental ⇒ [rental car → transit → rental car] + [rental car] |
| Scooter | scooter-rental | 1 | scooter_rental / scooter_rental / scooter_rental ⇒ [rental scooter → transit → rental scooter] + [rental scooter] |
| On-demand transport | on-demand | 1 | flexible / flexible / flexible ⇒ [on-demand → transit → on-demand] + [on-demand] |
| Own bike | bike | 1 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] |
| Own bike, Park&Ride | bike-park-ride | 1 | bike_park / bicycle / foot ⇒ [bike, park → transit → walk] + [bike] |
| Rental bike | bike-rental | 1 | bike_rental / bike_rental / bike_rental ⇒ [rental bike → transit → rental bike] + [rental bike] |
| Just cycle | bike-only | 1 | foot / bicycle / foot · no transit ⇒ [bike] |
| Just drive | car-only | 1 | foot / car / foot · no transit ⇒ [car] |
| Just scooter | scooter-only | 1 | foot / scooter_rental / foot · no transit ⇒ [rental scooter] |
| Own car, Park&Ride | car-park-ride | 1 | car_park / foot / foot ⇒ [car, park → transit → walk] + [walk] |
| Rental car | car-rental | 1 | car_rental / car_rental / car_rental ⇒ [rental car → transit → rental car] + [rental car] |
| Transit only | transit-only | 1 | foot / (unset) / foot ⇒ [walk → transit → walk] |
Same presets as option 1, same counts (see the table), one native select. In the demo app the list grows on demand: the main choices and "More…" first, the full list in groups once asked, custom last (the select rebuilds and reopens itself). Here the list is shown as the demo shows it once "More…" was picked: fully open, in groups. Picking "Custom…" reveals the three trip parts underneath, as in the demo.
| preset | modePreset | queries | accessMode / directMode / egressMode ⇒ result shapes |
|---|---|---|---|
| Public transit | transit-only | 1 | foot / (unset) / foot ⇒ [walk → transit → walk] |
| Bike | bike-only | 1 | foot / bicycle / foot · no transit ⇒ [bike] |
| Car | car-only | 1 | foot / car / foot · no transit ⇒ [car] |
| Scooter | scooter-only | 1 | foot / scooter_rental / foot · no transit ⇒ [rental scooter] |
| Walk | walk-only | 1 | foot / foot / foot · no transit ⇒ [walk] |
| On-demand transport | on-demand | 1 | flexible / flexible / flexible ⇒ [on-demand → transit → on-demand] + [on-demand] |
Google style: pick the mode, then refine it in place. The toggles are additive, "enable on top of": own vehicle stays, rental and parked are extra shapes, each one another OTP 2 query. Scooter is rental only, so no toggle. Both toggles on = the multimodal preset (one call, three queries); one toggle = two presets, two calls. Every toggle adds a query, so it adds a page cursor to move along with the others.ℹ see: one cursor for many queries, how the preset recipe pages Compare with option 5 where the switch replaces the shape instead.
| mode + toggles | modePreset | queries | accessMode / directMode / egressMode ⇒ result shapes |
|---|---|---|---|
| Walk & transit | public-transit | 1 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] |
| Bike | bike | 1 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] |
| Bike + rental | bike + bike-rental | 2 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] bike_rental / bike_rental / bike_rental ⇒ [rental bike → transit → rental bike] + [rental bike] |
| Bike + Park&Ride | bike + bike-park-ride | 2 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] bike_park / bicycle / foot ⇒ [bike, park → transit → walk] + [bike] |
| Bike + both | bike-multimodal | 3 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] bike_park / bicycle / foot ⇒ [bike, park → transit → walk] + [bike] bike_rental / bike_rental / bike_rental ⇒ [rental bike → transit → rental bike] + [rental bike] |
| Car | car | 2 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] foot / car / foot · no transit ⇒ [car] |
| Car + rental | car + car-rental | 3 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] foot / car / foot · no transit ⇒ [car] car_rental / car_rental / car_rental ⇒ [rental car → transit → rental car] + [rental car] |
| Car + Park&Ride | car + car-park-ride | 3 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] foot / car / foot · no transit ⇒ [car] car_park / foot / foot ⇒ [car, park → transit → walk] + [walk] |
| Car + both | car-multimodal | 3 | foot / car / foot · no transit ⇒ [car] car_park / foot / foot ⇒ [car, park → transit → walk] + [walk] car_rental / car_rental / car_rental ⇒ [rental car → transit → rental car] + [rental car] |
| Scooter | scooter-rental | 1 | scooter_rental / scooter_rental / scooter_rental ⇒ [rental scooter → transit → rental scooter] + [rental scooter] |
Variation: the mode list stays clean, one switch below applies to the chosen mode (greyed out for walk, rental only for scooter) and replaces the shape rather than adding one. "Own + rental at once" is what option 4 does, at the cost of extra queries. Every position is exactly one modePreset; only own car runs two queries and inherits their paging caveat.ℹ see: one cursor for many queries, how the preset recipe pages
| mode · vehicle | modePreset | queries | accessMode / directMode / egressMode ⇒ result shapes |
|---|---|---|---|
| Walk & transit | public-transit | 1 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] |
| Walk & transit | public-transit | 1 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] |
| Walk & transit | public-transit | 1 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] |
| Bike · own | bike | 1 | bicycle / bicycle / bicycle ⇒ [bike → transit → bike] + [bike] |
| Bike · rental | bike-rental | 1 | bike_rental / bike_rental / bike_rental ⇒ [rental bike → transit → rental bike] + [rental bike] |
| Bike · Park&Ride | bike-park-ride | 1 | bike_park / bicycle / foot ⇒ [bike, park → transit → walk] + [bike] |
| Car · own | car | 2 | foot / foot / foot ⇒ [walk → transit → walk] + [walk] foot / car / foot · no transit ⇒ [car] |
| Car · rental | car-rental | 1 | car_rental / car_rental / car_rental ⇒ [rental car → transit → rental car] + [rental car] |
| Car · Park&Ride | car-park-ride | 1 | car_park / foot / foot ⇒ [car, park → transit → walk] + [walk] |
| Scooter · rental | scooter-rental | 1 | scooter_rental / scooter_rental / scooter_rental ⇒ [rental scooter → transit → rental scooter] + [rental scooter] |
One planner-v2-1 query = one [access → transit → egress] + [direct] pair, both shapes come back together, unless the direct mode is fast enough to trigger the transit-vs-street filter (car, taxi): then the transit shapes need their own query. Several queries means merging, deduping and paging them together: the preset recipe does it server side behind one modePreset and one pageCursor (planner-v2 merges up to 9 queries too, but leaves one cursor per query to the client).
| Option | Queries per search | Why |
|---|---|---|
| Settings · user preferences | +0 | Per-request parameters (pace, caps, transfers, accessibility, subscriptions, language) shared by every query of the search: they change the itineraries, never the number of queries. |
| Settings · restrict modes, as the only control | 1 + allowed kinds | Walk & transit by default = 1. Every extra allowed street mode is another vehicle kind to query for, up to 10 with everything allowed: planner-v2 again. Used as a filter on top of the other options it costs nothing. |
| 1 Monomodal presets (+ more, + custom) | 1 | A preset is one triple and the vehicle presets are the -only variants (transit off), so nothing needs a second query and every itinerary carries the vehicle asked for. Vehicle + transit combinations live in option 2 or in custom. |
| 2 Multimodal presets | 1 to 3 | Bare vehicle name = every way to use it with transit: Bike and Car run 3 queries, walk, scooter, on-demand run 1. The single-query variants keep qualified names behind "more options". |
| 3 Simple select with progressive reveal | 1 | Same presets as option 1, same count, different widget. |
| 4 Rent and park toggles, additive | 1 to 4 | Bike: own + rental + parked = up to 3. Car: 2 by itself, up to 4 with both toggles. Scooter and walk: 1. |
| 5 Rent or park switches | 1, Car (own): 2 | Own / rental / parked is one triple by construction; only own car needs the extra transit query. |
transportModes, no query multiplication there.Rule of thumb: options 1, 3 and 5 keep planner-v2-1 at one query (two for own car in option 5), which is the whole point of the recipe versus planner-v2. The settings screen belongs on top of them: user preferences as parameters on every query, restrict modes as a filter. Option 4 and custom-with-lists are the two places where the fan out sneaks back in; if a customer wants that, the honest answer is the preset recipe, or a client that fires 2 to 4 planner-v2-1 calls in parallel and shows them as separate tabs rather than merging them. One api follow-up would remove the remaining exception: a product-grade way to keep transit next to a car trip in one query (a keepTransitAlternatives flag mapping to listAll with the notices stripped server side).