Merry on Main is the website for the City of Pleasanton’s Christmas celebration in Pleasanton, Texas, at Pleasanton River Park. Vendors, sponsors, River Park Wonderland tree entrants and parade entries apply there to take part. I built it in September 2026 as the third site on my WordPress multisite, and city staff share their own spreadsheets with it.
The problem
The city already ran the event from its own spreadsheets, fed by its own Google Forms. The site had to fit that: look like an event, take sign-ups from the public, and put each one where staff already looked, without anyone copying rows by hand. It also had to keep working when staff edited their forms, and lose nothing if Google was briefly unavailable.
What I built
- A theme for the event: a child of WordPress’s Twenty Twenty-Five in evergreen, cranberry and gold on warm cream, with CSS string lights, and snowfall on the home page that stops for anyone who asks their device for reduced motion.
- Five sign-up forms: vendors, River Park Wonderland trees, sponsors, the Lighted Christmas Night Parade and the Bike Parade. The vendor and tree forms ask exactly the city’s own questions, and the parade form carries the city’s rules word for word, each one a box to tick.
- A sponsor page with the city’s six packages and a printable form, and a parade route map that opens full size on a phone or a computer.
- Organiser tools in the WordPress admin: a CSV export and a printable page for each form, a switch to close a form, editable price lists, phone alerts for each new sign-up, and a test mode.
- Confirmation mail to each applicant in the site’s colours, with replies going to the city’s Civic Center.
How it works
Every sign-up is saved privately in WordPress first. Then the site appends it as one row to a separate “Website signups” tab in the city’s spreadsheet for that form, through the Google Sheets API. The city’s own response tab is never written, and values are written as plain text, so nothing an applicant types runs as a formula.
If Google refuses or times out, the row is retried every hour, up to six times. Each retry checks for the sign-up’s reference first, so a retry never adds a duplicate. Organisers hear about the first failure and the last, and if the sixth attempt fails the whole sign-up is emailed to the Civic Center, so nothing is lost.
Spam is turned away by a hidden field, a check that the form wasn’t filled in impossibly fast, a per-connection limit and Akismet. The form pages are never served from the page cache, so those checks always run.
The code lives in the multisite’s Git repository and changes by pull request, and one script deploys the theme and the forms plug-in after checking every PHP file. The plug-in returns at once on any other site.
Decisions and trade-offs
- Write to the spreadsheet through its API, not into the city’s Google Form. Posting to the form looked simpler, but it broke every time staff edited a question. The cost is that a change to the city’s form doesn’t reach the site’s copy by itself; I update it by hand.
- Hold suspected spam, never drop it. A sign-up Akismet flags goes nowhere until an organiser releases it, and one nobody releases goes to the bin after 30 days. Losing a real applicant costs more than an organiser’s click. If Akismet can’t answer, the sign-up goes through.
- A CSV and a printable page, not a PDF library. The server has no PHP package manager, so every added library would be third-party code copied in and kept current by hand. Organisers use the browser’s own Save as PDF.
- Only the price lists are editable. Rules and payment methods stay in code. An empty box falls back to the built-in list, which is also the way back.
What’s next
The city has yet to run its own end-to-end test in test mode.
Built with
WordPress multisite, a block theme, PHP, WP-Cron, the Google Sheets API, Akismet, Amazon SES, ntfy, nginx, Cloudflare, Git and GitHub.