Skip to main content
Dev Sac
Web App

MenuFindr | Sacramento Restaurant Directory

WordPress restaurant directory hub for the Sacramento area, built on a custom plugin with 7 database tables, structured menu data, free Nominatim geocoding, a REST write API, and a Stripe-backed listing claim flow. GeneratePress child theme, ACF Pro, no CI required.

WordPress PHP 8.2 ACF Pro GeneratePress Stripe REST API
0
Custom DB Tables
0
Shipped Deliverables
0
Save Pipeline Steps

Who it helps

MenuFindr helps Sacramento diners find which restaurants serve the dish they want, and helps restaurant owners get their menus found. You want pad thai. Which Sacramento restaurants have it? Without a directory like this, you would need to check individual restaurant websites, many with menus buried in PDFs or outdated pages. Yelp and Google Maps let you search restaurants, but you cannot search by dish. The question “who has good chicken mole near me?” should be quick to answer.

Traditional 1. Pick restaurant 2. Read menu 3. Hope they have it MenuFindr 1. Search "pad thai" 2. See all restaurants 3. Compare prices

How it is built

MenuFindr is the directory hub for the Sacramento restaurant cluster. It runs on WordPress, but almost nothing about it is off the shelf. The work lives in two in-tree deliverables: a plugin called menufindr-core that carries the entire data model, and a GeneratePress child theme that renders it.

The reason it is a directory rather than a pure search engine is that the data problem comes first. You cannot search menus by dish until the menus exist as structured data, and restaurant menus in the wild are PDFs, images, and hand-typed HTML. So the plugin is built around getting menu data into a real schema and keeping it there.

The plugin

menufindr-core registers the custom post types and taxonomies, defines the ACF field groups, and owns 7 custom database tables for the data that does not belong in WordPress post meta. On top of that it provides a read and write REST API, a bulk importer, geocoding, and the listing claim flow.

Menus are modeled as a structured repeater with sections and items, which is what makes dish-level search possible. A WYSIWYG menu field exists as a fallback for restaurants whose menus refuse to be structured, but the repeater is the primary path and everything downstream reads from it.

WordPress 7.0 / PHP 8.2 menufindr-core CPTs · taxonomies · ACF fields · 7 DB tables REST read + write · importer · geocode · claim flow Directory pages Structured menus Claimed listings

Geocoding without a paid API

Every restaurant needs coordinates for maps and proximity. The obvious answer is the Google Geocoding API. The directory runs on free Nominatim instead, which means living inside a 1 request per second courtesy limit and an hourly cap.

That constraint shaped the design. Geocoding runs as a throttled queue on the WordPress save pipeline rather than inline, failures retry through wp-cron instead of blocking a save, and the whole subsystem sits behind a kill switch so it can be disabled without touching code. The result has no per-request fee and no API key to rotate or leak.

The save pipeline runs in a fixed order on every restaurant save: geocode, then sync, then location assignment, then slug generation. Running the steps in that order keeps every map pin matched to its address.

Slugs that do not move

Restaurant URLs are location-based and frozen: title, city, state. Once a listing saves for the first time, the slug locks and stops regenerating.

Correcting a restaurant’s punctuation never rewrites its URL, so inbound links keep pointing at the right page. Older bare-title URLs still resolve through WordPress permalink guessing.

Claiming a listing

Restaurant owners can claim their listing through a Stripe-backed flow. Claimed listings unlock the premium presentation layer: maps, photo galleries, and a reservation button. Unclaimed listings still get a complete, useful directory page, because a directory full of thin placeholder pages is worth nothing to the diners it is supposed to serve.

Everything premium gates on a single claim status field, which keeps the authorization surface small enough to reason about.

How it ships

There is no CI on this project, by design. The host strips authorization headers at origin and caches GET responses heavily, so read-back checks through an automated pipeline would not be reliable.

Releases are versioned zips uploaded through wp-admin, and verification happens through writes rather than reads: POST something, then read it back with a cache buster and confirm the id that came back this run. Tests are plain PHP files with a small assertion helper, covering the pure logic. The parts that touch the database, the geocoder, and REST auth get verified against the live site, because that is the only place they behave honestly.

Where it stands

Structured menus shipped in the 1.9 line and are live-verified on the production site.

For your technical team

  • WordPress 7.0 on PHP 8.2 with a GeneratePress child theme and ACF Pro.
  • A menufindr-core plugin owns the custom post types, taxonomies, ACF field groups, and 7 custom database tables.
  • Menus are a structured ACF repeater of sections and items, with a WYSIWYG fallback.
  • REST read and write API plus a bulk importer.
  • Nominatim geocoding runs as a throttled queue with wp-cron retries and a kill switch.
  • Stripe-backed listing claims, with premium features gated on one claim status field.
  • Releases ship as versioned zips through wp-admin, with plain PHP tests for the pure logic and write-then-read verification on the live site.

Want something like this for your business?

Send a short email about the problem you want solved and get a written scope back. No sales call required.

Start With a Free Email Scope