Case study · A clinic website with its own editorial layer

The clinic now edits its own website

A private clinic in Budapest: 22 specialties, over a hundred priced services, twenty staff members. The heart of the work became editability.

The situation
You run a clinic, and every small change to the site needs a developer — a price, a consultation hour, a new doctor. And the content is plenty: specialties, services, prices, schedules.
The approach
Sheet-style editor screens in the WordPress admin, with two-way sync to the public pages; appointment booking wired in at four levels; and a clean, dedicated interface for the manager.
The result
The clinic maintains its own content — prices, hours, news, photos — without a developer. The collaboration continues on monthly maintenance.

The work in numbers

Key facts of the clinic website
Collaboration started2024 — by porting the previous website
The new website2026, from the clinic's graphic designer's plans
Specialties22, more than twenty with custom subpages
Priced services104 live items
Staff on the site20 — doctors, assistants, reception
Editor sheets in the admin8 — from prices to the gallery
Booking wired inat 4 levels
Operationsmonthly maintenance
Project
a private healthcare practice
Role
Website port and rebuild, editorial system, booking integration, operations
Arrangement
Monthly maintenance
One field, several appearances The manager edits a price or consultation hour on the sheet-style editor screen. After saving, the same value refreshes on every public appearance by itself: the price list, the specialty subpage, the doctor's page and the contact-page schedule table. The editor never touches the page builder. Editor sheet a table in the admin row-level save, change marks Price-list page the price updates in its own row Specialty subpage the service list and price bands Doctor's page consultation hours, booking button Contact-page schedule table day-by-day, builds itself save · nobody touches the page builder during editing
A value lives in one place, and every appearance refreshes from there. What changes on the sheet changes on the public page — and never the other way round.

save · nobody touches the page builder during editing

The content was plenty — the looks were fine

I have worked with the clinic since 2024: I ported its previous website to WordPress, then maintained it for two years. The clinic wanted to edit the content itself from the start, and I gave a screen-shared walkthrough for it. A page builder, though, is not made for that work: in it a price is not data but a box inside the page structure — changing it means reaching into the layout. So for two years the daily edits kept coming back to me.

I built the new website in early 2026, from the clinic's graphic designer's plans. From then on the question was not the looks, but who will maintain the content of 22 specialties, over a hundred priced services and twenty staff members — and the answer became: the clinic itself.

A table, not a page builder

Eight sheet-style editor screens went into the admin: prices, consultation hours, specialties, colleagues, career positions, news, reviews, gallery. Rows and columns, like a spreadsheet — with filters, sorting, per-row change marks and bulk save.

The part that matters comes after saving: whatever the manager edits refreshes on every public appearance by itself. The price on the price list and the specialty subpage, the consultation hours on the doctor's page and in the contact-page table. On the first weekend after handover, late in the evening, word came that the clinic had started editing the site in the admin, and had uploaded the specialty photos that night.

Those same evening emails carried questions too — where, for instance, the specialty descriptions could be edited. What was still missing from the interface, I learned from those questions.

Booking at four levels

Choosing the booking system was part of the shared work too: the manager first experimented with Google Calendar, then we scoped a custom build, and in the end she chose Medio, which the clinic had known from before.

Appointments have run on Medio since, and the website connects to it at four levels: specialty, doctor and service links, plus an embedded booking widget. The buttons lead to the right place on their own — and where there is no link, no button appears.

What this means if your situation is similar

If you run a practice or a clinic, the most expensive part of your website is not the build but the ageing: last year's price, the doctor who left, the wrong consultation hour. At this clinic the answer became moving the editing close to the manager, not the manager close to the technology.

The developer does not disappear — the custom subpages, the integrations and the operations stayed with me, on a monthly arrangement. What moved over was the daily content: the part that belongs at the clinic.

If you run a healthcare or other content-heavy service and every change to your site waits for a developer, write a few sentences about what changes most often at your place. One exchange of emails usually shows how much a dedicated editor interface would give.

kristof@kristofkarner.com
Technical details

From here on: how the editor sheets are built, the manager's own admin, the rollback-protected price migration, a production incident and its recovery, and the disguised error messages of the go-live.

Size of the implementation

Size of the implementation
Custom modules78 files, 19,586 lines
Price migration91 items into posts, 95 archived — with a rollback snapshot
Custom specialty subpages20+, from the client's designs
Environmentslocal → dev → production, via WP-CLI
Checked on the local copy — the custom modules
$ ls mu-plugins/mfk-*.php | wc -l ; wc -l mu-plugins/mfk-*.php | tail -1
      78
   19586 total

The editor sheets

What turns a table into an editorial tool

The eight sheets share one base: a header that stays fixed while scrolling, combinable filters, sortable columns, a pop-up editor for long texts. Modified rows carry a mark until they are saved, saving is done in bulk, and a short confirmation follows — green or red.

Ordering is decided on the sheets too: services can be drag-and-drop reordered within each specialty, and that order is what appears on the price list and the subpages. The relationships are two-way — assign a service to a colleague, and the colleague appears on the service as well.

The manager's own admin

The manager does not log into the full WordPress admin but into a version cut to her work. She received a dedicated role with narrow capabilities: she can edit content, but settings, plugins and updates are out of reach. The left menu is hidden entirely; everything sits in the top bar — the eight sheet tabs, and a Refresh website button that clears the cache in plain language.

The core of the role guard — names generalised for this pagePHP
/** The editor sheets: slug => title — for the admin-bar tabs + the whitelist. */
function mfk_editor_sheets() {
    return [
        'mfk-rendelesek'       => 'Prices',
        'mfk-rendelesi-idok'   => 'Consultation hours',
        'mfk-szakteruletek'    => 'Specialties',
        'mfk-orvosok'          => 'Colleagues',
        // … careers, news, reviews, gallery
    ];
}

/** Scope guard: the simplification applies ONLY to the editor role. */
function mfk_editor_is() {
    if ( ! is_user_logged_in() ) return false;
    $u = wp_get_current_user();
    return $u && in_array( MFK_EDITOR_ROLE, (array) $u->roles, true );
}

If she strays onto any other admin page — even by direct URL — the system redirects her back to the sheets. And there is a feedback button too: a message box that sends me an email. The support path is one click, not a lost phone number.

The specialty subpages

The site's main look arrived from the clinic's graphic designer, as Figma plans. The plan for the specialty subpages, though, came from the manager herself: she made AI-generated mockups and picked the strongest as the base — dietetics came out best. That became the shared frame; the differences were built onto it specialty by specialty.

The requests arrived in stages, by email. There was a Sunday when the changes for nine specialties came in nine separate emails — the closing summary already on Monday, in the small hours. Symptom lists with icons, a step-by-step process section, expandable service descriptions, a dynamic price table: what is shared comes from the template; where they differ, those emails decided.

The price migration, rollback-protected

The price list was originally a hand-built page-builder structure — 116 rows in a single container. For the sheets to edit it, every item had to become a post: 91 items became service posts, and 95 orphaned old items went into an archive — not into the bin.

  1. A snapshot of every affected entry before the migration.
  2. Every database operation logged, in replayable form.
  3. Items carried over in stages, verified stage by stage.
  4. Orphans archived, not deleted — old data stays retrievable.
  5. A restore script that rebuilds from the snapshot at any moment.

The restore script was never needed. But that is not the point — the point is that there was a way back the whole time.

The incident my own process caused

In June 2026, while a development copy was being made, the field linking services to specialties came unhooked on the live site. The client noticed the symptom: on the price list, every item slid under Other fees. The most likely cause was a short window in which the copy's configuration still pointed at the live database.

The recovery was surgical. The development copy held the intact links, but its other fields were already stale — a blind restore would have caused damage. So after a backup, only that one field was restored, with a cross-database operation, and the result was verified: the links went from zero back to a hundred and one, with everything else untouched.

I am not hiding this case, because it shows two things. One: between live and development environments, the moment of copying is itself a risk. Two: the value of a recovery is not its speed but the discipline of its scope — restoring only what provably went missing.

Here the development copy became the source of an incident; elsewhere the very same arrangement saved everything, when a finished site never went live — one tool, two outcomes.

The parts that are not obvious

The next four things surfaced during the go-live, and each disguised itself as something else.

The timeout that was a full disk

The client's browser upload kept failing, and everyone suspected a timeout. The real cause: the live hosting had 784 kilobytes of free space left — the quota was full. After a clean-up, the upload worked by itself. The quota is now the first thing I check before any migration.

The corrupted archive that was a version mismatch

The restore stopped with a corrupted-archive message — and the archive was fine. The local machine had packed it with a newer migration plugin than the one unpacking it live. After updating the plugin, the restore ran without changing anything else.

The old look that was a stopped cache

After the successful move, the live site showed the old look in places. The cause: files left behind by a long-deactivated cache plugin, still serving pages from 2025. Emptying the cache folder brought the fresh content everywhere.

The passwords that travel with the encryption keys

After the move, the mail authentication broke. WordPress encrypts stored passwords with the installation's own keys — a migration carries the content, but the keys stay with the live site, so the encrypted password is unreadable there. The fix is no trick: the passwords have to be re-entered on the live site.

Questions about this project

How can a clinic edit its own website?

By moving the editing out of the page builder and into an interface built for it. At this clinic, sheet-style editor screens went into the WordPress admin — prices, consultation hours, specialties, colleagues, news, reviews — with row-level editing, bulk save and change indicators. Whatever the manager edits there refreshes on the public pages by itself: the price on the price list and the subpages, the consultation hours on the doctor's page and in the contact-page table. She never has to touch the page builder.

How do you integrate appointment booking into a medical practice website?

At this clinic the Medio booking system provides the appointments, and the website connects to it at four levels: a specialty-level link, a doctor-level link, a service-level link, and an embedded booking widget. The links are stored as fields, so the booking buttons lead to the right place on their own — and where a service has no link, the button does not appear at all. The links are maintained in the same sheet-style admin as everything else.

What does a non-technical manager need to work safely in the WordPress admin?

A dedicated role with narrow capabilities, and an interface that shows only what concerns them. Here the manager received her own role: she can edit content, but has no access to settings, plugins or updates. The left admin menu is hidden entirely; everything lives in the top bar — the editor-sheet tabs and a Refresh website button that clears the cache in plain language. If she strays anywhere else, the system redirects her back to the sheets.

How do you migrate a price list of a hundred items safely?

With rollback protection: before the migration, a snapshot is taken of every affected entry, and every database operation is logged in replayable form. At this clinic 91 price-list items became posts this way, and 95 orphaned old items went into an archive — not into the bin. The restore script could have rebuilt the original state from the snapshot and the log at any moment. It was never needed — but that is not the point.

What happens when data relationships get lost on a live site?

It happened here: while a development copy was being made, the field linking services to specialties came unhooked on the live site — every item on the price list slid under Other fees. The recovery was surgical: after a backup, only that one field was restored from the development copy's intact data, because other fields there were already stale and a blind restore would have caused damage. The result was verified: the links went from zero back to a hundred and one, and nothing else changed.

Why would a migration report a corrupted archive when nothing is corrupted?

Because an error message does not always describe the cause. In this move, the corrupted-archive message hid a version mismatch: the local machine had packed the archive with a newer migration plugin than the one trying to unpack it on the live site. After updating the plugin, the restore ran through. And there was another disguise before it: the upload was not failing on a timeout — the live hosting had 784 kilobytes of free space left. The quota was full.

Is it worth building a custom subpage for every specialty?

It is worth it where the specialties genuinely differ. At this clinic more than twenty specialties received custom subpages — symptom lists with icons, step-by-step process sections, expandable service descriptions, a dynamic price table — because dermatology and laboratory diagnostics do not ask for the same structure. The shared base comes from a template; the differences were built per specialty, following the client's requests.

Summary

The collaboration with this private Budapest healthcare practice began in 2024, with porting the previous website to WordPress; the new site, built from the clinic's graphic designer's Figma plans, went up in early 2026. Behind it an editorial system was built: eight sheet-style editor screens in the WordPress admin — prices, consultation hours, specialties, colleagues, careers, news, reviews, gallery — with per-row change marks, bulk save and two-way sync to the public pages. Appointments run on the Medio system, wired in at four levels; the clinic's manager received a dedicated role and a clean, branded admin with a single plain-language cache-refresh button and a built-in feedback form. The 91-item price migration ran with a rollback snapshot and an operation log; one production incident — specialty links unhooked while a development copy was being made — was resolved with a backup-first, single-field cross-database restore. The implementation is 78 custom modules and 19,586 lines; more than twenty specialties received custom subpages, built from the client's AI-generated mockups. Operations are monthly, and the daily content is maintained by the clinic itself.

Karner Kristóf

Independent developer in Budapest. I built this editorial system and I operate it — questions are welcome at the address in the contact section.

← All case studies