Decision Log
Why we made the choices we did, and what we considered before making them.
Each entry is the decision and the context that made it the right call at the time. The reasoning ages better than the specifics — if you're wondering "why is it like this?" the answer is probably here.
Cloudflare R2 for Media Offload
Decision: All media uploads are stored in Cloudflare R2 (S3-compatible object storage) and served via CDN at static.marketofchoice.com. WordPress stores metadata locally but the actual files live in R2.
Context: WP Engine charges for storage and has limits on disk space. The MoC media library is large — product photos, maker profiles, recipe images, weekly specials PDFs. Before R2, the media library on the marketing site alone was 17.6 GB (later optimized to 11.3 GB, but still substantial). R2 has free egress (unlike S3, which charges per GB transferred), making it significantly cheaper for a media-heavy site. The s3-uploads plugin handles the S3 protocol, s3-r2-support.php configures R2-specific endpoints, and uploads-to-cdn.php rewrites URLs so visitors load images from the CDN rather than WP Engine.
The alternative was keeping everything on WP Engine's disk. This would have worked, but storage costs would have grown steadily, and there's no CDN benefit. S3 was also considered but R2's zero egress fees made it the obvious choice for a site that serves lots of images to lots of visitors.
If you inherit this site and see image URLs pointing at a Cloudflare domain, do not "fix" them by re-uploading everything to WP Engine's media library. You'll double the storage cost and break every existing image reference across both sites. The R2 offload is working correctly — understand it before changing it.
WP Engine for Hosting
Decision: Both sites hosted on WP Engine's managed WordPress platform.
Context: WP Engine is the safe enterprise choice — it's the answer that doesn't get questioned in a meeting. The agency (Grady Britton) likely chose it for that reason. It provides managed WordPress hosting with automatic backups, staging environments, and a support team that understands WordPress.
The trade-offs are real: SSH access is restricted, Git deploys are limited to their specific workflow (push to their remote or use their CLI), staging workflows are basic, and the ability to run arbitrary tooling is either restricted or behind upcharges. You're paying premium prices for a sandbox that fights you if you want to do anything non-standard.
Before WP Engine, the reservations site was on AWS (EC2 at IP 44.231.187.108) with CloudFront CDN. It was underpowered — AWS alerted about CPU performance during Thanksgiving 2023, and PHP memory limits caused reporting failures. The migration to WP Engine was done in two phases: marketing site first (March 2025), then reservations site (planned after Easter 2025, staggered to reduce risk). The marketing site migration was straightforward. DNS was coordinated with MoC IT (Sam).
WooCommerce for a Reservation System (Without Payments)
Decision: The catering reservation site is built on WooCommerce even though no payment processing occurs.
Context: This was inherited, not chosen. The reservations site was rebuilt in September 2023 for the Thanksgiving holiday season, and WooCommerce was already the platform from the previous AWS-hosted version. It provides a familiar product catalog, cart, checkout flow, and order management UI — all of which the reservation system actually uses. What it doesn't use: payments, shipping, taxes, coupons, refunds, or most of the inventory management.
A potential rebuild using Next.js + Payload CMS was scoped in March 2026. The core data models are relatively simple (products, stores, orders, seasons, supper club config), and seasonal logic that's 1000+ lines of WordPress hooks would become a few utility functions. But the existing system works, the client knows how to use it, and the rebuild cost isn't justified by the current pain level.
The practical consequence: ~50+ plugins sit in the plugins/ directory, most of them WooCommerce plumbing. Updates require caution because the WooCommerce ecosystem is tightly coupled. The moc-reports plugin has its own cache-bypass fallback after an EverCache incident broke report data.
Separate Sites vs. WordPress Multisite
Decision: Two independent WordPress installations instead of multisite.
Context: The sites have completely different requirements. The marketing site is mostly static content in Avada's Fusion Builder — location pages, blog posts, specials. The reservations site is a WooCommerce order system with seasonal logic, Supper Club management, custom reports, and a heavily customized child theme. Multisite would force them to share a database, plugin activations, and update cycles. A WooCommerce update that breaks the reservations checkout shouldn't also risk the marketing site going down.
The trade-off is some duplication — both have their own docker-compose.yml, their own R2 config, their own Avada license. But they can be deployed, updated, and rolled back independently, which matters when the catering site is taking holiday orders and any downtime costs real money.
MU-Plugins with Priority Loader (yax-loader)
Decision: All custom business logic on the reservations site lives in must-use plugins loaded by yax-loader.php with priority-based dependency resolution.
Context: MU-plugins can't be accidentally deactivated through the WordPress admin. This matters because the reservations site has ~30 custom plugins that control pricing, seasonal availability, Supper Club logic, and order routing. If someone accidentally deactivates moc-seasonal-manager.php during a holiday push, products become visible/invisible at the wrong times and orders break.
The yax-loader scans the mu-plugins/ directory for PHP files, reads priority headers, resolves dependencies, and loads them in order. Disabling is done by renaming to .disabled — no admin UI, no risk of accidental clicks. Regular plugins (the moc-* ones in plugins/) are for things that benefit from admin UI management, like moc-reports where store staff need access.
Stock Status Controls Visibility (Not Draft/Publish)
Decision: Products stay published across all seasons. Visibility is controlled entirely by stock status (in-stock/out-of-stock), never by changing post status to draft or private.
Context: This was a deliberate architectural choice for three reasons. First, URLs stay stable — an out-of-stock product doesn't 404, it's just hidden from the catalog (though direct URL access does 404 via moc-currently-unavailable.php). Second, SEO benefits from products remaining in the index even when temporarily unavailable. Third, it avoids the publish/draft churn that would happen every season transition.
The gotcha: MoC staff sometimes change seasonal items to "Private" instead of using the out-of-stock model. This happened in January 2026 when 103 holiday products had to be fixed back to published-but-OOS. The seasonal inventory manager now has a "lock" capability — when an item's status is locked, the automated sweep skips it.
Seasonal Manager and Supper Club as Independent Systems
Decision: moc-seasonal-manager.php and moc-supper-club-manager.php are completely independent plugins with no shared logic.
Context: Both control product visibility, which makes it tempting to merge them. But the business logic is fundamentally different. Seasons (easter, thanksgiving, christmas) operate on date ranges and toggle stock status. Supper Club operates on a weekly rotation cycle with per-location activation dates. A change to one system shouldn't risk breaking the other — and during holiday season, when both are active simultaneously, coupling would create a debugging nightmare.
The convention: never mix seasonal and Supper Club logic in the same WordPress hook or filter. If a feature legitimately needs to consider both systems, create a separate plugin that checks each independently.
GTM Container Injection via Theme (Not Plugin)
Decision: The Google Tag Manager container (GTM-MTKDTZ4) is loaded by a JavaScript file in the theme (Moc-Avada/js/google-tag-manager.js), not by the GTM4WP plugin's container code injection setting.
Context: The GTM4WP plugin generates dataLayer ecommerce events (view_item, add_to_cart, purchase), which is valuable. But its container code injection was unreliable with Avada's theme architecture. Loading GTM via a theme JS file gives more control over placement and timing. The GTM4WP plugin logs console warnings about the container "not found" — this is expected and harmless because the container is already loaded by the theme.
The product ID prefix in GTM4WP must be set to gla_ to match the Google Listings & Ads plugin's product sync format. This was missed in November 2024 and caused 100% unmatched item IDs in Google Ads until it was caught and fixed in March 2026.
PDFs Excluded from R2 Offload
Decision: PDFs stay on WP Engine's local storage instead of being offloaded to R2.
Context: The exclude-pdf-from-s3.php mu-plugin keeps PDFs local. This was a practical choice — PDFs on the reservations site are primarily cooking instruction documents and legal/compliance files. They're accessed less frequently than images, don't benefit as much from CDN distribution, and keeping them local simplifies the backup story (WP Engine's daily backups capture them automatically). It also avoids a class of debugging issues where PDF URLs need to resolve differently than image URLs.