A card-based pizzeria menu for Grav: sections, doughs, and EU-compliant allergen labelling. Everything is edited from the admin panel โ the people running the restaurant never touch a file.
The plugin ships its own templates, forms and CSS, so it works on any Grav theme and modifies none of them.
๐ฎ๐น Documentazione in italiano
Four page types, which show up in the admin's Page Type dropdown:
| Type | Purpose |
|---|---|
| Pizzeria Menu | The menu page. You need exactly one |
| Menu Section | A section: Classics, Specials, White pizzasโฆ |
| Pizza | One pizza |
| Dough | A dough type, with an optional surcharge |
On the page you get a responsive card grid, automatic photo cropping with a placeholder for pizzas that have no photo yet, formatted prices, vegetarian and spicy badges, a sold out state, allergens with the official EU numbering, and a collapsible legend carrying the legal wording.
A pizza's section is the page that contains it โ there is no "category" field to keep in sync. Reordering and re-categorising is drag & drop in the page tree.
- Grav 2.0+
- PHP 8.3+ (same as Grav 2)
- No other plugins, no external dependencies
Download pizzeria-menu.zip from the Releases
page, then:
unzip pizzeria-menu.zip -d /path/to/grav/user/plugins/
php bin/grav clearcacheOr let the bundled script do the copy, permissions and cache flush:
unzip pizzeria-menu.zip && cd pizzeria-menu
./install.sh /path/to/grav --demogit clone https://github.com/LucaCraft89/grav-plugin-pizzeria-menu.git \
/path/to/grav/user/plugins/pizzeria-menu
php bin/grav clearcacheThe folder must be named
pizzeria-menu. Grav ties the plugin folder name to its config key. GitHub's green "Download ZIP" button producesgrav-plugin-pizzeria-menu-main/, which installs but never picks up its configuration โ the menu then renders with no currency, no labels and no allergens. Use a release archive,./build-zip.sh, or rename the folder.
./install.sh /var/www/html --demo # also install a sample menu
./install.sh /srv/grav --demo --docker grav-1 # clear the cache inside a container
./install.sh /var/www/html --no-chown # leave file ownership alone--demo drops in a ready-made menu: 5 sections, 14 pizzas, 6 doughs.
Admin โ Plugins โ Pizzeria Menu should be listed and enabled. Admin โ Pages โ Add Page: the four new page types should appear in Page Type. If they don't, the cache wasn't cleared.
Menu (Pizzeria Menu)
โโโ Classics (Menu Section)
โ โโโ Margherita (Pizza)
โ โโโ Marinara
โโโ Specials
โ โโโ โฆ
โโโ Neapolitan (Dough)
โโโ Wholemeal
- Add Page โ Pizzeria Menu โ title it
Menu. - Inside it: Add Page โ Menu Section.
- Inside a section: Add Page โ Pizza.
- Doughs go directly under the Menu, not inside a section.
| Field | Notes |
|---|---|
| Title | The name on the card |
| Photo | Drop an image in. Cropped automatically, 3:2 works best |
| Price | Number only, dot decimal: 8.50 โ rendered as โฌ 8,50 |
| Ingredients | One line, printed under the name |
| Allergens | Checkboxes, the statutory list |
| Vegetarian / Spicy | Badges |
| Available | No greys the card out and marks it sold out, keeping it on the menu |
| Long description | Shown only on the pizza's own page |
Without a photo the card falls back to a placeholder and keeps its height, so the grid never goes ragged.
Admin โ Plugins โ Pizzeria Menu, or
user/config/plugins/pizzeria-menu.yaml.
| Key | Default | What it does |
|---|---|---|
base_template |
partials/base.html.twig |
The theme layout the menu pages extend |
load_css |
true |
Load the plugin's stylesheet |
currency |
'โฌ ' |
Price prefix (the trailing space is intentional) |
photo.card_width / card_height |
600 / 420 |
Crop for cards |
photo.single_width / single_height |
1200 / 700 |
Crop for the single page |
photo.placeholder |
๐ |
Shown when there is no photo |
labels.* |
Italian | Every string the menu prints |
allergeni.opzioni |
14 entries | Short labels for the checkboxes |
allergeni.legale |
14 entries | Full legal wording for the legend |
base_template is normally the only key you need to touch. Most Grav themes use
partials/base.html.twig; check what your theme's templates/default.html.twig
extends. The plugin's templates render inside a Twig block named content; if
your theme uses a different block name, override the templates (below).
The plugin doesn't use Grav's translation system โ every string lives under
labels and is editable from the admin panel. Defaults ship in Italian. Swap
them plus allergeni (the EU list has official wording in every member-state
language) and the menu is fully translated without touching a template.
Theme template paths are registered before the plugin's, so any file with the
same name in user/themes/<theme>/templates/ wins:
cp user/plugins/pizzeria-menu/templates/partials/pizza-card.html.twig \
user/themes/<theme>/templates/partials/pizza-card.html.twigSame for pizza-menu.html.twig, categoria.html.twig, pizza.html.twig,
impasto.html.twig, partials/pizza-allergens.html.twig and
macros/pizza.html.twig.
The stylesheet has no hardcoded colors. Everything routes through --pz-*
tokens, and each token first tries the matching Quark 2 variable (--q2-*). On
Quark 2 the menu inherits the site's light/dark theme and accent color for free;
on any other theme it falls back to its own values, which have a light and a dark
variant. To force your own:
:root { --pz-accent: #d4380d; --pz-radius: 4px; }Set load_css: false if you'd rather write the whole stylesheet yourself.
.pizza-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); }17rem is the minimum card width โ raise it for bigger cards and fewer columns.
The column count adapts on its own; there is no fixed number to change.
Forms live in blueprints/pages/. To add a "lactose free" switch to pizzas:
header.lactose_free:
type: toggle
label: 'Lactose free'
ordering@: header.piccante
default: 0
highlight: 1
options: { 1: 'Yes', 0: 'No' }
validate: { type: bool }Fields are chained with ordering@: title โ foto โ price โ ingredients โ allergeni โ vegetariana โ piccante โ available. If you splice a field into the
middle, update the ordering@ of the one that used to follow, or the chain
breaks and the field drops to the bottom.
Then print it in the card:
{% if p.header.lactose_free %}<span class="pizza-tag">Lactose free</span>{% endif %}The list and its numbering come from Annex II of Regulation (EU) 1169/2011.
It lives in one place, the plugin config, in two forms: opzioni (short labels,
feeding the admin checkboxes) and legale (full legal text, feeding the legend).
The blueprint wires to it with
config-options@: plugins.pizzeria-menu.allergeni.opzioni, so there is no second
copy to keep in sync โ change a label once and the panel, the cards and the
legend all follow.
Pages store the numbers only:
allergeni:
1: true
7: trueDon't renumber it โ the numbering is statutory. Relabelling is fine.
The plugin displays whatever allergens you tick; the accuracy of that data is the responsibility of whoever fills in the menu.
The new page types don't show up in Add Page.
The page-type list is cached. php bin/grav clearcache โ inside Docker,
docker exec -w /var/www/html -u www-data <container> php bin/grav clearcache.
Menu pages render without the theme's layout.
base_template points at the wrong file for your theme.
Content shows raw HTML tags (<p>โฆ</p>).
A template is printing page.content without |raw. The plugin's templates
already do this correctly; if you copied one into your theme, add the filter.
Allergens render as bare numbers.
A partial can't see config, which happens when an {% include %} uses only โ
that flag isolates the context and takes Grav's global variables with it. Drop
only.
500 error after running a CLI command.
You ran bin/grav as root and the regenerated cache is now root-owned.
chown -R www-data:www-data <grav-root>/cache, and run CLI commands as the web
server user.
pizzeria-menu/
โโโ pizzeria-menu.php registers page types, templates and CSS
โโโ pizzeria-menu.yaml default config, allergen list included
โโโ blueprints.yaml the plugin's own settings form
โโโ blueprints/pages/ the page forms
โ โโโ pizza-menu.yaml
โ โโโ categoria.yaml
โ โโโ pizza.yaml
โ โโโ impasto.yaml
โโโ templates/
โ โโโ pizza-menu.html.twig sections, grids, doughs, legend
โ โโโ categoria.html.twig
โ โโโ pizza.html.twig
โ โโโ impasto.html.twig
โ โโโ partials/pizza-card.html.twig
โ โโโ partials/pizza-allergens.html.twig
โ โโโ macros/pizza.html.twig photo resolution and placeholder
โโโ css/pizzeria-menu.css
โโโ demo/03.menu/ optional sample menu
โโโ install.sh scripted install
โโโ build-zip.sh builds an install-ready archive
The plugin hooks five events: onGetPageBlueprints and onGetPageTemplates to
register the page types, onTwigTemplatePaths for the templates,
onTwigSiteVariables for the stylesheet, onPluginsInitialized to wire the rest.
Field and page-header names are Italian (prezzo aside, they read price,
ingredients, allergeni, vegetariana, piccante, available,
supplemento, descrizione). They are kept as-is for backwards compatibility
with existing menus; the admin labels are what users actually see, and those are
configurable.
- No cart and no online ordering.
- One price per pizza โ no per-size pricing.
- Sections are static; there is no client-side category filter.
- One language at a time, driven by
labels.
Issues and pull requests welcome at https://github.com/LucaCraft89/grav-plugin-pizzeria-menu.
MIT.