A reusable DocuSign service wrapper for Node.js applications with support for JWT authentication, template-based and custom HTML contracts
npm install docusign-azimA reusable, production-ready DocuSign service wrapper for Node.js applications with support for JWT authentication, template-based contracts, and fully customizable HTML documents with SOLID architecture.
- ✅ JWT Authentication - Secure authentication using JWT tokens
- ✅ Template-based Contracts - Send contracts from DocuSign templates
- ✅ Custom HTML Rendering - 4 flexible ways to customize document HTML
- ✅ SOLID Architecture - Extensible renderer system following best practices
- ✅ Embedded Signing - Generate signing URLs for embedded signing experience
- ✅ Document Download - Download signed documents as PDFs
- ✅ TypeScript Support - Full TypeScript definitions included
- ✅ Framework Agnostic - Works with Express, NestJS, or any Node.js framework
- 🆕 HTML Template Support - Use custom templates with {{placeholder}} syntax
- 🆕 Custom Renderer Functions - Full programmatic control over HTML generation
- 🆕 Reusable Renderer Classes - Create and share custom renderers across projects
``bash`
npm install docusign-azim
Before using this package, you need to:
1. Create a DocuSign Developer account at developers.docusign.com
2. Create an Integration Key (Client ID)
3. Generate an RSA keypair for JWT authentication
4. Get your User ID and Account ID
5. Grant admin consent for your integration
`typescript
import { DocuSignService } from "docusign-azim";
import * as fs from "fs";
// Initialize the service
const docusign = new DocuSignService({
integrationKey: "your-integration-key",
userId: "your-user-id",
accountId: "your-account-id",
baseUrl: "https://demo.docusign.net/restapi", // Use https://na1.docusign.net/restapi for production
privateKey: fs.readFileSync("./keys/private.key", "utf8"), // Or pass file path
redirectUrl: "https://yourapp.com/docusign/callback",
});
// Send a contract from a template
const result = await docusign.sendContractFromTemplate({
templateId: "template-id",
signerEmail: "signer@example.com",
signerName: "John Doe",
customFields: {
company_name: "Acme Corp",
contract_date: "2024-01-15",
},
});
console.log("Signing URL:", result.signingUrl);
console.log("Envelope ID:", result.envelopeId);
`
`typescript`
interface DocuSignConfig {
integrationKey: string; // Your DocuSign Integration Key
userId: string; // Your DocuSign User ID (GUID format)
accountId: string; // Your DocuSign Account ID
baseUrl: string; // API base URL
privateKey: string; // Private key (PEM string or file path)
redirectUrl: string; // Redirect URL after signing
appUrl?: string; // Optional: App URL for iframe embedding
oauthBaseUrl?: string; // Optional: OAuth base URL (default: account-d.docusign.com)
}
`env`
DOCUSIGN_INTEGRATION_KEY=your-integration-key
DOCUSIGN_USER_ID=your-user-id
DOCUSIGN_ACCOUNT_ID=your-account-id
DOCUSIGN_BASE_URL=https://demo.docusign.net/restapi
DOCUSIGN_PRIVATE_KEY_PATH=./keys/private.key
DOCUSIGN_REDIRECT_URL=https://yourapp.com/callback
`typescript`
const result = await docusign.sendContractFromTemplate({
templateId: "abc123-template-id",
signerEmail: "client@example.com",
signerName: "Jane Smith",
subject: "Service Agreement - Please Sign",
emailBody: "Please review and sign this service agreement.",
customFields: {
client_name: "Jane Smith",
project_name: "Website Development",
start_date: "2024-02-01",
total_amount: "$5,000",
},
products: [
{
no: 1,
item: "Web Development",
qty: 1,
price: "$5,000",
total: "$5,000",
},
],
});
`typescript`
// Use the built-in default quotation template
const result = await docusign.sendContractWithCustomData({
signerEmail: "client@example.com",
signerName: "Jane Smith",
subject: "Quotation Q-2024-001",
customFields: {
no: "Q-2024-001",
date: "2024-01-15",
project: "Website Redesign",
subtotal: "$4,500",
tax: "$450",
total: "$4,950",
},
products: [
{
no: 1,
item: "UI/UX Design",
qty: 40,
price: "$75/hr",
total: "$3,000",
},
{
no: 2,
item: "Frontend Development",
qty: 20,
price: "$75/hr",
total: "$1,500",
},
],
});
`typescript
// Provide your own HTML template with {{placeholders}}
const customTemplate =
Invoice #{{invoiceNumber}}
Client: {{signerName}}
Date: {{currentDate}}
| Item | Qty | Price | Total |
|---|
Sign Here
;const result = await docusign.sendContractWithCustomData({
signerEmail: "client@example.com",
signerName: "Jane Smith",
htmlTemplate: customTemplate,
documentName: "Professional Invoice",
signatureAnchor: "Sign Here",
signaturePosition: { x: 450, y: 700 },
customFields: {
companyName: "Acme Corporation",
invoiceNumber: "INV-2024-001",
total: "$5,000.00",
},
products: [...],
});
`$3
`typescript
import type { HtmlRenderData } from "docusign-azim";// Full programmatic control over HTML generation
const customRenderer = (data: HtmlRenderData): string => {
const { customFields, products, signerName, currentDate } = data;
// Calculate totals dynamically
const total = products?.reduce(
(sum, p) => sum + parseFloat(p.total.replace(/[$,]/g, "")), 0
);
return
For: ${signerName}
Date: ${currentDate}
${p.item}: ${p.total}
).join('')}Sign Here
;
};const result = await docusign.sendContractWithCustomData({
signerEmail: "client@example.com",
signerName: "Bob Johnson",
htmlRenderer: customRenderer,
documentName: "Dynamic Proposal",
customFields: {
title: "Business Proposal",
},
products: [...],
});
`$3
`typescript
const signingInfo = await docusign.getSigningUrl({
envelopeId: "envelope-id",
signerEmail: "client@example.com",
signerName: "Jane Smith",
});console.log("Signing URL:", signingInfo.signingUrl);
console.log("Expires at:", signingInfo.expiresAt);
`$3
`typescript
const pdfBuffer = await docusign.downloadDocument({
envelopeId: "envelope-id",
documentId: "combined", // or specific document ID
});// Save to file
fs.writeFileSync("signed-contract.pdf", pdfBuffer);
`$3
`typescript
const refreshed = await docusign.refreshSigningUrl("envelope-id");console.log("New signing URL:", refreshed.signingUrl);
console.log("Recipient info:", refreshed.recipientInfo);
`API Reference
$3
Send a contract from a DocuSign template.
Parameters:
-
templateId - DocuSign template ID
- signerEmail - Signer's email
- signerName - Signer's full name
- customFields - Object with field names and values
- subject - (Optional) Email subject
- emailBody - (Optional) Email body
- products - (Optional) Array of products for DocuSign Gen templatesReturns:
Promise$3
Send a contract with custom HTML content. Supports 4 rendering methods:
1. Default Renderer - Built-in professional quotation template
2. HTML Template - Provide template string with
{{placeholders}}
3. Renderer Function - Full programmatic control
4. Renderer Class - Reusable custom renderer extending BaseHtmlRendererParameters:
-
signerEmail - Signer's email
- signerName - Signer's full name
- customFields - Object with field names and values
- subject - (Optional) Email subject
- emailBody - (Optional) Email body
- products - (Optional) Array of products for quotation table
- htmlTemplate - (Optional) Custom HTML template string with {{placeholders}}
- htmlRenderer - (Optional) Custom renderer function (data: HtmlRenderData) => string
- documentName - (Optional) Document name (default: "Service Agreement")
- signatureAnchor - (Optional) Text to anchor signature (default: "Sign Here")
- signaturePosition - (Optional) Signature position { x: number, y: number }Returns:
PromiseExample with all options:
`typescript
await docusign.sendContractWithCustomData({
signerEmail: "client@example.com",
signerName: "John Doe",
subject: "Please sign this document",
emailBody: "Review and sign at your convenience",
documentName: "Business Proposal",
signatureAnchor: "Sign Here",
signaturePosition: { x: 450, y: 700 },
htmlTemplate: "...", // OR htmlRenderer: (data) => "...",
customFields: {
companyName: "Acme Corp",
invoiceNumber: "INV-001",
},
products: [...],
});
`$3
Generate or regenerate a signing URL.
Parameters:
-
envelopeId - DocuSign envelope ID
- signerEmail - Signer's email
- signerName - Signer's full name
- recipientId - (Optional) Recipient ID
- clientUserId - (Optional) Client user IDReturns:
Promise$3
Download a signed document.
Parameters:
-
envelopeId - DocuSign envelope ID
- documentId - (Optional) Document ID, defaults to "combined"Returns:
Promise$3
Refresh signing URL for an envelope.
Parameters:
-
envelopeId - DocuSign envelope IDReturns:
PromiseIntegration with Express.js
`typescript
import express from "express";
import { DocuSignService } from "docusign-azim";const app = express();
const docusign = new DocuSignService({
// ... config
});
app.post("/send-contract", async (req, res) => {
try {
const result = await docusign.sendContractFromTemplate({
templateId: req.body.templateId,
signerEmail: req.body.email,
signerName: req.body.name,
customFields: req.body.fields,
});
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get("/signing/:envelopeId", async (req, res) => {
try {
const result = await docusign.refreshSigningUrl(req.params.envelopeId);
res.redirect(result.signingUrl);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
`Integration with NestJS
`typescript
import { Injectable } from "@nestjs/common";
import { DocuSignService } from "docusign-azim";@Injectable()
export class ContractService {
private docusign: DocuSignService;
constructor() {
this.docusign = new DocuSignService({
integrationKey: process.env.DOCUSIGN_INTEGRATION_KEY!,
userId: process.env.DOCUSIGN_USER_ID!,
accountId: process.env.DOCUSIGN_ACCOUNT_ID!,
baseUrl: process.env.DOCUSIGN_BASE_URL!,
privateKey: process.env.DOCUSIGN_PRIVATE_KEY_PATH!,
redirectUrl: process.env.DOCUSIGN_REDIRECT_URL!,
});
}
async sendContract(data: any) {
return this.docusign.sendContractFromTemplate(data);
}
}
`Error Handling
The package throws descriptive errors for common issues:
`typescript
try {
const result = await docusign.sendContractFromTemplate({
// ... params
});
} catch (error) {
if (error.message.includes("consent_required")) {
// Admin consent needed - redirect user to consent URL
console.log("Visit consent URL:", error.message);
} else if (error.message.includes("invalid_grant")) {
// Invalid credentials
console.error("Check your integration key, user ID, or private key");
} else {
console.error("Error:", error.message);
}
}
`Development
$3
`bash
npm install
npm run build
`$3
`bash
Login to npm
npm loginPublish
npm publish
`DocuSign Setup Guide
$3
1. Go to DocuSign Admin (or production admin)
2. Navigate to Integrations → Apps and Keys
3. Click Add App and Integration Key
4. Note your Integration Key
$3
1. In your integration settings, click Generate RSA
2. Download the private key and save it securely
3. Copy the public key (DocuSign stores this automatically)
$3
1. In DocuSign Admin, go to Users
2. Click on your user
3. Copy the API Username (GUID format)
$3
1. In DocuSign Admin, go to Settings → API and Keys
2. Copy your API Account ID
$3
After setting up your integration, run your application once. It will provide a consent URL. Visit this URL and grant consent for the impersonation scope.
🎨 Custom HTML Rendering (v1.1.0+)
The package provides 4 flexible ways to customize document HTML, following SOLID principles:
$3
Just pass your data - uses professional built-in template:
`typescript
await docusign.sendContractWithCustomData({
signerEmail: "client@example.com",
signerName: "John Doe",
customFields: { no: "Q-001", total: "$5,000" },
products: [...]
});
`$3
Provide custom HTML with
{{placeholder}} syntax:`typescript
const template = Invoice: {{invoiceNumber}}
Client: {{signerName}}
Date: {{currentDate}}
Sign Here
;await docusign.sendContractWithCustomData({
...params,
htmlTemplate: template,
});
`Available placeholders:
-
{{fieldName}} - Any custom field
- {{signerName}} - Signer's name
- {{currentDate}} - Current date formatted
- {{productsTable}} - Auto-generated table rows
- {{#hasProducts}}...{{/hasProducts}} - Conditional if products exist
- {{#noProducts}}...{{/noProducts}} - Conditional if no products$3
Full programmatic control:
`typescript
import type { HtmlRenderData } from "docusign-azim";const myRenderer = (data: HtmlRenderData): string => {
const { customFields, products, signerName, currentDate } = data;
// Your custom logic
const total = products?.reduce((sum, p) =>
sum + parseFloat(p.total.replace(/[$,]/g, "")), 0
);
return
${signerName} - ${currentDate}
Sign Here
;
};await docusign.sendContractWithCustomData({
...params,
htmlRenderer: myRenderer,
});
`$3
Create reusable renderer classes:
`typescript
import { BaseHtmlRenderer, type HtmlRenderData } from "docusign-azim";class MyCompanyRenderer extends BaseHtmlRenderer {
constructor(private branding: { color: string; logo: string }) {
super();
}
render(data: HtmlRenderData): string {
// Use helper methods
const rows = this.generateProductRows(data.products);
const safeName = this.escapeHtml(data.signerName);
return
Sign Here
;
}
}// Reuse across your app
const renderer = new MyCompanyRenderer({
color: "#1a73e8",
logo: "https://mycompany.com/logo.png"
});
await docusign.sendContractWithCustomData({
...params,
htmlRenderer: (data) => renderer.render(data),
});
`BaseHtmlRenderer helper methods:
-
replacePlaceholders(template, fields, signerName) - Replace {{placeholders}}
- generateProductRows(products) - Generate HTML table rows
- escapeHtml(text) - Prevent XSS attacks$3
For detailed examples, best practices, and advanced usage, see:
- Custom HTML Guide - Comprehensive rendering documentation
- Advanced Examples - Real-world examples
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues or questions:
- GitHub Issues: Create an issue
- DocuSign Support: developers.docusign.com
- Documentation: See CUSTOM_HTML_GUIDE.md for detailed rendering docs
Changelog
$3
- ✨ Added 4 flexible HTML rendering methods
- ✨ Template string support with {{placeholder}} syntax
- ✨ Custom renderer function support for full control
- ✨ Reusable renderer classes extending BaseHtmlRenderer
- ✨ SOLID architecture with Strategy Pattern
- ✨ Conditional template sections ({{#hasProducts}}`)