Skip to main content

Events Overview

Keelstone uses a small set of typed postMessage messages to communicate between your Salesforce LWC components and the Excel taskpane. Some messages are one-way (LWC → taskpane); others trigger a response back to the LWC.

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

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);
}

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

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;
}
}

Security

The taskpane validates the origin of all incoming 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. Responses are sent back to event.source with '*' as the target origin.