Architecture Overview
What's where, what talks to what, and where it's hosted.
Two independent WordPress installations. Not multisite — separate databases, separate themes, separate repos, separate deploy pipelines. They share a Cloudflare R2 bucket for media, but that's it.
System Diagram
The Two Sites
| Marketing Site | Reservations Site | |
|---|---|---|
| URL | marketofchoice.com | reservations.marketofchoice.com |
| Purpose | Store locations, specials, recipes, blog, maker profiles, careers | Catering orders, Supper Club reservations, seasonal holiday ordering |
| WP Engine install | marketofchoice | marketofccat |
| Theme | Avada + Avada-Child-Theme | Avada + Moc-Avada |
| WooCommerce | No | Yes — but no payment processing |
The reservations site is a reservation/ordering system with no payment processing. No Stripe, no PayPal, no payment gateway. Customers browse a menu, build a cart, and submit a pickup reservation. WooCommerce is overkill for this — the entire payments/shipping/taxes/coupons/refunds stack goes unused.
Media and CDN
All media is offloaded to Cloudflare R2 (S3-compatible object storage with free egress). This is not optional infrastructure — it's where the images live. If you don't understand R2, read the Decision Log before touching anything.
- Marketing site media:
static.marketofchoice.com - Reservations site media:
static.marketofchoice.com/catering - PDFs are excluded from R2 offload on the reservations site
- Credentials:
S3_UPLOADS_*env vars inwp-config.custom.php, from.envlocally and WP Engine's environment in production
Reservations Site — Where the Complexity Lives
MU-Plugin System
All custom business logic lives in src/wp-content/mu-plugins/. The yax-loader.php auto-loads plugins with priority-based dependency resolution. Disable any plugin by renaming it to .disabled.
The seasonal manager and Supper Club manager are completely independent systems. They must never be mixed in the same hook or filter. See Gotchas.
Core business plugins (moc-*):
| Plugin | What It Does |
|---|---|
moc-seasonal-manager.php | Controls product availability across five seasons (standard, easter, thanksgiving, christmas, extendedChristmas). ~1000+ lines. Has a "lock" feature to prevent automated sweeps from overriding manual stock decisions. |
moc-supper-club-manager.php | Rolling weekly Supper Club menu across 14 store locations. Wednesday-based cycle. ~967 lines. |
moc-pricing-manager.php | Centralized pricing: weight-based ("Estimated price based on $X.XX/lb"), per-lb, "Special Order" for $0, price overrides via _moc_price_override meta. |
moc-currently-unavailable.php | Overrides "Out of Stock" text. Hides OOS from catalog. Shows "Currently Unavailable" for Supper Club. |
moc-limited-quantity.php | Per-product max quantity (default: 4). Meta keys: _moc_max_quantity, _moc_enable_max_quantity. |
moc-hide-quantity-field.php | Hides quantity input for weight-based products. |
moc-raw-product.php | Auto-appends "(Raw - requires cooking at home)" to titles. |
moc-supper-club-datalayer.php | Pushes supper_club boolean to GA4 data layer for audience segmentation via GTM4WP. |
seasonal-product-prefix.php | Prefixes unpublished seasonal products with the season name for admin clarity. |
disable-cart-fragments.php | Disables WooCommerce cart fragment AJAX for performance. |
Infrastructure plugins (yax-*): yax-loader.php (auto-discovery + dependency resolution), yax-common/ (service container), yax-cleanup/ (admin declutter), yax-disable-comments/, yax-plugin-modifications/, yax-version-sync/.
CDN/Media: s3-r2-support.php (R2 config), uploads-to-cdn.php (URL rewriting), exclude-pdf-from-s3.php (PDFs stay local).
Theme: Moc-Avada
Child theme at src/wp-content/themes/Moc-Avada/ with 24 PHP files in functions/:
Notable Plugins (Regular)
Custom MoC plugins in src/wp-content/plugins/:
| Plugin | What It Does |
|---|---|
moc-reports | Six custom report types: Pickup, Supper Club Pickup, Sales, Supper Club Sales, Turkey Orders, Financial. Includes cache-bypass fallback after EverCache incident. |
moc-woo-scheduled-stock | Scheduled stock status changes at specific dates. Meta keys: _oos_at, _is_at. |
moc-order-status | Custom order statuses (2 Day Reminder, auto-completion). |
moc-allergen-info | Allergen display on product pages. |
moc-supper-club | Supper Club frontend display. |
moc-supper-club-mailchimp-tag | Tags SC customers in MailChimp. |
moc-continue-shopping | Continue Shopping button in cart. |
moc-previously-frozen-product | Marks products as previously frozen. |
moc-structured-content | Schema.org structured data markup. |
Key third-party plugins:
| Plugin | What It Does |
|---|---|
woocommerce | Core commerce engine (used for cart/checkout/orders — no payment processing). |
google-listings-and-ads | Syncs products to Google Merchant Center with gla_ prefix. |
duracelltomi-google-tag-manager | GTM4WP — generates dataLayer ecommerce events. |
weight-based-pricing-for-woocommerce | Weight-based pricing support. |
flexible-checkout-fields + flexible-checkout-fields-pro | Custom checkout fields (store selection, pickup date). |
woocommerce-pdf-invoices-packing-slips + pro | PDF packing slips for store staff. |
woocommerce-order-status-manager | Custom order status definitions. |
woocommerce-sequential-order-numbers-pro | Sequential order numbering. |
woo-schedule-stock-manager | Additional stock scheduling. |
wordpress-seo | Yoast SEO (also on reservations site, not just marketing). |
mailchimp-for-woocommerce | MailChimp integration for checkout email signups. |
mailgun | Transactional email delivery. |
s3-uploads | S3/R2 media offload (Composer dependency). |
wp-crontrol | WP-Cron management and debugging. |
side-cart-woocommerce | Side cart/mini cart UI. |
admin-columns-pro | Enhanced admin list table columns. |
capability-manager-enhanced | User role/capability management. |
Product Data Model
- SKU (PLU): The canonical product identifier. Always use SKU, never WP post IDs. The client references everything by PLU number. API:
wc/v3/products?sku=XXXXX. - product_availability: ACF meta field (field_6722be5565a68). Array of seasons like
["standard", "easter"]. NULL = standard. - Weight-based pricing: Identified by
weight_based_pricingmeta = 1. Two fields must stay in sync:_moc_price_override(per-lb display) andregular_price(per_lb x max_weight_range). - Allergens: Stored in TWO places — the Allergens product attribute (for filtering) AND the
<strong>Allergens:</strong>line in the description HTML. Both must match. - Category IDs: Bakery=193, Kitchen=197, Appetizer=192.
- Supper Club tags: Supper Club (222), Standard (249), Chef's Favorite (248), Add-ons (229).
Tracking and Analytics
| System | Details |
|---|---|
| GTM | Container GTM-MTKDTZ4. Injected via theme JS (Moc-Avada/js/google-tag-manager.js), NOT via GTM4WP plugin setting. |
| GTM4WP | Generates dataLayer ecommerce events. Product ID prefix must be gla_ to match Merchant Center. |
| Google Ads | Account 470-848-8665. Merchant Center 5592882755. Products synced via Google Listings & Ads plugin with gla_ prefix. |
| MNTN | Pixel on both sites. Conversion pixel on /order-received. |
| Meta | Pixel on both sites. |
Marketing Site — Simpler but Avada-Heavy
The marketing site is mostly static content managed through Avada's Fusion Builder. The child theme (Avada-Child-Theme) has custom templates and a functions/ directory, but the majority of page content lives in the WordPress database as serialized Fusion Builder shortcodes in post content — not in template files. See Notes for what this means in practice.
Key Plugins
| Plugin | What It Does |
|---|---|
| Yoast SEO | SEO meta. MadFish Digital provides recommendations. |
| WP Ultimate Post Grid | Powers dynamic content on store location pages (manager photos, featured makers). |
| Gravity Forms | Contact forms, vendor application form at /new-vendor-information/. |
| PublishPress Revisions | Scheduled content updates for weekly specials. |
| Custom Post Type UI | Custom post types for specials, recipes. |
| Google Site Kit | GA4 integration. |
Content Patterns
- Store locations: Avada pages with WPUPG grid shortcodes at
marketofchoice.com/locations-{slug}/. Dynamic content (manager, maker, blog) pulled from regular posts. - Weekly specials: Custom post type. PDF + CSV + Issuu embed. Updated weekly.
- Recipes: Custom post type with ACF fields (Serves, Difficulty, Prep Time, Cook Time).
- Maker profiles: Regular posts. When removing, scrub all mentions — home page, local photos, Amplify pages, specialty pages.
Local Development
Six Docker containers: WordPress FPM, MySQL 8.0, Nginx (serves static files + FastCGI proxy), Caddy (HTTPS termination), WP-CLI, Dozzle (log viewer). No Redis locally — WP Engine uses Memcached in production, which isn't replicated in the local dev stack.
npm run setup # First-time: generates Caddyfile, updates /etc/hosts
npm run start # Start all containers
npm run stop # Stop containers
npm run logs # All container logs
npm run logs:wordpress # PHP-FPM only
npm run shell # Bash into WordPress container
npm run wp -- <cmd> # WP-CLI (e.g., npm run wp -- plugin list)
npm run build # Build dist/ for deployment- Local URL:
https://reservations.marketofchoice.localhost - DB:
localhost:3306 - Dozzle:
http://localhost:8888
Three containers: WordPress (Apache), MariaDB, Caddy. No Nginx, no Redis, no WP-CLI container.
docker compose up -d # Start containers
docker compose down # Stop containers- Local URL:
https://marketofchoice.localhost - DB: MariaDB 10.6