Skip to main content

Events Overview

Keelstone uses two types of events to communicate between your Salesforce LWC components and the Office taskpane:

  • Outbound events (LWC → taskpane): postMessage messages that trigger Office API calls
  • Office events (taskpane → LWC): real-time document change notifications forwarded to your flow

How messages reach Excel

LWC components run in Salesforce's domain and cannot call Excel APIs directly. When running inside the Keelstone add-in taskpane via Lightning Out, window.parent is the taskpane itself:

LWC component

└─ window.parent.postMessage(payload, '*')

Excel taskpane (window.addEventListener('message'))

Excel.run() → reads/writes workbook

event.source.postMessage(response, '*') [for two-way messages]

LWC component (window.addEventListener('message'))

Your LWC only needs to call window.parent.postMessage. The taskpane handles all Excel API calls.

Message format

All messages are plain JSON objects with a type discriminator:

{
type: 'KEELSTONE_WRITE',
// ...type-specific fields
}

Outbound events (LWC → taskpane)

EventDescription
KEELSTONE_INSERTInsert a new worksheet from a base64 .xlsx file (template merge)
KEELSTONE_WRITEWrite a 2D array to a range; optionally creates an Excel Table + named range
KEELSTONE_QUERYExecute SOQL server-side and write results to a range
KEELSTONE_GET_RANGERequest the current Excel selection address
KEELSTONE_SAVE_TABLESRead all tables on the active sheet and POST to /api/data/save

Response events (taskpane → LWC)

Some messages trigger a response posted back to the LWC via event.source.postMessage. Listen with window.addEventListener('message') and filter by event.data.type.

ResponseTriggered byDescription
KEELSTONE_WRITE_RESULTKEELSTONE_WRITE{ tableName, address, rowCount } — table name is null when namedRange: false
KEELSTONE_RANGE_RESPONSEKEELSTONE_GET_RANGE{ address } — the full sheet-qualified selection address
KEELSTONE_SAVE_RESULTKEELSTONE_SAVE_TABLES{ saved, errors } — record counts from /api/data/save

KS_OFFICE_EVENT — Real-time document events

The taskpane monitors the open document and forwards events to any open flow dialog or inline taskpane flow via window.postMessage. These are not triggered by LWC code — they arrive automatically whenever the user interacts with the document.

{
type: 'KS_OFFICE_EVENT',
eventType: 'excel:selectionChanged', // or word:selectionChanged, excel:changed, etc.
data: { ... } // event-specific payload
}

Supported event types

eventTypeAppFires whendata payload
word:selectionChangedWordUser selects or moves the cursor{ selectedText: "..." }
excel:selectionChangedExcelUser selects a range{ address: "Sheet1!A1:B3", values: [[...]] }
excel:changedExcelCell content changes{ address: "A1", worksheetId: "..." }
excel:sheetActivatedExcelUser switches to a different sheet{ worksheetId: "..." }
ppt:selectionChangedPowerPointUser selects a slide{ slides: [{ id, index, title }] }

Events are debounced at 250 ms to prevent flooding during rapid selections. No flow or action configuration is required — forwarding is active whenever the taskpane is open.

Listening for office events in your LWC

connectedCallback() {
this._handler = this._onMessage.bind(this);
window.addEventListener('message', this._handler);
}

disconnectedCallback() {
window.removeEventListener('message', this._handler);
}

_onMessage(event) {
const msg = event.data;
if (!msg) return;

// Office event from the taskpane
if (msg.type === 'KS_OFFICE_EVENT') {
const { eventType, data } = msg;

if (eventType === 'excel:selectionChanged') {
this.selectedRange = data.address;
this.selectedValues = data.values;
}
if (eventType === 'word:selectionChanged') {
this.selectedText = data.selectedText;
}
if (eventType === 'excel:sheetActivated') {
this.activeSheet = data.worksheetId;
}
return;
}

// Standard taskpane response events
switch (msg.type) {
case 'KEELSTONE_RANGE_RESPONSE':
this.activeRange = msg.address;
break;
case 'KEELSTONE_WRITE_RESULT':
this.tableName = msg.tableName;
this.recordCount = msg.rowCount;
break;
case 'KEELSTONE_SAVE_RESULT':
// show toast or update UI
break;
}
}

How events reach flows launched in a dialog

When the flow runs in a dialog (the default Launch Target), the relay server bridges the events:

taskpane emits ks:office-event on its socket

server looks up the dialog socket for the same session

server relays ks:office-event to the dialog

flow-host converts it to window.postMessage({ type: 'KS_OFFICE_EVENT', ... })

your LWC receives it via window.addEventListener('message')

For flows launched in the taskpane itself (Launch Target = Taskpane), events are posted directly without the relay.

Listening for responses in your LWC

Always clean up message listeners in disconnectedCallback:

connectedCallback() {
this._handler = this._onMessage.bind(this);
window.addEventListener('message', this._handler);
}

disconnectedCallback() {
window.removeEventListener('message', this._handler);
}

Security

The taskpane validates the origin of all incoming outbound event messages:

if (!/\.salesforce\.com$|\.force\.com$/.test(event.origin)) return;
if (!msg?.type?.startsWith('KEELSTONE_')) return;

Only messages from Salesforce domains with a KEELSTONE_ type prefix are processed. KS_OFFICE_EVENT messages flow in the opposite direction (taskpane → LWC) and do not require origin validation on the receiving end.