Skip to main content

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:

  1. The taskpane opens your Screen Flow in a Salesforce dialog (Office) or inline (Google Workspace)
  2. The flow runs normally — screens, decisions, assignments, actions
  3. 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
  4. 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:

  1. In Flow Builder, open the Variables panel
  2. Create a new variable:
    • API Name: KeelstoneSessionId
    • Data Type: Text
    • Available for input: ✓
    • Available for output: ✗

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.

warning

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 LabelAPI NameDescription
KS: Get Excel RangeKS_GetRangeReturns the address and values of the current selection
KS: Write to ExcelKS_WriteRangeWrites a 2D array of data to a range or named range
KS: Add SheetKS_AddSheetCreates a new worksheet and optionally writes data to it
KS: Query & WriteKS_QueryWriteRuns SOQL and writes results as a formatted Excel Table
KS: Add Chart (from range)KS_AddChartCreates a chart from an existing data range
KS: Add Chart (from data)KS_AddChartFromDataCreates a chart from data passed directly in the flow
KS: Delete ChartKS_DeleteChartRemoves a named chart from the workbook
KS: List ChartsKS_ListChartsReturns all chart names in the workbook
KS: Get Cell CommentsKS_GetCellCommentsReturns comments on a specific cell address
KS: Add Cell CommentKS_AddCellCommentAdds a comment to a cell
KS: Reply to Cell CommentKS_ReplyToCellCommentAdds a reply to an existing comment thread
KS: Resolve Cell CommentKS_ResolveCellCommentMarks a comment as resolved
KS: Delete Cell CommentKS_DeleteCellCommentDeletes a comment
KS: Get Document PropertyKS_GetDocumentPropertyReads a custom workbook property by key
KS: Set Document PropertyKS_SetDocumentPropertyWrites a custom workbook property (persists in the file)
KS: Get Document SettingsKS_GetDocumentSettingsReturns all developer-set document properties as a JSON string

Word Actions

Action LabelAPI NameDescription
KS: Generate Word DocumentKS_GenerateWordDocumentMerges Salesforce data into a Word template and inserts it
KS: Insert at CursorKS_InsertAtCursorInserts text, HTML, or OOXML at the cursor position
KS: Insert ParagraphKS_InsertParagraphAppends a paragraph with an optional Word style
KS: Insert TableKS_InsertTableInserts a formatted table from headers and row data
KS: Get Document TextKS_GetDocumentTextReturns all body text from the open document
KS: Get CommentsKS_GetCommentsReturns all review comments in the document
KS: Get Selection CommentsKS_GetSelectionCommentsReturns comments on the current selection
KS: Add CommentKS_AddCommentAdds a review comment at the cursor
KS: Reply to CommentKS_ReplyToCommentReplies to an existing review comment
KS: Resolve CommentKS_ResolveCommentMarks a review comment resolved or unresolved
KS: Delete CommentKS_DeleteCommentDeletes a review comment
KS: Scan KeywordsKS_ScanKeywordsSearches the document for a list of keywords
KS: Get Document PropertyKS_GetDocumentPropertyReads a custom document property by key (set documentType = Word)
KS: Set Document PropertyKS_SetDocumentPropertyWrites a custom document property (set documentType = Word)
KS: Get Document SettingsKS_GetDocumentSettingsReturns all developer-set document properties as a JSON string (set documentType = Word)

PowerPoint Actions

Action LabelAPI NameDescription
KS: Manage SlidesKS_ManageSlidesAdd, remove, reorder, or update slides
KS: Set Shape TextKS_SetShapeTextReplace the text in a named shape on a slide
KS: Format ShapeKS_FormatShapeApply fill, border, and font formatting to a shape
KS: Add Text BoxKS_AddTextBoxInsert a text box on a slide at specified coordinates
KS: Add Image to SlideKS_AddImageToSlideInsert a base64 image onto a slide
KS: Add Table to SlideKS_AddTableToSlideInsert a table onto a slide with optional pre-populated values
KS: Duplicate SlideKS_DuplicateSlideDuplicate a slide and insert the copy after the original
KS: Move SlideKS_MoveSlideMove a slide to a new position in the deck
KS: Get Slide NotesKS_GetSlideNotesRead the speaker notes for a slide
KS: Set Slide NotesKS_SetSlideNotesWrite speaker notes for a slide
KS: Set Slide BackgroundKS_SetSlideBackgroundSet a solid colour background on a slide
KS: Get Slide TextKS_GetSlideTextReturn all text on a slide concatenated
KS: Export SlideKS_ExportSlideExport a slide as a base64 PPTX (requires PowerPoint 2024)
KS: Get Slide ImageKS_GetSlideImageRender a slide as a PNG image (requires PowerPoint 2024)
KS: Get TagsKS_GetTagsRead all custom tags from a slide
KS: Set TagKS_SetTagWrite a custom key/value tag to a slide

New Excel Actions (v1.6+)

Action LabelAPI NameDescription
KS: Format RangeKS_FormatRangeApply font, fill, and alignment to a cell range
KS: Clear RangeKS_ClearRangeClear values, formats, or both from a range
KS: Merge RangeKS_MergeRangeMerge or unmerge cells
KS: Number FormatKS_NumberFormatSet the number format on a range (e.g. currency, date, percentage)
KS: Sort RangeKS_SortRangeSort a range by one or more columns
KS: Find & ReplaceKS_FindReplaceFind and replace text in a sheet
KS: AutofitKS_AutofitAuto-fit column widths and/or row heights
KS: Copy RangeKS_CopyRangeCopy values from one range to another
KS: Conditional FormatKS_ConditionalFormatAdd colour scale, data bar, or custom formula-driven formatting
KS: Add HyperlinkKS_AddHyperlinkInsert a hyperlink into a cell
KS: Get Used RangeKS_GetUsedRangeReturn the address, dimensions, and values of the used range
KS: Activate SheetKS_ActivateSheetSwitch the active worksheet
KS: Rename SheetKS_RenameSheetRename a worksheet
KS: Delete SheetKS_DeleteSheetDelete a worksheet
KS: Protect SheetKS_ProtectSheetLock or unlock a worksheet
KS: Freeze PanesKS_FreezePanesFreeze rows and/or columns
KS: Insert RowsKS_InsertRowsInsert blank rows at a given position
KS: Delete RowsKS_DeleteRowsDelete rows at a given position
KS: Insert ColumnsKS_InsertColumnsInsert blank columns at a given position
KS: Delete ColumnsKS_DeleteColumnsDelete columns at a given position
KS: Hide / Unhide RowsKS_HideRowsShow or hide a row range
KS: Hide / Unhide ColumnsKS_HideColumnsShow or hide a column range
KS: Get TablesKS_GetTablesList Excel Table names in the workbook
KS: Add Table RowKS_AddTableRowAppend a row to a named Excel Table
KS: Get Named RangesKS_GetNamedRangesList all named ranges and items in the workbook

New Word Actions (v1.6+)

Action LabelAPI NameDescription
KS: Find & ReplaceKS_WordFindReplaceFind and replace text throughout the document body
KS: Format TextKS_FormatTextApply font formatting to text matches or the current selection
KS: Insert ImageKS_InsertImageInsert an inline image from base64
KS: Get Header / FooterKS_GetHeaderFooterRead header or footer text
KS: Set Header / FooterKS_SetHeaderFooterWrite header or footer text
KS: Insert BreakKS_InsertBreakInsert a page or section break
KS: Get TablesKS_WordGetTablesList tables in the document
KS: Add Table RowKS_WordAddTableRowAppend a row to a table
KS: Get BookmarksKS_GetBookmarksList all bookmarks in the document
KS: Insert BookmarkKS_InsertBookmarkInsert a bookmark at the current selection
KS: Go To BookmarkKS_GoToBookmarkNavigate to a bookmark
KS: Insert HyperlinkKS_InsertHyperlinkInsert a hyperlink at the current selection
KS: Track ChangesKS_TrackChangesEnable or disable change tracking
KS: Accept All ChangesKS_AcceptAllChangesAccept all tracked changes

Shared / Server-Side Actions

These actions run server-side and do not require the taskpane to be open.

Action LabelAPI NameDescription
KS: Generate DocumentKS_GenerateDocumentMerges Salesforce data into a Word or Excel template stored in Salesforce Files; saves output back to Files
KS: Build Merge ContextKS_BuildContextSerializes a single SObject into a JSON merge context string. Use as the first step in a multi-table chain.
KS: Add Table to ContextKS_AddTableAppends a record collection to an existing merge context JSON string under a named key. Chain multiple calls for unlimited related collections.
KS: Generate ChartKS_GenerateChartRenders a chart as a PNG image (base64)
KS: Generate PDFKS_GeneratePdfConverts a document to PDF
KS: Fill PDF FormKS_FillPdfFormFills AcroForm fields in a PDF

Building a Flow: Step-by-Step

Example: Write Opportunity Data to Excel

  1. Create a new Screen Flow in Flow Builder

  2. Add the KeelstoneSessionId variable

    • API Name: KeelstoneSessionId, Text, Available for input
  3. Add a Get Records element to query your Opportunity

  4. 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)
      • targetA1 (or a named range)
  5. Create a Keelstone Action record pointing to this flow

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

FieldTypeDescription
kstone__Label__cTextDisplay name shown on the tile
kstone__Action_Type__cPicklistFlow (only supported type for custom actions)
kstone__Action_Target__cTextAPI name of the Screen Flow
kstone__Document_Type__cPicklistExcel / 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__cCheckboxInactive records are hidden from the taskpane
kstone__Icon__cTextSalesforce utility icon name (e.g. utility:database)
kstone__Order__cNumberDisplay order within the group
kstone__Group__cLookupGroups tiles under a collapsible header
kstone__Launch_Target__cPicklistDialog (recommended) or Taskpane
kstone__Dialog_Height__cNumberDialog height as % of screen height
kstone__Dialog_Width__cNumberDialog width as % of screen width

Keelstone_Group__c

Groups organize action tiles under collapsible section headers.

FieldTypeDescription
kstone__Label__cTextSection header text
kstone__Icon__cTextSalesforce utility icon name
kstone__Order__cNumberDisplay 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.

FieldDescription
Template_Key__cUnique, stable slug. Example: "welcome-packet". Embedded in generated documents; used for action filtering.
Active__cInactive templates are ignored by KS: Generate Document
Document_Type__cWord, 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

  1. Create a Keelstone_Template__c record with a unique Template_Key__c slug
  2. Attach a .docx or .xlsx template file to the template record
  3. Add merge placeholders in the template using the field's API name: {FirstName}, {Level__c}, {Account.Name}
  4. For repeating rows (e.g. a table of bookings), use {#tableName}...{/tableName} loop syntax
  5. 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:

SyntaxUse
{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

InputTypeDescription
templateKeyStringDeveloper key of a Keelstone_Template__c record. Looks up the latest attached file automatically. Required.
contextJsonStringMerge 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.
mergeDataStringJSON string merged on top of contextJson. Use for computed values (today's date, generated-by name, etc.) that override contextJson for matching keys.
filenameStringOutput filename including extension, e.g. Welcome Packet.docx. Change to .pdf to convert on the fly.
linkedEntityIdStringRecord ID to link the generated file to in Salesforce Files
externalLinkBooleanIf 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

OutputTypeDescription
contentDocumentIdStringContentDocumentId of the generated file
documentUrlStringPublic download URL (populated when externalLink is true)
templateKeyStringEcho of the templateKey input. Pass to KS: Insert Document so the taskpane embeds the key in the document's custom properties.
successBooleanTrue if generation succeeded
errorMessageStringError 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_GenerateDocumentKS_BulkGenerateDocument
SOQL queries4N1 (license check only)
HTTP calloutsN1
DML statements2N0
Apex heapO(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:

FieldTypeDescription
templateKeyStringTemplate_Key__c on a Keelstone_Template__c record — must be identical for all requests
contextJsonStringMerge context as a JSON string
mergeDataStringOptional JSON merged on top of contextJson at highest priority
filenameStringOutput filename including extension, e.g. 'Ada Welcome.docx'
linkedEntityIdStringSalesforce record ID to attach the generated file to
externalLinkBooleanIf true, a public download URL is returned in the result

Result fields:

FieldTypeDescription
contentDocumentIdStringContentDocumentId of the generated file
documentUrlStringPublic download URL (when externalLink = true)
successBoolean
errorMessageStringPer-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);
Batch size

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.

Multiple templates

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:

KeyDescription
_ks_doc_idContentDocumentId of the source template file, written when a document is inserted
_ks_template_keyTemplate 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:

PropertyValue
_ks_doc_idContentDocumentId of the generated file
_ks_template_keyTemplate 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__c junction 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 TypeFires whenPayload
word:selectionChangedUser selects text in Word{ selectedText: "..." }
excel:selectionChangedUser selects a range in Excel{ address: "Sheet1!A1:B3", values: [[...]] }
excel:changedCell content changes in Excel{ address: "A1", worksheetId: "..." }
excel:sheetActivatedUser switches to a different sheet{ worksheetId: "..." }
ppt:selectionChangedUser 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 valueInput format
text (default)Plain text string
htmlHTML string — supports <p>, <strong>, <em>, <ul>, etc.
ooxmlOpen Office XML (for precise formatting control)

Pattern: Clause library stored in a custom object

  1. Create a custom object Clause__c with fields: Name, Content__c (Rich Text), Category__c
  2. Build a Screen Flow that queries clauses, lets the user pick one, and calls KS_InsertAtCursor with Content__c as the content and contentType = 'html'
  3. 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:

PlanUsersActionsMerge limitDocument Types
FreeUnlimited1100 per user / monthWord & Google Docs
BasicUnlimitedTemplate Builder onlyUnlimitedWord & Google Docs
ProUnlimitedUnlimitedUnlimitedExcel, 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

  1. Install the package to your sandbox/scratch org
  2. Assign Keelstone_User to your user
  3. Verify the post-install ECA was created (Setup → External Client Apps)
  4. Create Action records pointing at your test flows
  5. 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:

NameURL
KeelstoneApphttps://app.keelstone.dev

All invocable actions POST to endpoints under this domain. Do not remove this remote site setting.


Support and Community