Skip to main content

Building Custom LWCs

Keelstone includes a managed LWC bundle — kstone/api — that gives your components direct access to Office operations. Extend one of its classes, call a method, and the result comes back as a normal JavaScript await.

Install

The kstone/api bundle ships with the Keelstone managed package. No additional installation required.

Import

import { KSExcel } from 'kstone/api';

To use multiple product classes in the same file:

import { KSExcel, KSWord, KSPDF, KSCharts } from 'kstone/api';

Class hierarchy

LightningElement (platform)
└── DocumentAPI (internal — not exported)
├── KSExcel — Excel operations (requires Excel add-in)
├── KSWord — Word operations (requires Word add-in, Pro plan)
├── KSPDF — PDF generation (server-side, no add-in required)
└── KSCharts — Chart image generation (server-side, no add-in required)

DocumentAPI handles authentication and the server connection. You never interact with it directly — extend whichever product class matches your use case.

Minimal example

import { KSExcel } from 'kstone/api';
import { api } from 'lwc';

export default class MyReport extends KSExcel {
@api keelstoneSessionId; // required — see Flow Wiring

async connectedCallback() {
const records = await this.getMyData();
await this.ksWriteTable(
['Name', 'Revenue', 'Region'],
records,
'Sheet1!A1'
);
}
}

Re-declaring @api keelstoneSessionId

Salesforce flow screen wiring only populates @api properties declared on the component class itself — not inherited ones. Every component that extends a Keelstone class must re-declare this property:

export default class MyComponent extends KSExcel {
@api keelstoneSessionId; // must be on this class, not just in KSExcel
}

Without this, the flow cannot inject the session and every ks* call will throw at runtime.

Error handling

All ks* methods throw on failure. Wrap them in try/catch and display errors with a toast:

import { ShowToastEvent } from 'lightning/platformShowToastEvent';

async handleRun() {
try {
await this.ksQuery('SELECT Id, Name FROM Account LIMIT 100');
} catch (err) {
this.dispatchEvent(new ShowToastEvent({
title: 'Error',
message: err.message,
variant: 'error',
}));
}
}

Next steps