NocoDB
Prerequisites
- A PolyDoc account and API key - sign up for free, no credit card required. The free plan includes 150 PDF conversions per month.
- A cloud storage bucket you control (S3, GCS, Azure Blob, R2, Wasabi, …). A NocoDB webhook is fire-and-forget - it can't read PolyDoc's response back into the table - so PolyDoc uploads the PDF to a presigned URL of yours and the object lives at that URL. See the cloud storage guide for per-provider presigning recipes.
- A NocoDB base - NocoDB Cloud (free plan works) or self-hosted. The custom webhook payload, the Button field, and the Button's Run Webhook action are all available on the Free plan.
Use Case 1: Save the PDF to your bucket on record creation
Generate an invoice PDF whenever a new record is created in the invoices table, and upload it straight to your own cloud bucket.Record created → Webhook (custom payload) → PolyDoc → your bucket
Set up your table
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 |
In NocoDB you keep everything on one invoices table. Drop the separate line-items table and instead add three columns to invoices:
items_json(Long text): the line items as a JSON array string, e.g.[{"quantity":1,"name":"Web Design","description":"…","price":800}]. The script that creates the invoice row writes this.presigned_url(URL): a presigned PUT URL for your bucket, one per invoice row, populated by the same script.pdf_url(URL): optional. The webhook can't write back, but the finished PDF always lives at your presigned URL with the query string stripped, so you already know its address - store it here if you want it in the grid.
Create the webhook and trigger
Open the invoices table, switch to the Details tab, pick Webhooks, and click New Webhook. Configure:
- Trigger:
Record/ After insert. - Action:
HTTP Webhook, method POST, URLhttps://api.polydoc.tech/pdf/convert. - Headers tab - add three rows:
Authorization:Bearer YOUR_API_KEY(get your key from the PolyDoc Dashboard).Content-Type:application/jsonX-Sandbox:true
Build the custom payload
Open the Body tab and paste the custom payload below. NocoDB renders it with Handlebars on every trigger, substituting the new row's fields:
{
"source": "[template:YOUR_TEMPLATE_ID]",
"templateData": {
"invoice_number": {{ json event.data.rows.[0].invoice_number }},
"invoice_date": {{ json event.data.rows.[0].invoice_date }},
"invoice_due_date": {{ json event.data.rows.[0].invoice_due_date }},
"invoice_subtotal": {{ json event.data.rows.[0].invoice_subtotal }},
"invoice_tax_rate": {{ json event.data.rows.[0].invoice_tax_rate }},
"invoice_tax_amount": {{ json event.data.rows.[0].invoice_tax_amount }},
"invoice_total": {{ json event.data.rows.[0].invoice_total }},
"customer_name": {{ json event.data.rows.[0].customer_name }},
"customer_email": {{ json event.data.rows.[0].customer_email }},
"customer_street": {{ json event.data.rows.[0].customer_street }},
"customer_city": {{ json event.data.rows.[0].customer_city }},
"customer_country": {{ json event.data.rows.[0].customer_country }},
"items": {{{ event.data.rows.[0].items_json }}}
},
"cloudStorage": {
"presignedUrl": {{ json event.data.rows.[0].presigned_url }}
}
}
A few NocoDB-specific points:
- Text and date fields (
invoice_number, thecustomer_*fields,invoice_date,invoice_due_date): wrap each in the{{ json … }}helper. It adds the quotes and escaping for you, so don't put your own quotes around it. NocoDB date columns serialise as ISO strings (YYYY-MM-DD). - Number fields (
invoice_subtotal,invoice_tax_rate,invoice_tax_amount,invoice_total): the same{{ json … }}helper emits a real JSON number (not a quoted string), so the API gets1320, not"1320". - The items array (
items): use triple braces -{{{ event.data.rows.[0].items_json }}}. Triple braces inject the value raw, so the JSON array string initems_jsonlands as a real array. Double braces or thejsonhelper would wrap it in quotes and the API would reject it.
Save and generate
Click Save changes. Now create a test record in invoices with a fresh presigned_url and a populated items_json. Within a few seconds PolyDoc PUTs the finished PDF to your bucket - open the object URL (your presigned URL without its query string) to confirm.
Use Case 2: Generate the PDF on demand from a button
Add a Generate PDF button to each row so you can produce (or regenerate) an invoice PDF on demand, instead of only on creation.Click button → Webhook (Button trigger) → PolyDoc → your bucket
Create a Button-trigger webhook
Create a new webhook on the invoices table exactly like Use Case 1, Steps 2 and 3 - same POST to https://api.polydoc.tech/pdf/convert, same three headers, and the same custom Body - but set the Trigger to Button instead of Record / After insert. Give it a recognisable title such as PolyDoc: generate on click.
Add the Button field
Add a new field to invoices, set its type to Button, then:
- Label: e.g. Generate PDF.
- On click:
Run Webhook. - Webhook: select the Button-trigger webhook from Step 1.
Click to generate
Click the Generate PDF button on any row whose presigned_url and items_json are filled. NocoDB fires the webhook and PolyDoc uploads the PDF to your bucket, same as Use Case 1. Re-clicking regenerates it (against a fresh presigned URL).