Appsmith
Prerequisites
- A PolyDoc account and API key - sign up for free, no credit card required. The free plan includes 150 PDF conversions per month.
- An Appsmith instance: Community Edition (self-hosted, free) or Appsmith Cloud (free tier available).
- Your data in a database, REST API, or Google Sheets connected as an Appsmith datasource.
Use Case 1: Download invoice PDF from a table row
Add a Download PDF button to an Appsmith table. When clicked it reads the selected invoice row plus its line items, calls PolyDoc, and saves the resulting PDF directly from the browser.Button (onClick) → REST query (PolyDoc) → JS Object (Blob download)
Set up your data
Use invoices (one row per invoice) and invoice_items (line rows linked by invoice_number). Examples below - two tabs in Sheets; two tables or linked records elsewhere.
| invoice_number | invoice_date | invoice_due_date | invoice_subtotal | invoice_tax_rate | invoice_tax_amount | invoice_total | customer_name | customer_email | customer_street | customer_city | customer_country |
|---|---|---|---|---|---|---|---|---|---|---|---|
| INV-2026-001 | 2026-01-15 | 2026-02-14 | 1200 | 0.1 | 120 | 1320 | John Doe | john.doe@example.com | 456 Customer Avenue | Beautiful City | UK |
| invoice_number | quantity | name | description | price | original_price |
|---|---|---|---|---|---|
| INV-2026-001 | 1 | Web Design Services | Custom website design and development for company homepage | 800 | |
| INV-2026-001 | 1 | Logo Design | Professional logo design with 3 revision rounds | 300 | |
| INV-2026-001 | 1 | SEO Optimization | Search engine optimization for 10 target keywords | 100 | 150 |
Create a new Appsmith app. Open the JS tab, click + New JS Object, and replace its body with the data below — one invoice with its line items nested under items:
export default {
invoices: [
{
invoice_number: "INV-2026-001",
invoice_date: "2026-01-15",
invoice_due_date: "2026-02-14",
invoice_subtotal: 1200,
invoice_tax_rate: 0.1,
invoice_tax_amount: 120,
invoice_total: 1320,
customer_name: "John Doe",
customer_email: "john.doe@example.com",
customer_street: "456 Customer Avenue",
customer_city: "Beautiful City",
customer_country: "UK",
items: [
{ quantity: 1, name: "Web Design Services", description: "Custom website design and development for company homepage", price: 800 },
{ quantity: 1, name: "Logo Design", description: "Professional logo design with 3 revision rounds", price: 300 },
{ quantity: 1, name: "SEO Optimization", description: "Search engine optimization for 10 target keywords", price: 100, original_price: 150 },
],
},
],
};
Save and rename the JS Object to appData. Then drag a Table widget onto the canvas and set its Table data property to {{appData.invoices}}. Selecting the row exposes every column as Table1.selectedRow.* to the rest of the steps — including the nested items array.
Add the PolyDoc REST API datasource
Navigate to Datasources → + New datasource and choose Authenticated API. Configure:
- Name:
PolyDoc - URL:
https://api.polydoc.tech - Authentication type: Bearer token
- Bearer token: your PolyDoc API key (paste the raw
api_…string — noBearerprefix; Appsmith adds that to theAuthorizationheader automatically). Grab the key from the PolyDoc Dashboard.
Click Save. Every query built on this datasource will now send Authorization: Bearer api_… automatically — Step 3 just configures the path, headers, and body.
Create the line items and PDF queries
Create a query on the PolyDoc datasource from Step 2, name it generatePdf, and configure:
- Method: POST
- Path:
/pdf/convert - Headers:
X-Sandbox:true
- Body type: JSON
Paste this into the Body tab. Appsmith's mustache bindings expand inline, so text fields stay quoted ("{{...}}") and numeric / array fields are left unquoted:
{
"source": "[template:jlE-whg]",
"templateData": {
"invoice_number": "{{Table1.selectedRow.invoice_number}}",
"invoice_date": "{{Table1.selectedRow.invoice_date}}",
"invoice_due_date": "{{Table1.selectedRow.invoice_due_date}}",
"invoice_subtotal": {{Table1.selectedRow.invoice_subtotal}},
"invoice_tax_rate": {{Table1.selectedRow.invoice_tax_rate}},
"invoice_tax_amount": {{Table1.selectedRow.invoice_tax_amount}},
"invoice_total": {{Table1.selectedRow.invoice_total}},
"customer_name": "{{Table1.selectedRow.customer_name}}",
"customer_email": "{{Table1.selectedRow.customer_email}}",
"customer_street": "{{Table1.selectedRow.customer_street}}",
"customer_city": "{{Table1.selectedRow.customer_city}}",
"customer_country": "{{Table1.selectedRow.customer_country}}",
"items": {{Table1.selectedRow.items}}
}
}
Create a JS Object with the download function
Back in the JS tab, click + New JS Object and name it invoiceActions (a second JS Object alongside the appData created in Step 1). The function below runs the generatePdf query from Step 3, wraps the binary response in a Blob, and triggers a browser download:
export default {
async downloadPdf() {
// Call PolyDoc - response.data is the binary PDF (Step 3)
await generatePdf.run();
// Wrap the binary response in a Blob and trigger the save dialog
const blob = new Blob([generatePdf.data], { type: "application/pdf" });
const objectUrl = URL.createObjectURL(blob);
download(
objectUrl,
"invoice-" + Table1.selectedRow.invoice_number + ".pdf"
);
URL.revokeObjectURL(objectUrl);
},
};
Add a button and test
Drag a Button widget next to the table from Step 1 and set its label to Download PDF. In the onClick event, choose Execute a JS function, then pick invoiceActions.downloadPdf — the function authored in Step 4.
Select the seeded INV-2026-001 row and click the button. The browser saves a file named invoice-INV-2026-001.pdf. If nothing happens, open the Run history panel — it shows the raw PolyDoc response and any error envelope.
Use Case 2: Email the invoice PDF to the customer
Add a second Send Invoice button that emails the PDF as an attachment, using Appsmith's built-in SMTP datasource — no S3 bucket or third-party email API needed.Button (onClick) → JS Object → REST query (PolyDoc) → SMTP query
Add an SMTP datasource
Go to Datasources → + New datasource and choose SMTP. Configure with your email provider's relay settings:
- Name:
SendMail - Host: your SMTP server (e.g.
smtp.gmail.com,smtp.sendgrid.net) - Port:
587(STARTTLS) or465(implicit TLS) - Username: the SMTP login (often your sender address)
- Password: an app password for Gmail / Outlook / iCloud — the normal account password is rejected once 2FA is on. For transactional services (SendGrid, Mailgun, Postmark) this is the API credential they issue.
Click Test to verify the connection, then Save. A failed test usually means port/TLS mismatch (try the other of 587 / 465) or that 2FA blocks the plain account password.
Create the send email query
Create a query on the SendMail datasource from Step 1, name it sendInvoiceEmail, and fill in:
- From email: your sender address (must match what the SMTP provider authorises for the account in Step 1)
- To email:
{{Table1.selectedRow.customer_email}} - Subject:
Invoice {{Table1.selectedRow.invoice_number}} - Body type: Text, content e.g. Hi, please find your invoice attached.
- Attachments:
- File name:
invoice-{{Table1.selectedRow.invoice_number}}.pdf - Data:
{{generatePdf.data}} - Type:
application/pdf
- File name:
Add a sendInvoice function to the JS Object
Add a sendInvoice function to the invoiceActions JS Object created in Use Case 1, Step 4. It chains the three queries — line items, PDF, email — and shows an alert when SMTP confirms delivery:
export default {
// ... existing downloadPdf function ...
async sendInvoice() {
// Generate the PDF (binary response on generatePdf.data)
await generatePdf.run();
// Email it as a PDF attachment
await sendInvoiceEmail.run();
showAlert(
"Invoice sent to " + Table1.selectedRow.customer_email,
"success"
);
},
};
Add a button and test
Repeat the pattern from Use Case 1, Step 5: drag a Button widget alongside the existing Download PDF button, label it Send Invoice, and wire its onClick event to invoiceActions.sendInvoice — the function added in Step 3.
Select the seeded invoice row, click Send Invoice, and confirm the customer's inbox receives the email with the PDF attached. The Run history panel shows each query's response in order, which is the fastest way to spot whether the SMTP step or the PolyDoc call failed.