Skip to content

Powered by Grav

Coming soon — KahunaCart is in final testing. Join the list and be first to know. Join the list

CLI

KahunaCart ships eight console commands under bin/plugin kahunacart. This page documents all of them: arguments, options, output and exit codes.

Before you begin

  • Run every command from the Grav root, the directory that contains bin/.
  • export, import and seed refuse an out-of-date schema. They print The KahunaCart schema is out of date. Run bin/plugin kahunacart migrate first. and exit 1.

Command reference

Command Arguments and options What it does
migrate --status Apply pending database migrations.
status none Health check: database, migrations, job queue, providers.
work --budget=<seconds> Process pending jobs, then run maintenance.
sync --provider=<slug>, --push, --pull, --status Push or pull the catalog against a sync-capable provider.
export file (optional) Write the catalog to a YAML file, or to stdout.
import file (required), --dry-run Read a catalog file back in, upserting by slug.
seed --purge Fill the catalog with a demo store, or remove it again.
webhooks --provider=<slug>, --limit=<n>, --purge Recent webhook deliveries, per-provider totals, failure streaks.

migrate

Apply pending database migrations.

BASH
bin/plugin kahunacart migrate
bin/plugin kahunacart migrate --status
Option Default Effect
--status off List pending steps without applying them.

The command prints the database engine, then every pending step grouped by migration. Without --status it applies them and finishes with a count. Nothing pending prints Schema is up to date — nothing to migrate. and exits 0.

Migrations are step-level and idempotent, so re-running a partially applied migration picks up where it left off. There are no down migrations; recovery means restoring from backup. Applies run under a database lock, so two concurrent runs cannot interleave DDL.

Run this in your deploy pipeline on a MySQL or PostgreSQL store, where database.auto_migrate does not auto-apply. See Configuration → The auto_migrate policy.

BASH
# Typical deploy step
bin/plugin kahunacart migrate --status   # see what is coming
bin/plugin kahunacart migrate            # apply it

status

Health check across the database, migrations, job queue and providers.

BASH
bin/plugin kahunacart status

It prints:

  • Database — the dialect in use.
  • A MySQL strict-mode warning if the server is not strict. A non-strict MySQL silently truncates data.
  • Migrationsup to date, or a count of pending steps.
  • Jobs — pending, failed and completed counts.
  • Providers — slug, label and declared capabilities for each.
TXT
KahunaCart Status
=================

Database: sqlite
Migrations: up to date
Jobs: 0 pending, 0 failed, 47 completed
Providers:
  offline (Bank Transfer) []
  stripe (Credit / Debit Card (Stripe)) [hosted_checkout, refunds, partial_refunds]

Run this first when something is not working. A provider missing from the list is not configured, because providers only register once their credentials are present. Pending migrations explain most "column not found" errors, and a growing failed-job count means the queue is choking.

work

Process pending jobs, then run maintenance.

BASH
bin/plugin kahunacart work
bin/plugin kahunacart work --budget=120
Option Default Effect
--budget=<seconds> 50 How long the runner keeps claiming jobs before stopping.

Important

Grav's scheduler runs this every minute, registered as kahunacart-jobs on a * * * * * schedule. Run it by hand only when debugging, or when the scheduler is not set up.

Job types it handles:

Type Purpose
order.confirmation_email Order confirmation to the customer, plus the BCC copy.
order.payment_received_email Receipt for an order that was awaiting payment and is now paid.
order.shipped_email "Your order is on its way", when an order is marked as shipped.
order.refund_email Refund notice, queued when a provider confirms a refund.
order.grant_downloads Mint download grants for an order's digital lines.
customer.set_password_email The "choose a password" link for a new store account. Mints the token as it sends.
stock.release_expired Release expired stock holds.
cart.abandoned_sweep Abandoned-cart reminders.
notifications.dispatch One merchant notification, fanned out across matching channels.
notifications.low_stock_sweep Low-stock merchant warnings.
sync.push_product Push one product to a provider.
sync.pull_catalog Pull a provider's whole catalog.

After the queue drains, or the budget runs out, four maintenance tasks run on the same tick:

  1. Expired stock holds are released.
  2. The abandoned-cart sweep runs when cart.abandoned_enabled is on. It caps itself at 25 carts and stamps each one. Its failures are reported but not fatal.
  3. The low-stock sweep runs, stamping each variant so it warns once per slide under the threshold. Its failures are reported but not fatal.
  4. Inactive carts are purged per cart.purge_days, default 90, and webhook log rows are swept per payments.webhook_log_days, default 30. Set either to 0 to keep the rows forever.

Output looks like:

TXT
Jobs: 3 processed, 0 failed. Holds released: 1. Carts purged: 0. Webhook rows swept: 0.

The exit code is 1 if any job failed, 0 otherwise, so it works as a monitoring check.

Failed jobs are retried with a doubling backoff, capped at one hour, up to max_attempts, default 3. After that they sit in the failed bucket with last_error recording why.

Overlapping workers process each job exactly once, so running two workers is safe. A lock held for more than 300 seconds is treated as stale and the job can be claimed again.

sync

Push or pull the catalog against a sync-capable provider. See Sync for what syncs.

BASH
bin/plugin kahunacart sync
bin/plugin kahunacart sync --provider=polar
bin/plugin kahunacart sync --provider=polar --push
bin/plugin kahunacart sync --provider=polar --pull
bin/plugin kahunacart sync --provider=polar --push --status
Option Effect
--provider=<slug> Which provider. Required with --push or --pull; filters the status tables otherwise.
--push Push the local catalog to the provider.
--pull Pull the provider's catalog into the local one.
--status Show mappings, capabilities and directions.

Running sync with no action flag behaves as --status. Passing --push and --pull together is an error, and so is either one without --provider. Manual runs are not gated on the configured direction, which only governs automatic syncing.

The status output has two tables:

TXT
Mappings
--------
 Provider  Mapped  By state
 polar     12      synced: 11, stale: 1

Providers
---------
 Provider  Sync capabilities              Implements sync  Direction
 offline   —                              no               off
 polar     sync_products, sync_orders     yes              remote

stale mappings mean the provider reported a change that was not applied because it is not master here. Implements sync: no on a provider that declares sync capabilities is a bug in that plugin. A push or pull failure prints the error and exits 1.

export

Write the catalog to a YAML file.

BASH
bin/plugin kahunacart export catalog.yaml
bin/plugin kahunacart export            # to stdout, for piping or redirecting
bin/plugin kahunacart export - > catalog.yaml
Argument Effect
file Where to write it. Omit it, or pass -, to print to stdout with no decoration.

The file holds categories with their nesting, landing-page setting and picture; products; every variant; option axes and their values; category and tag assignments; image references; and translated text. Orders, customers, coupons and settings are not included.

Four things about the format matter before you edit one by hand:

  • Everything is referenced by slug, never by row id.
  • Amounts are quoted decimal stringsprice: '28.00', never 28.00. Unquote one and the import refuses the file.
  • Images are paths, not bytes, relative to the store's images root. A category's picture rides under image:.
  • Tags are labels matched on their derived slug, so Organic finds an existing organic.

display: is written only when it is not auto, and an empty translations: block is omitted. Output is ordered by slug, so the same catalog always exports to the same file.

YAML
# KahunaCart catalog export
# Amounts are decimal strings in the currency named above; import refuses a bare number.
version: 1
currency: USD
categories:
  - slug: apparel
    title: Apparel
    position: 0
    display: both
    image:
      path: 3/apparel-8c21f0a91b3e.jpg
      alt: A rail of shirts
  - slug: shirts
    title: Shirts
    parent: apparel
    position: 0
products:
  - slug: reef-runner-tee
    title: Reef Runner Tee
    type: physical
    status: published
    tax_class: standard
    categories:
      - apparel
      - shirts
    tags:
      - organic
      - sale
    images:
      - path: products/tee.jpg
        alt: A tee
        position: 0
    variants:
      - sku: KC-TEE-M
        title: Medium
        is_default: true
        position: 0
        price: '28.00'
        compare_price: '35.00'
        stock_managed: true
        stock_qty: 4
        weight_grams: 180

import

Read a catalog file back in.

BASH
bin/plugin kahunacart import catalog.yaml
bin/plugin kahunacart import catalog.yaml --dry-run
Argument or option Effect
file The catalog file to read. Required.
--dry-run Validate the file and report what it holds without writing anything.

Important

Import never deletes. A slug in the file that exists here is updated, one that does not is created, and anything in the store and not in the file is left as it was. Running the same file twice is the same as running it once.

What that means in practice:

  • Products and categories are matched on slug.
  • Variants are matched on SKU when they have one, and on position when they do not.
  • Category assignments are added, not replaced. A file that puts a product in apparel does not take it out of sale.
  • Tag assignments are added too. Remove a tag in the admin instead.
  • Categories are imported in two passes, every row first and parent: second, so a file may list a child before its parent.
  • Existing images are left alone, alt text included.

The whole import runs in one transaction, so a malformed file leaves the catalog as it found it. On success it reports how many categories, products, variants, images, options, option values and translations it wrote.

A file exported in a different currency is refused, not converted.

Validation happens up front and fails with a sentence naming the problem: an unknown category parent, a duplicate slug, or a price that is not a decimal string. Run --dry-run first on any file somebody sent you.

The YAML both commands read and write is a narrow subset: block mappings and sequences, quoted and plain scalars, comments. Anchors, flow collections, block scalars and tab indentation are refused by name.

seed

Fill an empty catalog with a demo store.

BASH
bin/plugin kahunacart seed
bin/plugin kahunacart seed --purge
Option Effect
--purge Remove what the seed file names instead of creating it. Lists everything first and asks.

It creates a nine-category tree — Clothing → Mens → Tops/Bottoms/Accessories, Clothing → Womens → Tops/Accessories, and a flat Digital Goods — plus eleven products filed on the leaves and tagged organic, new and sale.

The command is idempotent: a slug that already exists is skipped and never updated, so it cannot overwrite a real product.

It is also reversible. --purge removes exactly the slugs the seed file names, deepest categories first, with their pictures. Seeded tags are removed only when nothing else is wearing them. Existing orders are unaffected either way.

Caution

--purge deletes catalog rows. The confirmation defaults to no, so running it with --no-interaction aborts rather than wiping a catalog.

Digital products are seeded with no files attached, and the command says so when it finishes. Until you upload one, a digital order completes normally and grants nothing.

No photographs ship either. Drop a picture named after a product's slug into seed/images/, or after a category's slug into seed/images/categories/, and the seeder imports it. Both directories are optional. See seed/README.md.

webhooks

Show what the payment providers have been sending.

BASH
bin/plugin kahunacart webhooks
bin/plugin kahunacart webhooks --provider=stripe --limit=50
bin/plugin kahunacart webhooks --purge
Option Default Effect
--provider=<slug> all Only show deliveries for this provider.
--limit=<n> 25 How many deliveries to list. Capped at 200.
--purge off Delete deliveries past the retention window and stop.

It prints recent deliveries with their time, provider, action, status, reference and error, then per-provider totals with the current failure streak. A streak counts consecutive failed or invalid deliveries since the last success.

A failure streak is reported, never acted on: KahunaCart never disables a provider automatically. Check the provider's webhook secret and endpoint URL first.

--purge sweeps rows older than payments.webhook_log_days, default 30. At 0, deliveries are kept forever and --purge deletes nothing. The worker tick does the same sweep on its own.

With no deliveries recorded, the command names the registered providers instead.

Run the scheduler

If bin/plugin kahunacart work produces results by hand but nothing happens on its own, Grav's scheduler is not installed in cron:

BASH
bin/grav scheduler --jobs      # is kahunacart-jobs registered?
bin/grav scheduler --install   # print the crontab line to add
bin/grav scheduler --run       # force a run now, to prove it works

Warning

Without the scheduler in cron, order confirmations never send, download grants are never minted, stock holds never expire, and carts are never purged. This is the most common cause of "the customer never got their download". See Troubleshooting.