Skip to main content

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

PropertyTypeRequiredDescription
templateIdStringYesContentDocumentId of the Excel template in Salesforce Files
mergeDataStringYesJSON string of data to merge. Keys map to template placeholders
saveToRecordIdStringYesRecord ID to attach the generated file to
filenameStringYesOutput 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 ID input → {!$Record.Id} (from the record page)
  • Capture outputs: mergeData → flow variable varMergeData, saveToRecordId → flow variable varSaveToId
  • Set Hide Footer = true (headless screen, no Next button shown)

Screen 2 — Add kstone:keelstoneGenerateDocument:

  • Template ID → flow variable varTemplateId (admin sets this)
  • Merge Data{!varMergeData}
  • Save to Record ID{!varSaveToId}
  • Output Filename → formula: {!$Record.Name} & " - Report.xlsx"

Quick action setup

  1. In Setup → Object Manager → Account → Buttons, Links, and Actions → New Action
  2. Action Type: Flow, Flow: select your flow
  3. Label: Generate Report
  4. 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

ScenarioBehavior
Any input missingError state: specific message identifying which input
Template not found in SF FilesApex exception → error state
Server merge failsError state with HTTP status
saveDocument failsError state (document was generated but not saved)

All errors display inline — the flow screen remains open so the user can read the message.