The Problem: Excel Geeks Meet API Complexity
If you’ve ever worked with Xero’s accounting data, you know the platform is incredibly powerful. But here’s the frustrating part: sometimes you just need to answer a simple question.
“How many unpaid invoices do we have?”
“Which bank accounts have unreconciled transactions?”
“Show me all payments from the last 30 days.”
These should be quick, 30-second queries. Instead, you find yourself:
- Clicking through multiple Xero screens
- Exporting reports that almost (but not quite) show what you need
- Manually filtering data in Excel
- Or worse, asking someone to write a custom integration
For those of us who love Excel and think in terms of rows and columns, there had to be a better way.
The Solution: A Direct Line to Your Data
I built the Xero Query Tool as a simple, no-nonsense interface between you and the Xero API. Think of it as “SQL for accountants” — you select what you want to see, hit execute, and get clean tabular data ready for export.
What It Does
- Pre-configured Scenarios: Common queries like “Unpaid Invoices” or “Recent Payments” are ready to go — just one click.
- Direct API Access: Queries hit the Xero API directly with minimal overhead
- CSV Export: Every result can be exported to CSV for further analysis in Excel
- Aggregated Views: Special queries like “Unreconciled Bank Transactions” automatically group and sum data by account
- Session-Based Security: Your credentials never leave the session — no database storage, no permanence
How It Actually Works
The Architecture
The tool is built as a Next.js application with a clean separation of concerns:
┌─────────────┐
│ Browser │ User enters Xero Custom Connection credentials
│ (React) │ Selects scenario, sets parameters, executes query
└──────┬──────┘
│
▼
┌─────────────┐
│ API Route │ Validates credentials, manages authentication
│ (/api/xero) │ Acts as secure proxy to Xero API
└──────┬──────┘
│
▼
┌─────────────┐
│ Xero Auth │ Handles OAuth2 client_credentials flow
│ Module │ Caches tokens (5min buffer), discovers tenants
└──────┬──────┘
│
▼
┌─────────────┐
│ Xero API │ Returns raw data (invoices, transactions, etc.)
│ │
└──────┬──────┘
│
▼
┌─────────────┐
│ Transform │ Aggregates if needed (e.g., by bank account)
│ & Format │ Formats currency with locale awareness
└──────┬──────┘
│
▼
┌─────────────┐
│ Display │ Sortable table with CSV export
│ Results │ Regional formatting (£1,234.56 vs 1.234,56 £)
└─────────────┘
Key Technical Decisions
1. Session-Based Credentials
No database, no permanent storage. You enter your Xero Custom Connection credentials, they live in memory for the session, then they’re gone. This eliminates a huge security surface area.
2. Configuration as JSON
All query scenarios live in public/scenarios.json. Want to add a new query? Just edit the JSON file — no code changes needed:
{
"id": "unpaid-invoices",
"name": "Unpaid Invoices",
"endpoint": "https://api.xero.com/api.xro/2.0/Invoices",
"parameters": [{
"name": "where",
"type": "hidden",
"value": "Status==\"AUTHORISED\" AND AmountDue>0"
}]
}
3. Smart Aggregation
Some queries benefit from aggregation. For example, “Unreconciled Bank Transactions” needs to:
- Fetch all bank accounts (even those with no transactions)
- Group unreconciled transactions by account
- Show count and total balance per account
The tool handles this automatically when aggregateByAccount: true is set in the scenario.
4. Locale-Aware Formatting
Using Intl.NumberFormat with the browser’s locale settings means:
- UK users see
£1,234.56 - Australian users see
£1,234.56(same number format) - European users might see
1.234,56 £(regional grouping)
The currency stays GBP, but the formatting adapts to user preferences.
5. Hydration Safety
Next.js Server-Side Rendering can cause “hydration errors” when server and client render differently. We avoid this by:
- Using deterministic ISO date formatting (YYYY-MM-DD)
- Formatting numbers consistently on both sides
- Doing locale-specific formatting only AFTER data loads
Why I Built This
The Real Story
I needed to match 200 transactions from a CSV against Xero’s bank transactions. Specifically:
- Match by exact amount
- Date within ±14 days
- Exclude any PayPal accounts
- Extract bank account name and status
The Xero UI wasn’t designed for this kind of bulk matching. The API was perfect — but writing a one-off script felt wasteful. What if next week I need to query something else?
That’s when it clicked: What if I could configure queries without writing code each time?
The first version was a Python script. Then it grew into a web app. Now it’s a tool I use weekly for ad-hoc Xero queries.
Who This Is For
You might love this tool if you:
- ✅ Work with Xero data regularly
- ✅ Think in spreadsheets and CSV files
- ✅ Need quick answers that Xero’s UI makes complicated
- ✅ Want direct API access without writing code every time
- ✅ Value simple, focused tools over enterprise platforms
This probably isn’t for you if:
- ❌ You need real-time dashboards (this is for ad-hoc queries)
- ❌ You want to write data back to Xero (read-only by design)
- ❌ You need automated scheduled reports (manual execution only)
The Excel Geek Philosophy
This tool embodies a specific philosophy:
- Data should be accessible — If it’s in the system, you should be able to query it
- CSV is underrated — Sometimes you just need the raw data in a spreadsheet
- Configuration > Code — Add new queries by editing JSON, not writing JavaScript
- Session-based security — No permanent credential storage means fewer attack vectors
- Do one thing well — Query and export. That’s it.
Technical Highlights
Multi-Endpoint Queries
Some scenarios need data from multiple endpoints. The “Unreconciled Bank Transactions” query:
- Calls
/Accounts?where=Type=="BANK"to get all bank accounts - Calls
/BankTransactions?where=IsReconciled==falsefor unreconciled items - Merges the data client-side
- Shows ALL accounts (even those with zero unreconciled)
This happens automatically when you set:
{
"fetchAllAccounts": true,
"accountsEndpoint": "https://api.xero.com/api.xro/2.0/Accounts"
}
Integer vs Decimal Formatting
A subtle but important detail: when displaying aggregated counts vs. monetary amounts:
if (Number.isInteger(value)) {
return value.toString(); // Show "12" not "12.00"
}
if (Math.abs(value) >= 0.01) {
return value.toFixed(2); // Show "1234.56"
}
Counts display as integers (12 transactions), balances as currency (£1,234.56).
Custom Connection vs OAuth
Xero offers multiple authentication methods. I chose Custom Connection (client_credentials grant) because:
- ✅ No user redirect flow needed
- ✅ Simpler implementation for internal tools
- ✅ Long-lived credentials (user manages rotation)
- ✅ Perfect for server-to-server API access
The tradeoff? Users need to create their own Xero app. But the instructions make this straightforward.
Lessons Learned
1. Start Simple, Stay Simple
I was tempted to add features:
- User accounts and saved credentials
- Scheduled queries
- Dashboard visualizations
- Multi-org support
But every feature adds complexity. The tool’s power is its simplicity.
2. Configuration Files Are Powerful
By putting scenarios in JSON, I’ve made the tool extensible without code changes. Non-developers can add queries.
3. Regional Formatting Matters
Early versions hardcoded UK formatting. Then I tested with users in other regions — their browser settings expected different number formats. Intl.NumberFormat solved this elegantly.
4. Hydration Errors Are Sneaky
Next.js SSR means the server renders HTML, then React “hydrates” it on the client. Any difference causes errors. Locale-specific formatting (toLocaleDateString()) was the culprit — server and client produced different dates. Solution: deterministic ISO dates for SSR, locale formatting only after hydration.
5. Documentation Is Part of the Product
Including QUERY_CONFIGURATION.md with examples means users can extend the tool themselves. The best products teach users to be self-sufficient.
Try It Yourself
The tool is straightforward to set up:
-
Get Xero credentials:
- Go to developer.xero.com
- Create a “Custom Connection” app
- Copy your Client ID and Secret
-
Run the tool:
- Enter your credentials (session-only, not stored)
- Select a scenario from the dropdown
- Hit “Execute Query”
- Export results to CSV
-
Customize scenarios:
- Edit
nextjs_space/public/scenarios.json - Add your own queries
- Refresh browser — changes take effect immediately
- Edit
You can self host this, clone the repo at https://github.com/cclambie/xero-query-tool# or I will put this somewhere publicly accessible too.
What’s Next?
I’m keeping this tool minimal by design, but a few enhancements make sense:
- Date range presets for more scenarios (YTD, Last Quarter, etc.)
- Column filtering in the results table
- Dark mode (because why not?)
- Query templates for common patterns
But the core will stay the same: query Xero data, export to CSV, keep it simple.
Final Thoughts
This tool saved me hours of clicking through Xero’s UI or writing one-off scripts. If you’re an Excel geek who works with Xero, I hope it saves you time too.
The beauty of simple tools is they solve real problems without creating new ones. No user accounts, no database, no subscription fees — just you, your data, and a direct line to the Xero API.
Sometimes the best software is the kind that gets out of your way.
Built with: Next.js 14, TypeScript, Xero API v2.0
License: MIT
Author: An Excel geek who got tired of clicking
Status: In active use, battle-tested on real accounting data
Have questions or built something similar? I’d love to hear about it. The challenge of making APIs accessible to non-developers is endlessly fascinating.
Latest posts by Craig Lambie (see all)
- Building a Simple Xero Query Tool: Because Sometimes You Just Need the Data - January 6, 2026
- Why I’m Going Open Source with My Social Media and Browser - March 22, 2025
- Open Source as Resistance to Technofeudalism - March 15, 2025
