$ ps-lando

Hooks & recipes

Automate seed data, taxes, cache warmup, and project-specific customisations.

Since 0.4.0, ps-lando ships with a lightweight hooks mechanism so you can automate project-specific customisations (seed data, tax rules, cache warm-up, etc.) without forking the CLI.

How it works

Two convention folders live at the root of any ps-lando project:

FolderWhen it runs
init-scripts/After install-modules, before the smoke test.
post-scripts/After the smoke test, before the outro.

Drop any executable *.sh file in there. Scripts run in lexicographic order — prefix with 01-, 02-... for control. Non-executable files and non-.sh files are ignored.

Env vars injected into hooks

Every script receives this canonical bundle (env var names stable since 0.4.0; the v1.0 set added 4 new entries below):

PS_LANDO_PROJECT             # e.g. my-shop
PS_LANDO_DOMAIN              # e.g. my-shop.lndo.site
PS_LANDO_ADMIN_DIR           # e.g. admin233kcmq1wuumt38aojg (randomized)
PS_LANDO_ADMIN_EMAIL         # e.g. admin@example.com
PS_LANDO_PS_VERSION          # e.g. 9.1.0
PS_LANDO_PROJECT_DIR         # absolute path to the project
PS_LANDO_RECIPE_DIR          # only set for bundled recipes — path to recipes/<name>/

New in 1.0.0

PS_LANDO_THEME_NAME          # e.g. panda — resolved theme name (or "none")
PS_LANDO_PRESETS             # e.g. panda — comma-separated list of resolved presets ("" if none)
PS_LANDO_MODULES_INSTALLED   # POST-only — count of modules actually installed (e.g. 6)
PS_LANDO_RESOLVED_PLAN_JSON  # JSON blob of the full resolved plan; see fallback below
VariablePhaseExample
PS_LANDO_THEME_NAMEinit + postpanda, falcon, none
PS_LANDO_PRESETSinit + postpanda (single), panda,custom (multi), `` (none)
PS_LANDO_MODULES_INSTALLEDpost only6 (set after the install loop completes)
PS_LANDO_RESOLVED_PLAN_JSONinit + postA JSON blob describing the resolved theme, modules, hooks, etc.

PS_LANDO_RESOLVED_PLAN_JSON 32KB fallback

If the resolved plan JSON exceeds 32 KB (large module sets, deeply nested preset metadata), ps-lando writes it to a temp file under the project's .pslando-cache.json neighborhood and exposes the path with a leading @:

# Inline (default — under 32 KB)
PS_LANDO_RESOLVED_PLAN_JSON='{"theme":{"name":"panda",...}}'

# File fallback (over 32 KB)
PS_LANDO_RESOLVED_PLAN_JSON='@/abs/path/.pslando/resolved-plan-XXXX.json'

Hook scripts should detect both shapes:

#!/usr/bin/env bash
set -eu

if [[ "$PS_LANDO_RESOLVED_PLAN_JSON" == @* ]]; then
  plan="$(cat "${PS_LANDO_RESOLVED_PLAN_JSON#@}")"
else
  plan="$PS_LANDO_RESOLVED_PLAN_JSON"
fi

theme=$(echo "$plan" | jq -r .theme.name)
echo "Resolved theme: $theme"

By default, if a script fails the CLI logs a warning and continues. Pass --on-hook-failure fail to abort at the first failure.

Bundled recipes

ps-lando ships 6 recipes out of the box. Discover them with:

ps-lando hooks list
RecipePhaseRequiresWhat it does
demo-catalog-10initpanda10 demo products in a "Demo" category with picsum.photos images.
demo-customer-with-ordersinitA demo customer + Spain address + 3 orders in different states.
demo-cms-pagesinitAbout / Contact / Terms CMS pages in ES + EN.
spain-taxesinitES IVA 21% / 10% / 4% tax rules groups; ES default country + EUR.
clean-seedinitWipes PS built-in demo data (products 1-9, demo customers, images).
cache-warmuppostHits front + BO + category + cart + contact with curl to prime cache.

See Recipes catalog for the per-recipe deep dive (tables touched, idempotency, version compatibility).

Running recipes

Three ways to apply a recipe:

# 1. At create time — interactive multiselect after locale prompts
ps-lando create

# 2. At create time — non-interactive, pick by name
ps-lando create -y --recipes spain-taxes,demo-cms-pages

# 3. On an existing sandbox, one at a time
cd my-shop
ps-lando hooks run spain-taxes
ps-lando hooks run demo-cms-pages

The hooks command

Four subcommands:

SubcommandWhat it does
ps-lando hooks listLists discovered scripts in init-scripts/ + post-scripts/ and all bundled recipes.
ps-lando hooks run <name>Runs a bundled recipe by name (resolves to recipes/<name>/run.sh).
ps-lando hooks run-allRuns every executable script in init-scripts/ then post-scripts/. Useful after db reset if you want to re-seed.
ps-lando hooks install <name>Copies a bundled recipe into your project's init-scripts/ (or post-scripts/ if it's phase post) so every fresh create applies it automatically.

Example — install the Spain taxes recipe as a project-local hook:

ps-lando hooks install spain-taxes
# Creates: init-scripts/50-spain-taxes/run.sh

ps-lando hooks install spain-taxes --prefix 10
# Creates: init-scripts/10-spain-taxes/run.sh — runs earlier

On-failure policy

ps-lando create --on-hook-failure fail
ValueBehaviour
continue (default)Log a warning, keep running the rest of the pipeline.
failAbort create at the first failing hook.

Writing your own recipe

Any executable *.sh in init-scripts/ or post-scripts/ is automatically picked up. Example — seed a custom employee group, gated to the panda preset only:

# init-scripts/10-custom-group.sh
#!/usr/bin/env bash
set -eu
cd "$PS_LANDO_PROJECT_DIR"

# Skip if Panda preset isn't loaded — keeps the hook safe across themes.
case ",$PS_LANDO_PRESETS," in
  *,panda,*) ;;
  *) echo "panda preset not active — skipping"; exit 0 ;;
esac

echo "Theme: $PS_LANDO_THEME_NAME"

lando ssh -s appserver -c "mysql -hdatabase -ulamp -plamp lamp" <<SQL
INSERT IGNORE INTO ps_group
  (reduction, price_display_method, show_prices, date_add, date_upd)
VALUES
  (0, 0, 1, NOW(), NOW());
SQL

Or in a post-scripts/ hook, log how many modules ended up installed:

# post-scripts/99-summary.sh
#!/usr/bin/env bash
set -eu
echo "Sandbox '$PS_LANDO_PROJECT' ready: theme=$PS_LANDO_THEME_NAME presets=$PS_LANDO_PRESETS modules=$PS_LANDO_MODULES_INSTALLED"

Don't forget the executable bit:

chmod +x init-scripts/10-custom-group.sh

It will now run on every ps-lando create from this directory.

Use landoMysql style here-docs (piping SQL via stdin) to avoid the nested-quote hell of lando ssh -c 'mysql ... -e "..."'. See the bundled recipes in the npm package's recipes/ dir for working examples.

Next steps

On this page