Skip to main content

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_numberinvoice_dateinvoice_due_dateinvoice_subtotalinvoice_tax_rateinvoice_tax_amountinvoice_totalcustomer_namecustomer_emailcustomer_streetcustomer_citycustomer_country
INV-2026-0012026-01-152026-02-1412000.11201320John Doejohn.doe@example.com456 Customer AvenueBeautiful CityUK
invoice_numberquantitynamedescriptionpriceoriginal_price
INV-2026-0011Web Design ServicesCustom website design and development for company homepage800
INV-2026-0011Logo DesignProfessional logo design with 3 revision rounds300
INV-2026-0011SEO OptimizationSearch engine optimization for 10 target keywords100150

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 }}.

cloud.activepieces.com

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 is invoice_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.

cloud.activepieces.com

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_1 is 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).

cloud.activepieces.com

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:
  • Body Type: Raw
  • Body: {{ step_2 }} (Activepieces assigns step aliases step_1, step_2, ... in creation order, so the Code step from Step 4 is step_2)
  • Response is Binary: Yes - PolyDoc responds with the rendered PDF bytes
cloud.activepieces.com

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 the invoices sheet is customer_email)
  • Subject: Invoice {{ trigger.values.A }}
  • Body: Please find your invoice attached.
  • Attachments → File: {{ step_3.body }} - the HTTP step from Step 5 is step_3, and with Response is Binary on, its body property is the raw PDF bytes Gmail accepts as an attachment.
cloud.activepieces.com

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 }}).

cloud.activepieces.com

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}}
}
}
cloud.activepieces.com

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.

cloud.activepieces.com