Order Messenger for WooCommerce – Developer Documentation

This reference is for developers extending Order Messenger for WooCommerce: PHP services, hooks, templates, styling, JavaScript, the REST API and shortcodes. For setup and day-to-day use see the user documentation.

Services

↑ Back to top

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->getSettings();           // the WooCommerce settings tab

Messages are rows of the {prefix}wc_order_messages table. Dates are stored in UTC.

ColumnMeaning
idMessage id.
order_idThe order the conversation belongs to.
user_idThe order’s customer id, 0 for guests.
sender_idThe user who wrote the message, 0 for system entries.
attachment_idMedia library id of the attachment, or NULL.
messagePlain text.
type0 team, 1 customer, 2 order note, 3 status change, 4 scheduled (waiting), 5 scheduled (sent).
date_sent, date_readUTC timestamps; date_read is NULL while unread.
is_notified1 once an email went out or none was needed.
dataJSON: scheduled_date, notify, from and to for status changes, note_id, purchase_product_id.

Messages from code

↑ Back to top
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 or 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
}

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

Hooks

↑ Back to top

All hook names start with order_messenger/. The most used ones are listed here, grouped by purpose. The complete set is in the plugin source: search it for apply_filters and do_action.

Actions

↑ Back to top
ActionArgumentsWhen
messages/message_createdMessage $messageA message was stored, any type.
messages/message_updatedMessage $messageA message changed: read state, publish.
messages/before_delete, messages/message_deletedMessage $messageBefore and after a message is deleted.
messages/changed?Message $message, int $orderIdAnything changed in an order’s conversation; the unread counters listen here.
messages/scheduled_message_publishedMessage $published, Message $scheduledA scheduled message went out.
notifications/sentint $orderId, Message[] $messages, string $side, bool $sentA notification email was attempted; $side is admin or customer.
attachments/uploadedint $attachmentId, WC_Order $order, array $resultA customer upload was stored.
admin/product_page/messenger_tab_begin, messenger_tab_endint $productIdAdd fields to the product’s Order Messenger tab.
rest/register_routesstring $namespaceRegister your own routes next to the plugin’s.
upgradedstring $from, string $toAfter a data migration.

Permissions

↑ Back to top
FilterArgumentsPurpose
permissions/capabilitystring $capabilityThe capability that marks team members, manage_order_messenger by default.
permissions/can_managebool $allowed, int $userIdWhether a user is part of the team.
permissions/userCanViewMessengerbool $allowed, WC_Order $order, int $userIdWhether a customer may open a conversation.
permissions/userCanSendMessagebool $allowed, int $userId, WC_Order $order, string $textWhether a customer may write.
permissions/userCanSendAdminMessagebool $allowed, int $userId, WC_Order $orderWhether a team member may write to this order.
permissions/userCanDeleteMessage, userCanUnreadMessagebool $allowed, Message $messageMessage actions.
settings/allowed_roles_to_manage_admin_messenger/defaultstring[] $rolesDefault team roles.

Messages and content

↑ Back to top
FilterArgumentsPurpose
messages/before_saving_customer_messageMessage $message, WC_Order $order, ?array $fileChange a customer message before it is stored.
messages/before_saving_admin_messageMessage $message, $request, array $customData, WC_Order $order, int $attachmentIdChange a team message before it is stored.
messages/before_saving_system_messageMessage $message, WC_Order $orderChange an automatic entry.
messages/before_publishing_scheduled_messageMessage $published, Message $scheduledChange a scheduled message as it goes out.
message/sender_namestring $name, Message $messageThe name shown for a message.
message/formatted_textstring $html, ?string $textThe rendered text: links, line breaks.
message/max_lengthint $lengthCharacters allowed per message, on top of the setting.
system_messages/post_order_notebool $post, $note, WC_Order $orderSkip specific order notes.
config/rate_limitarray $limitCustomer messages per window: limit and window in seconds.
config/message_types_customer_should_be_notified, config/message_types_admin_should_be_notifiedint[] $typesWhich message types trigger emails for each side.
notification/email_delayint $minutesThe digest delay.

Settings, templates and storefront

↑ Back to top
FilterArgumentsPurpose
settings/sectionsSectionAbstract[] $sectionsAdd a settings section.
settings/settingsarray $fields, SectionAbstract $sectionChange the fields of a section.
settings/available_themesarray $palettesAdd a color palette: name to custom properties.
settings/available_file_typesarray $typesAdd an attachment type: key to title, mime and ext.
config/get_{key}mixed $value, array $argsEvery setting passes through a filter named after it, for example config/get_polling_interval.
template/locationstring $file, string $templateWhere a template is loaded from.
message/message_template_pagestring $path, string $place, Message $messageThe template used for a message.
frontend/conversation_configarray $config, WC_Order $orderThe data attributes the storefront script reads.
frontend/show_on_order_receivedbool $show, WC_Order $orderShow the conversation on the order confirmation page.
frontend/theme_integration_cssstring $cssExtra CSS printed with the storefront stylesheet.
order/customer_name, order/summarystring or array, WC_Order $orderHow an order is described in the admin app and the emails.
attachments/sideloadbool $sideload, array $file, ?WC_Order $orderAccept files that are not browser uploads: imports, tests.
rest/featuresarray $features, WC_Order $order, bool $isManagerWhat the composer offers for an order and viewer.
rest/messagearray $data, Message $message, string $contextThe JSON shape of a message.
admin/app_configarray $configThe configuration passed to the admin app.

Examples

↑ Back to top
// 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 );

Templates

↑ Back to top

Storefront templates live in the plugin’s views/frontend folder. Copy them 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 the script should 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, which receives the resolved file and the template name.

Styling

↑ Back to top

The customer-side conversation is theme-neutral on purpose: it inherits the page font, derives its lines and fills from the current text color and uses the theme’s own inputs and buttons. The named palettes set custom properties on .om-messenger and .om-conversations; set them yourself for a custom look:

.om-messenger {
    --om-primary-color: #2b6cb0;       /* store bubbles */
    --om-primary-text-color: #ffffff;
    --om-secondary-color: #f1f5f9;     /* customer bubbles */
    --om-secondary-text-color: #0f172a;
    --om-radius: 12px;
}

Useful classes: .om-messenger (the whole widget), .om-message with the modifiers –customer, –admin, –system and –own, .om-message__bubble, .om-message__meta, .om-attachment, .om-messenger__composer, .om-badge and .om-nav-badge (unread counts) and .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.

JavaScript

↑ Back to top

The storefront script has no dependencies. Each element with the data-om-messenger attribute 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. The polling interval is the Live updates setting; 0 switches polling off.

The admin app is a React application built on the WordPress components package. Its configuration is window.omMessengerConfig, filterable through order_messenger/admin/app_config, and the composer’s options for a given order come from the rest/features filter.

REST API

↑ Back to top

Namespace order-messenger/v1. Requests from the site itself authenticate with the WordPress cookie and an X-WP-Nonce header (the wp_rest nonce); external clients use application passwords. Customers reach only their own orders; guests pass the order key as key. Team routes require the manage_order_messenger capability.

RouteMethodParameters
/orders/{id}GETkey for guests. Returns the order summary, unread count and the features the composer may offer.
/orders/{id}/messagesGETbefore_id, after_id, per_page (up to 200), render = json or html. Newest page first; after_id is what live updates poll with.
/orders/{id}/messagesPOSTmessage, file (multipart, customers), attachment_id (team), notify, schedule_at (Y-m-d\TH:i:s in site time), custom_data, render. Answers 201 with the message.
/orders/{id}/readPOSTMark the viewer’s side of the conversation as read.
/messages/{id}DELETETeam only.
/messages/{id}/unreadPOSTTeam only.
/conversationsGETTeam only. search, unread_only, page, per_page (up to 100).
/unread-countGETTeam 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();

Errors use the usual WordPress REST shape with the HTTP status: 400 for an empty or invalid message, 403 for a viewer outside the conversation, 404 for an unknown order, 429 when a customer exceeds the rate limit. The routes of version 3 (/get/{limit}/{offset}, /send, /sendAdmin, /delete/{id}, /unread/{id}) still answer with their old shapes.

Shortcodes and endpoints

↑ Back to top
ItemDetails
[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 endpointsmessages (the list) and messenger/{order-id} (one conversation), registered through woocommerce_get_query_vars.
Admin pageadmin.php?page=om-messenger&order={id} opens a conversation on the Messenger page.

Extending the admin

↑ Back to top
  • Product tab: print extra fields between the admin/product_page/messenger_tab_begin and messenger_tab_end actions and save them on woocommerce_process_product_meta.
  • Settings: add a section with settings/sections (a class extending Settings\Sections\SectionAbstract) or add fields to an existing one with settings/settings. Every value is then available through Config and the config/get_{key} filter.
  • Composer features: rest/features decides, per order and viewer, whether attachments, scheduling and the email toggle are offered.
  • Order summary: order/summary and order/customer_name change how an order is shown in the conversation header and the emails.
  • Messages: before_saving_admin_message receives the REST request and the custom_data array, so a custom composer field can be stored in the message’s data and rendered with message/message_template_page.
  • Order actions: the Send purchasing message order action runs the purchase messages addon; call createSystemMessage() from your own actions for other automation.

Debugging

↑ Back to top
  • Background jobs: WooCommerce → Status → Scheduled Actions, group order-messenger. order_messenger/publish_scheduled_message runs once per scheduled message, order_messenger/publish_due_scheduled_messages every 10 minutes as a catch-up (scheduler/catch_up_interval), order_messenger/send_notification_digests while digests are enabled (notifications/digest_interval).
  • Notifications: the notifications/sent action reports every attempt with the side and whether wp_mail() accepted it. Digests skip messages that were read in the meantime.
  • Uploads: customer files live under uploads/order-messenger/ behind a deny rule and are served through admin-post.php?action=om_attachment with a nonce tied to the attachment. Use AttachmentService::getDownloadUrl( $attachment, $size, $orderKey ) to build links; a 403 from that endpoint means the viewer is not part of the conversation.
  • Rate limit: customers may send 30 messages per 10 minutes by default; the REST API answers 429. Change it with config/rate_limit.
  • Settings values: every setting can be overridden for a request with its config/get_{key} filter, which is the quickest way to test a configuration without saving it.
  • Templates: wrap your override in the template/before_render and template/after_render actions to confirm it is the one being loaded.

Support

↑ Back to top

For questions about the plugin, contact support with your WooCommerce.com account.

Use of your personal data
We and our partners process your personal data (such as browsing data, IP Addresses, cookie information, and other unique identifiers) based on your consent and/or our legitimate interest to optimize our website, marketing activities, and your user experience.