A minimalist web application for searching and downloading GTFS (General Transit Feed Specification) feeds from US transit agencies.
- Live Search: Search agencies by name, city, or state with real-time results
- Agency Pages: Direct access via
gtfs.co/[ntd-id]
format - Feed Filtering: Filter by transit mode and service type
- Bulk Downloads: Download all feeds for an agency or filtered selection
- Clean URLs: Pretty URLs like
gtfs.co/40028/mb-do
for individual feeds - Direct Downloads: One-click downloading without page navigation
- Frontend: HTML, CSS, Vanilla JavaScript
- Database: Supabase (PostgreSQL)
- Hosting: Netlify
- Icons: Lucide
- Analytics: Umami
gtfs-co/
├── index.html # Landing page with search
├── agency.html # Agency detail page
├── feed.html # Feed download page
├── styles.css # Unified styles
├── script.js # Search functionality
├── agency.js # Agency page logic
├── feed.js # Feed download logic
├── sync-node.js # Data sync script
├── netlify.toml # Routing configuration
└── .gitignore # Git ignore file
- Node.js (for data sync)
- Supabase account
- Netlify account
- GitHub repository
- Create a Supabase project
- Run the table creation SQL:
CREATE TABLE gtfs_agencies (
id SERIAL PRIMARY KEY,
ntd_id TEXT,
agency_name TEXT,
city TEXT,
state TEXT,
url TEXT,
weblink TEXT,
mode TEXT,
tos TEXT,
reporter_type TEXT,
organization_type TEXT,
service_area_population INTEGER,
service_area_sq_miles DECIMAL,
voms INTEGER,
npt_id TEXT,
legacy_ntd_id TEXT,
reporter_acronym TEXT,
doing_business_as_name TEXT,
primary_uza_name TEXT,
uza_name_list TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create search index
CREATE INDEX idx_gtfs_agencies_search ON gtfs_agencies USING GIN (
to_tsvector('english', COALESCE(agency_name, '') || ' ' ||
COALESCE(city, '') || ' ' ||
COALESCE(state, ''))
);
-- Create index on ntd_id
CREATE INDEX idx_gtfs_agencies_ntd_id ON gtfs_agencies (ntd_id);
-- Enable RLS and create policies
ALTER TABLE gtfs_agencies ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow public read" ON gtfs_agencies FOR SELECT USING (true);
CREATE POLICY "Allow public insert" ON gtfs_agencies FOR INSERT WITH CHECK (true);
CREATE POLICY "Allow public delete" ON gtfs_agencies FOR DELETE USING (true);
Update Supabase credentials in JavaScript files:
script.js
agency.js
feed.js
sync-node.js
Populate the database with USDOT API data:
node sync-node.js
This fetches ~1,554 records from https://data.transportation.gov/resource/2u7n-ub22.json
and processes JSON weblinks into clean URLs.
- Push to GitHub
- Connect Netlify to your repository
- Deploy with build settings:
- Build command: (leave blank)
- Publish directory:
.
The netlify.toml
handles routing:
/:ntdid/:mode
→feed.html
(direct downloads)/:ntdid
→agency.html
(agency pages)
- USDOT National Transit Database: https://data.transportation.gov/resource/2u7n-ub22.json
- Documentation: https://dev.socrata.com/foundry/data.transportation.gov/5ti2-5uiv
- Search:
gtfs.co
- Agency:
gtfs.co/40028
(Lee County) - Feed:
gtfs.co/40028/mb-do
(Lee County Motor Bus - Directly Operated)
- Clone repository
- Open with live server (VS Code Live Server, etc.)
- Test search and navigation
Re-run the sync script to refresh data:
node sync-node.js
The script automatically handles:
- JSON weblink parsing
- Duplicate removal (if unique constraints exist)
- Progress tracking
- Error handling
Umami analytics configured for:
- Page views
- Search interactions
- Download events
- Fork the repository
- Create feature branch
- Test thoroughly
- Submit pull request
GPL-3.0 License - see LICENSE file for details
- USDOT: For providing the National Transit Database API
- Transit Agencies: For maintaining GTFS feeds
- GTFS Community: For establishing the specification
For issues or questions:
- Open GitHub issue
- Check GTFS.org documentation
- Review USDOT API documentation