Authoring GTFS-Flex with GTFS·X
GTFS-Flex is an optional extension to the baseline GTFS specification that makes demand-responsive service — dial-a-ride, microtransit, deviated fixed-route — discoverable in trip planners. If you're new to the extension, start with the What is GTFS-Flex? primer. This page is the authoring guide: the Flex Zones panel lets you draw polygon zones, configure booking rules in a form, and validate before export.
When to use it
Flex applies any time service isn't a fixed route running on a fixed schedule:
- Microtransit / on-demand zones. A defined service area where riders book a ride and get picked up at their location (or a generated meet point).
- Dial-a-ride / paratransit. Same shape but oriented to ADA-eligible riders. The booking rule fields cover the prior-notice and contact requirements.
- Deviated fixed-route. A bus that runs a published route but will detour up to a quarter mile on request. Modeled as a hybrid: fixed-route stops plus a flex zone for the deviation area.
- Demand-responsive connectors from a fixed line to a low-density area.
Three ways to create a zone
Click + Add flex zone, then pick one of the three paths under Create new flex zone:
- Draw Zone on Map. Click to place polygon vertices, double-click to close. Exports as
locations.geojson. Multi-polygon zones (two disjoint service areas under the same booking) are supported: use + Polygon in the zone's Details to draw another polygon into the same zone. All of a zone's polygons export as oneMultiPolygonfeature. - Create Stop Group. A named group of existing stops, used when the "zone" is really a list of allowed pickup/drop-off points rather than a free area. Exports as
location_groups.txt+location_group_stops.txt. - Auto-generate from fixed routes. Buffer your existing bus routes by a configurable distance (default 0.75 mi) to produce a polygon zone. Useful for deviated fixed-route service where the deviation area is "X miles either side of the route."
Creating a zone also creates its paired route (route_type 715). You never add the route yourself.
Configuring a zone
Click Details on a zone to open its configuration. The fields, by section:
- Service area. The polygon area(s) and/or the stop group that make up the zone. A zone can carry both (a mixed zone): the polygon exports to
locations.geojson, the stop group tolocation_groups.txt, and the same flex trip references both. - Pickup start / Pickup end. Free-text
HH:MM:SS. Required: a zone with no pickup window is skipped entirely on export (no geometry, no booking rule, no trip). This one window covers both pickup and drop-off, and exports asstart_pickup_drop_off_window/end_pickup_drop_off_window. - Service pattern. Pick from your calendars. calendar_dates-only services are fine (a zone that only runs on event days), and their exceptions apply automatically. If the feed defines exactly one service, it is pre-picked for you.
- Booking. Writes a record in
booking_rules.txt: booking type (Real-time, Same-day, Prior day), prior-notice minutes (Same-day) or day/time cutoff (Prior day), booking phone, booking URL, info URL, and the rider message. The first time you touch a booking field, the phone and info URL are seeded from youragency.txtrecord; edit or clear them as needed. - Additional service windows. Multiple windows per zone (morning + evening shifts). Each extra window becomes its own flex trip, and can use its own service pattern.
- Travel-time estimation (advanced).
Safe duration factorandSafe duration offset (s), written totrips.txtassafe_duration_factor/safe_duration_offset. They let a trip planner pad its ETA for an on-demand leg so it doesn't suggest impossibly tight connections. Leave blank if unsure. - Fare. Link to a fare defined in the Fares panel. It exports as a
fare_rules.txtrow against the zone's paired route.
Continuous pickup / drop-off is not a zone field: it is set on the route (Routes panel) or per stop_time, and applies to deviated fixed-route hybrids where riders flag the bus down along a corridor.
Microtransit
Microtransit (an on-demand zone a rider books into, with no fixed stops and no fixed times) is the simplest thing Flex models. In GTFS·X it is one flex zone, one trip carrying a pickup/drop-off window, and one booking rule. Because a rider gets on and off anywhere inside the same area, the trip's stop_times reference that one zone twice: the spec requires two records for travel within a single location, so a one-row flex trip is invalid.
Each thing you fill in maps to one file in the exported feed:
| In the editor | What it writes |
|---|---|
| Draw the zone (and name it) | locations.geojson: one Feature for the zone, with the zone id at the top level of the Feature and only stop_name / stop_desc in properties. Several polygons in one zone merge into a single MultiPolygon Feature. |
| Pickup start / Pickup end | stop_times.txt: start_pickup_drop_off_window and end_pickup_drop_off_window on both rows of the flex trip. |
| Service pattern | trips.txt: the flex trip's service_id. |
| Booking fields | booking_rules.txt: one row for the zone, referenced from the stop_times rows via pickup_booking_rule_id / drop_off_booking_rule_id. |
| Travel-time estimation (advanced) | trips.txt: safe_duration_factor / safe_duration_offset (these are trips.txt fields, not stop_times fields). |
| The paired route (created for you) | routes.txt: route_type 715, Demand and Response Bus Service. |
Adding a microtransit zone to an existing feed
- Confirm demand response is enabled. Settings in the left rail opens Feature settings; the Demand response / paratransit card is on by default. If someone turned it off, turn it back on: while it's off, the Flex Zones panel is hidden.
- Open Flex Zones in the left rail. The panel is Flex Zones & Rules, and existing zones are listed under Service areas.
- Click + Add flex zone, then Draw Zone on Map. The map banner reads "Drawing Flex Zone — Click to add vertices, double-click to close polygon." Click once per corner of the service area and double-click to place the last vertex and close the polygon. The zone appears in the list, and its paired route is created at the same time.
- Name the zone. New zones arrive as "Zone 2", "Zone 3", and so on. Click the name, type the rider-facing name (for example "East Side Microtransit"), and press Enter. The paired route's short name follows the zone name, so renaming here renames both.
- Click Details and set the service window. Under Service schedule, enter
Pickup start(say08:00:00) andPickup end(say18:00:00). These are free-textHH:MM:SSfields, and they are what makes the zone exportable. - Pick the service pattern. Choose the calendar the zone runs on (weekdays, weekends, a dates-only service). The flex trip is written against that
service_id. - Fill in the booking rule. Under Booking, pick Real-time, Same-day, or Prior day. Same-day exposes
Min minutes ahead(60, for "call an hour ahead"); Prior day exposes a day/time cutoff. Then set the booking phone, booking URL, info URL, and the rider message riders will actually read ("Call at least 1 hour before your pickup"). The phone and info URL are pre-filled from your agency record the first time you touch this section. - Check the paired route. Open the Routes panel: the zone's route is in the Flex section, and its Route type reads Demand and Response Bus Service (
route_type715). Nothing to change, but the route color you set here is the color the zone is drawn in on the map. - Validate. Open Validation in the bottom bar and clear anything flagged. Errors block export.
- Export GTFS. The export dialog's file list should now include
locations.geojson(with your zone counted) andbooking_rules.txt.
route_type 715).What to look for in the zip
Open the exported zip and check four things:
stop_times.txthas two rows for the zone's trip, both with the samelocation_id, atstop_sequence1 and 2, both carrying the window, and with noarrival_timeordeparture_time. Thelocation_idis the zone's id; it is never surfaced in the editor.
trip_id,stop_sequence,location_id,start_pickup_drop_off_window,end_pickup_drop_off_window,pickup_type,drop_off_type,pickup_booking_rule_id,drop_off_booking_rule_id
flex-zone-1752345678901-trip,1,flex-zone-1752345678901,08:00:00,18:00:00,2,2,flex-zone-1752345678901-booking,flex-zone-1752345678901-booking
flex-zone-1752345678901-trip,2,flex-zone-1752345678901,08:00:00,18:00:00,2,2,flex-zone-1752345678901-booking,flex-zone-1752345678901-booking
locations.geojsonhas one Feature for the zone, with"id"at the top level of the Feature (not insideproperties), matching thelocation_idabove. A zone you drew as three polygons is still one Feature, with aMultiPolygongeometry.booking_rules.txthas the zone's rule, and itsbooking_rule_idis what the two stop_times rows point at.routes.txtcarriesroute_type715 for the paired route, andtrips.txtcarriessafe_duration_factor/safe_duration_offsetif you set them.
What Flex does not cover
A flex feed is a static description of where the service runs, when it runs, and how to book it. It is not a booking API. Publishing one does not make your service bookable from a trip planner, and it says nothing about what is happening right now:
- No real-time vehicle availability. Nothing in the feed says whether a vehicle is free, how many seats are left, or how long the wait is.
- No dynamic pricing. Fares are the static ones you assign; surge or distance-based on-demand pricing has no representation.
- No live booking. Riders book the way your booking rule tells them to (a phone number or your booking URL). A trip planner can show the rule and deep-link to your booking page; it cannot reserve the ride.
Live availability and in-app booking are the domain of GOFS and GTFS-OnDemand, which are separate specifications from GTFS-Flex. Set expectations accordingly when you tell riders the service is "in the app": what they will see is the zone, the hours, the fare, and how to call.
Paratransit and microtransit
GTFS does not distinguish between them. The words "paratransit" and "microtransit" appear nowhere in the spec, whose only vocabulary for this is "demand-responsive". Both are encoded exactly the same way: a locations.geojson zone, stop_times rows carrying a pickup/drop-off window instead of times, a booking_rules.txt entry, and route_type 715. There is no paratransit-specific route type, and no flag anywhere that marks a zone as one or the other. That is why the feature setting is called Demand response / paratransit and this page calls it microtransit: they are the same machinery.
What differs is convention, not fields:
| Paratransit | Microtransit | |
|---|---|---|
| Booking type | Usually Prior day (book by a cutoff the day before) | Usually Real-time or Same-day |
| Zone shape | Usually the ADA corridor around the fixed routes | Usually a free-standing service area |
| Fare | Often a separate, higher fare | Usually the standard fare |
If you are modeling ADA complementary paratransit, Auto-generate from fixed routes is the path you want: its default buffer of 0.75 miles is the three-quarter-mile corridor the ADA requires around each fixed route, so the generated zone is the corridor.
The one thing to get right: rider eligibility is not machine-readable. Paratransit is restricted to certified riders; microtransit is open to everyone. GTFS has no field for that distinction anywhere, so a trip planner consuming your feed cannot tell that a paratransit zone is closed to the general public, and may route an ordinary rider into it. (rider_categories.txt and its eligibility_url are a Fares v2 concept about who qualifies for a fare, such as a senior discount. They do not say who is permitted to board.)
The only place to state the restriction is prose that riders read and machines ignore. Put it in the booking rule's Rider message (or the pickup / drop-off message), and in the route description, in plain terms: who is eligible, and how to get certified. Assume nothing downstream enforces it.
Map popups and editing
Click a zone on the map to get a popup with quick links to Edit Route (every flex zone has an automatically-paired route that appears in trip planners) and Edit Service Details. Zone fill color tracks the linked route's color, so styling a zone consistently is a matter of styling its route.
What gets exported
Flex export produces multiple files depending on the zone types:
locations.geojson: polygon zones, one Feature per zone, carrying the id at the top level of the Feature (not inproperties, which holds onlystop_nameandstop_desc). A zone drawn as several polygons is a singleMultiPolygonFeature, because the stop_times rows reference the zone by one id.booking_rules.txt: every booking rule referenced by an exported zone.location_groups.txt+location_group_stops.txt: for stop-group zones.routes.txt: each zone's paired route, atroute_type715 (Demand and Response Bus Service).trips.txt: one flex trip per service window, plussafe_duration_factor/safe_duration_offsetwhen set.- Extended
stop_times.txtcolumns:location_id(orlocation_group_id),start_pickup_drop_off_window,end_pickup_drop_off_window,pickup_booking_rule_id,drop_off_booking_rule_id. A flex trip gets two rows, withpickup_type=2anddrop_off_type=2and no arrival or departure time. For a single-area zone both rows carry the samelocation_id(travel within one location requires two records); for a mixed polygon + stop-group zone, row 1 is the polygon and row 2 is the group.
Zones missing a pickup window are skipped from the output entirely: no geometry, no booking rule, no trip. Give every zone a window before publishing, since a flex zone without one won't function in trip planners.
Edge cases and gotchas
- You can't import a zone boundary file. There is no GeoJSON or shapefile upload: the editor's file input takes a GTFS
.ziponly. Draw the boundary on the map, buffer your existing routes, or import a feed that already contains alocations.geojson. - A zone with no pickup window is silently absent from the zip. It isn't exported as an empty zone; it's skipped whole (geometry, booking rule, and trip). If a zone you expected is missing from
locations.geojson, check itsPickup start/Pickup endfirst. - Booking rule sharing is the right default. If five zones all use the same phone-based booking with the same prior-notice rules, define one booking_rule and link all five zones to it. The editor lets you reuse rules across zones.
- Polygon zones should not extend beyond your actual service area. A zone that reaches into a neighboring agency's territory will get suggested by trip planners as if you served it.
- Don't combine deviated-fixed-route flex with regular fixed-route shapes carelessly. If the same trip is both fixed-route and deviation-eligible, the trip needs to reference the flex zone in its stop_times, not be a separate trip.
- Some consumers don't render flex yet. Google Maps does; Apple Maps does partially; OpenTripPlanner does; many regional trip planners don't. The flex data you publish is correct, but you may need to communicate availability through other channels too.
- Travel-time estimation matters more than it sounds. A bad estimate produces missed connections downstream. Pad the "safe" factor on shared-ride services that have a lot of zigzag.
See also
- What is GTFS-Flex? — full primer on the spec extension: why publish, who needs it, tooling, and further reading.
- Routes & shapes — every flex zone has a paired route.
- Fares — zone-fare assignment.
- Validation — surfaces flex-specific errors.