Excel Storage
ExcelStorage lets you read profile data from Excel files and write automation output back to spreadsheets. Call flow.createExcelStorage() at the caller/runner level — not inside the flow class.
Setup
createExcelStorage() is called where you instantiate your flow (e.g. main.dev.ts):
import { AntidetectProvider } from '@hira-core/sdk'
import { MyFlow } from './flow'
async function bootstrap() {
const flow = new MyFlow(AntidetectProvider.GPM)
// ── Setup Excel Storage at the caller level ──
const storage = flow.createExcelStorage({
inputFile: {
filePath: './profiles.xlsx', // relative to callerDir
sheetIndex: 0,
profileNameColumn: 'A',
columns: {
'B': 'email',
'C': 'password',
},
},
outputFile: {
filePath: './profiles.xlsx', // same file, different columns
sheetIndex: 0,
profileNameColumn: 'A',
columns: {
'D': 'loginStatus',
'E': 'pageTitle',
},
},
}, __dirname) // resolve relative paths from this directory
// ── Read profiles from Excel ──
const profileSettings = await storage.parseProfileSettings()
console.log(`📊 Loaded ${profileSettings.length} profile(s) from Excel`)
// ── Create run params ──
const params = flow.createRunParams({
antidetect: { profileSettings },
execution: { concurrency: 3 },
globalInput: { targetUrl: 'https://example.com' },
window: { width: 1280, height: 720, scale: 0.6 },
})
await flow.run(params)
}
bootstrap()[!NOTE]
createExcelStorage()auto-attaches toFlowLogger— sowriteOutput()andwriteProfileInput()from BrowserUtils will also write to Excel automatically.
[!TIP] When
outputFileis configured, SDK also preloads Excel output intocontext.outputbeforebeforeBrowserOpened(). This lets flows skip completed profiles without opening Chrome.
[!IMPORTANT] Call
createExcelStorage()at the runner level (likemain.dev.ts), not inside the flow class. The flow class only needsscript().
parseProfileSettings()
parseProfileSettings(): Promise<IProfileSetting[]>Read Excel input file and return an array of profile settings. Each row becomes one profile. Row 1 is treated as header and skipped.
Example 1 — Read profiles from Excel
const profiles = await storage.parseProfileSettings()
// Returns:
// [
// { profileName: 'Profile 1', data: { email: 'a@test.com', password: '123' } },
// { profileName: 'Profile 2', data: { email: 'b@test.com', password: '456' } },
// ]Example 2 — Log loaded profile count
const profiles = await storage.parseProfileSettings()
console.log(`Loaded ${profiles.length} profiles from Excel`)Excel file format
| A (Profile Name) | B (email) | C (password) | D (loginStatus) | E (pageTitle) |
|---|---|---|---|---|
| Profile 1 | a@test.com | secret | ||
| Profile 2 | b@test.com | pass123 |
writeOutputRow(profileName, key, value)
writeOutputRow(
profileName: string,
key: TOutputKeys, // type-safe from config
value: ProfileOutputValue // string | number | boolean | object
): Promise<void>Write an output value to the Excel output file for a specific profile. Finds the row by profileName, creates a new row if not found.
Example 1 — Write login result
await storage.writeOutputRow('Profile 1', 'loginStatus', 'Success')
await storage.writeOutputRow('Profile 1', 'pageTitle', 'Dashboard')Example 2 — Manual write in runner
// Usually you don't need this — writeOutput() in BrowserUtils
// auto-writes to Excel. But you can do it manually:
for (const profile of profileSettings) {
const prev = await storage.readOutputForProfile(profile.profileName)
if (!prev.loginStatus) {
await storage.writeOutputRow(profile.profileName, 'loginStatus', 'Pending')
}
}writeProfileInputRow(profileName, key, value)
writeProfileInputRow(
profileName: string,
key: TProfileInputKeys,
value: string | number | boolean
): Promise<void>Write back to the input Excel file — useful for updating tokens, cookies, etc.
Example 1 — Save updated token
await storage.writeProfileInputRow('Profile 1', 'authToken', 'new-jwt-token')readOutputForProfile(profileName)
readOutputForProfile(
profileName: string
): Promise<Record<TOutputKeys, ProfileOutputValue | null>>Read all output values for a specific profile from the output Excel file.
Example 1 — Check previous run result
const previous = await storage.readOutputForProfile('Profile 1')
if (previous.loginStatus === 'Success') {
console.log('Already completed, skipping...')
}Example 2 — Resume from previous state
for (const profile of profileSettings) {
const prev = await storage.readOutputForProfile(profile.profileName)
if (prev.loginStatus === 'OK') {
console.log(`Skipping ${profile.profileName} — already done`)
}
}Initial output hydration before browser opens
When createExcelStorage() includes an outputFile, the SDK preloads output values
from Excel into context.output before beforeBrowserOpened() runs.
This means you can skip a completed profile without opening Chrome:
protected beforeBrowserOpened(context) {
if (context.output.loginStatus === 'Success') {
context.logger.success('Already completed — skip browser open')
return false
}
return true
}Excel example:
| A (Profile Name) | D (loginStatus) | E (pageTitle) |
|---|---|---|
| Profile 1 | Success | Dashboard |
| Profile 2 |
For Profile 1, context.output.loginStatus is available immediately in
beforeBrowserOpened().
Precedence
If the server/agent also passes existingOutputEntries, those values override
Excel values for the same keys:
null defaults → Excel output → existingOutputEntriesThis keeps production server output slots as the source of truth, while still supporting local Excel-driven runs.
Configuration Reference
IExcelFileConfig
| Property | Type | Default | Description |
|---|---|---|---|
filePath | string | — | Path to Excel file (absolute or relative to callerDir) |
sheetIndex | number | 0 | Sheet index (0-based) |
profileNameColumn | string | "A" | Column letter containing profile names |
columns | Record<string, TKeys> | — | Column letter → key mapping |
Same file for input and output
const storage = flow.createExcelStorage({
inputFile: {
filePath: './data.xlsx',
profileNameColumn: 'A',
columns: { 'B': 'email', 'C': 'password' },
},
outputFile: {
filePath: './data.xlsx', // same file!
profileNameColumn: 'A',
columns: { 'D': 'status', 'E': 'result' }, // different columns
},
}, __dirname)How auto-sync works
When you call writeOutput() or writeProfileInput() inside beforeBrowserOpened() or script():
context.browserUtils.writeOutput('status', 'OK')
↓
1. Updates context.output in memory (real-time)
2. Sends output event to Hira Agent/UI
3. Auto-writes to Excel (if ExcelStorage is set up)context.output is shared across lifecycle hooks for the current profile:
protected async beforeBrowserOpened(context) {
await context.browserUtils.writeOutput('status', 'A')
return true
}
async script(context) {
context.logger.info(String(context.output.status)) // "A"
await context.browserUtils.writeOutput('status', 'B')
}
protected afterBrowserClosed(context) {
context.logger.info(String(context.output.status)) // "B"
return true
}You don’t need to manually call storage.writeOutputRow() — it happens automatically.