HTML/CSS Templates
DocSpring supports four kinds of templates:
- PDF templates: Upload an existing PDF and add fields using the template editor.
- HTML/CSS templates: Create a new template from scratch using HTML and CSS.
- Word templates: Upload a Word document with tags in it.
- Page designs: Build a new PDF in the Page Designer, with no document to start from.
Our HTML/CSS templates use a CSS extension called SASS. We use the version that is compatible with standard CSS (SCSS).
This means that you can paste in any CSS, and it will Just Work™. SCSS also supports some powerful features, including variables and nested rules. Take a look at the SASS Guide to learn more.
Liquid works anywhere in your SCSS. It runs before the SCSS compiles, so a field’s value can set a variable or a property, and a condition can add rules:
$brand: {{ brand_color | default: "#1a56db" }};
.title { color: $brand; }
{% if paid %}.stamp { color: darken($brand, 10%); }{% endif %}Values are inserted as text: a value with quotes, semicolons or braces in it stays inside the string or property it’s in.
The template editor’s preview fills each field with sample text, which an SCSS function such as darken() can’t use as
a color. Give a field that a function uses a default value, and the preview uses that instead.
Liquid
Section titled “Liquid”HTML templates use the Liquid markup language. This allows you to insert field values into your HTML. You can also add advanced logic, such as conditions and loops.
In the template editor, your Liquid tags are highlighted, and typing {{ or {% suggests your fields’ names. Click a
{{ }} tag to change what it prints. Problems with your Liquid, such as an {% if %} that’s never closed, are listed
above the code with their line numbers: click one to go to its line.
Referencing Fields
Section titled “Referencing Fields”Use the {{ }} syntax to insert a field value in your template.
If you have a field called “name”, you can render that value in your template like this:
<h2>{{ name }}</h2>Use a dot to access nested fields, and square brackets for arrays:
<h2>{{ person.name }}</h2><h2>{{ names[2] }}</h2>HTML Escaping
Section titled “HTML Escaping”The template’s Escape HTML in Data setting decides how values are printed. It’s on for new templates. Templates
created before October 2026, or with API v1, have it off. Change it in the template’s settings, or with
html_escape_data when you update the template.
When it’s on, {{ value }} prints a value as text: a < or & in it shows as that character, and can’t add HTML to
the page. Use the raw filter for a value that holds HTML you trust:
<p>{{ customer_notes }}</p><div>{{ terms_html | raw }}</div>Put raw last: a filter after it escapes what it adds. The escape and newline_to_br filters work as before and
never escape a value twice, so {{ notes | escape | newline_to_br }} prints the same with the setting on or off.
When it’s off, every value is printed as HTML, as it is. Use {{ value | escape }} for text that could contain < or
&. Before you turn the setting on for a template, add | raw to the values that hold HTML.
Liquid Filters
Section titled “Liquid Filters”When you upload an existing PDF, our template editor includes features such as date formatting, formulas for numbers, “uppercase” for text fields, etc. These features have been removed from our HTML template editor, because these can be achieved by using Liquid “filters”.
For example, you can use the upcase filter to convert text to uppercase:
<h2>{{ full_name | upcase }}</h2>See the Liquid Filters page for more information about available filters.
Please contact us if you would like us to add a new Liquid filter.
Signatures
Section titled “Signatures”You must use the {% signature %} Liquid tag to render signatures in your HTML template.
Signatures can be provided as either an image, or text (e.g. the user typed their name and selected a font). The signature tag will handle both of these cases:
{% signature signature_field %}Behind the scenes, image signatures will be rendered in an <img> element with a signature-image class:
<img class="signature-image" src="data:image/png;base64,..." />Text signatures will import the required typeface, and render the name inside a <span> element with a signature-text class:
<link href="https://fonts.googleapis.com/css?family=Dancing+Script" rel="stylesheet"/>...<span class="signature-text" style="color: black; font-size: 16px; font-weight: normal; display: inline-block; font-family: 'Dancing Script';"> John Smith</span>You can pass the following options to the signature tag, to control the width and height of image signatures, or the font size and weight of text signatures. (These options are added as inline CSS styles.)
| Option | Signature Type | Default Value |
|---|---|---|
width | Image | (from image) |
height | Image | (from image) |
font-size | Text | 16px |
font-weight | Text | normal |
color | Text | black |
There is also a preview_type option, which allows you to control the type of signature that is shown in preview PDFs. We show an image signature by default. Use preview_type: text to show a text signature.
Examples:
{% signature signature_field, width: 300px %}{% signature signature_field, preview_type: text, font-size: 24px, color: #333%}Initials
Section titled “Initials”You must use the {% initials %} Liquid tag to render initials in your HTML template.
Initials behave exactly like signatures — they can be provided as an image or as text, and the initials tag handles both cases:
{% initials initials_field %}Image initials are rendered in an <img> element with an initials-image class:
<img class="initials-image" src="data:image/png;base64,..." />Text initials will import the required typeface and render the initials inside a <span> element with an initials-text class:
<link href="https://fonts.googleapis.com/css?family=Dancing+Script" rel="stylesheet"/>...<span class="initials-text" style="color: black; font-size: 16px; font-weight: normal; display: inline-block; font-family: 'Dancing Script';"> JS</span>The initials tag accepts the same options as the signature tag:
| Option | Initials Type | Default Value |
|---|---|---|
width | Image | (from image) |
height | Image | (from image) |
font-size | Text | 16px |
font-weight | Text | normal |
color | Text | black |
Use the preview_type option to control how initials appear in preview PDFs. Preview image initials are shown by default. Use preview_type: text to display text initials instead.
Examples:
{% initials initials_field, width: 100px %}{% initials initials_field, preview_type: text, font-size: 18px, color: #333 %}Images
Section titled “Images”You can upload static images under the “Images” tab. Use the template_image_url tag to get
the image URL:
<img src="{% template_image_url IMAGE_ID %}" />To include an image or barcode field (i.e. submitted in an API request),
you can use the field value as the src attribute for an img tag.
This will refer to an internal URL for the image:
<img src="{{ photograph }}" />If you have an optional image field, you should use a condition in case the image was not provided:
{% if photograph %} <img src="{{ photograph }}" /> {% endif %}You can use the date filter to format a date field:
<h2>{{ date | date: "%B %-d, %Y" }}</h2>You can use the special "now" string to get the current date and time:
<h2>{{ "now" | date: "%B %-d, %Y" }}</h2>We also support an in_time_zone filter that allows you to convert dates and times into different timezones:
<h2>{{ "now" | in_time_zone: "America/New_York" | date: "%B %-d, %Y" }}</h2>Here is a list of timezones that you can use with the in_time_zone filter.
Metadata
Section titled “Metadata”You can use the metadata tag to access metadata from your submission:
<h2>{% metadata foo %}</h2>Template Details
Section titled “Template Details”You can use the template tag to access data about your template:
<h2>{% template id %}</h2><h2>{% template name %}</h2><h2>{% template version %}</h2><h2>{% template version_published_at %}</h2>Fields
Section titled “Fields”When you save your code in the template editor, DocSpring adds a field for each new value its tags use, in the HTML,
the header and footer HTML, and the SCSS. Fields stay when you delete their tags, so delete a field you no longer need
in the field list. The editor only removes a field it added while you were still typing, such as discount_ra on the
way to discount_rate, if you haven’t changed the field.
{{ customer.name }}adds acustomerobject with anamefield.{% for item in line_items %}adds aline_itemslist, and{{ item.price }}inside the loop adds apricefield to each item.
Each new field’s type comes from how the tags use it: a value formatted with the date filter becomes a date field,
and one used with currency or times a number field. Other values are text fields. New fields are required. Click a
field to change its type, make it optional or set a default value. Optional fields that aren’t in the submission data
are blank, so you can check them with {% if %}.
Renaming a field in the editor renames its tags in your code too, and Undo takes back both.
Page Settings
Section titled “Page Settings”Click the “Settings” button in the header to set up the pages of an HTML/CSS template:
- Page Format: Letter (the default), Legal, Tabloid, Ledger, CR80, A0, A1, A2, A3, A4, A5, A6, or Custom. Check Landscape to use a landscape page (not available for Custom).
- Page Size: The width and height of a Custom page, each from
0.05into200in(defaults to8.5inby11in). - Page Margins: The top, bottom, left, and right margins (each defaults to
1in). Set the margins to 0 to remove the white border around the page. The left and right margins together must be less than the page’s width, and the top and bottom margins less than its height. - Print Background: Renders backgrounds that your CSS sets (unchecked by default). Backgrounds aren’t drawn over the page margins.
Sizes and margins can use px (pixels), in (inches), cm (centimeters), or mm (millimeters). Give each one a
unit, such as 1in or 20mm: only 0 can go without one.
Page Breaks
Section titled “Page Breaks”You can force content to start on a new page by using the page-break-before CSS property.
.new-page { page-break-before: always;}<p class="new-page">This paragraph will start on a new page.</p>View documentation for the page-break-before, page-break-after, and page-break-inside properties.
Headers and Footers
Section titled “Headers and Footers”You can add a header or footer to each page in the “Header HTML” and “Footer HTML” tabs. CSS from the SCSS tab also applies to headers and footers.
You can use the pageNumber and totalPages classes to show the current page number and total pages. The following example shows how to add a footer with page numbers
(e.g. Page 1 of 2):
<footer id="footer" style="display: block; margin: 0 40px; padding-top: 3px; width: calc(100% - 80px); border-top: 1px solid #aaa; color: #aaa; text-align: right;"> <div> Page <span class="pageNumber"></span> of <span class="totalPages" ></span> </div></footer>Headers and footers can’t load anything from the network. A header or footer with a stylesheet (a <link> tag or an
@import), a font from a URL, or a typed signature (which loads a Google Font) fails with “The header or footer
couldn’t be printed”. Put their CSS in a <style> tag, use a font that’s installed or embedded as a data: URL, and
keep signatures in the body.
Limits
Section titled “Limits”- Generated PDF: up to 500 MB. Photos embedded at full camera resolution can go over this. Resize images to the size they are displayed at.
- Template HTML: up to 50 MB for the body HTML, and 5 MB each for the header HTML, the footer HTML and the SCSS. The other limits are in Size limits.
- Rendered HTML: up to 128 MB in total for the body, header and footer HTML once your data is filled in, including any images embedded in it as base64.
- Header and footer images: up to 25 MB each. They are embedded into the header or footer HTML.
- Image field values given as a URL: up to 100 MB each.
- Liquid and SCSS: 5 seconds in total to fill in a document’s Liquid and compile its SCSS, and 128 MB of memory for each of its HTML parts and its SCSS, on top of the size of your data. A loop that repeats far more often than it needs to is the usual cause of a template that goes over.
A document over one of these limits fails with an error that names the limit, and isn’t retried. A very large document can also take too long to render, and often does before its PDF reaches 500 MB. A render that runs out of time is retried a few times before it fails.
Previewing with Data
Section titled “Previewing with Data”A preview returns PNG images of a template’s pages printed with the data you send, not a PDF, so you can check how the template prints with your data before you publish it or send live data. DocSpring renders the template’s draft (or a published version) with the data and returns an image of each page. A preview makes no submission and uses no credits, and its pages are watermarked like a test PDF’s. Previews work for PDF templates too. The MCP server’s preview_template tool does the same for AI agents.
Generating PDFs
Section titled “Generating PDFs”Once you’ve set up your template and added your fields, you can generate a PDF by either filling out the web form, or making an API request. Create an HTML Template with the API walks through every step with curl.