Single-File Restaurant Menu + Admin β README
A tiny, batteries-included restaurant menu with an admin UI. Itβs one PHP file, uses SQLite for storage, and Bootstrap 5 for styling.
β¨ Features
- Public menu with search, category & Veg/Non-Veg filters
- Admin dashboard: add/edit/delete items and categories
- Image uploads (JPG/PNG/GIF/WebP, ~3 MB limit)
- JSON export/import of categories & items
- Built-in auth (admin user), CSRF protection, and basic rate limiting
- Zero-setup SQLite DB (
menu.db), single-file deploy - Sensible security headers + strict HTML escaping
π Quickstart
-
Requirements
- PHP 8.0+
- PDO SQLite & SQLite3 extensions enabled
fileinfoextension (for MIME checking)- Web server user must be able to write to the app directory (creates
menu.db/anduploads/)
-
Deploy
- Save the provided PHP file as
index.php(or any name) in a PHP-enabled web root. - Ensure an images fallback exists at
images/default-item.webp(see βAssetsβ below). - Visit the site root for the public menu:
/? - Visit the admin:
/?admin=1&page=dashboard- Default login:
admin / changemeβ immediately change it in Admin β Settings.
- Default login:
- Save the provided PHP file as
-
First steps (Admin)
- Create Categories in Admin β Categories
- Add Items in Admin β Add Item
- (Optional) Upload item images; otherwise the default image is used.
βοΈ Configuration
Edit the constants at the top of the file to suit your environment:
const APP_TITLE = 'Menu';
const UPLOAD_DIR = 'uploads';
const MAX_UPLOAD_BYTES = 3_000_000; // ~3 MB
const ADMIN_USER = 'admin'; // username is fixed
const DEFAULT_ITEM_IMG = 'images/default-item.webp'; // fallback image pathNotes
UPLOAD_DIRwill be auto-created if missing.DEFAULT_ITEM_IMGshould exist and be web-accessible. Createimages/and put a small WebP there (see βAssetsβ).ADMIN_USERis fixed to simplify the UI. Change only the password in Settings.
ποΈ Data model
settings(key TEXT PRIMARY KEY, val TEXT NOT NULL)- stores
admin_hash(password hash) and future settings
- stores
categories(id INTEGER PK, name TEXT UNIQUE NOT NULL)items(id INTEGER PK, name TEXT, price REAL, veg INTEGER, category_id INTEGER NULL, descr TEXT, img TEXT NULL)veg= 1 for Veg, 0 for Non-Vegcategory_idis nullable; deletes set toNULL(no cascade)
The database file is menu.db in the app directory. WAL mode is enabled for safe concurrent reads/writes.
π©βπ³ Admin UI
Open: /?admin=1&page=dashboard
- Dashboard: counts, upload dir size
- Items: search, filter by category, pagination, edit/delete
- Add Item: name, price, Veg/Non-Veg, category, description, image upload
- Categories: create/rename/delete
- Settings:
- Change password (requires current)
- Export JSON: downloads
categories&items - Import JSON: upload or paste JSON to append new categories/items
Image uploads
- Types:
image/jpeg,image/png,image/gif,image/webp - Max size:
MAX_UPLOAD_BYTES(~3 MB by default) - Stored under
UPLOAD_DIR/with timestamped, random filenames - Replacing an image for an item deletes the previous file (only if it resides under
UPLOAD_DIR/)
π½οΈ Public UI
Open: /?
Filters (query params)
qβ search in item name/descriptioncatβ filter by category id (int)vegβ""(all),"1"(Veg),"0"(Non-Veg)
Examples:
/?q=pasta/?cat=2&veg=1
Items are grouped by category; uncategorized items show under Uncategorized.
π Security
- Sessions: secure cookie flags (
httponly,samesite=Lax,secureif HTTPS) - CSRF: hidden token on all POST forms
- Auth: password stored with
password_hash()(bcrypt/argon per PHP default) - Rate limit: 5 failed logins β block for 60 seconds
- Headers:
X-Frame-Options: SAMEORIGINX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-origin
- Output: all dynamic content is escaped via
htmlspecialchars
Production checklist
- Serve over HTTPS
- Change the default password immediately
- Ensure
uploads/is not executable and only serves static files (typical shared hosting is fine) - Keep
DEFAULT_ITEM_IMGanduploads/paths outside any PHP-executing locations if you do custom server configs - Regularly back up
menu.dbanduploads/
π Import / Export
- Export: Admin β Settings β Download JSON Export (downloads categories & items, excluding images)
- Import: Admin β Settings β Import (file upload or paste JSON)
- Categories are inserted if missing (
INSERT OR IGNORE) - Items are appended; images are not imported (set
img: null) - For items,
category_idis used if provided; otherwise, if a"category"name is present and exists, itβs used.
- Categories are inserted if missing (
A minimal items JSON example:
{
"categories": [
{ "id": 1, "name": "Pasta" },
{ "id": 2, "name": "Desserts" }
],
"items": [
{ "name": "Aglio e Olio", "price": 8.5, "veg": 1, "category": "Pasta", "descr": "Garlic & olive oil." },
{ "name": "Tiramisu", "price": 6.0, "veg": 1, "category": "Desserts", "descr": "Classic Italian dessert." }
]
}π§± Assets
-
Default image path:
images/default-item.webp
Create the folder and add any small placeholder (e.g., 800Γ600 WebP).
If an item has no valid upload, this fallback is used. -
Uploaded images live under
uploads/and are referenced directly in<img src="...">.
π§ͺ Troubleshooting
- Blank page / DB error: ensure PHP has PDO SQLite and SQLite3 enabled.
- Uploads fail:
- Increase
upload_max_filesizeandpost_max_sizeinphp.iniaboveMAX_UPLOAD_BYTES. - Ensure the web server user can write to the app directory &
uploads/.
- Increase
- Default image missing: create
images/default-item.webpand make it readable by the server. - Stuck on default login warning: change password in Admin β Settings.
π§° Optional: Docker (example)
# Dockerfile
FROM php:8.3-apache
RUN docker-php-ext-install pdo pdo_sqlite
RUN apt-get update && apt-get install -y libmagic1 && rm -rf /var/lib/apt/lists/*
# fileinfo is built-in on official PHP images; ensure it's enabled if needed.
COPY . /var/www/html/
RUN mkdir -p /var/www/html/uploads /var/www/html/images && chown -R www-data:www-data /var/www/html
# Put a default placeholder to avoid 404s
# COPY images/default-item.webp /var/www/html/images/default-item.webpRun:
docker build -t menu-app .
docker run -p 8080:80 -v $PWD/uploads:/var/www/html/uploads -v $PWD/menu.db:/var/www/html/menu.db menu-appOpen: http://localhost:8080/
π Backups & Restore
- Backup: copy
menu.db,uploads/and (optionally)index.php&images/. - Restore: place files back; ensure permissions; done.
π License
Use freely for demos, test menus, or as a starting point. If you fork for production, audit and adapt to your needs.
π§ At-a-glance URLs
- Public menu:
/? - Admin login:
/?admin=1&page=dashboard - Admin sections:
/?admin=1&page=items/?admin=1&page=item_add/?admin=1&page=categories/?admin=1&page=settings
β Security & Design Notes (quick audit)
- Good: CSRF tokens; escaped output; password hashing; basic rate-limit; session cookie flags; MIME validation; size checks; deletion only within
uploads/. - Be aware: No user roles; single admin; images are served as-is (donβt place
uploads/under an exec-enabled path in custom setups); JSON import appends only (no dedupe beyond categories). - Tweakables:
MAX_UPLOAD_BYTES, default image path, Bootstrap CDN, and page titles.
Happy shipping! π