Administration

Technical information for developers

App addresses, REST API, order statuses, HPOS compatibility, cache exclusions, hooks for developers and database tables.

Applies to WP InPlate 1.35.2 · Updated:

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):

AddressApp
/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:

AddressFile
/wp-inplate/kds-manifest.webmanifest, /wp-inplate/kds-sw.jsKDS manifest and service worker
/wp-inplate/courier-manifest.webmanifest, /wp-inplate/courier-sw.jsCourier app manifest and service worker
/wp-inplate/radio-manifest.webmanifest, /wp-inplate/radio-sw.jsRadio 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 statusWooCommerce status
New, In preparation, Readyunchanged (the status is set by the payment, e.g. “Processing” or “On hold”)
On the waywc-in-transit – a status added by WP InPlate (delivery only)
Deliveredcompleted
Cancelledcancelled

The kitchen status, fulfillment method, time, table and assignment are saved in the order data.

Payment and shipping methods

IdentifierName
wpi_cash_pickupInPlate | Pay at the counter (pickup and eating in)
wpi_cash_deliveryInPlate | Pay the courier (delivery)
wpi_cash_tableInPlate | Pay at the table (QR table)
wp_inplate_shippingThe “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_display post type, edited in Gutenberg.
  • Staff permissions are WordPress capabilities with the inplate_ prefix (e.g. inplate_kds_access), and the staff role is inplate_operator.
  • WP InPlate creates its own database tables: InPlate dishes, delivery zones (inplate_zones, with the free delivery threshold free_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}/plan returns a plan with a hash, POST /variant-sets/{id}/apply runs it only with that hash; a changed plan – 409 plan_changed). The product ID doesn't change. A size without a price is a variation with the private status; 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 meta wpi_variant_sets. Add-on templates: variant_set_id, size_ids, and on items same_price, size_prices and – 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/method also 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 – the inplate_manage_menu or manage_woocommerce capability, saving variations also edit_products. The menu API (/menu, /menu/product/{slug}) and cross-sell suggestions return the variants field (sizes with prices, Omnibus, availability and weight) and the cheapest size's price and price_max. The variation ID goes to the cart (?wc-ajax=add_to_cart with the variation's product_id); POST /checkout/replace-cart-item accepts variation_id. Add-on and cross-sell templates have POST /{type}/{id}/duplicate.
  • Allergens: the dictionary in Support\Allergens – 14 built-in ones (ids gluten, crustaceans, eggs, fish, peanuts, soy, milk, nuts, celery, mustard, sesame, sulphites, lupin, molluscs, numbers 1–14) and own ones in the wp_inplate_allergens option (id own-…, numbers from 15, never reused). A dish's information is in the _wpi_allergens product meta (filled, contains, may_contain, ingredients with **…** highlights) – also for InPlate dishes (their product's meta). Add-on template items have an allergens column (DB 1.16), options – an allergens field 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_allergens meta; shown through woocommerce_order_item_meta_end (emails, thank-you page, My account) and woocommerce_after_order_itemmeta (wp-admin). REST: GET /wp-inplate/v1/allergens (public; for managers also usage and missing), 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) – the inplate_manage_menu or manage_woocommerce capability. The menu API and cross-sell suggestions return allergens (null when not filled in); the menu page gets the dictionary of names in wpInplateMenu.allergens. Allergen chart: the [wp-inplate-allergen-card] shortcode, the wp-inplate/allergen-card block. Settings in wp_inplate_settings: allergens_display (card_modal – default, modal_only, off; passed to the menu in wpInplateMenu.settings.allergens_display; with off the email hook prints nothing, while wp-admin and the allergen chart keep working) and allergens_on_labels (default false; POST /wp-inplate/v1/kds/label/{id} then returns allergens for 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 _price meta, on a status change and by the daily wp_inplate_price_history_reconcile cron (entries older than 400 days are deleted). The first fill after an update is done by the one-off wp_inplate_price_history_seed cron 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_hours option. In the cart the discount is a virtual WooCommerce coupon happy-hours-{id} (with no database entry); the order saves a copy of the rule in the _wp_inplate_happy_hours meta. REST: /wp-inplate/v1/happy-hours/status (public), /wp-inplate/v1/manager/happy-hours (management: inplate_manage_menu or manage_woocommerce, saving also edit_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 coupons promo-{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_promotions meta, 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/place takes automatic (the automatic discounts shown to the customer) and delivery_fee; a difference = 409 promotions_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, license can_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_consents user meta) and on the order (the _inplate_consents order meta): identifier, version, text, date, source.
  • Delivery addresses – the _inplate_addresses user 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_nip user 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:

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;
}
VariableDefaultUsed for
--wpi-account-accent--wpif-primaryButtons, the active menu item, the border of a field being edited
--wpi-account-accent-hover--wpif-primary-hoverA button on hover
--wpi-account-accent-textwhiteText on buttons
--wpi-account-accent-soft--wpif-primary-lightThe background of the active menu item, a field's glow
--wpi-account-surface--wpif-surfaceCard backgrounds
--wpi-account-soft--wpif-surface-2Secondary backgrounds (table headers, hover)
--wpi-account-border--wpif-borderBorders
--wpi-account-text--wpif-textText
--wpi-account-muted--wpif-text-2Secondary text, icons
--wpi-account-radius--wpif-radius-lgCard corner radius
--wpi-account-radius-sm--wpif-radiusButton and field corner radius
--wpi-account-nav-width250pxThe menu's width on a computer
--wpi-account-gap28pxThe gap between the menu and the content
--wpi-account-fontinherit (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

FilterArgumentsUsed for
wp_inplate_method_label$label, $methodChanging the name of the fulfillment method customers see (pickup, delivery, dine_in)
wp_inplate_account_styles$onfalse turns off the InPlate look in My account (the page is left to the theme)
wp_inplate_account_downloads$showWhether My account shows the Downloads tab. By default only when the store has a downloadable product

Actions

ActionArgumentsWhen
wp_inplate_kds_status_changed$order_id, $from, $to, $operator_idA kitchen status change
wp_inplate_kds_order_assigned$order_id, $target_id, $kds_statusAn order is assigned
wp_inplate_kds_payment_changed$order_id, $paid, $operator_idA payment status change in the KDS
wp_inplate_kds_delivery_problem$order_id, $reason, $operator_idA courier reported a failed delivery
wp_inplate_kds_delivery_problem_cleared$order_id, $reason, $operator_idA manager cleared the return
wp_inplate_kds_return_restored$order_id, $reason, $operator_idA 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_idA courier took over another courier's order with the QR code
wp_inplate_consent_changed$user_id, $consent_id, $givenA 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:

PHP
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.

  • Settings in wp_inplate_settings: multilingual_enabled (the feature's switch, default false – then Language::extra() is empty, while the chosen languages and translations stay), menu_languages (InPlate codes from Support\Language::CATALOG, without the main language), language_switch_auto (the switch above the menu and in the order form, default true; the front end gets it in wpInplateLang.auto) and deepl_api_key (never returned by GET /settings; deepl_api_key_saved, deleting: deepl_api_key_clear).
  • Translations in the {prefix}inplate_translations table (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, or p{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 lang parameter (the front end adds it itself); the team screens don't send it. Pages: ?lang= → the wpi_lang cookie → Accept-Language → en_US (when enabled) → the main language; the front end gets window.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 the wp-inplate/language-switch block (the language-switch bundle); 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 the wc-ajax=get_refreshed_fragments fragments).
  • A language without a WordPress pack (e.g. uk without the downloaded language): WordPress and WooCommerce in English, InPlate texts from languages/wp-inplate-{locale}.mo.
  • REST (the inplate_manage_menu or manage_woocommerce capability; 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 empty value deletes), 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:

Code
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.