Skip to content

Powered by Grav CMS + Helios Theme

Developers

Everything the print-on-demand add-on registers, in one place. The tables marked as generated are written from the plugin's own source by the docs tooling, so they can't drift from the code.

The providers

Code Name Driver Synced import Custom Signed webhooks Webhook setup
printful Printful Provider\Printful\PrintfulProvider Yes Yes Yes By the add-on
printify Printify Provider\Printify\PrintifyProvider Yes No When Printify signs By the add-on
gelato Gelato Provider\Gelato\GelatoProvider Templates, as custom links Yes No By hand, in Gelato's dashboard
kahunaprint Kahuna Print Co. Provider\Demo\DemoProvider Yes No No webhooks None needed

Several can be connected at once. Each product is made by exactly one, so checkout never has to choose between them. The provider code is also the carrier code a shipping method is bound to. The demo provider's settings live under demo_provider in the config; its card and carrier code is kahunaprint, not demo, because demo is the shipping add-on's demo carrier and core keeps one carrier driver per code.

REST routes

The admin routes are documented one group to a page, with every parameter:

Permissions

Permission Plugin Label Unlocks Routes Tools
kahunacart.pod.manage Print on demand Manage Print on Demand Artwork, Catalogue, Imports, Link runs, Links, Providers, Shipping methods 19 19
kahunacart.pod.orders Print on demand Send Print on Demand Orders Orders 3 3

Sending is a permission of its own because it spends the store's money at the provider, the same reason core separates kahunacart.orders.refund from kahunacart.orders.manage. Reading provider orders, the review queue and Check again take core's kahunacart.orders.view.

MCP tools

Print on demand publishes 26 tools over 30 admin routes, behind 3 permissions. An assistant calls each one by its name below with kahunacart_pod_ in front of it.

These 4 routes are not published as tools: POST /kahunacart/pod/products/{id}/artwork, GET /kahunacart/pod/links/export, POST /kahunacart/pod/links/import, POST /kahunacart/pod/providers/{code}/webhook/address.

Tool Route Permission Arguments What it does
list_pod_providers GET /kahunacart/pod/providers kahunacart.pod.manage none Every print-on-demand provider this store can use (Printful, Printify, Gelato, and the demo printer when it is on) with whether it is switched on, whether a key is pasted, whether orders go out for review first, and the capability matrix of what the driver can do
check_pod_provider POST /kahunacart/pod/providers/{code}/check kahunacart.pod.manage code, required Make a real call to the provider with the pasted token and report whether it works: which store it reached, which scopes the token has, and which of the ones the add-on needs are missing
register_pod_webhook POST /kahunacart/pod/providers/{code}/webhook kahunacart.pod.manage code required; reset optional Register this store's webhook address with the provider, so it says when an order is costed, sent, shipped, delivered, cancelled or put on hold, and when a blank goes out of stock
update_pod_provider PATCH /kahunacart/pod/providers/{code} kahunacart.pod.manage code required; enabled, mode, auto_sync, manual_approval_confirmed optional Change the switches on a provider's card: switch it on or off, choose whether paid orders wait for review or are sent automatically, and whether products made at the provider come in by themselves as drafts
add_pod_shipping_methods POST /kahunacart/pod/providers/{code}/shipping-methods kahunacart.pod.manage code, zone_ids required; services optional Add the provider's shipping methods to shipping zones
list_provider_products GET /kahunacart/pod/providers/{code}/products kahunacart.pod.manage code required; offset, limit optional The products the merchant has designed at the provider, a page at a time, each with its thumbnail, how many variants it has, whether it is switched off at the provider, and the KahunaCart product id it was imported as, or null when it is not in the store yet
get_provider_product GET /kahunacart/pod/providers/{code}/products/{ref} kahunacart.pod.manage code, ref required; markup optional One of the merchant's products at the provider, with every variant — its colour and size, SKU, cost in the provider's billing currency, retail price, and whether it can be made — and the import sheet beside it: a suggested selling price per variant in the store's currency, from the provider's retail price or from cost plus the markup
import_pod_product POST /kahunacart/pod/imports kahunacart.pod.manage provider, ref required; prices, accept_suggested, markup_percent optional Import a provider's product as a draft KahunaCart product of type Print on demand, with its options, one variant per variant switched on at the provider, its pictures and a link per variant
refresh_pod_product POST /kahunacart/pod/imports/{id}/refresh kahunacart.pod.manage id required; title, description, images, prices optional Read an imported product from the provider again and bring in its facts: cost, availability, SKU and weight on every variant still there, new variants added switched off for the merchant to price and switch on, and variants the provider dropped marked unavailable but kept
get_pod_links GET /kahunacart/pod/products/{id}/links kahunacart.pod.manage id, required Which provider makes each variant of a product, with the provider's references, the cost, whether the variant can be sold, and the reason when it cannot
set_pod_links PUT /kahunacart/pod/products/{id}/links kahunacart.pod.manage id, links, all required Switch variants of a product made by a provider on or off for sale
search_pod_catalogue GET /kahunacart/pod/providers/{code}/catalogue kahunacart.pod.manage code required; q, offset, limit optional Search a provider's catalogue of blank products for a custom product: a Printful tee, hoodie or poster with every colour and size it comes in
get_pod_blank GET /kahunacart/pod/providers/{code}/catalogue/{blank} kahunacart.pod.manage code, blank, all required One catalogue product with every variant: its colour and size, the blank id a custom link stores, its cost in the provider's billing currency, whether it can be made now, and the print areas it takes with the print-file size each wants
set_pod_made_by PUT /kahunacart/pod/products/{id}/made-by kahunacart.pod.manage id required; provider, mode, match_from, blanks, dry_run optional Set which provider makes a product, and from which blank each of its variants is made
list_pod_artwork GET /kahunacart/pod/products/{id}/artwork kahunacart.pod.manage id, required A custom product's print files: which print area and which variant each is for (none means every variant), its type, pixel size and the public address the provider fetches it from, with a warning when it would print under 150 dpi; and the print areas each variant still needs a file for
delete_pod_artwork DELETE /kahunacart/pod/artwork/{id} kahunacart.pod.manage id, required Delete a print file
assign_pod_links_by_sku POST /kahunacart/pod/links/assign kahunacart.pod.manage match, provider required; mode, blank, files, dry_run optional Link every variant whose SKU a rule catches to a provider: a SKU prefix, or a numeric range compared on the SKU's leading digits
list_pod_link_runs GET /kahunacart/pod/link-runs kahunacart.pod.manage offset, limit The bulk runs that assigned providers — SKU rules and links CSV imports — newest first, with what each did, the lines a CSV refused, and whether it has been undone
undo_pod_link_run POST /kahunacart/pod/link-runs/{id}/undo kahunacart.pod.manage id, required Undo a bulk run: remove the links it wrote, put back the links it replaced, take away the print files it added, and put back any product it left with no links as it was
get_pod_review_queue GET /kahunacart/pod/review kahunacart.orders.view limit Everything print on demand is waiting on a person for, with counts: provider orders that need attention (with the provider's own words), orders waiting to be sent with their cost, products auto-sync brought in as drafts, published products whose cost went up, and products with a variant no longer made or a print file missing
list_pod_orders GET /kahunacart/pod/orders kahunacart.orders.view status, provider, limit, offset Every provider order — one per provider per KahunaCart order — with its KahunaCart order number, provider, status, the provider's own status word, cost in the provider's billing currency, what the customer paid for those lines, the margin when both are in one currency, and which buttons apply
get_order_pod GET /kahunacart/orders/{id}/pod kahunacart.orders.view id, required The provider orders of one KahunaCart order: status, cost once the provider has worked it out, margin, the provider's words when something is wrong, its dashboard link, the lines sent, the parcels with their tracking, and which buttons apply
send_pod_order POST /kahunacart/pod/orders/{id}/send kahunacart.pod.orders id, required Send a provider order waiting in review to production
resend_pod_order POST /kahunacart/pod/orders/{id}/resend kahunacart.pod.orders id, required Send again a provider order that needs attention, once its cause is fixed
refresh_pod_order POST /kahunacart/pod/orders/{id}/refresh kahunacart.orders.view id, required Read a provider order back from the provider now and record what it says: status, cost, the provider's words, new parcels with their tracking, deliveries
cancel_pod_order POST /kahunacart/pod/orders/{id}/cancel kahunacart.pod.orders id, required Cancel a provider order at the provider: a draft is deleted, an order not yet started is cancelled and its cost goes back to the provider wallet

Public routes

Unauthenticated. {api} is the API plugin's base, /api/v1 by default.

Route When it is registered What it is
GET {api}/kahunacart/pod/public/artwork/{token} always Where a provider fetches a print file. The token in the path is the credential, and it changes whenever the file is replaced.
HEAD {api}/kahunacart/pod/public/artwork/{token} always The same file's headers, for a provider that checks before it fetches.
POST {api}/kahunacart/pod/public/webhook/{provider}/{secret} always Where a provider posts its webhooks. Checked for size (413), the secret (404) and, where the provider signs, the signature (401), then logged and answered with no call back to the provider. See Tracking.
GET {api}/kahunacart/pod/public/demo-track/{number} only while the demo provider is on The demo printer's tracking page. See The demo provider.

CLI

Command What it does
bin/plugin kahunacart-print-on-demand check Check a print-on-demand provider's token with a real call: the store it reaches and its scopes
bin/plugin kahunacart-print-on-demand demo:tick Move every open Kahuna Print Co
bin/plugin kahunacart-print-on-demand import Import a product from a print-on-demand provider as a draft, priced from the import sheet
bin/plugin kahunacart-print-on-demand links:export Write every print-on-demand link as a links CSV
bin/plugin kahunacart-print-on-demand links:import Assign providers to variants from a links CSV (sku, provider, mode, blank, file:…)
bin/plugin kahunacart-print-on-demand links:undo Undo a bulk run of print-on-demand links (a SKU rule or a links CSV)
bin/plugin kahunacart-print-on-demand orders List print-on-demand orders, read them back from the provider, or cancel one
bin/plugin kahunacart-print-on-demand products List the products designed at a print-on-demand provider, and which are already imported
bin/plugin kahunacart-print-on-demand record Record the test fixtures from a real Printful store, Gelato account or Printify shop (reads only, never orders), scrubbed
bin/plugin kahunacart-print-on-demand seed Fill the store with the demo printer's products, shipping methods and two orders (idempotent; --purge removes them again)
bin/plugin kahunacart-print-on-demand shipping Add a print-on-demand provider's shipping methods to shipping zones
bin/plugin kahunacart-print-on-demand webhook Register this store's webhook with a print-on-demand provider, or for Gelato print the address and the steps to add it by hand

No command sends an order to production.

Jobs

Handed to KahunaCart's job queue. Every one is safe to run twice.

Type When What it does
pod.submit_order When an order counts as paid, after a change, on Send again Looks the order up by reference, makes it at the provider if it is not there, and in auto mode sends it once the cost is known (waiting a minute at a time, up to six hours).
pod.sync_order After a webhook, from the poll, after a submit, on Check again Reads one provider order back and records its status, cost, words and parcels. One per order at a time.
pod.cancel_order Cancel at …, a cancelled or fully refunded KahunaCart order Cancels one provider order, or records that the provider would not.
pod.poll_orders From the worker tick Queues a read of every open order older than tracking.poll_hours, 25 at a time, and sends the daily waiting and token reminders.
pod.sync_catalog Daily, and on Printful's stock event Availability and cost for every link, and Gelato's stock for products that can run out.
pod.auto_sync A product webhook with auto-sync on Imports or refreshes one product as a draft, and answers Printify's Publish button.
pod.notify_synced The end of an hour with auto-synced drafts The hour's one pod.products_synced notification.
pod.links_import A links CSV committed from the admin Downloads each file once and writes the good rows as one run.
pod.shop_disconnected Printify's shop:disconnected Switches Printify off and tells you, when it is the shop this store sends to.
pod.sweep_webhooks Daily Deletes webhook log rows older than thirty days.

Notifications

Sent on the channels you choose in KahunaCart's Notifications settings.

Code Priority When
pod.needs_attention high A provider could not go ahead with an order, or it could not be sent. Carries the provider's words.
pod.waiting_review normal Once a day while orders have waited for Send longer than notify.waiting_after_hours.
pod.cancel_refused normal A cancelled order had already been started by the provider, so its cost stands.
pod.variant_unavailable normal Once a day, how many options a provider no longer makes since the last check.
pod.products_synced normal Auto-synced drafts arrived. One an hour at most.
pod.cost_increased normal A product you sell costs more to make than it did.
pod.negative_margin normal A provider's cost for an order is more than the customer paid for those items and their shipping.
pod.token_expiring normal Daily in the fortnight before a token's expiry date.

The review queue also has a dashboard alert, pod_queue, at the top of every KahunaCart admin screen. On KahunaCart 1.5.3 or later it also has a row of its own under Notifications → Store owner (alert.pod_queue, normal): one message when the queue first has something in it and a "Cleared" one when it is empty again.

A delivery to the webhook address that is turned away because the secret in the address is wrong or missing, or because the signature does not check out, is also sent to KahunaCart's Webhook rejected row (KahunaCart 1.5.3 or later), under the provider's name and the reason bad_secret, no_secret_configured or bad_signature. It is one message a day per provider and reason however many deliveries are refused. A slug that is not one of this store's providers is not reported. See Report a webhook your own endpoint refused.

Tables

Table What it holds
kahunacart_pod_links One row per variant made by a provider: the provider, synced or custom, the provider's references, the cost, whether it can be sold and why not.
kahunacart_pod_imports One row per imported provider product, for Refresh.
kahunacart_pod_artwork Print files: the product, the variant (none for every variant), the print area, the file, its token, and when it was replaced.
kahunacart_pod_link_runs Bulk runs (SKU rules and links CSVs) with what each wrote and replaced, for Undo.
kahunacart_pod_orders Provider orders: one per provider per order attempt, with the status, the provider's id and words, the cost, what was last sent, and the claim that keeps two actions from running at once.
kahunacart_pod_shipments Parcels, each tied to its core shipment (0 for a replacement parcel).
kahunacart_pod_webhooks The webhook delivery log, kept thirty days.

Migrations pod_0001_core to pod_0004_order_claims, run by KahunaCart's migrator. kahunacart reset empties the order and parcel tables with the sales.

Events

The add-on fires no event of its own. It listens to these core events:

onKahunaCartAccountPanelslistens

Add a panel to an account page

Payload registry, section, user, customer, account_base

onKahunaCartAdminSectionslistens

Contribute an admin screen (§8)

Payload sections

onKahunaCartCartLineValidatelistens

Refuse a cart line, or refuse what it is joining

Payload order_id, product, variant, qty, line_data, item_id, cart_items, errors

onKahunaCartCheckoutValidatelistens

Refuse an order before payment starts

Payload order, items, post, context (checkout, api or express), errors

onKahunaCartCollectEncryptedSettingslistens

Have a setting of your own stored encrypted without being a payment provider

Payload keys (a list of plugins.<plugin>.<key> paths; append yours and write the list back)

onKahunaCartDeclareFeatureCompatibilitylistens

Declare which core features your add-on understands

Payload features, codes

onKahunaCartOrderChangeConfirmedlistens

A change was applied to a new version

Payload order_id, change_id, change, version_from, version_to, order, restocked

onKahunaCartOrderPaidlistens

Money landed after the fact

Payload order_id

onKahunaCartOrderPanelslistens

Add a panel to a customer's receipt page and to their order emails

Payload registry, order, channel, site_url

onKahunaCartProductSavedlistens

A product was written — created, edited, published, unpublished, hidden, or one of its variants changed

Payload id, action (created, updated or variant)

onKahunaCartRefundAppliedlistens

A refund the provider confirmed has been applied to the goods

Payload order_id, order, transaction, items, shipping_minor, full

onKahunaCartRegisterCarrierRateProviderslistens

Plug a live carrier into shipping

Payload registry (a Shipping\Carrier\CarrierRegistry; call add() on it)

onKahunaCartRegisterDashboardAlertslistens

Tell a merchant about something in your add-on that needs their attention, on the dashboard rather than in an email

Payload registry (an Admin\Alerts\AlertRegistry to call register() on)

onKahunaCartRegisterHookslistens

Run a side effect inside an order flow with an undo that runs automatically, in reverse, when a later step fails

Payload registry (an Extension\Hooks\HookRegistry to call on() on)

onKahunaCartRegisterJobHandlerslistens

Register a background job type of your own

Payload registry

onKahunaCartRegisterMigrationslistens

Migrations are being collected. Append your own migrations directory

Payload paths

onKahunaCartRegisterNotificationslistens

Register the emails your plugin sends so they appear on the Notifications screen with a switch and a template picker

Payload registry

onKahunaCartRegisterProductTypeslistens

Register a kind of thing this store can sell

Payload registry

onKahunaCartRegisterResetTableslistens

Declare your add-on's transactional tables so a pre-launch reset takes them too, and leave your configuration tables out of it

Payload registry (an Ops\ResetTableRegistry to call add() or addAll() on)

onKahunaCartRegisterSettingsNamespaceslistens

Declare a namespace of scoped settings for your add-on, stored in kahunacart_settings with no columns of your own

Payload registry (a Extension\SettingsNamespaceRegistry to call register() on)

onKahunaCartRegisterZoneslistens

Declare a zone on a screen of your own so a fourth party can extend it, or mount a widget into a catalogued zone

Payload registry (a Extension\Zone\ZoneRegistry to call declare() and mount() on)

onKahunaCartShippingMethodExcludedlistens

Reinstate a refused shipping method, reword why it was refused, or attach carrier metadata

Payload method, calculation, package, active, reason, reasonKey, reasonParams, metadata, reinstate

onKahunaCartWorkerTicklistens

Put recurring work of your own on the store's own worker clock

Payload queue, now