Migrating from jsreport
This guide moves your jsreport templates into a Typefold workspace without rewriting them, so your application can render them by name or shortid as before.
Before you start
Section titled “Before you start”- Your templates use the Handlebars engine with the
chrome-pdforxlsxrecipe. See which templates import. - You can open the workspace in Studio (any workspace role can import and publish), or you have an API key with the import scopes if you import from a script.
1. Export from jsreport
Section titled “1. Export from jsreport”In jsreport Studio, export the folders and templates you want to move, together with the
scripts, assets, components and data they use. You get a .jsrexport file. It may be up to
20 MB.
2. Import in Studio
Section titled “2. Import in Studio”- In Studio, open Workspace → Editor. Click + above the explorer and choose Import…, or choose Import into folder… in the … menu of a folder.
- Drop the
.jsrexportfile, or click Choose files and select it. The Import jsreport export dialog lists:- each template it will import, with its format and the folder it lands in;
- notes on what the import changed so the template runs here, such as a removed
afterRenderstep, and on referenced files the export does not contain, with the path to add them at; - templates it cannot import, with the reason.
- Check Import into folder. It defaults to a folder named after the file, such as
invoicesforinvoices.jsrexport. Use/for nested folders, or clear it to import at the workspace root. - Click Import file.
The import publishes each template it writes. If one cannot be published, you see a warning with the reason, and it stays a draft until you fix it and click Publish.
3. Check the imported templates
Section titled “3. Check the imported templates”- Read every note from step 2, and add any missing files at the paths they name.
- If a template signs its PDFs, a workspace owner or administrator uploads the certificate under Manage → Signing certificates with Upload certificate, using the name the note gives.
- Open each imported template in Studio and click Update preview with its sample data.
- Compare the output with jsreport’s. Chrome compatibility lists where the renderer follows Chrome’s print layout, and CSS support what it does not support.
- Publish any template that stayed a draft.
4. Render the imported reports
Section titled “4. Render the imported reports”Your application renders imported templates by name through the Reports API. Recipe names are not needed:
curl $PDF_URL/v1/reports/render \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ --fail-with-body -o invoice.pdf \ -d '{ "format": "pdf", "template": { "name": "Invoice" }, "data": { "number": "INV-1001" } }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] pdf = await client.RenderReportAsync(new RenderReportRequest{ Template = TemplateRef.ByName("Invoice"), Data = new { number = "INV-1001" },});
await File.WriteAllBytesAsync("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient, TemplateRef } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const pdf = await client.renderReport({ template: TemplateRef.byName("Invoice"), data: { number: "INV-1001" },});
await writeFile("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient, TemplateRef } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const pdf = await client.renderReport({ template: TemplateRef.byName("Invoice"), data: { number: "INV-1001" },});
await writeFile("invoice.pdf", pdf);import osfrom pathlib import Path
from typefold import TypefoldClient, RenderReportRequest, TemplateRef
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
pdf = client.render_report( RenderReportRequest( template=TemplateRef.by_name("Invoice"), data={"number": "INV-1001"}, ))
Path("invoice.pdf").write_bytes(pdf)PDF_URL and API_KEY are set as in the quickstart. The
Reports API guide lists the scopes the key needs and the other request
options.
- Shortids. To render by the shortid your application stored in jsreport, send
"template": { "shortid": "rkJTnK2ce" }, or useTemplateRef.ByShortidin .NET,byShortidin TypeScript and JavaScript, orby_shortidin Python. - Listing templates. If your application listed templates with
odata/templates, callGET /v1/reportsinstead (ListReportsAsync,listReportsorlist_reports). It returns each template’sshortidtoo. - Clients. The .NET, TypeScript and JavaScript and Python clients replace jsreport’s own clients.
Once the output matches, switch your application over.
Import with the API
Section titled “Import with the API”Two endpoints do what the Studio dialog does: one inspects the archive, one imports it. Both take
the archive as base64 in JSON, with an optional folderPath to import into, such as
Projects/Invoices. Missing folders are created. Without folderPath, the archive lands at the
workspace root. Every field is in the API reference.
-
Build the request body:
Terminal window # On macOS, use: base64 -i invoices.jsrexportprintf '{"archiveBase64":"%s","folderPath":"Invoices"}' \"$(base64 -w0 invoices.jsrexport)" > import.json -
Inspect the archive. This changes nothing:
Terminal window curl $PDF_URL/v1/reports/import/inspect \-H "x-api-key: $API_KEY" \-H "content-type: application/json" \--data @import.json{"items": [{"name": "Invoice","format": "pdf","assets": 4,"folderPath": "Invoices","changes": ["Script \"format-rows\": its afterRender step was removed; only beforeRender runs.","File \"logo.png\" is not in the export; the template renders once the workspace has it at /Invoices/shared/logo.png."]}],"unsupported": [{"name": "Shipping labels","recipe": "html-to-xlsx","engine": "handlebars"}]}changesholds the notes for each template, andunsupportedthe templates that will be skipped, with areasonwhen the recipe is supported but something the template uses is not. -
Import it:
Terminal window curl $PDF_URL/v1/reports/import \-H "x-api-key: $API_KEY" \-H "content-type: application/json" \--data @import.json{"imported": 1,"updated": 0,"skipped": 0,"unpublished": [{"name": "Invoice","message": "Missing workspace files: Invoices/shared/logo.png (referenced by template.html)."}]}importedcounts new templates,updatedthose that got a new draft, andskippedthose left as they were (see import again).unpublishedlists the templates that stayed drafts, with why. Fix what each message names and publish them, then carry on with step 3.
The import writes every template or none. One template that cannot be published does not stop the others.
Scopes
Section titled “Scopes”| Action | Endpoint | Scopes |
|---|---|---|
| Inspect an archive | POST /v1/reports/import/inspect |
template:read |
| Import an archive | POST /v1/reports/import |
template:write; asset:manage to upload fonts, images and workbooks; template:publish to publish the imported templates |
Without asset:manage, the import leaves out fonts, images and workbooks, and templates that use
them report the files as missing. Without template:publish, every template stays a draft and is
listed in unpublished. See API keys and scopes.
Import again
Section titled “Import again”You can import the same or an updated export again. IDs stay stable, so applications that render by name, ID or shortid keep working.
- A template nobody has edited since its import gets a new draft and is published again. One imported from the identical archive is skipped.
- A template someone has edited in Typefold since its import is skipped, so your changes are never overwritten.
- A template you deleted comes back.
- A workspace file whose content changed is saved as a new revision. A global or folder script that is already in the workspace stays as it is.
Which templates import
Section titled “Which templates import”| jsreport recipe | Becomes |
|---|---|
chrome-pdf |
A PDF template, with its Chrome print settings in page.json. |
xlsx |
An XLSX template that fills the workbook the jsreport template used. |
Both need the Handlebars engine. Templates with other recipes or engines are listed as
unsupported and skipped; the rest of the archive still imports. A Handlebars chrome-pdf or
xlsx template is also skipped when:
- a script it uses does not parse, or is larger than 100 KB;
- it would run more than 10 scripts, counting global and folder scripts;
- its page size or margins cannot be mapped, such as format
A6or a margin inpt; - its name is longer than 120 characters.
The whole archive is refused when it is larger than 20 MB, is not a valid zip, has an entry that is missing required fields, or has two templates with the same name and format. The other import limits are in limits.
What maps to what
Section titled “What maps to what”jsreport folders become workspace folders under the folder you import into, and every file keeps its folder.
| In jsreport | In Typefold |
|---|---|
| Folder | Workspace folder |
Handlebars chrome-pdf or xlsx template |
Template, with a stable ID, that keeps its jsreport shortid |
| Template content | The template’s HTML source |
| Template helpers | helpers.js |
Chrome headerTemplate and footerTemplate, with displayHeaderFooter |
header.html and footer.html |
| Chrome print settings, PDF settings, language | page.json (see page settings) |
| Component | Partial, rendered by {{component "..."}} as before |
HTML, .xml and .txt assets |
Partials |
.js assets |
Scripts |
.css assets |
Stylesheets |
.json assets |
Localizations (translations) |
.ttf, .otf, .woff2, .png, .jpg, .jpeg, .svg and .xlsx assets |
Uploaded fonts, images and workbooks (needs asset:manage) |
| Data item | Sample data file, used for previews |
| Global script | Script that runs before every render |
| Folder script | Script that runs before renders in that folder and below |
| Script assigned to a template | Script beside the template, which the template runs by name |
Blank header and footer templates are left out, and Chrome’s default date, title and URL bands are not drawn. Assets of other types are not imported. Data items become sample data for previews only; your application still sends the live data with every render request.
Paths keep working:
- Root paths such as
{#asset /shared/logo.png @encoding=dataURI}and{{component "/shared/address"}}are rewritten to start at the folder you imported into. - jsreport finds a file named without a folder anywhere in the export. When exactly one file has that name, the import rewrites the reference to that file’s path from the template’s folder.
PDF templates also get body { font-family: Arial, sans-serif; } ahead of their own styles,
because jsreport’s Chrome prints in Arial unless told otherwise. Your own CSS still wins.
Page settings
Section titled “Page settings”A chrome-pdf template’s settings become its page.json:
| jsreport setting | page.json |
|---|---|
format (A3, A4, A5, Letter, Legal) |
Page size. Any other format makes the template unsupported. |
width and height |
Custom page size, when no format is set. |
| Neither | Letter, as in jsreport. |
landscape |
Orientation. |
marginTop, marginRight, marginBottom, marginLeft |
Margins in millimetres. Lengths in px, mm, cm or in; a bare number is pixels. An unset side is 0. |
pageRanges |
Page ranges, when they are valid. |
preferCSSPageSize |
preferCssPageSize, so an @page size prints on its own paper as in jsreport. |
pdfMeta title, author, subject, creator, producer, language, keywords |
PDF document properties. Keywords are split at commas. |
pdfPassword with a password or owner password |
Passwords and permissions. As in jsreport, permissions it does not name are not granted. |
pdfSign |
A signature with its reason. The certificate is named after the jsreport asset without its extension: Company seal.p12 becomes Company seal. |
| pdf-utils operations | operations, naming the other template. Disabled operations are skipped (see what changes). |
pdfAccessibility with PDF/UA on |
PDF/UA-1, if the template has a heading (see what changes). |
Template localization.language |
Locale. Without one, en-US. |
What works the same
Section titled “What works the same”Templates that rely on these need no changes:
- Paths. Partials,
{#asset}paths and{{component}}calls resolve as in jsreport. See how paths resolve. - Components.
{{component "path"}}renders a partial the way jsreport components do. See reuse a block with component. - Helpers and scripts. Helper files and
beforeRenderscripts take the same forms as in jsreport.require("moment")offers a subset of Moment for formatting dates.require("bwip-js")offers only the synchronoustoSVG, so a barcode helper that usedtoBuffermust switch totoSVGor to the built-inqrCodeandbarcodehelpers. - Localization. In scripts,
require("jsreport-proxy")offerslocalization.localize(key), which returns the label for the request’s language, or the key when the translation file has none. - pdf-utils.
pdfCreatePagesGroup,pdfAddPageItem,pdfDestandpdfFormFieldwork as in jsreport. Other templates read$pdf, also once per page withrenderForEveryPage. See page data and form fields. @pagerules. Margins and orientation from a template’s@pagerule apply as Chrome printed them, and its size too when the template hadpreferCSSPageSize.- Header and footer classes. The
pageNumberandtotalPagesclasses from Chrome’s print templates work unchanged inheader.htmlandfooter.html. See Lay out pages. - Fonts. The name in
font-familyneed not match the name inside the font file, sofont-family: Regularkeeps working, and fonts embedded as data URLs work too.Arialhas the same metrics as in jsreport’s Chrome on Linux, so lines and pages break in the same places. - Layout. Full-page sections built with
html, body { height: 100% }keep one section per page, as in Chrome. - Workbooks. Imported
xlsxtemplates become workbook templates, and the row commands of jsreport’sxlsxrecipe work the same. - Inline Handlebars. Text that jsreport rendered with the
htmlrecipe can go through string rendering with"engine": "handlebars".
What changes on import
Section titled “What changes on import”The import adjusts these so templates run here. Each appears as a note in the import dialog and in the inspect response.
afterRendersteps are removed. A script with onlyafterRenderis left out; one with both keeps itsbeforeRender. Do any post-processing in your application after rendering.- pdf-utils
append,prependandmergeoperations become pages from other templates, naming the other template. An operation is left out, with a note, when its template is not achrome-pdftemplate in the same archive, or beyond the tenth. - Signing certificates are not imported. A template with
pdfSignfails to render until its.p12or.pfxfile is uploaded with the name the note gives (see step 3). - PDF/UA needs a heading. PDF/UA output here fails without an
h1toh6heading, which jsreport never required. If neither the template nor the partials it includes has one, PDF/UA is turned off with a note. Add a heading and import again, or ask for PDF/UA per request withpdf.pdfua.
What does not carry over
Section titled “What does not carry over”| jsreport feature | What to do instead |
|---|---|
Recipes other than chrome-pdf and xlsx, engines other than Handlebars |
Rebuild the template in Studio. |
Other jsreport-proxy calls, such as render, documentStore, folders and assets |
Send all the data the template needs with the render request. |
| Chrome scale, viewport size, print background and wait-for-JS | No equivalent. Check the layout in a preview. |
| pdf-utils PDF/A setting | Turn on PDF/A for the template. |
pdfFormField in a PDF/A template |
Turn PDF/A off for that template, or leave the fields out. |
$pdf read in a script |
Read $pdf in the template or its helpers. |
A script that makes any other jsreport-proxy call fails with an error naming the call. Scripts
have no network, file system or Node.js APIs; see
template language.
Not supported lists the general gaps and their workarounds.