Repository navigation
Releases
v2.4.0
Added
- Sync and async code examples on the template page. The Code tab of the Word/PDF and Excel template pages now
has two modes: Sync — wait for the file (the call answers with a download link; the examples also download
the file) and Async + webhook (step 1 queues the job with your webhook URL, step 2 is a ready-made webhook
receiver for Node.js/Express and ASP.NET Core with signature check, plus a signed test call for any language).
Examples are built from the template's current test values in cURL, Node.js and .NET. - Docs: "Sync vs async & webhooks". When to use which, the full async flow, the webhook body, retry rules and
receiver examples.
Docker: docker pull qorstack/report:2.4.0 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.4.0 · Node SDK: npm i qorstack-report-sdk@2.4.0
v2.3.2
Changed
- Images and webhooks may point anywhere. The backend no longer refuses private / internal addresses for image
URLs, QR logos and webhooks, and no longer rejectsminio:paths outside the template's own builder assets.
IMAGE_FETCH_ALLOW_PRIVATE_NETWORKSandWEBHOOK_ALLOW_PRIVATE_NETWORKSwere removed (no longer read).
Only http/https URLs, size and time limits still apply.
Fixed
- Rendering failed with
400when an image URL was a presigned link to another app's bucket on the same S3 server
(for example a signature image an API caller keeps in its own MinIO bucket): the link was turned into a storage
path of ours and then rejected. Such links now stay ordinary URLs and are downloaded.
Docker: docker pull qorstack/report:2.3.2 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.3.2 · Node SDK: npm i qorstack-report-sdk@2.3.2
v2.3.1
Fixed
- Project import preview (
POST /api/projects/{id}/import?dryRun=true) returned400in 2.3.0: the new optional
streamquery flag was declared as required. A test now checks every endpoint for value-type query parameters
that are required by accident.
Docker: docker pull qorstack/report:2.3.1 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.3.1 · Node SDK: npm i qorstack-report-sdk@2.3.1
v2.3.0
Added
- Live import progress (Pro). Importing a project now shows every font and template as it is imported, with a
progress bar and the reason for anything that failed, then a summary with Go to Templates.
API:POST /api/projects/{id}/import?dryRun=false&stream=truestreamsapplication/x-ndjsonprogress lines. - Regenerate previews. Re-render template previews with the fonts installed now — after uploading or importing
fonts, previews made before the renderer had them show a substitute. Templates page: Regenerate previews (every
template of the project, with progress); template editor: ⋯ → Regenerate preview;
API:POST /api/templates/{key}/preview. After an import that added fonts, the Templates page offers it.
Changed
- Import commits each font and template on its own, fonts first. A template that fails no longer undoes the
others; it is listed with its reason, and importing the same file again picks up only what is missing.
Fixed
- Copy buttons (API key on Create project, API keys page, template keys, code blocks) did nothing on installs
opened over plain HTTP (a NodePort, an intranet IP); they fall back to a method that works without HTTPS.
Docker: docker pull qorstack/report:2.3.0 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.3.0 · Node SDK: npm i qorstack-report-sdk@2.3.0
v2.2.2
Added
STORAGE_PUBLIC_ENDPOINT(optional). Set it to the address your storage already serves to browsers
(e.g.https://storage.example.com) and download links become S3 presigned URLs for that address, as in
v1: browsers download from storage directly andPUBLIC_URLis not needed for downloads. Unset (default),
links stay{PUBLIC_URL}/api/files/{token}and storage needs no public address.MINIO_PUBLIC_ENDPOINT/
Minio__PublicEndpointfrom v1 are read as fallbacks.
Changed
- Image URLs that point at this deployment's own storage (
STORAGE_ENDPOINT/STORAGE_PUBLIC_ENDPOINT)
are fetched even on a private network, soIMAGE_FETCH_ALLOW_PRIVATE_NETWORKSis only needed for images on
other intranet servers. - Enterprise source builds run Pro without
LICENSE=pro(setLICENSE=ossto turn it off). A license file
(PRO_LICENSE_FILE) is still validated as before; the published images are unchanged and still need one.
Fixed
- The docs page had an empty band under the navbar.
Docker: docker pull qorstack/report:2.2.2 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.2.2 · Node SDK: npm i qorstack-report-sdk@2.2.2
v2.2.1
Fixed
- Sign-in over plain HTTP (a Kubernetes NodePort, an intranet IP). Sign-in cookies were always Secure
outside localhost, so browsers dropped them onhttp://and the web UI could not stay signed in. The
cookies now follow the address in the browser (from the request'sOrigin/Referer): Secure on
https://, plain +SameSite=Laxonhttp://— no setting needed, behind any number of proxies.
AUTH_COOKIE_SECURE=true|false(new, optional) forces either way. - The template editor's preview head squeezed the Template / Result tabs under the Auto-preview switch on
1920px screens. Its labels now follow the panel's own width (CSS container queries), and a very narrow
panel wraps the actions instead of overlapping. - The docs navbar is as wide as the docs columns below it.
Docker: docker pull qorstack/report:2.2.1 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.2.1 · Node SDK: npm i qorstack-report-sdk@2.2.1
v2.2.0
Added
- Import shows what differs before you confirm (Pro). The review lists every template as New, Changed,
No changes or Skipped, with what changed: template file, fields added / removed, name, saved test data,
and a note when only the name matches a template with another key. Identical templates are folded away.
The API's dry run (POST /api/projects/{id}/import?dryRun=true) returns the same detail in
templates[].changesandtemplates[].unchanged. - Export & import has its own page in the project sidebar (
/project/{id}/transfer), apart from
Project settings.
Changed
- Import compares content, not version numbers. Version numbers are counted per deployment, so:
identical templates are skipped as "no changes" whatever their numbers; a changed template with the same
number is imported as the next free version (it used to be skipped as "already up to date"). A newer
version in the target is still never overwritten. The template name is updated on import. - Easier-to-read dashboard. Brighter secondary text and placeholders, quieter dividers, fields without
a border until hover / focus, sentence-case page titles, filled tabs and badges. Corners stay square. - Template editor. Version and Replace file stay one click away; Rename, Download original and Delete
moved into the ⋯ menu. The preview head is one row (Template / Result, Auto-preview, Download, Generate).
Fixed
- Opening a template from the Templates list without reloading asked the API for the project id and showed
"not found" until refresh. - The editor header no longer overlaps the template name on laptop-width screens.
- The Fonts page had two scrollbars.
Docker: docker pull qorstack/report:2.2.0 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.2.0 · Node SDK: npm i qorstack-report-sdk@2.2.0
v2.1.1-rc.1
Pre-release that exercises the automated release pipeline (Docker Hub, NuGet, npm trusted publishing).
No product changes from 2.1.0 — stay on 2.1.0 / latest.
Docker: docker pull qorstack/report:2.1.1-rc.1 · .NET SDK: dotnet add package Qorstack.Report.Sdk --version 2.1.1-rc.1 · Node SDK: npm i qorstack-report-sdk@2.1.1-rc.1
v2.1.0
Changed
- Live Preview is free. Auto-render while you edit a template now works without a Pro license.
/featuresstill returnslivePreview(now alwaystrue).
Fixed
og:site_nameis the product name (was "Qorstack Report Engine").
Docker: docker pull qorstack/report:2.1.0 · .NET SDK: dotnet add package Qorstack.Report.Sdk · Node SDK: npm i qorstack-report-sdk
v2.0.0
Upgrade guide: README › Upgrading to v2.
Staying on v1? Pin image tags to 1.1.0 (the last v1 release).
⚠ BREAKING
-
One image, one port; the API moved under
/api. Web UI and API ship as one image,
qorstack/report(rootDockerfile), on port 8080: web UI at/, API at/api
(e.g.POST /api/render/word/template, downloads at/api/files/{token}, health at/api/health,
reference at/api/docs).qorstack/report-api/qorstack/report-weband port 3000 are gone.
Compose has oneappservice;API_URL,FRONTEND_PORT,REPORT_API_TAG/REPORT_WEB_TAGand
NEXT_PUBLIC_SERVICEwere removed — setPUBLIC_URL(andAPP_PORT).CORS_ORIGINSis now only
for other origins. API clients add/apito the base URL; SDK 1.0 and the MCP server do it
automatically. GitHub sign-in config moved to the backend (GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,
GOOGLE_CLIENT_ID), and the code exchange now runs server-side. -
Storage uses one bucket on any S3 server.
STORAGE_BUCKET(defaultqorstack-report)
with folderstemplate/,generated-report/(incl.temp-download/, deleted after 1 day)
andfonts/replaces thetemplates/reports/fontsbuckets.MINIO_BUCKET_TEMPLATE,
MINIO_BUCKET_REPORT,Minio:TemplateBucketandMinio:ReportBucketwere removed.
Copy existing data once withselfhost/migrate-storage.sh keep-minioorto-rustfs(rclone);
the backend logs a warning when old-bucket data was not migrated. -
RustFS is the default storage. The self-host compose starts RustFS (Apache-2.0) with no
extra settings; MinIO stays available through thedocker-compose.minio.ymloverride (image pinned
from quay.io — MinIO left Docker Hub), anddocker-compose.external-s3.ymlpointsSTORAGE_*at
any external S3. Unreachable storage now gives a clear 503 and an unhealthystoragehealth check. Settings areSTORAGE_*/Storage:*; v1MINIO_*/Minio:*still work as fallbacks. -
Downloads go through the API. Every link (
downloadUrl, previews, fonts, history) is now
{PUBLIC_API_URL}/files/{token}— signed, time-limited, streamed by the backend with Range
support — instead of a presigned storage URL. Storage no longer needs a public address:
MINIO_PUBLIC_ENDPOINTis no longer needed and port 9000 is not published. -
Postgres is no longer published on the host (port 5432), so it can't clash with a local
Postgres. Tools that connected from the host (DBeaver, psql) need the commented
127.0.0.1:${DB_HOST_PORT:-5432}:5432line underpostgresindocker-compose.yml, ordocker exec. -
font-syncer uses rclone (
rclone/rclone) instead of the MinIO client, so it works with
any S3 server. -
Render request shape. Every v1 alternative form was removed and now returns
400
naming the field and the expected shape:replace: { "customer": "ACME" } → [{ "key": "customer", "value": "ACME" }] image/qrcode/barcode: { "logo": { "src": "…" } } → [{ "key": "logo", "src": "…" }] table rows: [{ "item": "A", "qty": 2 }] → "columns": ["item", "qty"], "rows": [["A", 2]] generateTotals / numberFormat: { "amount": "sum" } → [{ "key": "amount", "value": "SUM" }] sort: "date desc" → [{ "field": "date", "direction": "desc" }] verticalMerge / collapse: "group" → ["group"]Row length must match
columns; sort / merge / totals must name existing columns. -
POST /render/excel/templateis Excel in, Excel out —fileTypeand the PDF
options were removed (always.xlsx). -
Image URLs in requests may no longer point at private or internal addresses
(localhost, 10.x, 172.16–31.x, 192.168.x, 100.64–127.x, cloud metadata). Set
IMAGE_FETCH_ALLOW_PRIVATE_NETWORKS=truefor intranet image servers.minio:paths are
accepted only for the template's own builder assets. -
zipOutputis a Pro feature (it was documented as Pro but never enforced); without Pro it
now returns403. -
Website is dark only (the light theme and theme toggle were removed).
-
SDK 1.0.0 (Node / .NET) uses the new types. Stay on API 1.1.0 / SDK 0.x if you still
send the old forms.
Added
- Project export & import (Pro): Project settings → Export & import. Export downloads one ZIP
(manifest + latest active version of every template, its test data and builder images, uploaded
fonts); import shows a preview (create / update / skip) and then creates new templates with the same
key and adds newer versions — equal or older versions are skipped.GET /projects/{id}/export,
POST /projects/{id}/import?dryRun=true|false; size limitPROJECT_IMPORT_MAX_MB(200). - Request limits (env, 0 = unlimited):
RENDER_MAX_BODY_MB(30),RENDER_MAX_TABLE_ROWS
(50,000 per table),RENDER_MAX_ITEMS_PER_FIELD(1,000 per replace / image / qrcode / barcode).
Over a limit →400naming the field and the setting. - Validation: all errors in one
400, keyed by JSON path (table[0].rows[12],replace[3].key). templateVersion(optional) renders a stored older version; unknown version →404.- Async renders (Pro):
"async": truereturns202 { jobId, status: "pending", statusUrl };
pollGET /render/jobs/{id}(freshdownloadUrlon success). OptionalwebhookUrlreceives
POST { jobId, status, downloadUrl?, expiresIn?, error? }, signed with
X-Qorstack-Signature: sha256=<HMAC>whenWEBHOOK_SIGNING_SECRETis set; delivery state is stored
on the job (migration006_webhook-delivery.sql, 3 tries: now, +10 s, +60 s), so retries survive restarts.
The queue lives in PostgreSQL (report_jobs, migration005_render-queue.sql): jobs survive restarts,
several backend instances can share it (FOR UPDATE SKIP LOCKED,LISTEN/NOTIFY), a crashed worker's job
is picked up again when its lease ends, and infrastructure errors are retried (10 s, 60 s; 3 tries).
Settings:RENDER_ASYNC_WORKERS(2),RENDER_JOB_LEASE_SECONDS(300),RENDER_POLL_SECONDS(5),
RENDER_MAX_PENDING_JOBS_PER_USER(100 →429). - Website: new Press design, new logo/icons and a generated link-preview image.
selfhost/migrate-storage.sh, fully commented.env.examplefiles,init.sh/ migration script
downloadable from/selfhost/.
Fixed
- Public Excel API calls no longer overwrite the template's saved builder payload.
- Server-side request forgery through image URLs and cross-template
minio:paths. System.Security.Cryptography.Pkcspinned to 10.0.13 (CVE-2026-84008, CVE-2026-70162; it comes in
through Microsoft.Data.SqlClient). Image builds failed restore on these advisories.- Startup: database migrations finish before anything else starts (render worker, storage, HTTP),
so a fresh upgrade no longer logscolumn ... does not exist; a failed migration stops startup. - Project pages follow the project in the URL: a stale or deleted project link shows
"Project not found" instead of another project (Settings › Delete acted on the wrong one). - Account: a failed password change no longer reports success; a failed GitHub sign-in shows an
error instead of spinning; the system admin is told their login comes fromADMIN_EMAIL/
ADMIN_PASSWORD; Google/GitHub buttons show only when configured. - Editor: test values save automatically (with a leave-page warning while unsaved); Rename no
longer sends inline images in the URL; "Make current" asks first; Generate opens the preview on
mobile; Excel tables get the same summary and filter as PDF. - Excel editor no longer runs a hidden render each time it opens (it added a History entry and
its result was never used). - History and Templates filter and search on the server, so rows beyond the first page are found;
live polling no longer duplicates rows. - Template gallery shows each template rendered with its sample data
(GET /templates/gallery/{id}/thumbnail; regenerate withpython gallery/_thumbnails.py). The gallery
samples use the v2 request shape, so they can be sent to the API as-is, and the sales report's total comes fromgenerateTotalsinstead of an unsupported{{table_sum:...}}marker. - Self-host:
verify.shreadsAPP_PORT; docs and the agent prompt use theappservice,bash verify.shand
docker compose up -d; one Free vs Pro list across the site; SDK examples include/api.
Docker: docker pull qorstack/report:2.0.0 · .NET SDK: dotnet add package Qorstack.Report.Sdk · Node SDK: npm i qorstack-report-sdk