Excel Input/Output Flow
Build a flow that reads profile credentials from an Excel file and writes automation results back to the same file. This is the recommended approach for local development and testing.
What youβll learn:
- Setting up
ExcelStoragefor reading/writing - Excel file format and column mapping
- Running flows locally with
ts-node - Reading profile data and writing output to Excel
Prepare the Excel file
Create profiles.xlsx with this structure:
| A (Profile Name) | B (username) | C (password) | D (status) | E (result) |
|---|---|---|---|---|
| Profile 1 | admin | secret123 | ||
| Profile 2 | user2 | pass456 | ||
| Profile 3 | user3 | pass789 |
- Column A β profile names (must match GPM/Hidemium profile names)
- Columns B-C β input data (read by the flow)
- Columns D-E β output data (written by the flow, leave empty)
Define the config
Create src/config.ts:
import { defineFlowConfig } from '@hira-core/sdk'
export const config = defineFlowConfig({
globalInput: [
{ key: 'targetUrl', label: 'Target URL', type: 'text', required: true },
],
profileInput: [
{ key: 'username', label: 'Username', type: 'text', required: true },
{ key: 'password', label: 'Password', type: 'text', required: true },
],
output: [
{ index: 0, key: 'status', label: 'Login Status' },
{ index: 1, key: 'result', label: 'Page Title' },
],
})Write the flow
Create src/flow.ts:
import {
AntidetectBaseFlow,
AntidetectProvider,
BrowserUtils,
FlowLogger,
IScriptContext,
} from '@hira-core/sdk'
import { config } from './config'
type FlowConfig = typeof config
export class ExcelFlow extends AntidetectBaseFlow<FlowConfig> {
// Use GPM as the anti-detect provider
constructor(provider: AntidetectProvider = AntidetectProvider.GPM) {
super(provider, new FlowLogger('ExcelFlow'), config)
}
async script(context: IScriptContext<FlowConfig>) {
const browser = new BrowserUtils(context)
const { globalInput, profileInput, output } = context
// 1. Log what we received from Excel
await browser.logProfileInput()
// Log:
// π Profile Input:
// {
// username: "admin",
// password: "secret123"
// }
// 2. Skip if already completed
if (output.status === 'OK') {
context.logger.info('Already completed, skipping')
return
}
// 3. Navigate and login
await browser.goto(globalInput.targetUrl)
// Log: π Navigate β https://example.com
await browser.type('#username', profileInput.username)
// Log: β¨οΈ Type "admin" β #username
await browser.type('#password', profileInput.password)
// Log: β¨οΈ Type "secret123" β #password
await browser.click('#login-btn')
// Log: π±οΈ Click: #login-btn
await browser.waitForNavigation()
// 4. Check result and write output β goes to Excel columns D, E
const isLoggedIn = await browser.exists('#dashboard', 10000)
if (isLoggedIn) {
const title = await context.page.title()
await browser.writeOutput('status', 'OK')
// Log: π€ Write Output [status] = OK
await browser.writeOutput('result', title)
// Log: π€ Write Output [result] = Dashboard
} else {
await browser.writeOutput('status', 'FAILED')
// Log: π€ Write Output [status] = FAILED
await browser.screenshot('login-failed.png')
}
}
}Create the dev runner
Create src/main.dev.ts β this is the local dev entry point that sets up Excel storage:
import { AntidetectProvider } from '@hira-core/sdk'
import { ExcelFlow } from './flow'
async function bootstrap() {
console.log('π§ Starting local dev runner...')
// You can switch provider here:
// AntidetectProvider.GPM β GPM Login (port 19995)
// AntidetectProvider.HIDEMIUM β Hidemium (port 2222)
const flow = new ExcelFlow(AntidetectProvider.GPM)
// ββ Setup Excel Storage ββ
const storage = flow.createExcelStorage({
inputFile: {
filePath: './profiles.xlsx', // relative to callerDir
sheetIndex: 0,
profileNameColumn: 'A',
columns: {
'B': 'username', // Column B β profileInput.username
'C': 'password', // Column C β profileInput.password
},
},
outputFile: {
filePath: './profiles.xlsx', // same file!
sheetIndex: 0,
profileNameColumn: 'A',
columns: {
'D': 'status', // Column D β writeOutput('status', ...)
'E': 'result', // Column E β writeOutput('result', ...)
},
},
}, __dirname)
// ββ 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, // 3 profiles in parallel
maxRetries: 1, // retry failed profiles once
keepOpenOnError: true, // keep browser open on error for debugging
},
globalInput: {
targetUrl: 'https://example.com/login',
},
window: { width: 1280, height: 720, scale: 0.6 },
})
// ββ Run! ββ
try {
console.log('βΆοΈ Running Flow...')
await flow.run(params)
console.log('β
Done! Check profiles.xlsx for results.')
} catch (err) {
console.error('β Failed:', err)
}
}
bootstrap()Run locally
npx ts-node src/main.dev.tsOutput in terminal:
π§ Starting local dev runner...
π Loaded 3 profile(s) from Excel
βΆοΈ Running Flow...
π Profile Input: { username: "admin", password: "secret123" }
π Navigate β https://example.com/login
β¨οΈ Type "admin" β #username
β¨οΈ Type "secret123" β #password
π±οΈ Click: #login-btn
π€ Write Output [status] = OK
π€ Write Output [result] = Dashboard
β
Done! Check profiles.xlsx for results.After running, your Excel file will be updated:
| A | B | C | D | E |
|---|---|---|---|---|
| Profile 1 | admin | secret123 | OK | Dashboard |
| Profile 2 | user2 | pass456 | OK | Home |
| Profile 3 | user3 | pass789 | FAILED |
Project Structure
my-excel-flow/
βββ src/
β βββ config.ts β defineFlowConfig()
β βββ flow.ts β ExcelFlow class
β βββ main.dev.ts β Local dev runner with ExcelStorage
βββ profiles.xlsx β Input/Output Excel file
βββ package.json
βββ tsconfig.jsonKey Points
createExcelStorage()β set up column mapping for input and output- Same file for I/O β input and output can share the same Excel file (different columns)
parseProfileSettings()β read profiles from Excel (row 1 = header, skipped)- Auto-sync β
writeOutput()from BrowserUtils automatically writes to Excel - Provider switching β pass
AntidetectProvider.GPMorAntidetectProvider.HIDEMIUMto constructor
Last updated on