keelstoneGenerateDocument
A managed package Flow Screen component that generates an Excel document and saves it to Salesforce Files. It takes a JSON data payload and a template ID as inputs — it does no data fetching of its own.
Package: Keelstone (managed, namespace kstone)
Component name: kstone:keelstoneGenerateDocument
Flow target: lightning__FlowScreen
Input properties
| Property | Type | Required | Description |
|---|---|---|---|
templateId | String | Yes | ContentDocumentId of the Excel template in Salesforce Files |
mergeData | String | Yes | JSON string of data to merge. Keys map to template placeholders |
saveToRecordId | String | Yes | Record ID to attach the generated file to |
filename | String | Yes | Output filename including extension, e.g. "Account Report.xlsx" |
What it does
When the flow screen renders, the component runs automatically:
1. Validate all four inputs are present
2. Fetch template bytes → KeelstoneDocumentController.getTemplate(templateId)
3. Merge with data → POST https://app.keelstone.dev/api/docs/generate
4. Save to Salesforce → KeelstoneDocumentController.saveDocument(base64, filename, saveToRecordId)
5. Show success or error state
The flow's Next / Finish button is not blocked — the component displays its own success/error state, and the user advances the flow normally.
The composable pattern
This component is designed to be wired to a custom data-fetching LWC that runs on the screen before it. The data-fetcher queries whatever Salesforce data is needed for the document and outputs it as mergeData JSON. This separation means keelstoneGenerateDocument works for any object, any data shape, and any template.
Quick Action → Screen Flow
│
├── Screen 1: Your custom data-fetcher LWC
│ @api (output) mergeData ← builds JSON from Apex query
│ @api (output) saveToRecordId ← the record to attach the file to
│
└── Screen 2: kstone:keelstoneGenerateDocument
templateId ← flow variable (set by admin)
mergeData ← from Screen 1 output
saveToRecordId ← from Screen 1 output
filename ← flow variable or formula
Template placeholder syntax
The keys in your mergeData JSON become the top-level namespaces in the template. For example:
{
"account": { "Name": "Acme Corp", "BillingCity": "San Francisco" },
"opportunities": [
{ "Name": "Renewal 2025", "Amount": 45000, "StageName": "Proposal" }
]
}
Corresponding Excel template cells:
{account.Name}
{account.BillingCity}
{#opportunities}
{Name} | {Amount} | {StageName}
{/opportunities}
See Template Variables for the full placeholder reference.
Building a custom data-fetcher LWC
Create a headless Flow Screen LWC that queries Salesforce and outputs mergeData. Here's a complete example for an Account with related Opportunities:
Apex controller
public with sharing class AccountDocDataController {
@AuraEnabled
public static Map<String, Object> getAccountData(Id accountId) {
Account acc = [
SELECT Id, Name, Phone, Website,
BillingStreet, BillingCity, BillingState, BillingPostalCode,
Owner.Name, AnnualRevenue, NumberOfEmployees
FROM Account
WHERE Id = :accountId
LIMIT 1
];
List<Map<String, Object>> opps = new List<Map<String, Object>>();
for (Opportunity o : [
SELECT Name, Amount, StageName, CloseDate, Owner.Name
FROM Opportunity
WHERE AccountId = :accountId
ORDER BY CloseDate DESC
LIMIT 50
]) {
opps.add(new Map<String, Object>{
'Name' => o.Name,
'Amount' => o.Amount,
'Stage' => o.StageName,
'CloseDate' => String.valueOf(o.CloseDate),
'Owner' => o.Owner.Name
});
}
return new Map<String, Object>{
'account' => acc,
'opportunities' => opps
};
}
}
LWC component
// accountDocData.js
import { LightningElement, api, wire } from 'lwc';
import { CurrentPageReference } from 'lightning/navigation';
import getAccountData from '@salesforce/apex/AccountDocDataController.getAccountData';
export default class AccountDocData extends LightningElement {
// Flow output variables — available to subsequent screens
@api mergeData = '';
@api saveToRecordId = '';
// Injected by the flow when running on a record page
@api recordId;
async connectedCallback() {
try {
const data = await getAccountData({ accountId: this.recordId });
this.mergeData = JSON.stringify(data);
this.saveToRecordId = this.recordId;
} catch (e) {
// Surface the error — flow will stay on this screen
console.error('AccountDocData error:', e);
}
}
}
<!-- accountDocData.html — intentionally blank, this is a headless screen -->
<template></template>
<!-- accountDocData.js-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__FlowScreen</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__FlowScreen">
<property name="recordId" type="String" label="Record ID" />
<property name="mergeData" type="String" label="Merge Data (output)"
role="outputOnly" />
<property name="saveToRecordId" type="String" label="Save To Record ID (output)"
role="outputOnly" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Flow configuration
In the Screen Flow builder:
Screen 1 — Add c:accountDocData:
- Set
Record IDinput →{!$Record.Id}(from the record page) - Capture outputs:
mergeData→ flow variablevarMergeData,saveToRecordId→ flow variablevarSaveToId - Set Hide Footer = true (headless screen, no Next button shown)
Screen 2 — Add kstone:keelstoneGenerateDocument:
Template ID→ flow variablevarTemplateId(admin sets this)Merge Data→{!varMergeData}Save to Record ID→{!varSaveToId}Output Filename→ formula:{!$Record.Name} & " - Report.xlsx"
Quick action setup
- In Setup → Object Manager → Account → Buttons, Links, and Actions → New Action
- Action Type: Flow, Flow: select your flow
- Label:
Generate Report - Add to the Account page layout via Page Layouts or the Lightning App Builder
Using in a subscriber org
In a subscriber org (outside the Keelstone package), import the Apex controller with the kstone namespace:
// In your custom LWC
import getTemplate from '@salesforce/apex/kstone.KeelstoneDocumentController.getTemplate';
import saveDocument from '@salesforce/apex/kstone.KeelstoneDocumentController.saveDocument';
The keelstoneGenerateDocument component is referenced in flows as kstone:keelstoneGenerateDocument.
Error handling
| Scenario | Behavior |
|---|---|
| Any input missing | Error state: specific message identifying which input |
| Template not found in SF Files | Apex exception → error state |
| Server merge fails | Error state with HTTP status |
saveDocument fails | Error state (document was generated but not saved) |
All errors display inline — the flow screen remains open so the user can read the message.