Configuration
This page lists every KahunaCart config key, its default, and what it does. All of them live in user/config/plugins/kahunacart.yaml.
Most keys also have a field on the plugin's settings page in the admin. Keys marked YAML-only have no admin field.
Clear the route cache after a plugin update
The API plugin caches its route table on disk under cache://api/. Installing, enabling, disabling, or removing a plugin invalidates that cache automatically. Updating an already-enabled plugin does not, so its new API endpoints return 404 while the admin screens that call them look installed.
After updating KahunaCart or any provider plugin, clear the cache:
bin/grav clear-cache
To drop only the route table, delete the directory:
rm -rf cache/api
Note
Route caching is skipped while system.debugger.enabled is true. This is why the problem appears in production and not in development.
Store
| Key | Default | Effect |
|---|---|---|
enabled |
true |
Master switch. Off means no storefront routes, no admin screen, no API endpoints. |
route |
/shop |
Base route for the storefront. Every other storefront route lives under it: /shop/cart, /shop/product/{slug}, /shop/webhook/{slug}. Changing it changes your webhook URLs, so update them at every provider. |
currency |
USD |
ISO 4217 code, exactly 3 characters. Stored once per order, so changing it does not rewrite history. |
order_number_start |
1000 |
First sequential order number. Numbers come from an atomic database sequence, so they never collide or repeat. |
builtin_css |
true |
Enqueues the shipped storefront stylesheet on KahunaCart routes. Set to false when your theme owns all storefront styling. See Storefront → Styling and Theming. |
Product images
Product images live in user/images/kahunacart/products/{productId}, which the web server serves directly. KahunaCart decides an upload's format by reading the file, never by its extension.
| Key | Default | Effect |
|---|---|---|
images.max_upload_mb |
8 |
Largest single product image the admin upload endpoint accepts. |
images.derivatives |
true |
Lets Grav generate resized copies for cards and thumbnails. false serves the originals. |
Storefront SEO
These apply to product and category pages, and nowhere else.
| Key | Default | Effect |
|---|---|---|
seo.enabled |
true |
Emits JSON-LD, meta description, Open Graph, and Twitter cards. |
seo.canonical |
false |
Emits <link rel="canonical">. Kahuna and Quark2 emit their own, so enable this only for a theme that does not. |
seo.sitemap |
true |
Contributes product and category URLs to the Sitemap plugin when it is installed. |
Storefront search
| Key | Default | Effect |
|---|---|---|
search.limit |
50 |
Most results one search page shows at {route}/search?q=. |
Tax
The flat rate below is a fallback engine. As soon as one tax zone exists, the zone engine takes over, and tax.rate, tax.apply_to_shipping, and tax.label stop being read. See Tax.
| Key | Default | Effect |
|---|---|---|
tax.rate |
'0' |
Flat percent as a string, such as '20' or '8.25'. Ignored once tax zones exist. |
tax.included |
false |
Catalog prices already contain tax. Applies to both engines. The contained amount is back-calculated and shown on the receipt as an included adjustment that does not move the total. |
tax.apply_to_shipping |
false |
Whether the flat rate taxes shipping too. Ignored by the zone engine, which decides this per rate. |
tax.label |
'Tax' |
Label on the flat-rate adjustment. Ignored by the zone engine, which uses each rate's own label. |
tax.basis |
shipping |
Which address decides the zone: shipping or billing. The other address is the fallback when the chosen one has no country. This governs shipping zone matching too. |
Shipping
The flat rate applies until the first shipping zone exists. See Shipping.
| Key | Default | Effect |
|---|---|---|
shipping.flat_rate |
'0' |
Decimal string in the store currency, such as '5.99'. 0 means no shipping charge. Applied to any order containing a physical item. Ignored once shipping zones exist. |
shipping.label |
'Shipping' |
Label on the flat-rate adjustment. |
Stock
| Key | Default | Effect |
|---|---|---|
stock.hold_minutes |
15 |
How long a stock hold keeps stock reserved while a customer is away at a redirect payment provider. Holds expire on their own, so an abandoned payment never strands stock. 0 reserves nothing. |
Cart
| Key | Default | Effect |
|---|---|---|
cart.purge_days |
90 |
Days of inactivity before an unfinished cart, its items, and its adjustments are deleted. 0 keeps carts forever. Runs on every worker tick. |
cart.abandoned_enabled |
false |
Sends a one-time reminder email to customers who left something in their cart. Requires the Email plugin. |
cart.abandoned_hours |
24 |
Hours a cart must sit untouched before it earns its reminder. Each cart is mailed once, ever. |
A cart qualifies for a reminder only if it has an email address on it. In practice that means the customer submitted the checkout form and then did not pay.
Emails
Both emails render from templates/emails/*.twig and are overridable in your theme like any other Grav template.
| Key | Default | Effect |
|---|---|---|
emails.store_copy_to |
'' |
BCC address for every order confirmation. Empty sends no copy. |
emails.abandoned_subject |
'' |
Subject line for the abandoned-cart reminder. Empty uses the translated default, "You left something in your cart". |
Checkout
| Key | Default | Effect |
|---|---|---|
checkout.require_terms |
false |
Shows a terms checkbox on the checkout form and refuses the submit without it. |
checkout.default_country |
'' |
ISO 3166-1 alpha-2 code the country select starts on, such as US. Empty means the customer chooses. |
Address validation
Optional delivery-address checking through a third-party geocoder, billed to your own account with that provider. It stays off until you set both a provider and a key. See Address validation.
| Key | Default | Effect |
|---|---|---|
checkout.address_validation.provider |
none |
none, mapbox, or google. |
checkout.address_validation.api_key |
'' |
Your own key. Server-side only, never sent to the browser. |
checkout.address_validation.mode |
suggest |
suggest offers corrections at checkout. off checks and stamps the order silently. |
Note
Address validation never blocks a sale. A service that is down, slow, or out of quota is logged and waved through.
Customer accounts
The account area needs the Login plugin, which owns the sign-in form, forgot password, magic links, two-factor, and remember-me. KahunaCart never authenticates anybody itself and never writes to a user account YAML. account.db_identity decides only where a customer's credentials are kept. See Customer accounts.
| Key | Default | Effect |
|---|---|---|
account.db_identity |
true |
Storefront customers sign in against KahunaCart's own database instead of user/accounts/*.yaml. Admin accounts are unaffected and always win a lookup. Read Store accounts before changing it. |
account.checkout_registration |
true |
Offers guests an opt-in account at checkout. The account is opened at order completion and the customer gets an emailed link to set a password. Requires account.db_identity. |
account.invite_expiry_days |
7 |
How long a set-password link stays clickable. After that the ordinary forgot-password form works for them. |
account.claim_by_email |
true |
When someone logs in with no order history, matches them to a guest customer record with the same email address and hands them that history. A record another account already holds is never reassigned. |
Warning
Turn account.claim_by_email off if anyone can register with an email address they do not own. See the trade-off.
Invoices
Invoices print at {route}/order/{hash}/invoice. No PDF is generated; the browser's print dialog is the export.
| Key | Default | Effect |
|---|---|---|
invoice.store_name |
'' |
Printed at the top of every invoice. Empty falls back to the site title. |
invoice.store_address |
'' |
Your business address, one line per line. |
invoice.tax_id |
'' |
VAT or tax registration number. Omitted from the invoice when empty. |
invoice.footer |
'' |
Markdown printed at the foot of every invoice. |
Offline payments
All three keys are on the plugin settings page, under Commerce → Offline Payment.
| Key | Default | Effect |
|---|---|---|
offline.enabled |
true |
Registers the built-in offline provider as a payment option. |
offline.label |
'Bank Transfer' |
What the customer sees at checkout. |
offline.instructions |
'' |
Markdown shown on the confirmation page for offline orders, such as where to send the money. |
Digital delivery
See Digital products for the full picture, including the nginx and Apache configuration the non-PHP delivery methods need.
| Key | Default | Effect |
|---|---|---|
downloads.grant_on |
paid |
When download grants are created. paid waits for funds to be captured. completed grants as soon as the order is placed, which is what offline methods need. |
downloads.default_limit |
~ (unlimited) |
Downloads allowed per grant when the file itself sets no limit. |
downloads.default_expiry_days |
~ (never) |
Grant lifetime in days when the file itself sets none. |
downloads.method |
php |
php, xaccel, or xsendfile. Anything unrecognized falls back to php. |
downloads.xaccel_prefix |
/kahunacart-files/ |
Internal nginx location that maps to the files directory. Used only by the xaccel method. |
downloads.path |
'' |
Where downloadable files live. Empty means user-data://kahunacart/files. Absolute paths and Grav stream URIs both work. An absolute path outside the document root is the safest setup. |
downloads.max_upload_mb |
512 |
Largest single file the admin upload endpoint accepts. PHP's upload_max_filesize and post_max_size still apply and are usually the lower limit. |
default_limit and default_expiry_days read as unset when empty, non-numeric, or zero or below. Clearing the admin field means unlimited or never, not zero.
Merchant notifications
Outbound notifications to Slack, Discord, or an endpoint of your own. No channel is configured by default. See Notifications for payload details and the signing contract.
| Key | Default | Effect |
|---|---|---|
notifications.enabled |
true |
Master switch. false silences every channel without deleting them. |
notifications.low_stock_threshold |
5 |
Stock at or below this earns a stock.low event. 0 reports only when an item has run out. |
notifications.timeout |
5 |
Seconds allowed per channel per POST. Capped at 15. |
notifications.channels |
[] |
The list of channels. |
Each entry in notifications.channels takes these keys.
| Key | Effect |
|---|---|
label |
Name shown in the admin, such as Sales. |
type |
slack, discord, or webhook. |
url |
The endpoint you paste from the service. |
enabled |
Whether this channel sends. |
events |
List of event names, such as [order.completed, payment.received], or all. |
secret |
Signs the request body. Webhook channels only. |
Webhook log and job warnings
| Key | Default | Effect |
|---|---|---|
payments.webhook_log_days |
30 |
Webhook delivery rows older than this are swept by the worker. 0 keeps them forever. |
jobs.stale_after_minutes |
15 |
The admin warns when the oldest waiting job is older than this. 0 disables the warning. |
Important
Background work is queued and drained by Grav's scheduler. If the scheduler is not running, the queue piles up and customers who paid for a download never get one. See Troubleshooting.
Catalog sync
YAML-only. The keys are provider slugs the base plugin cannot know in advance, so there is no admin field for them. See Sync.
sync:
polar:
direction: remote
| Direction | Meaning |
|---|---|
local |
The local catalog is master. Admin product writes queue a push job per local-master provider. |
remote |
The provider is master. Its catalog webhooks apply locally, and pull jobs re-pull it. |
off |
Default. Nothing runs automatically. Anything unrecognized reads as off. |
Direction governs automatic work only. A manual bin/plugin kahunacart sync --push --provider=<slug> or --pull --provider=<slug> always does what you asked.
Database
| Key | Default | Effect |
|---|---|---|
database.type |
sqlite |
sqlite, mysql, pgsql, or connection. |
database.auto_migrate |
sqlite |
When schema updates are applied automatically. See The auto_migrate policy. |
database.sqlite.path |
'' |
Empty means user-data://kahunacart/db/kahunacart.sqlite. |
database.mysql.host |
localhost |
MySQL or MariaDB host. |
database.mysql.port |
3306 |
MySQL or MariaDB port. |
database.mysql.dbname |
'' |
Database name. |
database.mysql.username |
'' |
Database user. |
database.mysql.password |
'' |
Database password. |
database.pgsql.host |
localhost |
PostgreSQL host. |
database.pgsql.port |
5432 |
PostgreSQL port. |
database.pgsql.dbname |
'' |
Database name. |
database.pgsql.username |
'' |
Database user. |
database.pgsql.password |
'' |
Database password. |
database.connection |
'' |
Name of a connection defined in the Database plugin. Read only when database.type is connection. |
SQLite
The zero-configuration default. KahunaCart creates the directory, writes deny-all protection files into it, chmods the database file to 0640, and opens it with journal_mode=WAL, foreign_keys=ON, busy_timeout=5000, synchronous=NORMAL, and temp_store=MEMORY.
MySQL / MariaDB
KahunaCart connects with charset=utf8mb4 and assumes the server runs in strict mode. bin/plugin kahunacart status warns you when it does not, because a non-strict MySQL truncates data silently instead of refusing it.
PostgreSQL
A standard connection. There is nothing extra to configure.
Named connections
Set database.type to connection to hand connection management to the Database plugin. Put the connection name in database.connection:
database:
type: connection
connection: commerce
KahunaCart pulls the PDO handle from that connection and wraps it in the matching dialect. Two errors are possible:
database.type is "connection" but database.connection is empty— setdatabase.connection.named connections require the "database" plugin— install the Database plugin.
The auto_migrate policy
| Value | Behaviour |
|---|---|
sqlite (default) |
Auto-migrates only when the engine is SQLite. Server databases are left alone. |
auto |
Always auto-migrates, on every engine. |
manual |
Never auto-migrates. bin/plugin kahunacart migrate is the only way schema changes get applied. |
When auto-migration is active, the check runs on every non-admin frontend request and on every KahunaCart admin API request. It is a single "is anything pending" query, and it applies migrations only when something is.
Warning
Migrations are up-only. There are no down migrations, so the recovery model is to restore from backup. Applies run under a database run-lock, so two concurrent requests, or a request racing a CLI run, cannot interleave DDL.
Related
- Getting started — install, first product, first sale.
- Tax — tax zones and rates.
- Shipping — shipping zones and rates.
- Digital products — download grants and delivery methods.
- Customer accounts — store accounts and the account area.
- CLI — every
bin/plugin kahunacartcommand.