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)
| Event | Description |
|---|---|
KEELSTONE_INSERT | Insert a new worksheet from a base64 .xlsx file (template merge) |
KEELSTONE_WRITE | Write a 2D array to a range; optionally creates an Excel Table + named range |
KEELSTONE_QUERY | Execute SOQL server-side and write results to a range |
KEELSTONE_GET_RANGE | Request the current Excel selection address |
KEELSTONE_SAVE_TABLES | Read 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.
| Response | Triggered by | Description |
|---|---|---|
KEELSTONE_WRITE_RESULT | KEELSTONE_WRITE | { tableName, address, rowCount } — table name is null when namedRange: false |
KEELSTONE_RANGE_RESPONSE | KEELSTONE_GET_RANGE | { address } — the full sheet-qualified selection address |
KEELSTONE_SAVE_RESULT | KEELSTONE_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.