Getting data out of SharePoint without writing a throwaway script

Every team that runs on Microsoft 365 ends up with data trapped in SharePoint lists. A project tracker, an asset inventory, an intake queue, a register of approvals — the list does its job inside the SharePoint interface, and then one day you need to ask it a real question. How many items are still open? Who owns the ones that are overdue? Which rows have a blank status field that someone forgot to fill in?

That's the moment SharePoint stops being helpful.

The usual answers are all some version of building a small project to ask a small question. You export to Excel and get a snapshot that's stale the moment it lands, one you'll re-export by hand next week. You build a Power Automate flow — a lot of clicking for something you need to run once. You write a Graph script, do the auth dance, page through the results, and throw the script away. Or you reach for PnP PowerShell: install the module, sign in, and try to remember the cmdlets you haven't touched since the last time you needed them. None of this is hard, exactly. It's friction, and it sits between you and a question you could answer in five seconds if the data were in a database.

So I made it behave like one.

xql is a command-line tool that runs real SQL against a SharePoint list. You point it at a list, sign in once with your own Microsoft 365 account, and ask:

xql> SELECT Title, Owner, DueDate
     WHERE Status != 'Done' AND DueDate < '2026-06-01';

That's the whole thing — no flow, no script, no export. There's no FROM clause, either: xql is already pointed at one list, so you just ask. And the WHERE isn't filtering rows after they arrive — xql translates it into an OData query that runs on SharePoint's side, so a list with tens of thousands of items returns the handful you asked for instead of dragging every row across the wire.

It writes, too — SELECT, UPDATE, INSERT, DELETE, the four verbs you already know. And every write previews before it commits:

xql> UPDATE SET Status = 'Done' WHERE Title = 'Patch CVE in parser';
Would update 1 row in Tasks:
  SET Status = 'Done'
Sample:
| row | Title               |
| --- | ------------------- |
| 6   | Patch CVE in parser |
Apply? [y/N]:

xql tells you how many rows it would change and shows you a sample, then waits for you to say yes. You don't touch data you didn't mean to touch.

The same binary runs against a plain CSV file with the identical grammar. That sounds like a footnote, but it's the part I use most: I prototype a query against a CSV on my laptop — no network, no live data at risk — get it right, then point the exact same statement at the real list. The CSV and the SharePoint list are just two backends behind one language.

It's a single Go binary with nothing to install alongside it, open source under the MIT license. The install steps and the full grammar are on the xql page; you can have it running in about a minute.

I built xql because getting data out of SharePoint shouldn't mean writing a throwaway script every time, and because a tool this small had no business not existing. Under the convenience is a position I hold firmly: your data should be queryable where it lives. The further you have to move it before you can use it, the more of these little projects pile up — and the staler and more error-prone everything gets.

That gap, between where Microsoft 365 keeps your data and where you can actually work with it, is most of what I do for clients. SharePoint, Graph, Power Automate, and the seams between them. If your team would rather put that behind you for good than build another one-off, that's exactly the kind of work I like being brought in on.

Get in touch See xql →