This reference is for developers extending Order Messenger: template overrides, styling, hooks, the REST API and the PHP services. For setup and day-to-day use see the user documentation. Everything below applies to version 4 of the plugin; both the free and the premium version share this code.
Architecture at a glance
The plugin lives in the U2Code\OrderMessenger namespace. Services are registered in a container and reached through typed getters:
use U2Code\OrderMessenger\Core\ServiceContainer;
$container = ServiceContainer::getInstance();
$container->getMessageService(); // write messages
$container->getMessageRepository(); // read and query messages
$container->getAttachments(); // uploads and the download endpoint
$container->getNotifications(); // email notifications
$container->getScheduler(); // Action Scheduler jobs
$container->getPermissions(); // who may do what
$container->getLicense(); // free / premium
$container->getSettings(); // the WooCommerce settings tab
Messages are rows of the {prefix}wc_order_messages table. Dates are stored in UTC.
| Column | Meaning |
|---|---|
id | Message id. |
order_id | The order the conversation belongs to. |
user_id | The order’s customer id (0 for guests). |
sender_id | The user who wrote the message; 0 for system messages. |
attachment_id | Media library id of the attachment, or NULL. |
message | Plain text, up to 3000 characters. |
type | 0 team, 1 customer, 2 order note, 3 status change, 4 scheduled (waiting), 5 scheduled (sent). |
date_sent / date_read | UTC timestamps; date_read is NULL while unread. |
is_notified | 1 once an email went out (or none was needed). |
data | JSON: scheduled_date, notify, from/to for status changes, note_id, purchase_product_id. |
Sending and reading messages from code
use U2Code\OrderMessenger\Core\ServiceContainer;
use U2Code\OrderMessenger\Entity\MessageType;
use U2Code\OrderMessenger\Services\MessageException;
$container = ServiceContainer::getInstance();
$order = wc_get_order( 123 );
try {
// From the store to the customer. Sender 0 shows the store signature.
$container->getMessageService()->sendAdminMessage( $order, get_current_user_id(), 'Your parcel ships tomorrow.', array(
'notify' => true, // email the customer
'attachment_id' => 0, // a media library id
'schedule_at' => new DateTimeImmutable( '+2 days 10:00', wp_timezone() ), // omit to send now
) );
// From the customer (permissions are checked: the customer, or a guest with the order key).
$container->getMessageService()->sendCustomerMessage( $order, $order->get_customer_id(), 'Thanks!' );
// A system entry, for example from a shipping plugin.
$container->getMessageService()->createSystemMessage( $order, MessageType::service(), 'Shipped with DHL, tracking 123456.', array( 'tracking' => '123456' ) );
} catch ( MessageException $e ) {
// $e->getCode() is the HTTP status the REST API would answer with (400, 403, 429).
error_log( $e->getMessage() );
}
// Reading.
$repository = $container->getMessageRepository();
$messages = $repository->getForOrder( $order->get_id(), array( 'context' => 'admin', 'limit' => 50 ) );
$unread = $repository->countUnreadForOrder( $order->get_id(), 'admin' ); // waiting for the team
$repository->markReadForOrder( $order->get_id(), 'admin' );
foreach ( $messages as $message ) {
$message->getMessage(); // text
$message->getMessageType()->isCustomer(); // who wrote it
$message->getDateSent(); // DateTimeImmutable in the site timezone
$message->getAttachment(); // MessageAttachment or null
}
context is admin for the team’s view and customer for the customer’s; it decides which message types are included and whose unread state is meant.
Template overrides
Storefront templates live in the plugin’s views/frontend folder and can be copied into your theme under order-messenger-for-woocommerce/, keeping the path below frontend/:
your-theme/order-messenger-for-woocommerce/my-account/messenger.php
your-theme/order-messenger-for-woocommerce/my-account/dialogs.php
your-theme/order-messenger-for-woocommerce/my-account/no-access.php
your-theme/order-messenger-for-woocommerce/my-account/no-conversations.php
your-theme/order-messenger-for-woocommerce/message/message-attachment.php
your-theme/order-messenger-for-woocommerce/message/message-types/admin-message.php
your-theme/order-messenger-for-woocommerce/message/message-types/customer-message.php
your-theme/order-messenger-for-woocommerce/message/message-types/service-message.php
your-theme/order-messenger-for-woocommerce/message/message-types/order-status-message.php
your-theme/order-messenger-for-woocommerce/message/message-types/scheduled-message.php
The conversation template receives $order, $orderId, $orderKey, $messages (oldest first), $totalMessages, $hasMore, $canSend, $filesEnabled, $config (the data attributes the script reads) and $presenter. Message templates receive $message, $order and $orderKey. Keep the data-om-* attributes and the om-* classes if you want the script to keep working: it sends messages, polls for new ones and opens the lightbox through them.
The notification emails are regular WooCommerce templates. Copy them from the plugin’s views/emails folder to your-theme/woocommerce/emails/: admin-messenger-notification.php, customer-messenger-notification.php, partials/messages.php and the plain/ variants.
The lookup can be redirected with the order_messenger/template/location filter (arguments: resolved file, template name).
Styling
The customer-side conversation is intentionally theme-neutral: it inherits the page font, mixes its lines and fills from the current text color and uses the theme’s own inputs and buttons. The named palettes set these custom properties on .om-messenger and .om-conversations; you can set them yourself for a custom look:
.om-messenger {
--om-primary-color: #2b6cb0; /* store bubbles */
--om-primary-color-darker: #245a93;
--om-primary-text-color: #ffffff;
--om-secondary-color: #f1f5f9; /* customer bubbles */
--om-secondary-color-darker: #e2e8f0;
--om-secondary-text-color: #0f172a;
}
Useful classes: .om-messenger (the whole widget), .om-message with --customer, --admin, --system, --own modifiers, .om-message__bubble, .om-message__meta, .om-attachment, .om-messenger__composer, .om-badge and .om-nav-badge (unread counts), .om-conversations (the My Account list). Theme-specific rules can be added with the order_messenger/frontend/theme_integration_css filter, which is how the plugin gives Storefront a chat icon in the account menu.
Hooks
All hook names start with order_messenger/. The most useful ones are listed here; grep the plugin for the complete set.
Actions
| Action | Arguments | When |
|---|---|---|
messages/message_created | Message $message | A message was stored (any type). |
messages/message_updated | Message $message | A message changed (read state, publish). |
messages/message_deleted | Message $message | After a message was deleted; messages/before_delete fires before. |
messages/changed | ?Message $message, int $orderId | Anything changed in an order’s conversation; the unread counters listen here. |
messages/scheduled_message_published | Message $published, Message $scheduled | A scheduled message went out. |
notifications/sent | int $orderId, Message[] $messages, string $side, bool $sent | A notification email was attempted; $side is admin or customer. |
attachments/uploaded | int $attachmentId, WC_Order $order, array $result | A customer upload was stored. |
admin/product_page/messenger_tab_begin / _end | int $productId | Add fields to the product’s Order Messenger tab. |
upgraded | string $from, string $to | After a data migration. |
rest/register_routes | string $namespace | Register your own routes next to the plugin’s. |
Permissions
| Filter | Arguments | Purpose |
|---|---|---|
permissions/capability | string $capability | The capability that marks team members (default manage_order_messenger). |
permissions/can_manage | bool $allowed, int $userId | Whether a user is part of the team. |
permissions/userCanViewMessenger | bool $allowed, WC_Order $order, int $userId | Whether a customer may open a conversation. |
permissions/userCanSendMessage | bool $allowed, int $userId, WC_Order $order, string $text | Whether a customer may write. |
permissions/userCanSendAdminMessage | bool $allowed, int $userId, WC_Order $order | Whether a team member may write to this order. |
permissions/userCanDeleteMessage / userCanUnreadMessage | bool $allowed, Message $message | Message actions. |
settings/allowed_roles_to_manage_admin_messenger/default | string[] $roles | Default team roles. |
Messages and content
| Filter | Arguments | Purpose |
|---|---|---|
messages/before_saving_customer_message | Message $message, WC_Order $order, ?array $file | Change a customer message before it is stored. |
messages/before_saving_admin_message | Message $message, $request, array $customData, WC_Order $order, int $attachmentId | Change a team message before it is stored. |
messages/before_saving_system_message | Message $message, WC_Order $order | Change an automatic entry. |
messages/before_publishing_scheduled_message | Message $published, Message $scheduled | Change a scheduled message as it goes out. |
message/sender_name | string $name, Message $message | The name shown for a message. |
message/formatted_text | string $html, ?string $text | The rendered text (links, line breaks). |
message/message_template_page | string $path, string $place, Message $message | The template used for a message. |
system_messages/post_order_note | bool $post, $note, WC_Order $order | Skip specific order notes. |
config/rate_limit | array{limit:int, window:int} | Customer messages per window (30 per 10 minutes by default). |
notification/email_delay | int $minutes | The digest delay. |
config/message_types_customer_should_be_notified / _admin_ | int[] $types | Which message types trigger emails for each side. |
Settings, templates and storefront
| Filter | Arguments | Purpose |
|---|---|---|
settings/sections | SectionAbstract[] $sections | Add a settings section. |
settings/settings | array $fields, SectionAbstract $section | Change the fields of a section. |
settings/available_themes | array $palettes | Add a color palette (name → custom properties). |
settings/available_file_types | array $types | Add an attachment type (key → title, mime, ext). |
config/get_{key} | mixed $value, array $args | Every setting passes through a filter named after it, e.g. config/get_polling_interval. |
template/location | string $file, string $template | Where a template is loaded from. |
frontend/conversation_config | array $config, WC_Order $order | The data attributes the storefront script reads. |
frontend/show_on_order_received | bool $show, WC_Order $order | Show the conversation on the order confirmation page. |
frontend/theme_integration_css | string $css | Extra CSS printed with the storefront stylesheet. |
order/customer_name, order/summary | string|array, WC_Order $order | How an order is described in the admin app and emails. |
attachments/sideload | bool $sideload, array $file, ?WC_Order $order | Accept files that are not browser uploads (imports, tests). |
rest/features | array $features, WC_Order $order, bool $isManager | What the composer offers for an order and viewer. |
rest/message | array $data, Message $message, string $context | The JSON shape of a message. |
license/can_use_premium_code | bool $premium | Premium features on or off. Always true in the WooCommerce.com version. |
Examples
// Post a message when an order is completed.
add_action( 'woocommerce_order_status_completed', function ( $order_id ) {
$order = wc_get_order( $order_id );
\U2Code\OrderMessenger\Core\ServiceContainer::getInstance()->getMessageService()
->sendAdminMessage( $order, 0, 'Your order is complete. Thank you for shopping with us!', array( 'notify' => true ) );
} );
// Show a role name instead of the team member's name.
add_filter( 'order_messenger/message/sender_name', function ( $name, $message ) {
return $message->getMessageType()->isFromStore() ? 'Support team' : $name;
}, 10, 2 );
// Allow SVG attachments.
add_filter( 'order_messenger/settings/available_file_types', function ( $types ) {
$types['svg'] = array( 'title' => 'SVG', 'mime' => 'image/svg+xml', 'ext' => 'svg' );
return $types;
} );
// Keep the conversation off the order confirmation page.
add_filter( 'order_messenger/frontend/show_on_order_received', '__return_false' );
// Let customers write only while the order is not completed.
add_filter( 'order_messenger/permissions/userCanSendMessage', function ( $allowed, $user_id, $order ) {
return $allowed && ! $order->has_status( 'completed' );
}, 10, 3 );
REST API
Namespace order-messenger/v1. Requests from the site itself authenticate with the WordPress cookie and an X-WP-Nonce header (wp_rest nonce); external clients use application passwords. Customers may only reach their own orders; guests pass the order key as key. Team routes require the manage_order_messenger capability.
| Route | Method | Parameters |
|---|---|---|
/orders/{id} | GET | key (guests). Returns the order summary, totals, unread count and the features the composer may offer. |
/orders/{id}/messages | GET | before_id, after_id, per_page (≤ 200), render = json or html. Newest page first; after_id is what live updates poll with. |
/orders/{id}/messages | POST | message, file (multipart, customers), attachment_id (team), notify, schedule_at (Y-m-d\TH:i:s, site time), custom_data, render. Answers 201 with the message. |
/orders/{id}/read | POST | Mark the viewer’s side of the conversation as read. |
/messages/{id} | DELETE | Team only. |
/messages/{id}/unread | POST | Team only. |
/conversations | GET | Team only. search, unread_only, page, per_page (≤ 100). |
/unread-count | GET | Team only. Unread customer messages across all orders. |
// From a page on the same site.
const response = await fetch( wpApiSettings.root + 'order-messenger/v1/orders/123/messages', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': wpApiSettings.nonce },
body: JSON.stringify( { message: 'Hello from the storefront' } ),
} );
const { message } = await response.json();
The routes of version 3 (/get/{limit}/{offset}, /send, /sendAdmin, /delete/{id}, /unread/{id}) still answer with their old shapes for integrations that rely on them.
Background jobs
Scheduled messages and email digests run on WooCommerce’s Action Scheduler in the order-messenger group:
| Hook | Schedule |
|---|---|
order_messenger/publish_scheduled_message | One action per scheduled message, at its time; argument message_id. |
order_messenger/publish_due_scheduled_messages | Every 10 minutes, publishes anything that was missed (scheduler/catch_up_interval filter). |
order_messenger/send_notification_digests | Every few minutes while digests are enabled (notifications/digest_interval filter). |
Attachments
Customer uploads go through wp_handle_upload() with the allowed types from the settings, are renamed to random names, stored under uploads/order-messenger/ behind an .htaccess deny rule, and registered as private media library items linked to the order. Team attachments are ordinary media library files. Every file is served by admin-post.php?action=om_attachment, which checks that the viewer belongs to the conversation (customer, guest with the order key, or team) and streams the file with a nonce that is tied to the attachment. Use AttachmentService::getDownloadUrl( $attachment, $size, $orderKey ) to build such a link, and the attachments/sideload filter to feed files that are not browser uploads.
Shortcodes and endpoints
| Item | Details |
|---|---|
[order_chatroom order_id=""] | One conversation; falls back to the view-order or messenger query var. |
[order_chatrooms limit="10" current_page="1"] | The current customer’s conversations. |
[order_chatroom_unread_messages_count order_id=""] | Unread count for an order, or for the customer when empty. |
| My Account endpoints | messages (the list) and messenger/{order-id} (one conversation), registered through woocommerce_get_query_vars. |
| Admin page | admin.php?page=om-messenger&order={id} opens a conversation on the Messenger page. |
JavaScript
The admin app is a React application on @wordpress/components; its configuration is window.omMessengerConfig (filterable through admin/app_config). The storefront script is dependency-free: each [data-om-messenger] element gets a Conversation instance available as element.omConversation, with poll(), loadOlder() and send(). window.omFrontendConfig carries the polling interval and the unread count for the account menu badge.
Free and premium
The WordPress.org version ships the Freemius SDK; the WooCommerce.com version does not. LicenseManager::canUsePremiumCode() is the only place that asks the SDK, and the license/can_use_premium_code filter sits on top of it. Attachments are the premium feature: on the free plan customer uploads are refused with a 403 and the team composer shows a locked control; everything else works identically in both versions.