XLTX to JPG API
POST an .xltx file to /v2/convert/xltx/to/jpg for a JPEG of each printed page. With ActiveWorksheetOnly=true, 120 DPI and quality 80, the sample returns its Estimate sheet as one 1020 × 1320 image of 99,705 bytes.
- Endpoint
- POST /v2/
convert/ xltx/ to/ jpg - Accepts
- xltx
- Sample timing
- 0.797 s median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/xltx/to/jpg \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@job-estimate-template.xltx \
--form-string ActiveWorksheetOnly=true \
--form-string ImageResolution=120 \
--form-string ImageQuality=80 \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/xltx/to/jpg",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("job-estimate-template.xltx", "rb"))],
data={
"ActiveWorksheetOnly": "true",
"ImageResolution": "120",
"ImageQuality": "80",
},
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "job-estimate-template.xltx";
form.append("File", new Blob([await readFile(file)]), file);
form.append("ActiveWorksheetOnly", "true");
form.append("ImageResolution", "120");
form.append("ImageQuality", "80");
const url = "https://api.pdfconversionapi.com/v2/convert/xltx/to/jpg";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0.797,
"Files": [
{
"FileName": "job-estimate-template-1.jpg",
"FileExt": "jpg",
"FileSize": 99705,
"FileData": "/9j/4AAQSkZJRgABAgEAeAB4…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.

- Input
- job-estimate-template.xltx (10 KB)
- Output
- job-estimate-template-1.jpg (97 KB)
- Source
- Written for this site: a fictional home-repair company's job estimate template with three sheets, one of them hidden, saved as an Excel template (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
ActiveWorksheetOnlyboolean, default false | Keeps only the sheet that was selected when the template was saved. The sample's active sheet is Estimate, so Rates is skipped. |
WorksheetNamestring | Keeps one sheet by its name. |
WorksheetIndexinteger | Keeps one sheet by its position; an index past the last sheet returns 400 with the count.Values: 1 to the sheet count |
ImageQualityinteger, default 85 | JPEG quality. The sample uses 80.Values: 1–100 |
ImageResolutioninteger, default 150 | Drawing resolution, also written into the JFIF header when sent: the sample's file says 120 DPI.Values: 36–600 |
PageSizeenum, default default | Paper for every printed sheet instead of the template's own.Values: letter, legal, tabloid, ledger, statement, executive, lettersmall, a3, a4, a4small, a5, b4, b5 |
PageOrientationenum, default default | Orientation for every printed sheet.Values: default, portrait, landscape |
Scaleinteger | Print percentage; it turns off fit-to-width.Values: 10–300 |
FitToPageboolean, default false | Squeezes each sheet to one page wide. |
ClearPrintAreaboolean, default false | Prints the whole used range, not the saved print area. |
PageRangestring | Encodes only these pages.Values: e.g. 1 |
ImageWidthinteger | Pixel width of each JPEG.Values: 1–20000 |
ImageHeightinteger | Pixel height of each JPEG.Values: 1–20000 |
BackgroundColorstring, default white | Paper color under unfilled cells.Values: a color name, #RRGGBB or R,G,B |
ColorSpaceenum, default default | gray gives a one-channel JPEG.Values: rgb, gray, monochrome |
Passwordstring | Password of an encrypted template. |
FileNamestring | Base name for the JPEGs. |
StoreFileboolean, default false | Links valid for 3 hours instead of base64. |
Not supported yet
Setting one of these to anything but its default returns 400.
Refused when set: AutoFitColumns, ThousandsSeparator, DecimalSeparator, DateFormat.
What carries over, and what doesn't
Kept
- Only the active Estimate sheet, on one Letter page drawn at 120 DPI
- "Estimate 1 of 1" in the footer, counted for the pages actually drawn
- Cell shading, the dark slate-blue header band (#2F4858) and right-aligned currency values
- Calculated totals and the template's fixed text, such as its 30-day validity note
Lost or not yet supported
- Exact pixels. JPEG compression is lossy; quality 80 gives 99,705 bytes for this page.
- Any transparency:
TransparentColordoes nothing for JPEG output. - The template's saved date. Its =TODAY() cell is refreshed to the day you convert.
- The Rates sheet, since the request asks for the active sheet only. The hidden Notes sheet would be left out anyway: it is drawn only when named or picked by index.
Limits
- One template per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- Up to 2,000 page images per request; each at most 16,000 pixels per side and 100,000,000 pixels.
- 120 seconds per conversion, then 504.
Where teams use it
Form previews on an intranet
List the company's estimate, expense and timesheet templates with a JPEG of each, so staff pick the right file without opening Excel.
Email a picture of a blank form
A JPEG opens in every mail client. Send it to a customer who should see the estimate layout before you fill it in.
Store previews next to uploads
When users upload templates to your app, save a JPEG of the active sheet beside each one for search results and lists.
Questions
Which sheet does ActiveWorksheetOnly use?
The one that was selected when the template was last saved, recorded in the file as the active tab. In the sample that is Estimate, so the result is one JPEG. Without the option, every visible sheet is drawn: Estimate and Rates, two images.
Why does the date in the picture change every day?
The sample's Date cell holds =TODAY(), and functions like it are worked out again during the conversion. Other formulas keep the results saved in the template, so a total saved wrong prints wrong.
Does the JPEG know its DPI?
When you send ImageResolution, yes: the JFIF header of the sample says 120 dots per inch. Without it the page is still drawn at 150 DPI, but the header carries no density.
Convert your first XLTX file in a minute.