My CRM runs leads, quotes and contracts, and I'm keeping it. What it has never seen is the website side — who downloaded a guide, who booked a call, what the team invoiced, what a couple said anonymously. So I built one page for that. No database, no monthly fee.
My CRM does its job, and none of this is about replacing it. Leads, quotes, contracts, invoicing, the automations that chase a couple who hasn't signed yet — that all lives in my CRM, it works, and I'm not rebuilding any of it.
But a studio collects a great deal that never touches a CRM. Someone downloads a guide — that's an email. Someone books a call through my website — a different email, in a different thread. A second shooter sends an invoice — another one, which I would find again in November by searching their first name and hoping. A new team member fills in the onboarding form and their gear list goes wherever the others went. A couple leaves anonymous feedback after delivery, which by design has no name attached and so has nowhere to sit in a client record at all.
And there was a spreadsheet. I built it in January with real conviction, gave it colour-coded tabs, and opened it perhaps twice more that year. Every photographer I know has this spreadsheet. It is always beautiful and always three months out of date, because keeping it current means doing the same work twice — once when the thing happens, and again when you remember to write it down.
The obvious answer is to buy a second piece of software for the website side. There are good ones. But it would have been another subscription, another login, another system to learn, and — this was the part that stopped me — another place where my information would live, separate from the place it was already arriving.
I didn't have a data problem. I had a viewing problem.
Every one of those things had already been captured perfectly, at the exact moment it happened, by my own website. The booking form captured the booking. The guide form captured the email address. The invoice form captured the invoice. None of it was lost — it had simply been delivered to me as email, which is a terrible way to look at a list of two hundred things and a wonderful way to look at one thing.
That reframing is the entire post, really. The forms on my site all run through Netlify Forms — the form handling built into my hosting, which I was already paying nothing extra for. Every submission was sitting in an inbox I could reach programmatically. What was missing wasn't storage. It was a window.
There are only four moving parts, and none of them is a database. I rebuilt the whole page for a studio that doesn't exist so you can click through it — open the demo, then come back for how it works.
There's a page at /admin on my site. It's left out of my sitemap and carries a noindex tag, so search engines skip it entirely. It isn't linked from anywhere — no menu item, no footer link. You get there by typing the address, which means in practice you get there by already knowing it exists.
The page shows a single input. What you type gets sent away to be checked against a key stored in my hosting settings — it is never compared against anything written into the page itself. That distinction matters more than it sounds: a password written into a web page is not a password, because anyone can read the page. After the first successful unlock, my browser remembers it, so I sign in once per device rather than once per visit.
Behind the page sits a serverless function — a tiny program that only runs when it's called. It checks my key, and only if that passes does it go and ask my host's own API for every form on the site and every submission in it. Then it hands that back to the page as plain data. The credential it uses to make that request lives on the server and never travels to the browser, so it can't be lifted out of the page by someone poking around.
At the top, a row of stat cards — how many subscribers, how many bookings, how many of each thing. Below that, one table per form, newest first, so the most recent inquiry is the first row I see rather than something I scroll to find. At the bottom, links to my analytics and search console, because this is the page I open when I want to know how the business is doing.
Two details I'd defend to anyone building their own. My team roster is grouped by what people actually do — photography, video, assistant — rather than listed alphabetically as one long list, and each person shows their gear, their hourly rate, and whether they have a car. That last column looks trivial written down and is the one I check most, because a venue two hours upstate changes who I can realistically send. Sorting by role and surfacing those three facts turned a list into something I make decisions from.
The other is a single button that exports my newsletter list as a CSV file. One button, one job. It exists because the alternative — copying addresses out of an inbox by hand — is exactly the kind of task I would put off until the newsletter didn't get sent.
Nothing. Zero a month.
The form handling is included in my hosting's free tier. The serverless function is included in the same free tier, and a page that one person opens a few times a week doesn't come close to any limit worth thinking about. The page itself is a file on a site I was already paying to host. There is no database, so there is no database bill, and there is nothing to migrate if I ever move.
I want to be careful here, because "free" gets oversold. This saved me nothing on my CRM — I still pay for that, happily, because it does things this page doesn't and shouldn't. What it saved me was buying a second subscription just to see the half of my business the CRM was never going to hold.
This is a viewer, not a CRM, and it isn't trying to become one. It reads. It does not write. I can't edit a record, add a note to a couple, tag someone as a hot lead, move an inquiry through pipeline stages, or mark anything as done. If you want the thing that nudges you to follow up on Thursday, this is not it and won't become it without turning into a real application.
That constraint has an upside worth naming: because nothing on this page can change or delete anything, the worst thing a bug can do is show me the wrong number. I sleep fine with a read-only page in a way I wouldn't with one that could quietly wipe a year of inquiries.
And one shared password is exactly as strong as it sounds. For a solo studio — one person, one key, one device — it's proportionate. For a team of fifty it isn't remotely enough: there are no individual accounts, no way to give someone access to the team roster but not the client list, and no record of who looked at what. If more than a couple of trusted people need in, you've outgrown this pattern, and the right move is proper accounts rather than a longer password.
My honest read: build this, run it for six months, and you'll learn precisely which three things you wish it could do. That list is worth more than any feature comparison chart, because when you do go shopping for real software you'll be buying against your own requirements instead of somebody's marketing page.
Here's the whole thing. Open your website's project folder and paste this into Claude Code (or any AI coding agent) — it's written to be handed over as-is.
My website is a static site hosted on Netlify and my forms use Netlify Forms. Build me a private admin dashboard: 1. Create a page at /admin that is excluded from the sitemap and has a noindex meta tag. 2. Gate it with a password: an input that sends the key to a serverless function; store the key in localStorage after the first successful unlock. 3. Create a Netlify serverless function that (a) checks the key against an ADMIN_KEY environment variable, (b) fetches every form and its submissions from the Netlify API using a NETLIFY_AUTH_TOKEN environment variable, (c) returns them as JSON with no-store caching. Never expose the token to the browser. 4. On the page, render a stat card for each form's count, then a table per form (newest first). Escape all user-submitted values before injecting HTML. 5. Ask me which forms I have and which columns matter for each before writing the tables. 6. Walk me through creating the Netlify personal access token and setting both environment variables — do not print or ask for the secret values in chat.
Three of those lines are doing quiet, important work, and I'd leave them in even if you change everything else.
"Never expose the token to the browser." The credential that reads your form submissions can read all of them. It belongs on the server, full stop. Without that instruction an assistant may well produce something that works perfectly and leaks the key to anyone who views the page source.
"Escape all user-submitted values before injecting HTML." Your form data is written by strangers on the internet. Displaying it without treating it as untrusted text is how a form submission ends up running as code inside your own admin page. You will never notice this is missing, because it looks completely fine right up until it isn't.
"Do not print or ask for the secret values in chat." Your access token should be typed into your hosting dashboard by you and pasted nowhere else — not into a chat window, not into a file, not into a screenshot for a support thread. Ask to be walked through creating it, not handed a command with the real value already in it.
And line 5 is the one that makes the result yours rather than generic: it tells the assistant to ask which forms you have and which columns matter before it builds any tables. Answer that properly. "Bookings" is a table nobody reads; "date, couple, venue, package, deposit paid" is a table you'll open on a Tuesday morning. The difference between a dashboard you check and one you forget is almost entirely in which columns you chose.
These are the systems my studio actually runs on, and I publish the exact prompt for each one — not a summary of it, the thing I used. Take them, change the details to fit your business, and stop paying for the parts you can own.
Click through the demo— Alex Knight · No. 1: booking page · No. 2: feedback form · No. 3: package builder