Skip to Content
ExamplesExcel Input/Output

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 ExcelStorage for 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 1adminsecret123
Profile 2user2pass456
Profile 3user3pass789
  • 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.ts

Output 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:

ABCDE
Profile 1adminsecret123OKDashboard
Profile 2user2pass456OKHome
Profile 3user3pass789FAILED

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.json

Key Points

  1. createExcelStorage() β€” set up column mapping for input and output
  2. Same file for I/O β€” input and output can share the same Excel file (different columns)
  3. parseProfileSettings() β€” read profiles from Excel (row 1 = header, skipped)
  4. Auto-sync β€” writeOutput() from BrowserUtils automatically writes to Excel
  5. Provider switching β€” pass AntidetectProvider.GPM or AntidetectProvider.HIDEMIUM to constructor
Last updated on