Case Study · Platform Architecture
A zero-touch WordPress directory platform — no daily admin required.
A large national member-based organization managing thousands of listings manually. I built a self-serve directory: WooCommerce subscriptions, free listings, expirations, and frontend dashboards — all hands-off.
- daily admin actions
- 0
- lifecycle paths (paid + free)
- 2
- customer self serve
- 100%
A large national member-based organization managed a massive, highly trafficked directory: businesses, real estate listings, jobs, and events, all in one place, accessed by tens of thousands of users a month. The platform had become a victim of its own success. Every listing required manual intake, manual billing, manual lifecycle management. The admin burden was unsustainable.
The mandate was to make the directory self-serve, end to end. Users should be able to register, pay, submit, manage, and expire their own listings — without any backend exposure to WordPress, and without daily intervention from the corporate team. Here’s how a custom WordPress platform looks when the operational design is as important as the technical one.
The situation.
The client manages a national directory with thousands of listings across multiple categories. Some listings are paid (businesses, lodgings, real estate, sponsors) and require recurring billing. Others are free (jobs, events, news) but need strict expiration rules so the database doesn’t fill up with stale data.
The previous workflow had grown organically. Listing submissions came in via email or web forms. A staff member triaged them, invoiced manually for the paid ones, set up the WordPress posts by hand, configured taxonomies and metadata, uploaded media, and then revisited each listing periodically to check whether subscriptions had lapsed or events had passed. It worked. It also consumed nearly every working hour of the small operations team.
Off-the-shelf directory themes had been evaluated and ruled out. They couldn’t handle two distinct lifecycles (paid vs. free) running in parallel, the strict expiration logic, or the volume the directory needed to support.
The constraints.
The mandate was to engineer a fully self-serve platform where:
- Paid and free listings follow completely different lifecycles. Paid listings need WooCommerce Subscriptions, recurring billing, tier transitions on payment status changes. Free listings need frictionless intake — no cart, no required account upfront, no billing flow — but with strict expirations.
- Users never see the WordPress backend. All listing management, all media uploads, all profile editing happens through frontend forms.
- Time-sensitive listings expire automatically. Jobs, events, and similar listings should silently disappear when their date passes, without requiring an admin to clean up.
- Subscription state changes propagate automatically. When a payment fails or a subscription is canceled, the listing’s visibility should adjust without intervention.
- Spam protection without CAPTCHA friction. The platform relies on frictionless submission. Blocking spam couldn’t come at the cost of user-visible challenges.
- Existing listings should be claimable. Many directory entries had been added by admins on behalf of businesses years prior. Those businesses should be able to claim their listing and take over editing it.
What I built.
A dual-track submission engine.
The two lifecycles required two completely separate intake flows.
For free listings, the process is disconnected from WooCommerce entirely. Users submit a Gravity Form, and the Advanced Post Creation (APC) add-on instantly publishes the resulting custom post type to the live site. No cart. No checkout. No friction.
For paid listings, I built an enforced single-item cart flow. A custom validation function (force_single_item_cart_smart) actively prevents users from mixing products or stacking subscriptions by emptying the cart when a new listing form is submitted. Gravity Shop bridges the form data into a WooCommerce cart, routing the user straight to checkout with their listing pre-attached.
Solved the guest-checkout race condition.
The thorniest technical hurdle involved guest checkouts for paid listings. APC generates the WordPress post a fraction of a second before WooCommerce generates the new user account. The post existed before the user did — so WordPress assigned it to an orphaned author ID (0).
Rather than slowing post creation to wait for user creation, I built a reverse-tracing mechanism. The script hooks gform_advancedpostcreation_post_after_creation, runs after the post is safely in the database, then traces backward: reads the Gravity Forms Entry ID, queries the WooCommerce Order Item Meta (gspc_gf_entry_ids) to find the parent order, extracts the freshly-created customer ID, and retroactively reassigns the orphaned post.
The user never sees any of this happen. They check out as a guest, the system creates their account, and their listing is correctly attributed to them by the time they next log in.
A “Bait and Switch” listing-claiming system.
The directory had years of legacy listings that had been added by admins on behalf of businesses that hadn’t yet claimed them. The claiming flow needed to feel like normal editing — not like a multi-step approval queue.
When a user submits a claim with updated information, APC creates a temporary “Ghost Post.” A custom script immediately intercepts the Ghost Post, copies all the updated data (ACF fields, taxonomies, attached media, new author ID), pastes it over the existing live directory listing, and deletes the Ghost Post.
To the user, they simply updated their listing. To the database, a complex data merge happened in milliseconds — ownership and content transferred, no duplicates created, no admin intervention required.
Master status synchronization for subscription lifecycle.
Keeping the directory clean meant ensuring listings disappeared when payment stopped or when time expired. I built two automation engines, one for each case.
The subscription-sync script listens for WooCommerce webhook events. For tiered posts (businesses, lodgings), an expired subscription rewrites the taxonomy term, downgrading the listing from a paid “Enhanced” tier to a free “Complimentary” tier — but the post stays published. For binary posts (real estate), an expired subscription flips the post status from publish to draft immediately.
For free listings, I bypassed WordPress’s heavyweight post-loading mechanisms and built a native SQL expiration cron. It runs at midnight, identifies posts past their expiration date, and drafts them in a single batch. Along the way I had to resolve a subtle data-formatting conflict — Gravity Forms saves dates with dashes (2026-06-10), ACF expects integers (20260610), and standard meta_query was mathematically misreading the dashed dates and prematurely drafting live posts. The fix was strict SQL DATE casting in the cron query, which forces MySQL to parse both string variations as literal calendar dates.
Headless-style frontend dashboard.
Users should never see WordPress admin. I registered a custom WooCommerce endpoint (/my-account/edit-listing/) and injected it into the frontend “My Account” menu. Single-listing owners get an ACF frontend form. Multi-listing owners get a clean selection UI.
The hardest part was media handling. WordPress natively blocks Subscribers from uploading images to custom post types — a sensible default that broke our use case. Permanently elevating user roles would have been a security mistake. Instead, I engineered what I think of as a “capability sledgehammer”: a script that listens specifically for AJAX media uploads from the frontend form, elevates the user’s permissions for exactly the duration of the upload request, then immediately strips the elevated capabilities away.
Profile images go through strict pre-flight validation: format must be .jpg, dimensions must match the required directory size exactly (1400×790). Bad uploads are rejected before they touch the filesystem.
Invisible spam protection.
The platform relies on frictionless publishing. Front-loading CAPTCHAs would have killed it. I deployed defense in depth using invisible scoring layers:
- Gravity Forms honeypots catch rudimentary bots
- Custom dynamic JS tokens block headless-browser submissions
- Cloudflare Turnstile provides invisible network scoring on both intake forms and the WooCommerce checkout
- Akismet Enterprise analyzes text payloads on form submissions and product reviews to catch human-driven spam
The combination handles the spam volume without ever showing a user a challenge.
The outcome.
- Zero daily admin intervention required. Users register, purchase, submit, manage, upgrade, and let their listings expire without anyone on the corporate team having to act.
- Two lifecycle paths cleanly automated. Paid subscriptions and free listings each follow their own well-defined rules, in parallel, without colliding.
- Thousands of listings, all self-managed. Guest checkouts attribute correctly. Claims merge cleanly. Subscription state changes propagate automatically. The database stays current with no manual cleanup.
- A platform that scales without proportional admin growth. The corporate team’s workload no longer scales linearly with directory size. They can add categories, add listing types, and accept more users without budgeting for more staff hours.
Outcomes
Users register, pay, submit, manage, upgrade, and let their listings expire — with no admin intervention. Paid subscriptions and free listings follow separate lifecycles cleanly. Guest checkouts attribute correctly. Spam protection works without ever showing a user a challenge. And the corporate team’s workload no longer scales with directory size.
Let's talk about what you're building
No proposals. No pitch decks. Just a conversation about your project and whether I'm the right fit to build it.
Start a Conversation