Activepieces
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 Activepieces account - self-hosted (free, MIT-licensed) or Activepieces Cloud (free tier available).
- A Google account with access to Google Sheets and Gmail (for Use Case 1).
Use Case 1: Generate invoice PDF from a Google Sheet
Generate an invoice PDF for every new row added to a Google Sheet and email it to the recipient.Google Sheets (New Row Added) → Google Sheets (Find Rows) → Code → HTTP (PolyDoc) → Gmail (Send Email)
Set up your data
Create two Google Sheets tabs: invoices and invoice_items. Examples below - import via File → Import (CSV/XLSX).
| 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 |
Add the Google Sheets trigger
In Activepieces, click + New flow and give it a name. Click the trigger node, search for Google Sheets → New Row Added, connect your Google account, and pick the spreadsheet plus the invoices sheet you created in Step 1. Use the trigger's Test button to pull one sample row - this populates the step output so later steps can bind to fields like {{ trigger.invoice_number }}.
Find matching line items
Add a Google Sheets → Find Rows step after the trigger. Point it at the invoice_items sheet from the same workbook and filter for line items whose invoice_number matches the row from Step 2:
- Column Name:
invoice_number - Search Value:
{{ trigger.values.A }}- the trigger emits cells keyed by column letter, so column A isinvoice_number. Pick from the step data panel rather than typing so Activepieces serialises it as a binding, not a literal string. - Use Column Names: Yes - returns each row keyed by header name (
quantity,name, ...) instead of column letters. The Code step in Step 4 relies on this.
The step output exposes the matching rows on its rows property (referenced as {{ step_1.rows }} in the next step), with each row carrying its data on values.
Build the PolyDoc payload with a Code step
Add a Code step. It builds the full PolyDoc payload - the header fields from the trigger row plus the line items from the Find Rows step - so the HTTP step that follows just ships the result. Declare two inputs and map them from the previous steps:
header→{{ trigger }}(the trigger row from Step 2)rows→{{ step_1 }}(the Find Rows output from Step 3 -step_1is its auto-assigned alias)
Paste this script into the Code editor:
export const code = async (inputs: {
header: { row: number; values: Record<string, string> };
rows: Array<{ row: number; values: Record<string, string> }>;
}) => {
// Trigger cells arrive keyed by column letter (A, B, C, ...).
// Find Rows with "Use Column Names = Yes" keys items by header name.
const h = inputs.header.values;
const items = inputs.rows.map((row) => {
const v = row.values;
const item: Record<string, unknown> = {
quantity: Number(v.quantity),
name: String(v.name ?? ""),
description: String(v.description ?? ""),
price: Number(v.price),
};
if (v.original_price != null && v.original_price !== "") {
item.original_price = Number(v.original_price);
}
return item;
});
return {
source: "[template:YOUR_TEMPLATE_ID]",
templateData: {
invoice_number: h.A,
invoice_date: h.B,
invoice_due_date: h.C,
invoice_subtotal: Number(h.D),
invoice_tax_rate: Number(h.E),
invoice_tax_amount: Number(h.F),
invoice_total: Number(h.G),
customer_name: h.H,
customer_email: h.I,
customer_street: h.J,
customer_city: h.K,
customer_country: h.L,
items,
},
};
};
Replace YOUR_TEMPLATE_ID with your template short ID (e.g. a1b-c2d from the Dashboard Templates page - see Templates).
Call the PolyDoc API
Add an HTTP → Send HTTP request step after the Code step. The body is just the Code step's return value, passed through unchanged:
- Method: POST
- URL:
https://api.polydoc.tech/pdf/convert - Authentication: Bearer Token
- Token:
YOUR_API_KEY(get your key from the PolyDoc Dashboard) - Headers:
X-Sandbox:true
- Body Type: Raw
- Body:
{{ step_2 }}(Activepieces assigns step aliasesstep_1,step_2, ... in creation order, so the Code step from Step 4 isstep_2) - Response is Binary: Yes - PolyDoc responds with the rendered PDF bytes
Send the PDF by email
Add a Gmail → Send Email step. The PDF returned by the HTTP step in Step 5 is exposed as a file binding the attachment field accepts directly:
- Receiver Email (To):
{{ trigger.values.I }}(column I in theinvoicessheet iscustomer_email) - Subject:
Invoice {{ trigger.values.A }} - Body: Please find your invoice attached.
- Attachments → File:
{{ step_3.body }}- the HTTP step from Step 5 isstep_3, and with Response is Binary on, itsbodyproperty is the raw PDF bytes Gmail accepts as an attachment.
Publish and test
Click Test flow to run through Steps 2-6 with the sample row, open the test inbox to confirm the email arrived with a watermarked PDF, then click Publish in the top-right corner. The flow activates immediately and triggers on every new row added to the invoices sheet.
Use Case 2: Generate PDF from a webhook
Generate a PDF whenever an external service POSTs JSON to an Activepieces webhook - a form submission, a payment event, or any custom trigger.Webhook trigger → HTTP (PolyDoc) → Gmail (Send Email)
Create the Webhook trigger
Create a new flow and choose Webhook → Catch Webhook as the trigger. Activepieces shows a unique catch URL - copy it into your external service (e.g. as a Tally form webhook or a Typeform integration URL), submit one test entry, then click Test Trigger. The trigger wraps the received request as { method, headers, body, queryParams, rawBody }, so payload fields are reached via trigger.body.* (e.g. {{ trigger.body.invoice_number }}).
Call the PolyDoc API
Add an HTTP → Send HTTP request step with the same URL, auth, headers and Response is Binary: Yes settings as Use Case 1 Step 5. The difference is the body: there is no upstream Code step here, so the body is built directly as Body Type: JSON, binding each field to the webhook payload from Step 1:
{
"source": "[template:YOUR_TEMPLATE_ID]",
"templateData": {
"invoice_number": "{{trigger.body.invoice_number}}",
"invoice_date": "{{trigger.body.invoice_date}}",
"invoice_due_date": "{{trigger.body.invoice_due_date}}",
"invoice_subtotal": {{trigger.body.invoice_subtotal}},
"invoice_tax_rate": {{trigger.body.invoice_tax_rate}},
"invoice_tax_amount": {{trigger.body.invoice_tax_amount}},
"invoice_total": {{trigger.body.invoice_total}},
"customer_name": "{{trigger.body.customer_name}}",
"customer_email": "{{trigger.body.customer_email}}",
"customer_street": "{{trigger.body.customer_street}}",
"customer_city": "{{trigger.body.customer_city}}",
"customer_country": "{{trigger.body.customer_country}}",
"items": {{trigger.body.items}}
}
}
Send the PDF by email and publish
Add a Gmail → Send Email step that mirrors Use Case 1 Step 6. The HTTP step from Step 2 is step_1 in this flow, so the binary PDF lives at {{ step_1.body }} on the Attachments → File field. Bind To, Subject, and Body to the webhook payload ({{ trigger.body.customer_email }}, Invoice {{ trigger.body.invoice_number }}, etc.), then click Publish.