The Problem
I’m part of the Whitchurch-Stouffville community, and like many residents, I rely on our local public library. But there’s a challenge: Stouffville has the smallest collection among York Region’s 9 public libraries—only about ~35,000 books compared to Markham’s 278,000 or for that matter Aurora/Newmarket/Georgina of comparable population. Population stats here.
When I can’t find what I need locally, I switch to searching Markham’s catalog, since we have a subscription with them too. Each search means:
- Opening a new browser tab
- Typing the same query again
- Comparing results manually
- Remembering which library had what
It’s tedious. There had to be a better way.
The Discovery
All York Region libraries use BiblioCommons, an excellent platform that powers library catalogs for over 300 public libraries worldwide. Each library has its own BiblioCommons site:
- Stouffville:
wsplibrary.bibliocommons.com - Aurora:
aurora.bibliocommons.com - Markham:
markham.bibliocommons.com - Newmarket:
newmarketpl.bibliocommons.com - ...and 5 more
BiblioCommons offers fantastic features on each site: staff curated lists, author picks, community reviews, patron ratings, reading recommendations, event calendars, and more. It’s genuinely well-designed software.
But there was no way to search across multiple libraries at once.
The Question
I asked Claude (Anthropic’s AI assistant) via Kilo:
“Is there a way I can execute a search across all the libraries to find what I need? Are there any open source applications that does this or should I build it?”
Research Phase
Claude researched existing solutions:
What Exists:
- Library Extension - Browser extension that shows availability on Amazon/Goodreads
- Bibliophile - Checks Goodreads “to-read” shelf against BiblioCommons libraries
- Neither did what I needed: a unified search across my specific York Region libraries
Build Options Considered:
- ⚡ Bookmarklet: One-click opens 4 tabs (quickest)
- 📄 Local HTML page: Search form opening multiple tabs (simple)
- 🚀 Web/Mobile App: Aggregates results into one view (best UX)
The Breakthrough: Finding the Internal API
Here’s where it got interesting. I discovered that BiblioCommons uses an internal JSON API (they do not have a public-facing API documentation - that I could find):
POST https://gateway.bibliocommons.com/v2/libraries/{subdomain}/bibs/search
Body: {"query":"search term","searchType":"smart","view":"grouped"}
This wasn’t publicly documented, but it’s what powers their web interfaces. And it returns beautiful, structured JSON with everything we need:
- Search results
- Availability status
- Format information
- Holdings across branches
- And more
Mapping All 9 Libraries
With the API endpoint discovered, we mapped all York Region libraries:
Building the App
With AI as my pair programmer, we built a cross-platform solution:
- Web App: React + TypeScript
- Android App: Flutter
- Linux Desktop: Flutter
The Architecture
1. User enters search query
2. App queries all 9 libraries in parallel
3. Results aggregated and deduplicated
4. Displayed with availability status
5. User can filter by library, format, or availability
Simple. Fast. Effective.
Linux App

Mobile demo
The Stats Challenge: No Direct API
The PM in me introduced a scope creep - having solved the primary need - search across libraries, I turned to data. I wanted to know how many items are there in each and across all the libraries? Also, read the stats disclaimer about extrapolation of this data.
Here’s where it got technically interesting. I wanted to show collection statistics—how many books each library has, broken down by format. But BiblioCommons doesn’t have an endpoint for “total items in catalog.”
The Journey to Accurate Counts
Attempt 1: Query
'a'withsearchType: 'keyword'
- ❌ Only captured items with letter “a” in metadata
- Result: Vastly undercounted (~30k for Stouffville when books alone should be ~30k)
Attempt 2: Query
'*'withsearchType: 'keyword'
- ❌ Returned 0 results (wildcard doesn’t work with keyword search)
- ❌ No format field data returned
Attempt 3: Query
'*'withsearchType: 'smart'
- ❌ Returned counts but NO
fields.FORMATdata structure - We got numbers but no format breakdown
Final Solution: Query
'anywhere:(a the)'withsearchType: 'bl'
- ✅ Broad library search captures comprehensive results
- ✅ Returns accurate counts (or very close approximations)
- ✅ Includes format breakdown with fieldFilters
- ✅ Works consistently across all libraries
The Parsing Bug
Then we hit another bug. The logs showed:
Fields Available: ['0', '1', '2', '3', '4', ...]
NO FORMAT FIELD FOUND!
Wait... fields is an array? I assumed it was an object!
Looking at actual API responses revealed:
"fields": [
{"id": "FORMAT", "fieldFilters": [{"value": "BK", "count": 12345}, ...]},
{"id": "AUTHOR", "fieldFilters": [...]},
{"id": "STATUS", "fieldFilters": [...]}
]
The code was trying fields.FORMAT (object access) when it should have been finding the FORMAT element in the array. Fixed in both platforms by:
const formatField = fields.find(f => f.id === 'FORMAT');
What You Get
Grouped Format Display
Instead of showing raw format codes (BK, LPRINT, PICTURE_BOOK, GRAPHIC_NOVEL...), the app groups them logically:
---Made up numbers below---
- 📚 Books: 123,456 (combines hardcover, large print, picture books, graphic novels, board books, book+CD)
- 📱 eBooks: 45,678
- 🎧 Audiobooks: 23,456 (physical CDs + digital)
- 🎬 Movies & Video: 12,345 (DVD + Blu-ray + streaming)
- 📰 Magazines: 5,678 (print + digital)
- 🎮 Video Games: 1,234
- 🎵 Music: 3,456
Much cleaner than 16 individual format codes!
Opt-In Stats
Based on feedback, stats don’t autoload. Instead, you see a button: “View Collection Statistics”
Click it to see what’s available across all 9 libraries. This was important because:
- Stats aren’t the main purpose—search is
- The API call is somewhat expensive (queries all 9 libraries)
- Users searching for specific titles don’t need stats
- Stats are “scope creep”—nice to have, not essential
Acknowledging BiblioCommons
It’s worth emphasizing: BiblioCommons is excellent software. Their platform provides:
- Community Features: Patron reviews, ratings, shelves, user-generated lists
- Staff Curation: Librarians create themed lists, write reviews, highlight collections
- Event Management: Integrated program calendars with registration
- Reading Recommendations: Personalized suggestions based on your history
- Mobile Apps: Native iOS/Android apps for each library
- Enhanced Search: Smart algorithms that understand context
- Analytics: Libraries get insights into usage patterns
Each library’s individual BiblioCommons site is feature-rich and well-designed.
My app doesn’t replace any of that. It complements it by solving one specific problem: searching across multiple libraries when you have multiple memberships.
The Stats Disclaimer
About those collection numbers: they’re approximations. BiblioCommons doesn’t have a “give me your total item count” API endpoint.
Our method:
Query: anywhere:(a the)
Search Type: bl (broad library)
This broad query captures a comprehensive portion of each catalog while returning format breakdowns in the response. The counts aren’t perfect, but they’re representative and give you a sense of relative collection sizes.
But here’s the thing: the stats don’t matter much. That’s not why I built this. The core purpose is unified search. The statistics feature was scope creep—something I added because “why not?” and “it would be interesting to see.”
Hold that thought! What if stats matter to you? Because municipal elections are around the corner - how does this stats tie into the population, and in-turn, budget allocation and use, and there by influencing where your vote lies!
For searching specific titles across libraries? The stats are irrelevant. You just need the search results.
What’s Next?
Right now, this is a personal project running locally for my household. Both my partner and I use it regularly—it saves time and frustration.
But if there’s community interest, I could:
- Host it publicly for all York Region residents
- Add more features (hold management, reading history)
- Extend to other BiblioCommons library systems
- Open source the code
Try It Yourself
The app is available in three flavors:
- Web: Runs in any browser
- Android: Native mobile app
- Linux: Desktop application
All three stay in sync, sharing the same core logic and search algorithms.
Technical Deep Dive: The Stats Methodology
For fellow developers curious about the implementation:
The Problem
No direct API endpoint for “total items” or “format breakdown by count.”
The Solution
Use the search API creatively:
Query Crafting:
{
"query": "anywhere:(a the)",
"searchType": "bl",
"limit": 1
}
anywhere:(a the): Searches for items containing “a” OR “the” in any fieldsearchType: 'bl': Broad library search (captures more than ‘keyword’ or ‘smart’)limit: 1: We only need metadata, not actual results
Response Structure:
{
"catalogSearch": {
"pagination": {
"count": 278479 // ← Total matching items
},
"fields": [ // ← Array, not object!
{
"id": "FORMAT",
"fieldFilters": [
{"value": "BK", "count": 123456},
{"value": "EBOOK", "count": 45678},
{"value": "DVD", "count": 12345},
...
]
}
]
}
}
Key Discovery: fields is an array of field objects, not an object with named keys. This caused hours of debugging until we looked at actual API responses!
Grouping Logic:
// Combine related formats
Books = BK + LPRINT + PICTURE_BOOK + GRAPHIC_NOVEL + BOARD_BK + BOOK_CD
Audiobooks = AB + EAUDIOBOOK
Movies = DVD + BLURAY + VIDEO_ONLINE
// etc.
Caching:
- Stats cached for 24 hours locally
- Avoids hammering the API
- User can manually refresh if needed
Result: Approximate but representative collection statistics with format breakdowns, all without an official stats API.
Lessons Learned
- Undocumented APIs can be gold: BiblioCommons’ internal API is clean and well-structured
- Assumptions are dangerous: “Fields will be an object” cost us hours
- Logs are essential: Server-side logging revealed the array structure
- AI as pair programmer works: Claude helped architecture, debugging, and implementation
- Scope creep is real: Stats were “nice to have” that became a rabbit hole
- Test with real data: The difference between
'a','*', and'anywhere:(a the)'was huge
The Value Proposition
If you’re a York Region resident with memberships at Stouffville and Markham (or any combination), this app saves you:
- Time: One search instead of nine
- Frustration: No more tab juggling
- Discovery: See what’s available across the region
- Convenience: Available on web, Android, and desktop
For Stouffville residents especially, it opens up access to 1.5+ million items across York Region, not just our local 35,000.
Final Thought: Sometimes the best solutions come from personal frustration. I built this because I needed it. If it helps you too, that’s wonderful. If not, at least I learned a lot about library APIs, cross-platform development, and creative data extraction!
Built with Flutter, React, TypeScript, and Claude AI assistance. Not affiliated with York Region Libraries or BiblioCommons.