A macOS, Windows, and Linux desktop + web app for visually creating interactive PDF forms (AcroForms). Drag and drop field widgets onto any PDF background, configure properties, and export a fully interactive PDF compatible with Adobe Acrobat and other PDF viewers.
- Upload any PDF as a background template
- Place form fields by dragging onto the canvas: Text, Number, Checkbox, Radio, Dropdown, Calculated, Hidden (calc holder), Static Label
- Configure each field's appearance (border, background, transparency), typography (font, size, weight, colour), and behaviour (required, read-only, calculations)
- Multi-select fields with Shift+Click or Cmd/Ctrl+Click — edit shared properties in one go
- Drag-to-reorder fields in the sidebar
- Duplicate single or multiple selected fields
- Radio buttons grouped by name — place individual buttons anywhere on the page
- Calculated fields using built-in functions (Sum, Mean, Min, Max, Count, Product, Median) or custom JavaScript
- Hidden fields for intermediate multi-step calculations (invisible but participate in calc order)
- Export to a fully-compliant interactive PDF (AcroForm)
- Export project as JSON
- File > Save (Cmd+S) and File > Export PDF (Cmd+E) in the native app menu
- Desktop app via Electron (macOS, Windows, Linux)
- Web dev mode with Vite HMR (
npm run dev)
Download installers for macOS (universal), Windows, or Linux (deb/AppImage) from the releases page.
| Layer | Technology |
|---|---|
| Client | React 19, Vite, TypeScript, Tailwind CSS, Zustand, react-rnd, @dnd-kit |
| Server | Node.js, Express, better-sqlite3 |
| PDF rendering | pdfjs-dist (preview), pdf-lib (export) |
| Shared schema | Zod (@acroform/shared workspace) |
| Desktop | Electron |
| Build | electron-builder |
# Install all workspace dependencies
npm install
# Start Vite dev server + Express API concurrently (web mode)
npm run dev
# Start in Electron (opens native window, connects to Vite + Express)
npm run electron:dev
npm run electron:build
Produces platform installers in electron/release/:
- macOS:
.dmg(universal, Apple Silicon + Intel) - Windows:
.exeNSIS installer - Linux:
.AppImageand.deb
CI builds for all three platforms are triggered automatically on v* tags via GitHub Actions.
/client React SPA
/src/features/editor
Editor.tsx Top-level editor shell, save/export, IPC listener
PdfCanvas.tsx PDF page preview (pdfjs-dist, 1.5× scale); hosts FieldOverlays as children
FieldOverlay.tsx Draggable/resizable field widget (react-rnd)
FieldList.tsx Sidebar field list with drag-to-reorder (@dnd-kit)
PropertiesPanel.tsx Field property editor (single & multi-select)
/src/store/editorStore.ts Zustand state (project, selectedFieldIds, actions)
/server Express REST API
/src/services/compiler.service.ts PDF compilation (pdf-lib)
/src/services/projects.service.ts CRUD via better-sqlite3
/src/db/index.ts SQLite connection + schema init
/shared Zod schemas shared between client and server
/src/schema/project.ts All field type definitions + Project schema
/electron Electron main process
/src/main.ts Starts Express (production), creates BrowserWindow, builds File menu
/src/preload.ts Exposes electronAPI.onMenuAction() to renderer via contextBridge
Fields are stored in canvas pixels at 1.5× scale (matching pdfjs-dist rendering scale). Field overlays are positioned relative to the canvas element inside PdfCanvas, so y=0 = canvas top.
The compiler divides all coordinates by CANVAS_SCALE = 1.5 to get PDF points, then flips the Y axis (PDF_y = pageHeight - canvas_y - field_height) because PDF origin is bottom-left while the browser is top-left.
React UI → Zustand store → REST API (Express, default port 3001) → SQLite (projects.db)
↘ pdf-lib (PDF export)
- Node.js 20 or later
- npm 10 or later
| Platform | Minimum version |
|---|---|
| macOS | Ventura (Sequoia recommended) |
| Windows | Windows 10 x64 |
| Linux | Ubuntu 22.04+ or equivalent (x64) |
Linux in a VM or container: The Electron sandbox may not work in environments without Linux user namespace support (UTM, QEMU, Docker, WSL2). If the app crashes immediately with a SIGTRAP, run it with:
acroform-builder --no-sandbox
The icon was generated and committed at electron/buildResources/icon.icns. To regenerate it (macOS only — requires iconutil):
cd electron
npm run generate-icon
Contributions are welcome. Please open an issue before starting large changes.
- Fork the repository and clone your fork
- Create a branch:
git checkout -b my-feature - Make changes and test with
npm run electron:dev - Open a pull request against
main
- TypeScript strict mode throughout all workspaces
- Shared data structures live in
shared/src/schema/as Zod schemas — add new field types there first - Client components use Tailwind CSS utility classes; avoid inline styles except for drag-region and dynamic values
- Electron IPC uses
webContents.send('menu-action', actionName)strings — add new actions to bothmain.tsand the relevant renderer component - Match the style of surrounding code; no linter is currently configured
Please use GitHub Issues and include:
- OS and version (e.g. macOS 15.3, Windows 11, Ubuntu 24.04)
- Steps to reproduce
- Expected vs actual behaviour
- Browser/Electron console output if relevant