Developer Guide
This guide covers everything a Salesforce developer needs to build flows, configure actions, and integrate Keelstone's invocable action library into business processes.
How Keelstone Works
Keelstone bridges Salesforce Screen Flows and Microsoft Office or Google Workspace documents. When a user clicks an action tile in the taskpane:
- The taskpane opens your Screen Flow in a Salesforce dialog (Office) or inline (Google Workspace)
- The flow runs normally — screens, decisions, assignments, actions
- When the flow calls a Keelstone invocable action (e.g.
KS_WriteData), Keelstone routes the command to the user's open document and returns the result - The result is returned to the flow as the action's output variables
No custom LWC development is required for most use cases. All Keelstone capabilities are exposed as standard Invocable Actions usable in any Flow.
Actions with Document Type = Excel / Sheets work in both the Excel taskpane and the Google Sheets add-on. The same flow and action record serve both — Keelstone translates the commands transparently.
The KeelstoneSessionId
Every Keelstone invocable action requires a keelstoneSessionId input. This is a server-side session token that identifies which Office document to target.
Add it to every flow that uses Keelstone actions:
- In Flow Builder, open the Variables panel
- Create a new variable:
- API Name:
KeelstoneSessionId - Data Type: Text
- Available for input: ✓
- Available for output: ✗
- API Name:
The taskpane automatically passes this value when it launches your flow. You never need to set it manually — just declare the variable and wire it to each action's keelstoneSessionId input.
If KeelstoneSessionId is missing from a flow, Keelstone actions will fail with an authentication error. Always declare this variable.
Invocable Action Reference
All actions are in the Keelstone category in Flow Builder's Action picker.
Excel Actions
| Action Label | API Name | Description |
|---|---|---|
| KS: Get Excel Range | KS_GetRange | Returns the address and values of the current selection |
| KS: Write to Excel | KS_WriteRange | Writes a 2D array of data to a range or named range |
| KS: Add Sheet | KS_AddSheet | Creates a new worksheet and optionally writes data to it |
| KS: Query & Write | KS_QueryWrite | Runs SOQL and writes results as a formatted Excel Table |
| KS: Add Chart (from range) | KS_AddChart | Creates a chart from an existing data range |
| KS: Add Chart (from data) | KS_AddChartFromData | Creates a chart from data passed directly in the flow |
| KS: Delete Chart | KS_DeleteChart | Removes a named chart from the workbook |
| KS: List Charts | KS_ListCharts | Returns all chart names in the workbook |
| KS: Get Cell Comments | KS_GetCellComments | Returns comments on a specific cell address |
| KS: Add Cell Comment | KS_AddCellComment | Adds a comment to a cell |
| KS: Reply to Cell Comment | KS_ReplyToCellComment | Adds a reply to an existing comment thread |
| KS: Resolve Cell Comment | KS_ResolveCellComment | Marks a comment as resolved |
| KS: Delete Cell Comment | KS_DeleteCellComment | Deletes a comment |
| KS: Get Document Property | KS_GetDocumentProperty | Reads a custom workbook property by key |
| KS: Set Document Property | KS_SetDocumentProperty | Writes a custom workbook property (persists in the file) |
| KS: Get Document Settings | KS_GetDocumentSettings | Returns all developer-set document properties as a JSON string |
Word Actions
| Action Label | API Name | Description |
|---|---|---|
| KS: Generate Word Document | KS_GenerateWordDocument | Merges Salesforce data into a Word template and inserts it |
| KS: Insert at Cursor | KS_InsertAtCursor | Inserts text, HTML, or OOXML at the cursor position |
| KS: Insert Paragraph | KS_InsertParagraph | Appends a paragraph with an optional Word style |
| KS: Insert Table | KS_InsertTable | Inserts a formatted table from headers and row data |
| KS: Get Document Text | KS_GetDocumentText | Returns all body text from the open document |
| KS: Get Comments | KS_GetComments | Returns all review comments in the document |
| KS: Get Selection Comments | KS_GetSelectionComments | Returns comments on the current selection |
| KS: Add Comment | KS_AddComment | Adds a review comment at the cursor |
| KS: Reply to Comment | KS_ReplyToComment | Replies to an existing review comment |
| KS: Resolve Comment | KS_ResolveComment | Marks a review comment resolved or unresolved |
| KS: Delete Comment | KS_DeleteComment | Deletes a review comment |
| KS: Scan Keywords | KS_ScanKeywords | Searches the document for a list of keywords |
| KS: Get Document Property | KS_GetDocumentProperty | Reads a custom document property by key (set documentType = Word) |
| KS: Set Document Property | KS_SetDocumentProperty | Writes a custom document property (set documentType = Word) |
| KS: Get Document Settings | KS_GetDocumentSettings | Returns all developer-set document properties as a JSON string (set documentType = Word) |
PowerPoint Actions
| Action Label | API Name | Description |
|---|---|---|
| KS: Manage Slides | KS_ManageSlides | Add, remove, reorder, or update slides |
| KS: Set Shape Text | KS_SetShapeText | Replace the text in a named shape on a slide |
| KS: Format Shape | KS_FormatShape | Apply fill, border, and font formatting to a shape |
| KS: Add Text Box | KS_AddTextBox | Insert a text box on a slide at specified coordinates |
| KS: Add Image to Slide | KS_AddImageToSlide | Insert a base64 image onto a slide |
| KS: Add Table to Slide | KS_AddTableToSlide | Insert a table onto a slide with optional pre-populated values |
| KS: Duplicate Slide | KS_DuplicateSlide | Duplicate a slide and insert the copy after the original |
| KS: Move Slide | KS_MoveSlide | Move a slide to a new position in the deck |
| KS: Get Slide Notes | KS_GetSlideNotes | Read the speaker notes for a slide |
| KS: Set Slide Notes | KS_SetSlideNotes | Write speaker notes for a slide |
| KS: Set Slide Background | KS_SetSlideBackground | Set a solid colour background on a slide |
| KS: Get Slide Text | KS_GetSlideText | Return all text on a slide concatenated |
| KS: Export Slide | KS_ExportSlide | Export a slide as a base64 PPTX (requires PowerPoint 2024) |
| KS: Get Slide Image | KS_GetSlideImage | Render a slide as a PNG image (requires PowerPoint 2024) |
| KS: Get Tags | KS_GetTags | Read all custom tags from a slide |
| KS: Set Tag | KS_SetTag | Write a custom key/value tag to a slide |
New Excel Actions (v1.6+)
| Action Label | API Name | Description |
|---|---|---|
| KS: Format Range | KS_FormatRange | Apply font, fill, and alignment to a cell range |
| KS: Clear Range | KS_ClearRange | Clear values, formats, or both from a range |
| KS: Merge Range | KS_MergeRange | Merge or unmerge cells |
| KS: Number Format | KS_NumberFormat | Set the number format on a range (e.g. currency, date, percentage) |
| KS: Sort Range | KS_SortRange | Sort a range by one or more columns |
| KS: Find & Replace | KS_FindReplace | Find and replace text in a sheet |
| KS: Autofit | KS_Autofit | Auto-fit column widths and/or row heights |
| KS: Copy Range | KS_CopyRange | Copy values from one range to another |
| KS: Conditional Format | KS_ConditionalFormat | Add colour scale, data bar, or custom formula-driven formatting |
| KS: Add Hyperlink | KS_AddHyperlink | Insert a hyperlink into a cell |
| KS: Get Used Range | KS_GetUsedRange | Return the address, dimensions, and values of the used range |
| KS: Activate Sheet | KS_ActivateSheet | Switch the active worksheet |
| KS: Rename Sheet | KS_RenameSheet | Rename a worksheet |
| KS: Delete Sheet | KS_DeleteSheet | Delete a worksheet |
| KS: Protect Sheet | KS_ProtectSheet | Lock or unlock a worksheet |
| KS: Freeze Panes | KS_FreezePanes | Freeze rows and/or columns |
| KS: Insert Rows | KS_InsertRows | Insert blank rows at a given position |
| KS: Delete Rows | KS_DeleteRows | Delete rows at a given position |
| KS: Insert Columns | KS_InsertColumns | Insert blank columns at a given position |
| KS: Delete Columns | KS_DeleteColumns | Delete columns at a given position |
| KS: Hide / Unhide Rows | KS_HideRows | Show or hide a row range |
| KS: Hide / Unhide Columns | KS_HideColumns | Show or hide a column range |
| KS: Get Tables | KS_GetTables | List Excel Table names in the workbook |
| KS: Add Table Row | KS_AddTableRow | Append a row to a named Excel Table |
| KS: Get Named Ranges | KS_GetNamedRanges | List all named ranges and items in the workbook |
New Word Actions (v1.6+)
| Action Label | API Name | Description |
|---|---|---|
| KS: Find & Replace | KS_WordFindReplace | Find and replace text throughout the document body |
| KS: Format Text | KS_FormatText | Apply font formatting to text matches or the current selection |
| KS: Insert Image | KS_InsertImage | Insert an inline image from base64 |
| KS: Get Header / Footer | KS_GetHeaderFooter | Read header or footer text |
| KS: Set Header / Footer | KS_SetHeaderFooter | Write header or footer text |
| KS: Insert Break | KS_InsertBreak | Insert a page or section break |
| KS: Get Tables | KS_WordGetTables | List tables in the document |
| KS: Add Table Row | KS_WordAddTableRow | Append a row to a table |
| KS: Get Bookmarks | KS_GetBookmarks | List all bookmarks in the document |
| KS: Insert Bookmark | KS_InsertBookmark | Insert a bookmark at the current selection |
| KS: Go To Bookmark | KS_GoToBookmark | Navigate to a bookmark |
| KS: Insert Hyperlink | KS_InsertHyperlink | Insert a hyperlink at the current selection |
| KS: Track Changes | KS_TrackChanges | Enable or disable change tracking |
| KS: Accept All Changes | KS_AcceptAllChanges | Accept all tracked changes |
Shared / Server-Side Actions
These actions run server-side and do not require the taskpane to be open.
| Action Label | API Name | Description |
|---|---|---|
| KS: Generate Document | KS_GenerateDocument | Merges Salesforce data into a Word or Excel template stored in Salesforce Files; saves output back to Files |
| KS: Build Merge Context | KS_BuildContext | Serializes a single SObject into a JSON merge context string. Use as the first step in a multi-table chain. |
| KS: Add Table to Context | KS_AddTable | Appends a record collection to an existing merge context JSON string under a named key. Chain multiple calls for unlimited related collections. |
| KS: Generate Chart | KS_GenerateChart | Renders a chart as a PNG image (base64) |
| KS: Generate PDF | KS_GeneratePdf | Converts a document to PDF |
| KS: Fill PDF Form | KS_FillPdfForm | Fills AcroForm fields in a PDF |
Building a Flow: Step-by-Step
Example: Write Opportunity Data to Excel
-
Create a new Screen Flow in Flow Builder
-
Add the KeelstoneSessionId variable
- API Name:
KeelstoneSessionId, Text, Available for input
- API Name:
-
Add a Get Records element to query your Opportunity
-
Add an Action element
- Category: Keelstone
- Action: KS: Write to Excel
- Set inputs:
keelstoneSessionId→{!KeelstoneSessionId}data→ your 2D array of values (construct with a formula or Assignment)target→A1(or a named range)
-
Create a Keelstone Action record pointing to this flow
-
Open Excel with the taskpane → click your action tile
Using Action Outputs
Most actions return resultJson (a JSON string) and success (Boolean). Use a Decision element to branch on success, and use a formula to parse fields from resultJson if needed.
resultJson example from KS_GetRange:
{"address":"Sheet1!A1:C10","values":[["Name","Stage","Amount"],["Acme","Closed Won",50000]]}
Action Records and Groups
Keelstone_Action__c
Each action tile in the taskpane corresponds to one Keelstone_Action__c record.
| Field | Type | Description |
|---|---|---|
kstone__Label__c | Text | Display name shown on the tile |
kstone__Action_Type__c | Picklist | Flow (only supported type for custom actions) |
kstone__Action_Target__c | Text | API name of the Screen Flow |
kstone__Document_Type__c | Picklist | Excel / Sheets, Word / Docs, or PowerPoint / Slides — controls which taskpane shows the tile. Excel / Sheets tiles appear in both the Office Excel add-in and the Google Sheets add-on. |
kstone__Active__c | Checkbox | Inactive records are hidden from the taskpane |
kstone__Icon__c | Text | Salesforce utility icon name (e.g. utility:database) |
kstone__Order__c | Number | Display order within the group |
kstone__Group__c | Lookup | Groups tiles under a collapsible header |
kstone__Launch_Target__c | Picklist | Dialog (recommended) or Taskpane |
kstone__Dialog_Height__c | Number | Dialog height as % of screen height |
kstone__Dialog_Width__c | Number | Dialog width as % of screen width |
Keelstone_Group__c
Groups organize action tiles under collapsible section headers.
| Field | Type | Description |
|---|---|---|
kstone__Label__c | Text | Section header text |
kstone__Icon__c | Text | Salesforce utility icon name |
kstone__Order__c | Number | Display order |
Template Management
Keelstone_Template__c
The Keelstone_Template__c object is a stable registry for document templates. Instead of passing a ContentDocumentId in flows (which changes across orgs), use a developer-chosen Template Key slug.
| Field | Description |
|---|---|
Template_Key__c | Unique, stable slug. Example: "welcome-packet". Embedded in generated documents; used for action filtering. |
Active__c | Inactive templates are ignored by KS: Generate Document |
Document_Type__c | Word, Excel, or PowerPoint |
Attach the template file using standard Salesforce Files (ContentDocumentLink). KS: Generate Document always uses the most recently attached file.
Keelstone_Template_Action__c
Junction object between Keelstone_Template__c and Keelstone_Action__c. Controls which actions appear in the taskpane for a given document.
- An action with no junction records is global — it appears on all documents
- An action with at least one junction record is template-scoped — it only appears when the open document was generated from a matching template
Headless Document Generation
KS_GenerateDocument merges Salesforce data into a Word (.docx) or Excel (.xlsx) template stored in Salesforce Files and saves the output back to Files — no taskpane or Office add-in required. It runs entirely server-side and can be triggered from any flow type, including record-triggered automation.
How it works
- Create a
Keelstone_Template__crecord with a uniqueTemplate_Key__cslug - Attach a
.docxor.xlsxtemplate file to the template record - Add merge placeholders in the template using the field's API name:
{FirstName},{Level__c},{Account.Name} - For repeating rows (e.g. a table of bookings), use
{#tableName}...{/tableName}loop syntax - In Flow Builder, add a KS: Generate Document action element — no LWC or custom Apex needed
Template placeholder syntax
Keelstone uses the docxtemplater syntax with expression support for dot notation:
| Syntax | Use |
|---|---|
{FieldName} | Single field value |
{Relationship.FieldName} | Related field (e.g. {Account.Name}) |
{#listKey}{FieldName}{/listKey} | Repeat a row for each item in a collection |
{#listKey}{Rel.Field}{/listKey} | Dot notation works inside loops too |
Use the exact API name of the field as it appears in SOQL — {Preferred_Name__c}, not {PreferredName}.
KS: Generate Document inputs
| Input | Type | Description |
|---|---|---|
templateKey | String | Developer key of a Keelstone_Template__c record. Looks up the latest attached file automatically. Required. |
contextJson | String | Merge context as a JSON string. Build with KS: Build Merge Context + KS: Add Table, a custom Apex class, or directly in an LWC. Keys become template placeholders. |
mergeData | String | JSON string merged on top of contextJson. Use for computed values (today's date, generated-by name, etc.) that override contextJson for matching keys. |
filename | String | Output filename including extension, e.g. Welcome Packet.docx. Change to .pdf to convert on the fly. |
linkedEntityId | String | Record ID to link the generated file to in Salesforce Files |
externalLink | Boolean | If true, returns a public download URL |
templateKey is required. At least one of contextJson or mergeData (or template static fields) must provide merge data.
KS: Generate Document outputs
| Output | Type | Description |
|---|---|---|
contentDocumentId | String | ContentDocumentId of the generated file |
documentUrl | String | Public download URL (populated when externalLink is true) |
templateKey | String | Echo of the templateKey input. Pass to KS: Insert Document so the taskpane embeds the key in the document's custom properties. |
success | Boolean | True if generation succeeded |
errorMessage | String | Error detail if success is false |
Building merge context — three paths
KS: Generate Document accepts a contextJson string. There are three ways to build it:
Path 1 — Custom Apex (most powerful)
Write an @InvocableMethod class that queries whatever data you need — cross-object SOQL, multiple related lists, computed values — and returns contextJson. No dataTypeMappings required in the flow XML, and cross-object fields like Owner.Name work natively.
global class MyReportContext {
global class Request {
@InvocableVariable(required=true) global String recordId;
}
global class Result {
@InvocableVariable global String contextJson;
}
@InvocableMethod(label='Get My Report Context' category='Keelstone')
global static List<Result> execute(List<Request> reqs) {
Result res = new Result();
MyObject__c rec = [
SELECT Name, Owner.Name, (SELECT Name, Amount__c FROM Items__r)
FROM MyObject__c WHERE Id = :reqs[0].recordId
];
Map<String, Object> ctx = KS_ContextBuilder.fromRecord(rec);
res.contextJson = JSON.serialize(ctx);
return new List<Result>{ res };
}
}
Flow:
Action: My Report Context recordId → {!recordId} → contextJson
Action: KS: Generate Document
templateKey → "my-template"
contextJson → {!contextJson}
filename → "Report.docx"
linkedEntityId → {!recordId}
Path 2 — LWC JavaScript
Build contextJson in a headless Flow Screen LWC and pass it to the next action:
async connectedCallback() {
const data = await getMyData({ recordId: this.recordId });
this.contextJson = JSON.stringify(data); // @api output variable
}
Path 3 — Pure admin (no code)
Use KS: Build Merge Context to serialize a record, then chain KS: Add Table calls for each related list:
Get Records: Contact (Id, FirstName, LastName, Level__c, Preferred_Name__c)
Get Records: Booking__c where Contact__c = {!recordId}
Get Records: Credit__c where Contact__c = {!recordId}
│
▼
KS: Build Merge Context record → {!Get_Contact} → contextJson
KS: Add Table records → {!Get_Bookings} → contextJson
tableName → "bookings"
KS: Add Table records → {!Get_Credits} → contextJson
tableName → "credits"
KS: Generate Document
templateKey → "welcome-packet"
contextJson → {!contextJson}
filename → "Welcome Packet.docx"
linkedEntityId → {!recordId}
Template:
Dear {Preferred_Name__c},
Your level is {Level__c}.
{#bookings}
{Experience_Name__c} {Date__c} {Number_of_Guests__c} guests
{/bookings}
Chain as many KS: Add Table calls as needed — there is no limit.
Generating a PDF
Set filename to a .pdf extension — the backend converts the merged .docx to PDF automatically:
filename → "Welcome Packet.pdf"
The template itself is still a .docx; only the output changes format.
Bulk Document Generation
KS_GenerateDocument makes one HTTP callout and several SOQL queries per document. In a batch Apex context this multiplies by N — 200 records would require 200 callouts and 800+ SOQL queries, both far over the per-transaction limits.
KS_BulkGenerateDocument solves this with a single callout regardless of document count. The server fetches the template once, merges all documents in a loop, uploads each result to Salesforce Files, and returns only ContentDocumentIds. Apex never handles template bytes or does ContentVersion DML.
Governor limit comparison (N documents, same template):
KS_GenerateDocument | KS_BulkGenerateDocument | |
|---|---|---|
| SOQL queries | 4N | 1 (license check only) |
| HTTP callouts | N | 1 |
| DML statements | 2N | 0 |
| Apex heap | O(N × template size) | O(N × context size) |
KS_BulkGenerateDocument.generateAll(requests)
global static List<Result> generateAll(List<Request> requests)
All requests in a single call must use the same templateKey. The server resolves the template once from that key.
Request fields:
| Field | Type | Description |
|---|---|---|
templateKey | String | Template_Key__c on a Keelstone_Template__c record — must be identical for all requests |
contextJson | String | Merge context as a JSON string |
mergeData | String | Optional JSON merged on top of contextJson at highest priority |
filename | String | Output filename including extension, e.g. 'Ada Welcome.docx' |
linkedEntityId | String | Salesforce record ID to attach the generated file to |
externalLink | Boolean | If true, a public download URL is returned in the result |
Result fields:
| Field | Type | Description |
|---|---|---|
contentDocumentId | String | ContentDocumentId of the generated file |
documentUrl | String | Public download URL (when externalLink = true) |
success | Boolean | |
errorMessage | String | Per-document error; other documents still succeed |
Batch class example
global class GenerateWelcomePackets
implements Database.Batchable<SObject>, Database.AllowsCallouts {
global Database.QueryLocator start(Database.BatchableContext ctx) {
return Database.getQueryLocator(
'SELECT Id, Name, Level__c, Email__c FROM Contact WHERE Send_Packet__c = true'
);
}
global void execute(Database.BatchableContext ctx, List<SObject> scope) {
List<KS_BulkGenerateDocument.Request> reqs = new List<KS_BulkGenerateDocument.Request>();
for (SObject rec : scope) {
Contact c = (Contact) rec;
KS_BulkGenerateDocument.Request req = new KS_BulkGenerateDocument.Request();
req.templateKey = 'welcome-packet';
req.contextJson = JSON.serialize(new Map<String, Object>{
'Name' => c.Name,
'Level__c'=> c.Level__c,
'Email__c'=> c.Email__c
});
req.filename = c.Name + ' Welcome Packet.docx';
req.linkedEntityId = c.Id;
reqs.add(req);
}
List<KS_BulkGenerateDocument.Result> results = KS_BulkGenerateDocument.generateAll(reqs);
// Optionally inspect results for per-document errors
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].success) {
System.debug('Failed for ' + scope[i].Id + ': ' + results[i].errorMessage);
}
}
}
global void finish(Database.BatchableContext ctx) {}
}
Run it:
Database.executeBatch(new GenerateWelcomePackets(), 20);
Start with a batch size of 20. If your template is a simple letter, you can increase to 50. If it includes large images or complex Excel formulas, decrease to 10. The limiting factor is the 120-second callout timeout — the server must merge and upload all documents in the batch before it expires.
Each generateAll() call must use a single templateKey. If your batch needs to generate from different templates, make separate generateAll() calls per template key within the same execute() transaction — you still only use one callout per template.
Document Properties
Custom document properties store key/value string pairs inside the Office file itself. They persist when the file is saved, closed, and reopened.
Reserved keys
Two keys are managed by Keelstone and cannot be read or written by invocable actions:
| Key | Description |
|---|---|
_ks_doc_id | ContentDocumentId of the source template file, written when a document is inserted |
_ks_template_key | Template slug, written when a document is inserted — used to filter template-scoped actions |
Attempts to read these keys with KS_GetDocumentProperty return null. Attempts to write them with KS_SetDocumentProperty return success = false.
All other keys — including custom _ks_* keys you invent — are freely readable and writable.
Common patterns
Stamp a Salesforce record ID into a workbook:
KS: Set Document Property
key → "salesforce_account_id"
value → {!AccountId}
Read the record ID back on next open:
KS: Get Document Property
key → "salesforce_account_id"
Output: value → {!AccountId}
Read all developer-set properties at once:
KS: Get Document Settings
keelstoneSessionId → {!KeelstoneSessionId}
documentType → "Word"
Output: settingsJson → {!SettingsJson}
success → {!SettingsSuccess}
settingsJson is a JSON object string containing all non-reserved document properties. Use a formula to extract individual values.
This pattern lets flows "remember" which Salesforce record a document belongs to without the user having to look anything up, and is the foundation for auto-refresh patterns where a flow re-queries data based on stored record IDs.
Document Identity and Template-Scoped Actions
When KS: Insert Document writes a generated file into the open Word or Excel document, Keelstone automatically embeds two custom properties:
| Property | Value |
|---|---|
_ks_doc_id | ContentDocumentId of the generated file |
_ks_template_key | Template Key slug (e.g. "welcome-packet") |
These are written automatically — no flow configuration required.
On the next taskpane open, the taskpane reads _ks_template_key and sends it to the server with the config request. The server uses it to filter the action list:
- Actions with no
Keelstone_Template_Action__cjunction records are global — always shown - Actions with junction records are template-scoped — shown only when the open document's template key matches
This lets you build a completely different action menu for different document types without any custom code.
Office Event Echoing
The Keelstone taskpane continuously monitors the open document for changes and forwards those events to any open flow dialog or inline taskpane flow. LWC components inside flows can react to the document state in real time.
Supported events
| Event Type | Fires when | Payload |
|---|---|---|
word:selectionChanged | User selects text in Word | { selectedText: "..." } |
excel:selectionChanged | User selects a range in Excel | { address: "Sheet1!A1:B3", values: [[...]] } |
excel:changed | Cell content changes in Excel | { address: "A1", worksheetId: "..." } |
excel:sheetActivated | User switches to a different sheet | { worksheetId: "..." } |
ppt:selectionChanged | User selects a slide in PowerPoint | { slides: [{ id, index, title }] } |
Listening in an LWC
connectedCallback() {
this._handler = this._onOfficeEvent.bind(this);
window.addEventListener('message', this._handler);
}
disconnectedCallback() {
window.removeEventListener('message', this._handler);
}
_onOfficeEvent(event) {
if (event.data?.type !== 'KS_OFFICE_EVENT') return;
const { eventType, data } = event.data;
if (eventType === 'excel:selectionChanged') {
this.selectedRange = data.address;
this.selectedValues = data.values;
}
if (eventType === 'word:selectionChanged') {
this.selectedText = data.selectedText;
}
}
Events are debounced (250 ms) to prevent flooding during rapid selections. No flow or action configuration is required — event forwarding is active whenever the taskpane is open.
Clause / Snippet Insertion
KS_InsertAtCursor inserts content at the cursor position in the open Word document. Use it to build a clause library stored in Salesforce and surfaced through a flow.
Supported content types
contentType value | Input format |
|---|---|
text (default) | Plain text string |
html | HTML string — supports <p>, <strong>, <em>, <ul>, etc. |
ooxml | Open Office XML (for precise formatting control) |
Pattern: Clause library stored in a custom object
- Create a custom object
Clause__cwith fields:Name,Content__c(Rich Text),Category__c - Build a Screen Flow that queries clauses, lets the user pick one, and calls
KS_InsertAtCursorwithContent__cas thecontentandcontentType = 'html' - Create a Keelstone Action record pointing to the flow (Document Type = Word)
The Content__c rich text field produces HTML that KS_InsertAtCursor can insert directly — no conversion needed.
Plan Limits and Feature Gating
Actions always load regardless of plan. Merge limits only apply to document generation:
| Plan | Users | Actions | Merge limit | Document Types |
|---|---|---|---|---|
| Free | Unlimited | 1 | 100 per user / month | Word & Google Docs |
| Basic | Unlimited | Template Builder only | Unlimited | Word & Google Docs |
| Pro | Unlimited | Unlimited | Unlimited | Excel, Word, PowerPoint, PDF, Google Workspace |
When the merge limit is reached, KS_GenerateDocument returns success = false with a 429 error message. Handle this in your flow with a Decision element and a user-friendly screen.
Excel and PowerPoint invocable actions (KS_WriteRange, KS_QueryWrite, KS_ManageSlides, etc.) require the Pro plan — the server enforces this at the API level. Word and PDF actions are available on all plans.
Testing in a Scratch Org or Sandbox
- Install the package to your sandbox/scratch org
- Assign
Keelstone_Userto your user - Verify the post-install ECA was created (Setup → External Client Apps)
- Create Action records pointing at your test flows
- Open Excel/Word with the Keelstone add-in and log in to the sandbox org (use the sandbox custom domain, e.g.
mycompany--uat)
The Keelstone relay server supports any Salesforce org — production, sandbox, and scratch orgs all work without additional configuration.
Remote Site Settings
The package installs one required remote site automatically:
| Name | URL |
|---|---|
KeelstoneApp | https://app.keelstone.dev |
All invocable actions POST to endpoints under this domain. Do not remove this remote site setting.
Support and Community
- Email: support@keelstone.dev
- Documentation: https://docs.keelstone.dev
- Issues / feature requests: Contact support with your org ID