A powerful Electron-based desktop application for creating, managing, and executing browser automation workflows with a visual node-based editor.
Watch how to:
- Build your first automation
- Navigate to a website
- Extract data with variables
- Compare values using conditions
- Run the automation in a real browser window
- Visual Workflow Editor: Drag-and-drop node graph using ReactFlow
- Browser Automation: Execute automation steps in real Chromium windows
- Interactive Element Picker: Click-to-select CSS selectors from live pages
- Data Extraction: Extract text from elements and store in variables for reuse
- Variable System: Set, modify, and substitute variables using
{{varName}}syntax - Conditional Logic: Branching flows; use condition nodes together with variables for explicit loop control
- Import/Export: Save and share automation workflows as JSON
- TypeScript: Fully typed codebase with discriminated unions for type safety
- Electron - Cross-platform desktop application framework
- React - Component-based UI with hooks
- TypeScript - Type-safe development
- ReactFlow - Interactive node graph editor
- Tailwind CSS + shadcn/ui - Modern, accessible UI components
- Electron Forge - Build and packaging toolchain
src/
βββ main/ # Electron main process
β βββ index.ts # Main entry point & lifecycle
β βββ windowManager.ts # Window creation and management
β βββ automationExecutor.ts # Step execution engine
β βββ selectorPicker.ts # Interactive element picker
β βββ ipcHandlers.ts # IPC communication bridge
βββ components/ # React components
β βββ AutomationBuilder.tsx # Visual workflow editor
β βββ Dashboard.tsx # Automation management (Edit/Export actions rendered as buttons)
β βββ automationBuilder/ # Builder subcomponents
β βββ BuilderHeader.tsx
β βββ BuilderCanvas.tsx
β βββ AutomationNode.tsx
β βββ AddStepPopup.tsx
β βββ nodeDetails/ # Node configuration UI
β βββ NodeDetails.tsx
β βββ NodeHeader.tsx
β βββ StepEditor.tsx
β βββ ConditionEditor.tsx
β βββ stepTypes/ # Step-specific editors
β βββ customComponents/
βββ hooks/ # Custom React hooks
β βββ useNodeActions.ts # Node CRUD operations
β βββ useExecution.ts # Automation execution logic
βββ types/ # TypeScript type definitions
β βββ steps.ts # Automation step types (discriminated union)
β βββ flow.ts # ReactFlow graph types
β βββ automation.ts # Business domain types
β βββ index.ts # Barrel exports
βββ utils/
β βββ automationIO.ts # Import/export utilities
βββ preload.ts # Secure IPC bridge
βββ app.tsx # Root React component
βββ renderer.ts # Renderer process entry
The Electron main process is modularized into focused services:
- WindowManager: Creates and manages application windows (main UI + browser)
- AutomationExecutor: Executes automation steps via
webContents.executeJavaScript - SelectorPicker: Injects interactive element picker into browser pages
- IPC Handlers: Routes messages between renderer and main process
React-based UI with component hierarchy:
- App: Root component managing view routing (Dashboard β Builder β Credentials)
- AutomationBuilder: Visual editor using ReactFlow for node graphs
- Hooks: Shared logic for node management (
useNodeActions) and execution (useExecution)
Robust type definitions with discriminated unions for type safety:
// Each step type is uniquely identified by its 'type' field
type AutomationStep =
| StepNavigate // { type: "navigate", value: string }
| StepClick // { type: "click", selector: string }
| StepType // { type: "type", selector: string, value: string, credentialId?: string }
| StepExtract // { type: "extract", selector: string, storeKey?: string }
| StepSetVariable // { type: "setVariable", variableName: string, value: string }
| StepModifyVariable// { type: "modifyVariable", variableName: string, operation: ModifyOp, value: string }
| ... more variants
// TypeScript narrows types automatically:
switch (step.type) {
case "navigate":
// β
step.value is available
break;
case "click":
// β
step.selector is available
break;
}Uses context isolation with contextBridge for secure renderer β main communication.
The preload API now exposes additional executor helpers (variable init / query) and conditional execution helpers:
// preload.ts exposes limited API
contextBridge.exposeInMainWorld("electronAPI", {
openBrowser: (url) => ipcRenderer.invoke("browser:open", url),
runStep: (step) => ipcRenderer.invoke("browser:runStep", step),
runConditional: (condition) => ipcRenderer.invoke("browser:runConditional", condition),
initVariables: (vars) => ipcRenderer.invoke("executor:initVariables", vars),
getVariables: () => ipcRenderer.invoke("executor:getVariables"),
pickSelector: (url) => ipcRenderer.invoke("pick-selector", url),
});- User clicks "Pick Element" button in step configuration
- Renderer sends IPC request to main process
- Main process injects picker script into browser page
- User hovers (highlights) and clicks element
- Script generates unique CSS selector (e.g.,
html > body > div:nth-of-type(3) > button:first-of-type) - Selector sent back to renderer via IPC
- Step configuration auto-populated
The project now uses an explicit variable system instead of the legacy loopUntilFalse behavior.
- Add a
Set VariableorModify Variablestep to declare and change variables as your flow runs. - Use double-curly tokens
{{variableName}}inside selectors, step inputs, and API payloads; those tokens are substituted at runtime by the executor.
Example selector using a variable:
Selector: .product-list > div:nth-of-type({{index}})
Control how {{index}} changes by placing Modify Variable steps (increment/decrement/append/set) in your graph. This makes loop semantics explicit and easier to maintain.
Conditional nodes also support post-processing of extracted text (strip currency symbols, remove non-numeric characters, regex replace, and parse-as-number) to make comparisons robust (for example, comparing $29.99 numerically).
Automation flows are executed as directed graphs:
- Start at root node (id="1")
- Execute current node's step/condition
- Follow outgoing edges to next nodes:
- Regular nodes: 1 outgoing edge
- Conditional nodes: 2 edges ("if" or "else" branch)
- Recurse until no more nodes
- Mark nodes as "running" for visual feedback
Full documentation is available at: https://loopi.dyan.live/
The documentation site includes:
- Getting Started - Installation and first steps
- User Guide - Detailed feature documentation
- Architecture - System design and internals
- Examples & Tutorials - Real-world automation examples
- API Reference - Detailed API documentation
- FAQ - Frequently asked questions
- Contributing Guide - How to contribute
git clone https://github.com/Dyan-Dev/loopi.git
cd loopi
pnpm installpnpm start # Start Electron app with hot reloadpnpm run make # Package for current platform
pnpm run publish # Build and publish (requires config)
## Examples
Example automation JSON files are included under `docs/examples/` to help you test common scenarios quickly.
- `docs/examples/pagination_price_extraction_variable_loop.json`: Extract each product price across page and compare against a threshold using variable-driven loop. If price > specified amount, tick checkbox.
How to use an example:
1. Open the Automation Builder and choose _Import_ (or place the JSON into the import dialog).
2. Select one of the files under `docs/examples/` and import it into the editor.
3. Inspect nodes to see `Set Variable` / `Modify Variable` usage and condition transforms (e.g. `stripCurrency`, `parseAsNumber`).
4. Run the automation from the builder using the Run button. The executor will initialize variables and execute steps in the browser window.
Tip: Use the `Condition` node's "Post-process Extracted Text" options for robust comparisons when dealing with currency or noisy text.
## π§° Developer Notes
- **Tailwind / PostCSS**: This project uses Tailwind CSS (v4) processed via PostCSS. The canonical PostCSS config is `postcss.config.cjs`; there is also an ESM re-export `postcss.config.js` to accommodate different toolchains. If you change Tailwind or PostCSS plugins, update both configs and ensure `tailwind.config.cjs` `content` globs include any new file locations so utilities are generated.
- **Formatting & Linting (Biome)**: We use Biome for linting and formatting. Run `pnpm format` before committing to apply automatic fixes. The CI expects Biome checks to pass.- Define type in
src/types/steps.ts:
export interface StepCustom extends StepBase {
type: "custom";
customField: string;
}
export type AutomationStep =
| StepNavigate
| StepClick
| ...
| StepCustom; // Add to union
// Add to UI metadata
export const stepTypes = [
// ...
{ value: "custom", label: "Custom", icon: Icon, description: "Custom step" },
] as const;- Create editor in
src/components/automationBuilder/nodeDetails/stepTypes/:
import { Input } from "../../../ui/input";
import { Label } from "../../../ui/label";
import { SelectorButton } from "../customComponents";
import { StepProps } from "./types";
export function CustomStep({ step, id, onUpdate, onPickWithSetter }: StepProps) {
if (step.type !== "custom") return null;
return (
<>
<div className="space-y-2">
<Label className="text-xs">Custom Field</Label>
<Input
value={step.customField || ""}
onChange={(e) => onUpdate(id, "update", {
step: { ...step, customField: e.target.value }
})}
className="text-xs"
/>
</div>
</>
);
}-
Export from
stepTypes/index.tsand add toStepEditor.tsxswitch statement -
Add execution logic in
src/main/automationExecutor.ts:
case "custom": {
// Use the executor's substitution helper to resolve any `{{var}}` tokens
const value = this.substituteVariables(step.customField);
await wc.executeJavaScript(`console.log(${JSON.stringify(value)});`);
break;
}- Update
useNodeActions.tsto provide default initial values when creating new nodes.
- Context Isolation: Renderer process cannot directly access Node.js/Electron APIs
- Preload Script: Only exposes whitelisted IPC channels via
contextBridge - No Direct Node Access: Renderer uses async IPC for all privileged operations
- Credential Encryption: Credentials stored with placeholder encryption (! implement real crypto for production)
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
This project uses Biome for formatting and linting.
Before committing changes, please ensure your code is formatted:
pnpm formatIf you're using VS Code, you can enable automatic formatting and linting via Biome.
Install the official Biome VS Code extension from the Visual Studio Marketplace: here
To make Biome your default formatter:
- Open any supported file (e.g.,
.ts,.tsx,.js) - Open the Command Palette: View β Command Palette or
Ctrl/β + Shift + P - Select Format Document Withβ¦
- Select Configure Default Formatter
- Choose Biome
For advanced configuration and options, see the Biome reference documentation.
- See
CONTRIBUTING.mdfor contribution guidelines, coding style and PR workflow. - Please follow the
CODE_OF_CONDUCT.mdto help keep this community welcoming. - Security issues should be reported privately as described in
SECURITY.md.
For support, bug reports, or questions:
- Documentation: https://loopi.dyan.live/docs
- FAQ: https://loopi.dyan.live/docs/faq
- Email: support@dyan.live
- GitHub Issues: Report bugs or request features
- GitHub Discussions: Community discussions
This project is licensed under the MIT License.
- Electron - Cross-platform desktop apps
- ReactFlow - Node-based UI library
- Tailwind CSS - Utility-first CSS