A production-ready service that generates PDF, DOCX, PPTX, and XLSX documents from Salesforce data, deployed on Azure Container Apps.
Docgen enables both interactive and batch document generation directly from Salesforce records:
- Interactive Generation: Users click a Lightning Web Component button to generate and download documents
- Flow Automation: Invocable action queues document generation from record-triggered, scheduled, or screen flows
- Batch Processing: Apex Batch/Queueable classes enqueue thousands of documents for background processing
- Template-Based: Use Microsoft Word, PowerPoint, or Excel templates with merge fields for data population
- Multi-Object Support: Generate documents from Accounts, Opportunities, Cases, Contacts, Leads, and custom objects
- Composite Documents: Combine multiple data sources (SOQL queries or Apex providers) into one document with namespace isolation; XLSX uses the Own Template strategy
sequenceDiagram
participant U as User
participant LWC as LWC Button
participant APX as Apex Controller
participant NC as Named Credential
participant API as Node.js Service
participant SF as Salesforce
Note over U,SF: Interactive Generation Flow
U->>LWC: Click Generate PDF
LWC->>APX: Invoke with recordId and templateId
APX->>APX: Build JSON envelope and request hash
APX->>NC: POST /generate using client credentials
NC->>API: POST /generate with Authorization header
API->>SF: Get template by ContentVersionId
API->>API: Merge DOCX → Convert to PDF
API->>SF: Upload PDF file
API-->>NC: Return downloadUrl and contentVersionId
APX-->>LWC: Return downloadUrl
LWC-->>U: Open PDF in new tab
Note over APX,SF: Batch Generation Flow
APX->>SF: Insert Generated_Document__c rows (status=QUEUED)
Note over API,SF: Poller runs every 15s
API->>SF: Query queued documents
API->>SF: Lock and process (max 8 concurrent)
API->>API: Merge + Convert
API->>SF: Upload and link files
API->>SF: Update status (SUCCEEDED/FAILED)
Composite Documents combine data from multiple sources (SOQL queries or Apex providers) into a single PDF with namespace isolation.
sequenceDiagram
autonumber
participant U as User (Browser)
participant L as LWC Button
participant AX as Apex Controller
participant CD as Composite Document Config
participant DT as Document Template Config
participant NC as Named Credential AAD
participant N as Node Fastify API
participant SF as Salesforce REST and Files
Note over U,SF: Interactive Composite Generation - upload first then return download link
U->>L: Click Generate PDF button
L->>AX: generateComposite(compositeDocId, recordIds, outputFormat)
AX->>CD: Load Composite_Document__c
AX->>CD: Query Composite_Document_Template__c junction records ordered
loop For each junction record by Sequence__c
AX->>DT: Load referenced Docgen_Template__c
AX->>AX: Execute template data provider SOQL or Custom
AX->>AX: Store result in namespace key such as Account or Terms
end
AX->>AX: Validate no namespace collisions
AX->>AX: Build merged JSON envelope with all namespaces
AX->>AX: Compute RequestHash compositeDocId outputFormat dataHash
AX->>SF: Check for existing Generated_Document__c for idempotency
alt Cache Hit - SUCCEEDED within 24h
AX-->>L: Return existing downloadUrl
else Cache Miss
AX->>NC: POST generate using AAD client credentials
NC->>N: POST generate with Bearer AAD JWT
alt Strategy OWN_TEMPLATE
N->>SF: Download composite template ContentVersionId
N->>N: Merge template with full merged data envelope
else Strategy CONCATENATE_TEMPLATES
loop For each template in sequence
N->>SF: Download template ContentVersionId
N->>N: Merge with namespace data such as template1 plus Account data
end
N->>N: Concatenate merged DOCX files with section breaks
end
N->>N: Convert DOCX to PDF via soffice headless
N->>SF: Upload ContentVersion
N->>SF: Create ContentDocumentLinks for all parents
N->>SF: Update Generated_Document__c with SUCCEEDED status and OutputFileId__c
N-->>NC: Respond 200 with downloadUrl and contentVersionId
AX-->>L: Return downloadUrl
L-->>U: Open PDF
end
sequenceDiagram
autonumber
participant AX as Apex Batch or Queueable
participant SF as Salesforce
participant N as Node Poller Worker
Note over AX,SF: Batch composite generation driven by poller
AX->>SF: Query Composite_Document__c configuration
AX->>SF: Query Composite_Document_Template__c junction records
loop For each record in batch
AX->>AX: Build merged envelope across namespaces
AX->>AX: Compute RequestHash value
AX->>SF: Insert Generated_Document__c with Status QUEUED
end
loop Poller every 15 to 60 seconds
N->>SF: Pick up to twenty queued rows with lock expired
N->>SF: Set Status PROCESSING and LockedUntil to now plus two minutes
par Up to eight concurrent jobs
N->>SF: Fetch Composite_Document__c configuration
alt Strategy own template
N->>SF: Download composite template
N->>N: Merge template with full data envelope
else Strategy concatenate templates
N->>SF: Download all template ContentVersionIds
N->>N: Merge each template with its namespace data
N->>N: Concatenate documents with section breaks
end
N->>N: Convert DOCX to PDF
N->>SF: Upload file and link to parents set status succeeded
and On failure
N->>SF: Increment attempts apply backoff set failed after three attempts
end
end
- Dual Processing Modes: Interactive (synchronous) and batch (asynchronous) generation
- Composite Documents: Combine data from multiple sources with namespace isolation (Own Template or Concatenate Templates strategies)
- Template Caching: Immutable in-memory cache with LRU eviction (500 MB max)
- PDF Conversion: LibreOffice headless conversion with bounded concurrency (8 max per instance)
- Idempotency: SHA-256 request hash prevents duplicate generation within 24-hour window
- Secure Authentication: Azure AD OAuth2 inbound, Salesforce JWT Bearer outbound
- Scalable: Horizontal autoscaling (1-5 replicas) based on CPU utilization
- Observable: Azure Application Insights integration with custom metrics and distributed tracing
- Multi-Parent Linking: Automatically attach generated files to multiple related records
- Retry Logic: Exponential backoff for transient failures (1m → 5m → 15m)
- Node.js 20+
- Salesforce CLI (for Apex development)
- Docker (for containerization)
# Clone repository
git clone https://github.com/bigmantra/docgen.git
cd docgen
# Install dependencies
npm install
# Run tests
npm test
# Start development server
npm run devThe Salesforce components are available as an unlocked package for easy installation:
Package: 0HoWS00000009uX0AQ - Processity PBO
Option 1: Install via URL
Click the badge above or use this link:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04tWS000001jy7XYAQ
Option 2: Deploy metadata to org
# Deploy metadata to org
sf project deploy --target-org <YourOrg> --wait 30 --no-promptAfter Installation:
- Assign the
Docgen_Userpermission set to users who need access - Leave
Docgen_Settings__c.Named_Credential_Name__cblank for org-type defaults:Docgen_Node_API_Sandboxin sandbox orgs,Docgen_Node_APIin non-sandbox orgs. The Sandbox credential includeshttps://docgen-uat.mangostone-78031136.eastus.azurecontainerapps.io. An explicit setting takes precedence. Both useDocgen_AAD_Credential/Main; see setup and migration. - Set up External Credential principals for Azure AD authentication
# Authenticate to Dev Hub
sf org login web --set-default-dev-hub --alias DevHub
# Set Azure AD credentials (required for backend authentication)
export AAD_CLIENT_ID="your-azure-ad-client-id"
export AAD_CLIENT_SECRET="your-azure-ad-client-secret"
# Create and configure scratch org (automated - includes AAD setup)
./scripts/setup-scratch-org.sh
# Or manually
sf org create scratch --definition-file config/project-scratch-def.json --alias docgen-dev --duration-days 7
sf project deploy start --source-dir force-app
sf org assign permset --name Docgen_User
./scripts/configure-external-credential.sh docgen-dev- Upload a template: Navigate to the Docgen app → Docgen Templates tab → Create a template with a DOCX, PPTX, or XLSX file
- Add LWC button: Edit an Account/Opportunity/Case page → Drag
docgenButtoncomponent onto the layout - Generate: Click "Generate Document" → Select template → Use the template default format or choose an override → Generate
For detailed setup instructions, see Quick Start Guide.
| Document | Description |
|---|---|
| Quick Start Guide | Complete setup and installation guide for new developers |
| Scripts Reference | Detailed documentation for all helper scripts and Apex templates |
| Architecture Guide | Technical implementation details (authentication, caching, conversion, batch processing, composite documents) |
| Testing Guide | Running tests (Node.js, Apex, LWC, E2E) and CI/CD configuration |
| API Reference | REST API endpoints, request/response formats, error handling, composite envelope format |
| Template Authoring | Creating DOCX templates with merge fields, loops, conditionals, and composite namespaces |
| Excel Template Authoring | Creating XLSX templates with scalar fields, repeating rows, formulas, and composite constraints |
| Field Path Conventions | Data structure and field path syntax including namespace-scoped paths |
| ADRs | Architecture Decision Records (runtime, auth, worker, caching) |
| Document | Description |
|---|---|
| Deployment Guide | CI/CD workflows, deployment procedures, rollback strategies |
| Provisioning Guide | One-time environment setup in Azure |
| Runbooks | Operational procedures (scaling, key rotation, disaster recovery) |
| Monitoring & Dashboards | Application Insights dashboards, KQL queries, alert rules |
| Troubleshooting Index | Common issues and resolution steps |
| Document | Description |
|---|---|
| Admin Guide | Salesforce admin setup, adding support for new objects, creating composite documents |
| Admin Runbook | Administrative operations and troubleshooting |
| Named Credential Setup | Configuring Azure AD authentication from Salesforce |
| LWC Composite Button Guide | Configuring compositeDocgenButton component on Lightning pages |
| Flow Invocable Guide | Generating documents from Flow with the Docgen: Generate Document invocable action |
| LWC Document Selector Guide | Configuring the docgenDocumentSelector component (template/composite selection UI, preset lock, embedding) |
| Composite Batch Examples | Batch generation patterns for composite documents |
docgen/
├── src/ # Node.js TypeScript source
│ ├── auth/ # Azure AD authentication
│ ├── sf/ # Salesforce API client
│ ├── templates/ # Template cache and merging
│ ├── convert/ # LibreOffice conversion pool
│ ├── worker/ # Batch poller service
│ ├── obs/ # Observability (metrics, tracing)
│ └── routes/ # API endpoints
├── force-app/ # Salesforce metadata
│ └── main/default/
│ ├── classes/ # Apex (controllers, services, batch)
│ ├── lwc/ # Lightning Web Components
│ ├── objects/ # Custom objects and fields
│ └── tabs/ # Custom tabs
├── test/ # Jest tests
├── e2e/ # Playwright E2E tests
├── docs/ # Documentation
├── infra/ # Bicep infrastructure templates
└── .github/workflows/ # CI/CD workflows
- Docgen_Template__c: Template configuration (links to ContentVersion)
- Generated_Document__c: Document generation tracking and status
- Composite_Document__c: Multi-source document configuration
- Composite_Document_Template__c: Junction records linking composites to templates with namespaces
- Supported_Object__mdt: Multi-object configuration (Custom Metadata)
- DocgenController: Interactive generation controller for LWC (including composite generation)
- DocgenInvocable: Invocable action for queued document generation from Flow
- DocgenEnvelopeService: Request envelope builder with SHA-256 hashing (single and composite)
- StandardSOQLProvider: Data collection with Salesforce type/currency descriptors for shared backend ICU formatting (see locale configuration)
- CompositeDocgenDataProvider: Orchestrates multiple data providers with namespace isolation
- BatchDocgenEnqueue: Batch processing for mass generation (single and composite)
Test Coverage: 112 Apex tests with 86% code coverage
- docgenButton: Single-template document generation button (deployable to any record page)
- docgenProgressButton: Queued single-template generation with progress bar, requester-validated JPEG page preview, and Save/Cancel
- compositeDocgenButton: Composite document generation button with recordIds mapping
- docgenDocumentSelector: Template/composite document selection UI with search lookups and optional preset lock; embeds the generator buttons for a complete generation flow on any supported object
- docgenAdditionalPdfSelector: Attach additional PDF files to a generation request
- docgenTestPage: E2E testing wrapper component
The Docgen app includes:
- Docgen Templates tab (manage single-object templates)
- Composite Documents tab (manage multi-source templates)
- Generated Documents tab (track generation history for single and composite)
- Docgen Test Page tab (E2E testing interface)
| Layer | Technology |
|---|---|
| Runtime | Node.js 20+ with TypeScript |
| Web Framework | Fastify |
| Template Engine | docx-templates, ExcelJS, JSZip |
| PDF Conversion | LibreOffice (headless) |
| Authentication | Azure AD OAuth2 (inbound), Salesforce JWT Bearer (outbound) |
| Testing | Jest, Supertest, Nock, Playwright |
| Infrastructure | Azure Container Apps, Azure Container Registry, Azure Key Vault |
| Observability | Azure Application Insights, OpenTelemetry |
| CI/CD | GitHub Actions |
- GET /healthz: Liveness probe (always returns 200)
- GET /readyz: Readiness probe with dependency checks
- POST /generate: Generate PDF/DOCX/PPTX/XLSX from a template (requires Azure AD token)
- POST /worker/start: Start batch poller
- POST /worker/stop: Stop batch poller gracefully
- GET /worker/status: Current worker state
- GET /worker/stats: Detailed worker metrics
Per-replica metrics backing the Salesforce "System Status" page. Both carry a
replicaId, because a callout reaches one replica out of several.
- GET /metrics/performance: Processing times, throughput and per-stage timings over a rolling window
- GET /metrics/resources: CPU, memory, event loop delay, LibreOffice pool and template cache utilization
See API Reference for complete endpoint documentation.
We welcome contributions! To get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes and add tests
- Run tests and linting (
npm test && npm run lint) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- TypeScript: Strict mode enabled
- Linting: ESLint with Prettier
- Testing: Jest with 80%+ coverage target
- Commits: Conventional Commits format
MIT - see LICENSE file for details.
- Documentation: docs/
- GitHub Issues: https://github.com/bigmantra/docgen/issues
- Architecture Decisions: docs/adr/
Built with ❤️ by the Processity team