Introduction
Launching the site isn't the finish line. Seasonal event info, nearby shops opening and closing, new questions to add to the FAQ — for a rental property site, the real work starts after launch.
Going back to an agency every time means paying for it in both money and waiting time. In this article we'll walk through how we built an in-house admin dashboard so the operators can update the content themselves.
What belongs in the dashboard, and what doesn't
Draw the line at update frequency
Should something be editable from the dashboard, or should it live in a configuration file? We settled that question with a single test: how often does it actually change?
Making everything editable from the dashboard doesn't just inflate development cost — it also creates the risk that someone edits the parts nobody should touch. Accidentally wiping the cancellation policy would be a genuine disaster.
That's exactly why we made the email copy read-only. The dashboard lets you preview the wording, and any change goes through the development side.
How the dashboard is laid out
The dashboard is split into two zones: operations and content.
The landing point after login
Checking bookings and payments / issuing guest-only pages / previewing email copy / referencing operational workflows
Blog / news / FAQ / images / nearby spots
Handling today's bookings and sitting down to write content put you in completely different headspaces. Keeping them in separate zones instead of mixing them on one screen made the dashboard much easier to navigate.
How we protect the dashboard login
Two-step authentication
Anyone on the internet can open the dashboard URL. So we made authentication a two-step affair.
The moment you reach the dashboard URL, you're asked for a username and password
We check whether the address is on the allow list (the screen looks identical either way)
Enter the code sent by email. Valid for 10 minutes, five attempts
Valid for 8 hours, and re-verified on every action from that point on
We deliberately avoided a system where people have to remember a password, because passwords get reused and leaked. Instead: if you can receive the email, you can get in. Simple as that.
Keeping the "who gets in" list in environment variables
The list of email addresses allowed to log in lives in environment variables (server-side settings), not in the database.
There's a reason for that. If the list lived in the database, anyone who managed to break into the database could simply add themselves as an administrator. With environment variables, only someone who can change the server configuration can touch it. And when a staff member moves on, removing them from the settings instantly invalidates all their sessions.
Getting past the login screen is not a permission
Making it through the login screen isn't a reason to allow every action that follows. For individual operations — deleting an article, issuing a guest-only page — we re-check permissions every single time.
We also split roles into owner, administrator, editor, operations staff, and view-only, so we can allow someone to write articles without letting them see booking details.
Keeping an audit trail
Who did what, and when — all of it is recorded. Creating, updating, and deleting articles, uploading images, issuing guest-only pages. The point is to be able to trace "when did this start going wrong" if trouble hits.
That said, the log never stores the sensitive data itself. We record the fact that "a guest-only page URL was reissued," but not the URL that was issued.
Three sub-topics
Safe text editing
An editor that lets you write freely is also, left unchecked, a doorway for dangerous HTML. How we balanced editing freedom with safety is covered in "A Rich-Text Editor with Safe HTML."
Automatic image optimization
Upload a photo straight from your phone and you've just put a multi-megabyte file on the site. The mechanism that resizes and compresses automatically is explained in "An Optimized Image Upload Pipeline."
Publishing flow and multiple languages
Drafts, scheduled publishing, and rolling content out into four languages. The publishing setup that makes day-to-day operation easier is covered in "Drafts, Scheduled Publishing, Multilingual."
Wrapping up
Three things mattered most in designing this in-house CMS.
- Draw the line at update frequency — only frequently changing things go in the dashboard; the untouchables stay in config files
- Two-step authentication, permissions checked every time — getting past a screen is never a permission in itself
- Keep the admin list in environment variables — so nobody can add an administrator through the database
A Rich-Text Editor with Safe HTML — Double Sanitization Against XSS
How we made free-form editing and safety work together.
An Optimized Image Upload Pipeline
How resizing, compression, and location-metadata removal were automated.
Drafts, Scheduled Publishing, Multilingual — an Operator-Friendly Flow
How scheduled publishing and AI translation into four languages work.