This service enables you to generate custom PDFs from Google Docs templates by replacing placeholders, appending table rows, adding images, and sending webhooks upon completion.
Create a Google Document with {{placeholders}}.
POST a JSON payload containing the Google document_id and your key value pairs to /convert_jobs
To fetch the generated PDF, either poll the returned convert_job url
until document_url is available or use a webhook to receive a notification where to fetch the PDF.
Authentication either via API key in the URL or via oauth2 Bearer token.
Supported: Normal substitutions, tables, images and even images in tables! Let me know if you need more. sven+feature@template2pdf.com
Write some text, create a table and look where you would like to have an image.
Use placeholders like {{quote_id}}
or {{customer_name}}
in your document.
For tables, use placeholders like {{row_item_title}}
or {{row_item_quantity}}
inside the table row you want to replicate.
For images, use placeholders like {{image_sales}}
or {{image_logo}}
where you want to insert the image.
Once done, you can auto extract a basic json structure to use in your calls.
Authentication is required to access the Template2PDF API. You can authenticate using either an API key or an OAuth2 Bearer token.
Authorization Bearer YOUR_OAUTH2_ACCESS_TOKEN
https://template2pdf.com/oauth/authorize
https://template2pdf.com/oauth/token
curl -X POST https://<your-api-key>@template2pdf.com/convert_jobs.json \
-H "Content-Type: application/json" \
-d '{
"document_id": "1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek",
"substitutions": {
"{{quote_id}}": "2342",
"{{prepared_date}}": "2025-10-01",
"{{exp_date}}": "2025-12-01",
"{{customer_name}}": "Sven Tantau",
"{{address_line_0}}": "123 Main St",
"{{address_line_1}}": "Anytown, USA",
"{{total}}": "$640,-",
"{{sales_name}}": "John Doe",
"{{sales_email}}": "test@test.com"
},
"table_data": [
[
{
"{{row_item_title}}": "Software",
"{{row_item_quantity}}": "1",
"{{row_item_price}}": "$200,-",
"{{row_item_total}}": "$200,-"
},
{
"{{row_item_title}}": "Support Hours",
"{{row_item_quantity}}": "4",
"{{row_item_price}}": "$80,-",
"{{row_item_total}}": "$320,-"
},
{
"{{row_item_title}}": "Nice Box",
"{{row_item_quantity}}": "1",
"{{row_item_price}}": "$120,-",
"{{row_item_total}}": "$120,-"
}
]
],
"images": {
"{{image_sales}}": {
"url": "https://beastiebytes.com/images/me.png",
"width": 50,
"height": 50
}
}
}'
import requests
import requests
from requests.auth import HTTPBasicAuth
url = "https://template2pdf.com/convert_jobs.json"
headers = {
"Content-Type": "application/json"
}
data = {
"document_id": "1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek",
"substitutions": {
"{{quote_id}}": "2342",
"{{prepared_date}}": "2025-10-01",
"{{exp_date}}": "2025-12-01",
"{{customer_name}}": "Sven Tantau",
"{{address_line_0}}": "123 Main St",
"{{address_line_1}}": "Anytown, USA",
"{{total}}": "$640,-",
"{{sales_name}}": "John Doe",
"{{sales_email}}": "test@test.com"
},
"table_data": [
[
{
"{{row_item_title}}": "Software",
"{{row_item_quantity}}": "1",
"{{row_item_price}}": "$200,-",
"{{row_item_total}}": "$200,-"
},
{
"{{row_item_title}}": "Support Hours",
"{{row_item_quantity}}": "4",
"{{row_item_price}}": "$80,-",
"{{row_item_total}}": "$320,-"
},
{
"{{row_item_title}}": "Nice Box",
"{{row_item_quantity}}": "1",
"{{row_item_price}}": "$120,-",
"{{row_item_total}}": "$120,-"
}
]
],
"images": {
"{{image_sales}}": {
"url": "https://beastiebytes.com/images/me.png",
"width": 50,
"height": 50
}
}
}
response = requests.post(url, headers=headers, json=data, auth=HTTPBasicAuth('<your-api-key>', ''))
print(response.json())
<?php
$url = "https://<your-api-key>@template2pdf.com/convert_jobs.json";
$data = [
"document_id" => "1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek",
"substitutions" => [
"{{quote_id}}" => "2342",
"{{prepared_date}}" => "2025-10-01",
"{{exp_date}}" => "2025-12-01",
"{{customer_name}}" => "Sven Tantau",
"{{address_line_0}}" => "123 Main St",
"{{address_line_1}}" => "Anytown, USA",
"{{total}}" => "$640,-",
"{{sales_name}}" => "John Doe",
"{{sales_email}}" => "test@test.com"
],
"table_data" => [
[
[
"{{row_item_title}}" => "Software",
"{{row_item_quantity}}" => "1",
"{{row_item_price}}" => "$200,-",
"{{row_item_total}}" => "$200,-"
],
[
"{{row_item_title}}" => "Support Hours",
"{{row_item_quantity}}" => "4",
"{{row_item_price}}" => "$80,-",
"{{row_item_total}}" => "$320,-"
],
[
"{{row_item_title}}" => "Nice Box",
"{{row_item_quantity}}" => "1",
"{{row_item_price}}" => "$120,-",
"{{row_item_total}}" => "$120,-"
]
]
],
"images" => [
"{{image_sales}}" => [
"url" => "https://beastiebytes.com/images/me.png",
"width" => 50,
"height" => 50
]
]
];
$options = [
'http' => [
'header' => "Content-Type: application/json\r\n",
'method' => 'POST',
'content' => json_encode($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) {
die('Error');
}
var_dump($result);
?>
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse("https://template2pdf.com/convert_jobs.json")
headers = {
"Accept" => "application/json",
"Content-Type" => "application/json"
}
data = {
"document_id" => "1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek",
"substitutions" => {
"{{quote_id}}" => "2342",
"{{prepared_date}}" => "2025-10-01",
"{{exp_date}}" => "2025-12-01",
"{{customer_name}}" => "Sven Tantau",
"{{address_line_0}}" => "123 Main St",
"{{address_line_1}}" => "Anytown, USA",
"{{total}}" => "$640,-",
"{{sales_name}}" => "John Doe",
"{{sales_email}}" => "test@test.com"
},
"table_data" => [
[
{
"{{row_item_title}}" => "Software",
"{{row_item_quantity}}" => "1",
"{{row_item_price}}" => "$200,-",
"{{row_item_total}}" => "$200,-"
},
{
"{{row_item_title}}" => "Support Hours",
"{{row_item_quantity}}" => "4",
"{{row_item_price}}" => "$80,-",
"{{row_item_total}}" => "$320,-"
},
{
"{{row_item_title}}" => "Nice Box",
"{{row_item_quantity}}" => "1",
"{{row_item_price}}" => "$120,-",
"{{row_item_total}}" => "$120,-"
}
]
],
"images" => {
"{{image_sales}}" => {
"url" => "https://beastiebytes.com/images/me.png",
"width" => 50,
"height" => 50
}
}
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == "https") # Enable HTTPS
request = Net::HTTP::Post.new(uri.request_uri, headers)
request.basic_auth('<your-api-key>', '') # Pass the key as the username, and leave the password empty
request.body = data.to_json
response = http.request(request)
puts response.body
{
"id": 10438,
"payload": "<original payload>",
"status": "created",
"document_url": null,
"page_count": null,
"error_message": null,
"created_at": "2024-12-19T04:08:02.362Z",
"updated_at": "2024-12-19T04:08:13.684Z",
"url": "https://template2pdf.com/convert_jobs/10438.json"
}
After creating a convert job, you can retrieve the generated PDF using one of the following methods:
GET
request to the url
provided in the initial response.status
key in the JSON response.status
is created
or processing
, the job is still in progress.status
is completed
, the job is done, and the PDF is ready.status
is failed
, an error occurred during the conversion process.status
is completed
, look for the document_url
key in the JSON.document_url
contains the direct link to download the generated PDF.webhook
object in the request payload with the following fields:url
: The endpoint to notify.method
: The HTTP method for the webhook (e.g., POST
).headers
: Optional headers for the request.{
"document_id": "1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek",
....
"webhook": {
"url": "https://example.com/webhook",
"method": "POST",
"headers": {
"Content-Type": "application/json"
}
}
}
document_url
to the generated PDF.Method | Description | Pros | Cons |
---|---|---|---|
Polling | Fetch the job data periodically using the job URL. | Simple to implement. | Requires periodic requests. |
Webhook | Receive automatic notifications when the job is done. | Real-time updates, no polling needed. | Requires webhook setup. |
Choose the method that best suits your integration needs. For real-time updates, use the webhook approach. For simpler setups, polling works effectively.
Here are the details for creating a convert job to generate a PDF from a Google Docs template.
/convert_jobs.json
Creates a new convert job to process a Google Docs template.
application/json
Field | Type | Description |
---|---|---|
document_id |
string |
Google document ID of the template. Required. |
substitutions |
object |
Key-value pairs for placeholder substitutions. |
table_data |
array |
Table row data (optional). Each row is an array of objects. |
images |
object |
Images to add with defined placeholders, URLs, and sizes (optional). |
webhook |
object |
Webhook details for completion notification. |
The document_id
is a unique identifier for your Google Docs template. It can be found in the URL of the document. For example:
https://docs.google.com/document/d/1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek/edit
In this case, the document_id
is:
{
"document_id": "1bhgye-ZITHzIL0RzyFiUITNR20R1z_1q6FR91rdy_ek",
...
To ensure your document is accessible by the Template2PDF service:
document_id
from the URL.The substitutions
parameter allows you to replace placeholders in the Google Docs template with dynamic values. Each placeholder in the document must be unique. To make that easier you should consider wrapping all keys with something like double curly braces (e.g., {{placeholder}}
).
{
...
"substitutions": {
"{{quote_id}}": "2342",
"{{prepared_date}}": "2025-10-01",
"{{exp_date}}": "2025-12-01",
"{{customer_name}}": "Sven Tantau",
"{{address_line_0}}": "123 Main St",
"{{address_line_1}}": "Anytown, USA",
"{{total}}": "$640,-",
"{{sales_name}}": "John Doe",
"{{sales_email}}": "test@test.com"},
...
}
The table_data
parameter is an array of arrays, where each inner array defines rows in the table. Each object corresponds to a column in a row, mapping placeholder keys to their respective values.
row_
, template2pdf will be able to parse the document and extract a structure for you.
{
...
"table_data": [
[
{
"{{row_item_title}}": "Software",
"{{row_item_quantity}}": "1",
"{{row_item_price}}": "$200"
}
]
],
...
}
The images
parameter allows you to add images to your generated PDF. Each key represents a placeholder in the template, and the value is an object containing the image's URL and optional size specifications (width and height).
image_
, but if you follow this convention, template2pdf is able to parse the document and extract a structure for you.
{
"images": {
"{{image_sales}}": {
"url": "https://example.com/image.png",
"width": 100,
"height": 100
},
"{{image_logo}}": {
"url": "https://example.com/logo.png",
"width": 50,
"height": 50
}
},
...
}
Field | Type | Description |
---|---|---|
url |
string |
The URL of the image to insert. Required. |
width |
number |
The width of the image in pixels. Defaults to 50 if not specified. |
height |
number |
The height of the image in pixels. Defaults to 50 if not specified. |
{
...
"webhook": {
"url": "https://example.com/webhook",
"method": "POST",
"headers": {
"Content-Type": "application/json"
}
},
...
}
Field | Type | Description |
---|---|---|
url |
string |
The webhook URL to receive completion notifications. Required. |
method |
string |
The HTTP method to use for the webhook. Defaults to POST . |
headers |
object |
Headers to include in the webhook request. Defaults to {"Content-Type": "application/json"} . |
Payload will be the json representation of the completed convert job.
{
"id": 12345,
"document_url": "https://template2pdf.com/example_output.pdf",
"status": "completed",
"error_message": null
}
/convert_jobs.json
List all convert jobs.
/convert_jobs/:id.json
Get a specific convert job by ID.
/convert_jobs/:id.json
Delete a specific convert job by ID.
You can call the API to fetch a basic template json payload for a given document_id
/docs_structure/build.json?id=:id
Fetch a ready to use payload.
/docs_structure/show.json?id=:id
Just the used keys in a simple structure.
Template2PDF uses conventional HTTP response codes to indicate the success or failure of an API request. In general, codes in the 2xx range indicate success, codes in the 4xx range indicate an error due to the provided information (e.g., a required parameter is missing), and codes in the 5xx range indicate an error on the server's end.