This chapter is for agencies, freelancers and administrators who deploy WP InPlate on a client's website.
Compatibility
- WordPress 6.4+, PHP 8.1+, WooCommerce 8.0+ (tested up to 11.2).
- Compatibility with HPOS (WooCommerce High-Performance Order Storage) is declared.
- The elements on the website are Gutenberg blocks and shortcodes – they work in any WordPress-compatible theme.
- A custom login address (e.g. WPS Hide Login) is supported by the start screen and the team apps.
App addresses
WP InPlate registers its own addresses (WordPress rewrite rules):
| Address | App |
|---|---|
/wp-inplate/start/ | Team start screen |
/wp-inplate/kds/ | KDS |
/wp-inplate/courier/ | Courier app |
/wp-inplate/radio/ | Radio |
/wp-inplate/tv/ | TV launcher |
/wp-inplate/tv/{slug}/ | TV screen |
/wp-inplate/tv-manager/ | TV Manager |
/wp-inplate/table/{token}/ | The link from a table's QR code (a redirect to the table page) |
Every installable app has a manifest generated by the plugin (the name from the website's name and Settings → System → Phone and tablet apps) and a service worker:
| Address | File |
|---|---|
/wp-inplate/kds-manifest.webmanifest, /wp-inplate/kds-sw.js | KDS manifest and service worker |
/wp-inplate/courier-manifest.webmanifest, /wp-inplate/courier-sw.js | Courier app manifest and service worker |
/wp-inplate/radio-manifest.webmanifest, /wp-inplate/radio-sw.js | Radio manifest and service worker |
All addresses are relative to the website's address, so WordPress in a subdirectory (e.g. example.com/shop/) works the same: the KDS is at /shop/wp-inplate/kds/. The server must not serve .webmanifest and .js files under /wp-inplate/ as static files – these requests must reach WordPress.
These addresses need pretty permalinks. With the “Plain” format (?p=123) they return a 404 error – Readiness to sell shows it as a blocker with a Turn on pretty permalinks button (the “Post name” format). When the stored rules don't contain InPlate's (e.g. another plugin rebuilt them before they were added), InPlate rebuilds them itself on the next request. If the addresses still return a 404 error, save the permalink settings again (Settings → Permalinks → Save Changes).
The plugin's REST API uses the wp-inplate/v1 namespace (/wp-json/wp-inplate/v1/…).
Pausing orders is saved in a separate option, wp_inplate_ordering_pause, not in the settings, so saving the settings or the schedule never undoes it. Endpoint /wp-json/wp-inplate/v1/ordering-pause (GET – people with KDS access, POST and DELETE – the inplate_pause_orders or manage_woocommerce capability). A pause with a time limit ends at the first read after the time has passed, without cron.
Switched-off single times are saved in a separate option, wp_inplate_slot_blocks (for each method: the today or until_reset mode, the day, the “from” time and interval they were chosen with, the list of times). Saving the schedule doesn't undo them; a “Today only” switch-off disappears at the first read after today's window ends, without cron. Endpoints (the inplate_manage_menu or manage_woocommerce capability): GET /wp-json/wp-inplate/v1/schedule/slot-blocks, PUT and DELETE /schedule/slot-blocks/{pickup|delivery|dine_in}. While a method has switched-off times, POST /schedule rejects a change of its “from” time and interval (409 slot_blocks_active). When an order is placed, the server checks the chosen time (the window's grid, switch-offs, whether it has passed, the day shown to the customer) and, if needed, returns 409 slot_unavailable with the current list of times.
Cache
Exclude from full-page caching (cache plugins, server caches, CDNs):
- the menu page, the order page and the table ordering page,
- all
/wp-inplate/…addresses, - the
/wp-json/wp-inplate/…REST API.
Caching these addresses is the most common cause of an outdated menu and of problems with the first test order.
HTTPS and the server
- HTTPS is required to install the apps (PWA) and for push notifications.
- Push notifications require the OpenSSL PHP extension.
- Maps and geocoding use OpenStreetMap (map tiles and Nominatim) – the server and the administrator's browser must have access to
openstreetmap.org. - Scheduled tasks (e.g. turning on InPlate dishes at midnight, bringing back dishes turned off “for today only”) use WP-Cron. On low-traffic websites consider a system cron that calls
wp-cron.php.
Order statuses
| KDS status | WooCommerce status |
|---|---|
| New, In preparation, Ready | unchanged (the status is set by the payment, e.g. “Processing” or “On hold”) |
| On the way | wc-in-transit – a status added by WP InPlate (delivery only) |
| Delivered | completed |
| Cancelled | cancelled |
The kitchen status, fulfillment method, time, table and assignment are saved in the order data.
Payment and shipping methods
| Identifier | Name |
|---|---|
wpi_cash_pickup | InPlate | Pay at the counter (pickup and eating in) |
wpi_cash_delivery | InPlate | Pay the courier (delivery) |
wpi_cash_table | InPlate | Pay at the table (QR table) |
wp_inplate_shipping | The “InPlate – Delivery / Pickup” shipping method (the pickup address in the email) |
The server rejects an order with a payment method that doesn't match the fulfillment method.
The packaging fee is a WooCommerce fee line (WC_Order_Item_Fee) with the _wpi_packaging_fee (fulfillment method) and _wpi_packaging_gross (gross amount) metadata. WooCommerce saves the net fee and the tax separately; WP InPlate sets the tax included in the gross amount, so the customer pays exactly the amount from the settings, and the order's VAT lines add up.
The radio connects to the Atmosphere catalog only from the website's server – the browser gets no key and reads the catalog through /wp-json/wp-inplate/v1/radio/catalog (plays: POST /radio/play, the connection state for the administrator: /radio/status). The website confirms its license with signed Freemius API requests of its installation; Atmosphere checks them with Freemius and issues a session token valid for up to 12 hours (stored in the wp_inplate_atmosphere_token transient). The catalog is cached for 10 minutes (wp_inplate_radio_catalog). The website must be able to make outgoing HTTPS connections to wp-inplate-atmosphere.vercel.app.
The radio's My stack is the _inplate_radio_stack user meta – only references to the Atmosphere catalog (track, playlist and mood identifiers, up to 2,000 items), without stream addresses. The /wp-json/wp-inplate/v1/radio/stack endpoint (GET and PUT – the whole stack at once) is available to signed-in staff (not the store's customers) with a license that covers the radio; everyone sees and saves only their own stack.
Shared playlists are saved in the author's _inplate_radio_shared user meta (up to 30 playlists per author, a name of up to 60 characters; the same references as the stack), so they disappear together with the author's account. Endpoints: GET and POST /wp-json/wp-inplate/v1/radio/shared (the list of all and sharing – the same name, regardless of letter case, replaces the author's playlist) and DELETE /radio/shared/{author id}-{key}. Access as for the stack; only the author or a user with the manage_woocommerce capability can delete a playlist – otherwise the server returns 403.
Content types and tables
- TV screens are the
inplate_displaypost type, edited in Gutenberg. - Staff permissions are WordPress capabilities with the
inplate_prefix (e.g.inplate_kds_access), and the staff role isinplate_operator. - WP InPlate creates its own database tables: InPlate dishes, delivery zones (
inplate_zones, with the free delivery thresholdfree_from, DB 1.19), order assignments, status history, add-on templates, cross-sell templates, variant sets (inplate_variant_sets), QR tables, price history (inplate_price_history), happy hours rules (inplate_happy_hours) and promotions (inplate_promotions, DB 1.18). - Sizes are native WooCommerce variations: a variable product with one local attribute (the choice name from the set). WP InPlate saves the product type, the attribute and the variations only through the WooCommerce API and only after the preview is confirmed (
POST /variant-sets/{id}/planreturns a plan with a hash,POST /variant-sets/{id}/applyruns it only with that hash; a changed plan – 409plan_changed). The product ID doesn't change. A size without a price is a variation with theprivatestatus; going back to a regular product deletes the variations with the WooCommerce mechanism (woocommerce_product_type_changed). Variable products created in WooCommerce aren't changed. Meta:_wpi_variant_set(the set assigned to the product),_wpi_variant_skip(“No variants”),_wpi_variant_applied(the set whose sizes the product has),_wpi_variant_size(the variation's size), the category metawpi_variant_sets. Add-on templates:variant_set_id,size_ids, and on itemssame_price,size_pricesand – for a mandatory surcharge –methods(fulfillment methods:pickup,delivery,dine_in,table; an empty list = always; DB 1.15). Surcharges of cart items are kept separately (wpi_surcharges) and calculated at every cart recalculation by the fulfillment method (a QR table or the method chosen in the summary);POST /checkout/methodalso returns the items with new prices. REST:/wp-inplate/v1/variant-sets(CRUD,/duplicate,/bulk-assign,/grid,/plan,/apply),/terms/{id}/variant-sets,/products/{id}/variant-set– theinplate_manage_menuormanage_woocommercecapability, saving variations alsoedit_products. The menu API (/menu,/menu/product/{slug}) and cross-sell suggestions return thevariantsfield (sizes with prices, Omnibus, availability and weight) and the cheapest size's price andprice_max. The variation ID goes to the cart (?wc-ajax=add_to_cartwith the variation'sproduct_id);POST /checkout/replace-cart-itemacceptsvariation_id. Add-on and cross-sell templates havePOST /{type}/{id}/duplicate. - Allergens: the dictionary in
Support\Allergens– 14 built-in ones (idsgluten,crustaceans,eggs,fish,peanuts,soy,milk,nuts,celery,mustard,sesame,sulphites,lupin,molluscs, numbers 1–14) and own ones in thewp_inplate_allergensoption (idown-…, numbers from 15, never reused). A dish's information is in the_wpi_allergensproduct meta (filled,contains,may_contain,ingredientswith**…**highlights) – also for InPlate dishes (their product's meta). Add-on template items have anallergenscolumn (DB 1.16), options – anallergensfield in the options' JSON; a product's own add-ons – in_wpi_addons. WooCommerce fields aren't changed. An order item gets a copy of the allergens (of the dish and the chosen add-ons, with names) in the hidden_wpi_allergensmeta; shown throughwoocommerce_order_item_meta_end(emails, thank-you page, My account) andwoocommerce_after_order_itemmeta(wp-admin). REST:GET /wp-inplate/v1/allergens(public; for managers alsousageandmissing),POST,PUT /allergens/{own-id},DELETE /allergens/{own-id}(removes the allergen from dishes and templates),GET/PUT /allergens/product/{id},POST /allergens/bulk(product_ids,add,remove) – theinplate_manage_menuormanage_woocommercecapability. The menu API and cross-sell suggestions returnallergens(nullwhen not filled in); the menu page gets the dictionary of names inwpInplateMenu.allergens. Allergen chart: the[wp-inplate-allergen-card]shortcode, thewp-inplate/allergen-cardblock. Settings inwp_inplate_settings:allergens_display(card_modal– default,modal_only,off; passed to the menu inwpInplateMenu.settings.allergens_display; withoffthe email hook prints nothing, while wp-admin and the allergen chart keep working) andallergens_on_labels(defaultfalse;POST /wp-inplate/v1/kds/label/{id}then returnsallergensfor the items –[name, value]pairs, e.g.["Allergens", "Gluten, Milk"]– from the copy saved in the order). - The price history (Omnibus) is made of fixed-price periods for every product and variation, saved when a product is saved, on a direct change of the
_pricemeta, on a status change and by the dailywp_inplate_price_history_reconcilecron (entries older than 400 days are deleted). The first fill after an update is done by the one-offwp_inplate_price_history_seedcron event. Prices in WooCommerce aren't changed. - Happy hours rules are saved in versions: a change closes the current version and creates a new one, so the lowest price in 30 days takes earlier windows into account. The switch and the history of its changes are in the
wp_inplate_happy_hoursoption. In the cart the discount is a virtual WooCommerce couponhappy-hours-{id}(with no database entry); the order saves a copy of the rule in the_wp_inplate_happy_hoursmeta. REST:/wp-inplate/v1/happy-hours/status(public),/wp-inplate/v1/manager/happy-hours(management:inplate_manage_menuormanage_woocommerce, saving alsoedit_products),POST /wp-inplate/v1/checkout/method(the fulfillment method chosen in the summary). - Promotions (
inplate_promotions): the type (order_discount,free_delivery,buy_x_get_y,gift) with its configuration in JSON, days, hours, dates, channels,combine_coupons, the order. Product and order discounts are virtual WooCommerce couponspromo-{id}(with no database entry), calculated on every cart recalculation; free delivery lowers the InPlate delivery fee. The order saves a copy of the promotions in the_wp_inplate_promotionsmeta, and a €0 delivery line saves the fee before the promotion (_wpi_delivery_fee_original) and its name (_wpi_delivery_promotion).POST /wp-inplate/v1/checkout/placetakesautomatic(the automatic discounts shown to the customer) anddelivery_fee; a difference = 409promotions_changed. A zone's free delivery (free_from, only with a zone fee above 0) counts the products after discounts and gives the same 0 delivery line (named “Darmowa dostawa od 199 zł” in the base language); with a free delivery promotion the lower fee wins. REST:/wp-inplate/v1/promotions/status(public),/wp-inplate/v1/manager/promotions(permissions as for happy hours, licensecan_manage_promotions).
Customer data
- Sign-in codes – the database holds only their hash (HMAC), never the code itself. They expire after 10 minutes and work once.
- Consents – on the account (the
_inplate_consentsuser meta) and on the order (the_inplate_consentsorder meta): identifier, version, text, date, source. - Delivery addresses – the
_inplate_addressesuser meta (up to 10 addresses: name, city, street, number, flat, notes for the courier, coordinates) and_inplate_default_address. The WooCommerce billing and shipping addresses (My account → Addresses) aren't changed – they are the customer's data, e.g. for invoices. Every order has its own delivery address. Coordinates are set once when the address is saved (OpenStreetMap), the zones – for every order. - VAT number – one field added to the WooCommerce billing address (My account → Addresses), the
billing_inplate_nipuser meta. Apart from that the address forms are fully WooCommerce – InPlate doesn't hide or change anything in them. - Export and erasure – Tools → Export Personal Data / Erase Personal Data cover the consents and delivery addresses saved on the account.
The look of “My account”
When the InPlate look for “My Account” is on in Settings → Customer accounts, the WooCommerce My account pages get the wpi-account class on <body> and the account-look.css stylesheet. The rules apply only on the account pages and win over the theme, so the look is the same on every website.
You change colors, corner radii and spacing with CSS variables – without fighting specificity. Put them in the theme's stylesheet or in Appearance → Customize → Additional CSS:
body.wpi-account {
--wpi-account-accent: #e63946; /* buttons, active menu item */
--wpi-account-accent-hover: #c1121f;
--wpi-account-radius: 4px;
--wpi-account-font: "Inter", sans-serif;
}| Variable | Default | Used for |
|---|---|---|
--wpi-account-accent | --wpif-primary | Buttons, the active menu item, the border of a field being edited |
--wpi-account-accent-hover | --wpif-primary-hover | A button on hover |
--wpi-account-accent-text | white | Text on buttons |
--wpi-account-accent-soft | --wpif-primary-light | The background of the active menu item, a field's glow |
--wpi-account-surface | --wpif-surface | Card backgrounds |
--wpi-account-soft | --wpif-surface-2 | Secondary backgrounds (table headers, hover) |
--wpi-account-border | --wpif-border | Borders |
--wpi-account-text | --wpif-text | Text |
--wpi-account-muted | --wpif-text-2 | Secondary text, icons |
--wpi-account-radius | --wpif-radius-lg | Card corner radius |
--wpi-account-radius-sm | --wpif-radius | Button and field corner radius |
--wpi-account-nav-width | 250px | The menu's width on a computer |
--wpi-account-gap | 28px | The gap between the menu and the content |
--wpi-account-font | inherit (the theme's font) | Typeface |
The defaults come from the InPlate tokens (--wpif-*), so a website that has already changed the InPlate colors has them in My account too. For deeper changes, turn off the InPlate look (the setting or the wp_inplate_account_styles filter) and style the account in the theme.
Hooks for developers
Filters
| Filter | Arguments | Used for |
|---|---|---|
wp_inplate_method_label | $label, $method | Changing the name of the fulfillment method customers see (pickup, delivery, dine_in) |
wp_inplate_account_styles | $on | false turns off the InPlate look in My account (the page is left to the theme) |
wp_inplate_account_downloads | $show | Whether My account shows the Downloads tab. By default only when the store has a downloadable product |
Actions
| Action | Arguments | When |
|---|---|---|
wp_inplate_kds_status_changed | $order_id, $from, $to, $operator_id | A kitchen status change |
wp_inplate_kds_order_assigned | $order_id, $target_id, $kds_status | An order is assigned |
wp_inplate_kds_payment_changed | $order_id, $paid, $operator_id | A payment status change in the KDS |
wp_inplate_kds_delivery_problem | $order_id, $reason, $operator_id | A courier reported a failed delivery |
wp_inplate_kds_delivery_problem_cleared | $order_id, $reason, $operator_id | A manager cleared the return |
wp_inplate_kds_return_restored | $order_id, $reason, $operator_id | A courier brought a return back to delivery |
wp_inplate_ordering_paused | $pause (channels, end, reason, who, since when) | Orders are paused or the pause is changed |
wp_inplate_ordering_resumed | – | Orders are resumed (manually) |
wp_inplate_courier_order_taken_over | $order_id, $from_id, $to_id | A courier took over another courier's order with the QR code |
wp_inplate_consent_changed | $user_id, $consent_id, $given | A customer with an account gave or withdrew a consent (at account creation, in an order, in My account) |
Example – sending an order to an external system when the kitchen marks it as ready:
add_action( 'wp_inplate_kds_status_changed', function ( $order_id, $from, $to, $operator_id ) {
if ( 'ready' !== $to ) {
return;
}
// Your integration, e.g. sending a text message.
}, 10, 4 );Translations
Text domain: wp-inplate. Translation template: languages/wp-inplate.pot. Available translations: en_US, uk.
Menu in several languages
- Settings in
wp_inplate_settings:multilingual_enabled(the feature's switch, defaultfalse– thenLanguage::extra()is empty, while the chosen languages and translations stay),menu_languages(InPlate codes fromSupport\Language::CATALOG, without the main language),language_switch_auto(the switch above the menu and in the order form, defaulttrue; the front end gets it inwpInplateLang.auto) anddeepl_api_key(never returned byGET /settings;deepl_api_key_saved, deleting:deepl_api_key_clear). - Translations in the
{prefix}inplate_translationstable (DB 1.17):object_type,object_id,field,lang,value,source_hash(the md5 of the main-language text – a different hash = “to check”). Types:product(name,short_description,description,ingredients),product_cat(name,description),product_tag(name),addon_item(the template item's id, orp{product}:{id}for a product's add-ons;label,group),addon_option({item key}:{option id}),variant_set(axis),variant_size({set}:{size}),allergen(own ones),promotion(a typed promotion name;name),text(pickup_label,delivery_label,dine_in_label,unavailable_label,packaging_label,consent:{id}). Products, terms and WooCommerce fields aren't changed or copied. - The request's language: the InPlate REST API translates a response only with the
langparameter (the front end adds it itself); the team screens don't send it. Pages:?lang=→ thewpi_langcookie →Accept-Language→en_US(when enabled) → the main language; the front end getswindow.wpInplateLang. On a cached page the front end itself switches to?lang=when the customer's choice differs from the page's language. - The order is saved in the main language (items, add-on meta, fees); the order meta
_wp_inplate_customer_lang. Emails to the customer (WooCommerce and InPlate) are switched to the order's language (woocommerce_email_recipient_{id}→woocommerce_email_sent), as are the thank-you page and My account → order; item names, add-ons, the packaging fee and allergens are translated when displayed (woocommerce_order_item_name,woocommerce_order_item_get_formatted_meta_data,woocommerce_get_order_item_totals). - A separate switch: the
[wp-inplate-language-switch align="left|center|right"]shortcode and thewp-inplate/language-switchblock (thelanguage-switchbundle); without a multilingual site they print nothing. - The theme's WooCommerce mini cart (e.g. Kadence) is rendered in the page's language (
woocommerce_before_mini_cart…woocommerce_after_mini_cart, also in thewc-ajax=get_refreshed_fragmentsfragments). - A language without a WordPress pack (e.g.
ukwithout the downloaded language): WordPress and WooCommerce in English, InPlate texts fromlanguages/wp-inplate-{locale}.mo. - REST (the
inplate_manage_menuormanage_woocommercecapability; saving requires a license):GET /wp-inplate/v1/translations/languages,GET /translations?language=,PUT /translations(language,items[{type, id, field, value}]– the main-language text always from the server, an emptyvaluedeletes),POST /translations/machine(DeepL suggestions, without saving, up to 400 texts),GET /translations/export?language=(CSV),POST /translations/import(language,csv),POST /translations/deepl-check.
Adding a language
Every language from Support\Language::CATALOG can be turned on straight away – the menu content is translated, and the buttons and messages are in English. A full customer interface in a new language is added by a script in the plugin's repository:
node tools/i18n/add-language.mjs de_DE [--deepl-key=KEY]It creates the dictionary of the customer screens assets/src/i18n/de_DE.js (menu, widget, order form, delivery checker, cart, My account) and languages/wp-inplate-de_DE.po / .mo with the PHP texts customers see (order API, emails, thank-you page). DeepL fills in the texts or – without a key – English does; every new entry is marked REVIEW for checking. Running it again adds only new texts. Webpack builds every dict-*.js, and PHP finds the built dictionary by itself. Completeness: node tests/i18n/check.mjs --scope=front --locale=de_DE --only=missing-po,missing-js.