A Google Sheet is not a database, until you need it to behave like one.
When a small organization runs on spreadsheets that work, the answer is often not to migrate. It is to give the spreadsheet a better front door.
There is a specific kind of small-organization problem that never makes it into software design courses. A team has been running on spreadsheets for years. The spreadsheets work. People know where things are. Then the organization decides it needs to do something more with the data, and the question becomes whether to migrate into a real system or build something that meets the spreadsheet where it lives.
I spent a large portion of this past week on the second option, and the experience clarified something about what these projects actually are.
How the OPO sponsorship system came together
On Purpose Oregon runs a 16-person board with a paid executive director. Nobody on that board had a system for tracking 500-plus sponsorship prospects. They had a spreadsheet. They had some historical records of who had donated in the past. They had a dream list that a few board members had assembled from memory and relationships.
The work started as research strategy: figure out a regional weighting approach for Oregon-based prospects, build a scoring methodology that could reflect cause fit and not just revenue size, and populate the list with enough candidates to give the committee real options. That part of the work is documented in the prospect-database-build playbook I keep for this kind of engagement. It is not glamorous work. It is a lot of source-by-source enrichment across Apollo, RocketReach, Hunter, Serper, and ProPublica, and then a Claude-driven synthesis pass that produces an 11-section brief for each company.
The part I want to write about here is what happened after.
What a dashboard on top of a spreadsheet actually does
The CRM-light that went live on top of the OPO sponsor prospects sheet is a Next.js application that reads from and writes to a Google Sheet via the Sheets API. Operators connect their Gmail, compose outreach from inside the dashboard, and the system polls for replies and logs them to the activity feed. The sponsorship tier for each prospect, their assigned owner, their status, and every piece of outreach lives in the sheet, which means any board member with sheet access can still see everything the old way if they want to.
That last part matters. The team did not have to change how they work. The dashboard just gave them a better interface for the parts that were hard: filtering 548 companies by tier and region, bulk-assigning ownership, reading enriched research briefs without hunting through tabs, and tracking who reached out to whom without asking Wes or Mike directly.
The observable result from the methodology page, which I rewrote this week for external readers: a team that started Friday with 20 dream-list companies and 63 historical donors ended the weekend with a 548-company prospect database, tiered by research-rated scores, briefed at full or lean depth depending on tier, and wired into a functional CRM that pulls from the same sheet it has always used.
The pattern that repeats
This is the second time in recent months I have built a gated dashboard on top of a spreadsheet for a mission-driven organization with a small staff and an existing Google Workspace. The first was the OPO resource library, which is a simpler Tier 1 build: a publicly accessible microsite with gate authentication, pulling from a CSV, no write-back. The sponsor prospects dashboard is Tier 2: full read-write, multi-operator, Gmail integration, session-authenticated, deployed on Vercel with Upstash KV for token storage.
This week I also wrote the shell playbook for both tiers, ran it through two blind reproduction tests with a fictional client to close the gaps, and got it to a point where a developer who has never seen the work could produce a working Tier 1 build in four to six hours from the docs alone.
The playbook exists because I kept seeing the same shape of problem. An organization has data in a spreadsheet that nobody can act on from the spreadsheet itself. The data is fine. The interface is the problem. A dashboard gives the data an interface without requiring a migration, without requiring a new database, without asking a small nonprofit’s ED to learn a new system from scratch.
What the Gmail OAuth piece taught me
The CRM-light build included Gmail OAuth for outreach, which meant navigating the Google Cloud Console unverified app warning that appears when operators try to connect their accounts. This is the single moment where a board member clicks “Connect Gmail,” sees the word “unsafe,” and closes the browser.
The playbook now has an explicit section on this: what the screen looks like, what the exact button text says to proceed past it ( “Show advanced options,” then “Go to [app name] (unsafe)”), and why Google shows it for any internal tool that has not gone through their full verification process. The warning is real and accurate about what it means, which is that Google has not reviewed the application. For an internal tool used by five operators at a single nonprofit, verification is unnecessary overhead. But operators need to be told that before they see the screen.
I filed this as the highest-priority fix from the client-success review because it is the one moment where the entire system fails without the user doing anything wrong.
The rest of the week in brief
The artifact engine got a significant architecture decision this week. The original plan was to ship B Corp annual reports as polished PDFs using a Typst typesetting pipeline. After looking at the output against exemplars from Cotopaxi, King Arthur Baking, and Patagonia, I pivoted to Astro with Tailwind v4 and Vivliostyle for PDF derivatives. The decision was not about the current report quality. It was about what the engine needs to be: a pluggable platform for many artifact types, not a single-template PDF generator. The Astro renderer is now in place, the annual impact report is ported as the proof-of-life template, and the rendering platform v1 shipped this week.
The kariops.com site picked up a meaningful homepage revision: section-reveal rhythm on scroll, a portrait-split hero layout, buyer-problem copy replacing the title-first hero I had been running, and the Begin Here rename from Start Here. Lighthouse at 91 on performance on production.
The client-stack codebase got a hardening pass that included stripping PSBA-specific CSS tokens from the admin components so the engine can serve any consumer’s brand kit without the next client getting Portland State Business Accelerator colors in their backend. This was a reproduction-test finding from earlier in the week: a developer following the playbook would have landed with PSBA’s design language in a project that had nothing to do with PSBA.
What I keep coming back to
The spreadsheet-to-dashboard pattern works because it does not ask the organization to change what they already own. It asks them to use a better front door. For a nonprofit running on volunteer board hours and a single paid staff member, that is often the right level of intervention. A full CRM migration has a cost in time, money, training, and institutional knowledge transfer that the organization may not be able to absorb. A gated dashboard on top of their existing sheet has almost none of those costs.
The work I am most interested in is finding the other shapes of this problem. What else do mission-driven operators have in spreadsheets that would benefit from a better interface, without requiring them to change the underlying data or the people who maintain it?