New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@reportwright/viewer

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@reportwright/viewer

The ReportWright report viewer for any web app: parameters, paging, search, drill-down, exports. React is bundled inside.

latest
Source
npmnpm
Version
1.0.1
Version published
Weekly downloads
684
-59.81%
Maintainers
1
Weekly downloads
 
Created
Source

@reportwright/viewer

An embeddable report viewer and drag-and-drop report designer for any web page: view, page, search, drill down and export PDF, Excel, Word, PowerPoint, HTML or CSV in the browser.

npm license Socket

Live demo: mrarun005.github.io/reportwright-demo: the gallery is this viewer, the designer playground is mountDesigner with your own pasted JSON or CSV, and made by hand in the designer walks through building reports step by step.

The ReportWright report viewer in any web app (React is bundled inside, in a shadow root, so your styles do not leak in). A React app imports @reportwright/viewer/react-peer (and /react-peer/designer) instead, the same viewer using the app's own React, so the page loads React once; @reportwright/react does this for you:

Screenshots

The viewer in the live demo gallery The designer in the live demo

Install

npm i @reportwright/viewer @reportwright/fonts
cp -r node_modules/@reportwright/fonts/public public/pw     # serve fonts/, harfbuzz-subset.wasm, icc/ as static files

React and React DOM 18+ are peer dependencies (the default build carries its own copy inside the shadow root; /react-peer uses yours).

Quick start (30 seconds, plain HTML + a bundler)

<!-- index.html -->
<div id="report" style="height: 600px"></div>
<button id="save-pdf">Save PDF</button>
<script type="module" src="./main.js"></script>
// main.js
import { mountViewer } from '@reportwright/viewer';
import definition from './orders.pw.json';       // the report from @reportwright/engine's README examples

const viewer = mountViewer('#report', {
  definition,
  data: { orders: { rows: [{ customer: 'Asha Rao', amount: 1200.5 }, { customer: 'Ben Ode', amount: 80 }] } },
  fontsUrl: '/pw/fonts/',
});
viewer.on('ready', ({ pages }) => console.log(`${pages} page(s)`));
document.querySelector('#save-pdf').onclick = async () => open(URL.createObjectURL(await viewer.export('pdf')));

Run it with npx vite (Vite 7: set worker: { format: 'es' }, see below). Verified against this version in Chromium with Vite 7.

Examples

Embed the designer and keep the definition in your app.

import { mountDesigner } from '@reportwright/viewer/designer';
const designer = mountDesigner('#designer', {
  definition,
  fontsUrl: '/pw/fonts/',
  onSave: (def) => fetch('/api/reports/orders', { method: 'PUT', body: JSON.stringify(def) }),
  toolbox: { hide: ['map'] },
});
designer.isDirty();        // unsaved changes?
designer.getDefinition();  // the current definition

Re-run with new parameters without remounting.

viewer.setParameters({ minAmount: 100 });
viewer.on('parameters', (p) => history.replaceState(null, '', '?' + new URLSearchParams(p)));

Trim the toolbar and show render warnings while developing.

mountViewer('#report', { definition, data, fontsUrl: '/pw/fonts/', toolbar: { hide: ['powerpoint', 'csv', 'sidebar'] }, showWarnings: true });

React, Vue, Svelte, Angular: use @reportwright/react, @reportwright/vue, @reportwright/svelte or @reportwright/angular; they take the same options.

Features

AreaViewerDesigner
Navigationpages, thumbnails, document map, search, zoom, single / continuous / galley viewlive preview
Interactivityparameters, interactive sort, drill-down, drill-through (onDrill, reports)data panel, inspector, toolbox (each can be hidden)
Export in the browserPDF, PDF/A, Excel, Word, PowerPoint, HTML, CSV, JSON; printthe same, from the preview
Embeddingshadow root (no style leaks), CSP without 'unsafe-inline', nonceonSave, onOpen, readOnly, dataSourceTemplates, warnOnLeave
Performancerendering in a Web Worker; fonts fetched only when the layout needs them

Options reference

From dist/viewer.d.ts and dist/designer.d.ts.

mountViewer(target, options) → ViewerHandle

OptionTypeMeaning
definitionobjectthe report JSON
dataRecord<string, unknown>JSON by data source name (a definition with one source also takes the JSON itself)
fontsUrlstringURL of the served copy of @reportwright/fonts/public/fonts
server, report, paramsstring, string, objecta self-hosted server, a saved report id and its parameters
reportsRecord<string, object>definitions by id for drill-through and subreports without a server
lang, title, heightstringinterface language, document title, element height
toolbar{ hide?, add? }hide built-in items, add buttons ({ id, label, title?, after?, onClick(api) })
viewMode'single' | 'continuous' | 'galley'page layout on screen
showWarningsbooleanlist the render's warnings under the pages
exportOptions{ html?, drillLinks? }the HTML export: html interactive (default) | tables | static; drillLinks inline (default) | new-tab | off. Wins over the report's export and the server's settings. The Export menu offers HTML (interactive) and HTML (print); when the interactive file cannot be made the viewer says why and asks before exporting the print file
onReady(error | null) => voidthe first run ended
onDrill(report, params) => voidhandle a drill-through yourself
fonts, customFonts, uiFontCssextra report fonts and the interface font
snapshotobjectshow a stored run
allowHosts, unsafeFetch, fetchdata request guard (see below)
noncestringfor the fallback <style> under a CSP

mountDesigner(target, options) → DesignerHandle (unmount, getDefinition, save, isDirty): server, fontsUrl, report, definition, lang, height, onSave, onOpen, readOnly, toolbox: { hide }, panels: { data, inspector }, dataSourceTemplates, nonce, allowHosts, unsafeFetch, fetch, warnOnLeave (default true), onDirtyChange.

Browser support and limits

  • Modern evergreen browsers with ES modules, Web Workers and constructable stylesheets (an older browser without the last gets a <style> element). Node 20.16+ for the build tooling (engines).
  • Without the worker (a bundler that does not copy it) the report renders on the main thread: it works, but large reports make the page janky.
  • Sorted or grouped exports in the browser hold every row in memory (about 1.3 KB a row measured): keep them under about 100,000 rows, or render on the server with @reportwright/engine.
  • A browser cannot resolve DNS, so the private-address guard cannot catch a public name that points at a private address (see Data sources and private addresses).

@reportwright/engine (the same rendering in Node), @reportwright/fonts (required), @reportwright/fonts-cjk, @reportwright/cli, and the wrappers react, vue, svelte, angular.

Bundler setup (read this first)

The viewer renders in a Web Worker (engine.worker.js, one self-contained file next to the viewer). Bundlers that understand new Worker(new URL(...)) pick it up; two settings matter:

  • Vite 7: set worker: { format: 'es' } in vite.config.js. Without it the build fails with Invalid value "iife" for option "worker.format".
  • esbuild (plain bundling) does not follow new Worker(new URL(...)): copy the worker files next to your bundle: cp node_modules/@reportwright/viewer/dist/engine.worker*.js dist/. engine.worker.js has no static imports (its libraries are inside it); it dynamically imports ./engine.worker.bwip.js (barcodes, a file next to it) only when a report needs it, so copy both. Without it those reports render on the main thread, with the warning below.

If the worker cannot load, the viewer still renders, on the main thread (slow and janky at 100k rows), and logs once [reportwright] worker did not load (<reason>); rendering on the main thread. handle.usesWorker tells you which one you got.

Import it through your bundler (Vite, webpack, esbuild, Rollup, Next…). Its third-party libraries (@reportwright/pdf, @pdf-lib/fontkit, bwip-js, bidi-js) are ordinary dependencies, pinned to the versions the tests ran against, so the package itself holds only ReportWright's own readable code.

Without a server (library only)

npm i @reportwright/viewer @reportwright/fonts
# serve the fonts as static files, e.g. from your app's public folder
cp -r node_modules/@reportwright/fonts/public public/pw     # fonts/, harfbuzz-subset.wasm, icc/
import { mountViewer } from '@reportwright/viewer';
mountViewer('#report', {
  definition,                         // the report JSON (see @reportwright/engine/report)
  // data: JSON for each data source, by name. Match the data set's path: { orders: { rows: [...] } } when it reads "$.rows"
  // (docs/QUICKSTART.md), or { orders: [...] } when it has no path (the array is the rows). A mismatch gives empty tables.
  data: { orders: { rows: [...] } },
  // REST sources in the definition are fetched as usual
  fontsUrl: '/pw/fonts/',             // the copied fonts folder; PDF subsetting and PDF/A use ../harfbuzz-subset.wasm and ../icc/
});

Nothing calls /api; each font is downloaded once, and only when the layout first needs it (a report in Inter Regular and Bold fetches those two files, not all seven); the toolbar uses the system font.

If a font cannot be loaded (wrong fontsUrl, the folder not served), the report does not draw: the viewer shows "The report did not run. Font "Inter-Regular" did not load (HTTP 404)" with a Retry button, and onReady(error) is called with that error. Fix the path and retry; there is no fallback font. onReady(error) is called when the first run ends: null when it went through, or the error that stopped it. Bundled apps: serve the fonts folder at the URL you pass as fontsUrl; fontsUrl is not inferred from the package. Copying only fonts/ works too (PDFs then embed whole fonts, and PDF/A export needs icc/).

Serve fontsUrl with Cache-Control: public, max-age=31536000, immutable: the files do not change within a package version, so a returning visitor downloads none of them.

What works standalone, what needs the server

The npm packages are a library: nothing here needs a ReportWright server. The server is a separate, self-hosted app (not on npm): github.com/MrArun005/reportwright, run it with Docker (instructions). Options that need it are optional and hidden without it.

FeatureStandalone (definition, data, fontsUrl)With server
View, page, search, zoom, parameters, sort, drill-down, document mapyesyes
Export PDF, PDF/A, Word, Excel, PowerPoint, HTML, CSV (in the browser)yesyes
REST / JSON / XML / CSV data sources the browser can reach (CORS)yesyes
Designer: edit, preview, onSave / onOpen to your appyesyes
Load a saved report by id (report: 'id'), drill-through to another saved reportpass reports: { id: definition }yes
Designer Save without onSave (writes to the server), version historyno (Save is hidden)yes
SQL data sources (the server holds the connections)noyes
Server-side PDF for very large reports, schedules and e-mail, uploaded fonts, AI drafts, users and rolesnoyes

In Node, @reportwright/engine renders and exports with no server and no browser.

Data sources and private addresses

A report definition says which URLs its data comes from, and the viewer's browser requests them. So that a definition cannot point a viewer at your network, a data source on another host may reach public http(s) addresses only: literal private, loopback, link-local and CGNAT addresses (192.168.1.1, 127.0.0.1, [::1], 169.254.169.254, 100.64.0.1, in any spelling), localhost / .local / .internal names and other schemes are refused, redirects are checked hop by hop, and a parameter may fill a URL's path and query but not its host.

mountViewer(el, { definition, allowHosts: ['reports-api.corp.example'] }); // private hosts that may be reached
mountViewer(el, { definition, unsafeFetch: true });                          // no check (trusted definitions only)
mountViewer(el, { definition, fetch: myFetch });                             // every data request goes through yours

A browser cannot resolve DNS, so a public name that resolves to a private address is not caught. When definitions come from people you do not trust (a multi-tenant app), render on the server (its guard checks DNS), or pass a fetch that allows only the hosts you know. The same options apply to mountDesigner.

Content Security Policy

The viewer needs no 'unsafe-inline': its styles are constructable stylesheets, and the style attributes of the drawing are applied through CSSOM. The policy for a page with the viewer (standalone, fonts on the same origin):

default-src 'self';
script-src 'self';
style-src 'self';
font-src 'self';
img-src 'self' data: blob:;
connect-src 'self'

This is the policy the viewer's standalone page is tested under (PDF export included). Keep the viewer's script in a file (<script type="module" src="…">), not inline. The viewer adds a <style> element only in browsers without constructable stylesheets, and gives it the nonce you pass (mountViewer(el, { nonce })).

With the self-hosted ReportWright server

mountViewer('#report', { server: 'https://reports.example.com', report: 'loan-statement', params: { accountId: 'LN-1' } });

The ReportWright server must allow your site's origin (PW_EMBED_ORIGINS). PDF export and barcodes load only when used. MIT.

The designer is a second entry, loaded only by pages that use it:

import { mountDesigner } from '@reportwright/viewer/designer';
const designer = mountDesigner('#designer', {
  server: 'https://reports.example.com',
  definition,                                   // or report: 'id' to load it from the server
  onSave: (def) => saveToMyApp(def),            // without onSave, Save writes to the ReportWright server
  onOpen: () => pickAReport(),                  // shows an Open… button; return a definition
  readOnly: false,
  toolbox: { hide: ['map', 'chart'] },
  panels: { data: true, inspector: true },
  dataSourceTemplates: [{ label: 'Orders API', source: { name: 'orders', type: 'rest', url: 'https://api.example.com/orders' } }],
});
designer.getDefinition();

Types are included (ViewerOptions, DesignerOptions, …). Framework wrappers: @reportwright/react, @reportwright/vue, @reportwright/svelte, @reportwright/angular.

Viewer toolbar: toolbar.hide

toolbar: { hide: [...], add: [...] } hides built-in items. A group key hides the whole group; an item id hides that one item. Hiding every export format also hides the Export menu and its Export options button.

KeyHides
exportthe Export menu: every format and the Export options button (Print stays)
pdf, excel, word, powerpoint, html, csv, jsonthat one export format
printthe Print button
searchthe search box and its match controls
zoomzoom out, zoom value, zoom in, whole page, fit width
navigationfirst page, previous, page number, next, last page
sidebarthe page thumbnails and the document map buttons
parametersthe Parameters button
run, viewMode, fullScreen, statsthose single items (use their id)
exportOptionsthe Export options button (the formats stay)

Any other key is taken as an item id. Unknown keys hide nothing.

Render warnings: showWarnings

showWarnings: true shows the render's warnings (a data value that does not read as a number, an unknown field, content cut off at the page edge…) under the pages, as a collapsed list headed "8 warnings" or "1 warning". The default is false: readers of a finished report do not see them. The designer's Preview always shows them.

mountViewer('#report', { definition, data, fontsUrl: '/fonts/', showWarnings: true });

Controlling a mounted viewer: ViewerHandle

mountViewer returns a handle (the React, Vue, Svelte and Angular wrappers give you the same one through their ready callback or event):

MethodWhat it does
setParameters(params)merges params into the current parameters and runs the report again; the host element is not remounted
setDefinition(def)shows another report definition in the same element
goToPage(n)scrolls to page n (1-based)
getPageCount()the page count of the last run (0 before the first)
print()opens the print dialog for the PDF
export(format)Promise<Blob>: format is pdf, xlsx, docx, pptx, html, csv or json (or the item ids excel, word, powerpoint)
on(event, fn)listens for ready (a run finished, detail: { pages }), error (detail: message), page (detail: current page), parameters (detail: the parameters); returns an unsubscribe function
unmount()removes the viewer
const viewer = mountViewer('#report', { server, report: 'loan-statement' });
viewer.setParameters({ accountId: 'LN-2' });      // re-runs; 'ready' fires when it is done
document.querySelector('#save-pdf').onclick = async () => {
  const pdf = await viewer.export('pdf');         // a Blob of the report as shown
  open(URL.createObjectURL(pdf));
};

export() exports the last finished run. ready fires once that run is on screen, so calling export() inside a ready listener (or onReady) works.

Size

What a page downloads when the viewer mounts: 847 KB gzipped (0.7.3: 877 KB), readable unminified code with source maps. Loaded only when used: other interface languages (about 20 KB gzipped each), PDF export (@reportwright/pdf), barcodes (bwip-js), Excel/HTML/CSV export, the designer. Most of the first load is the font engine (fontkit) the layout measures text with, and React. The package is larger unpacked (two builds: React inside, and React from your app at @reportwright/viewer/react-peer, plus source maps); only one build reaches your page. PDF export, fontkit, barcodes, zip and bidi come from the dependencies above, so your bundler can share them with the rest of your app.

What a bundler writes is more than what a page loads: the lazy chunks are emitted too. Measured with esbuild (minified, code splitting) on 1.0.1: a viewer-only app (import { mountViewer } from '@reportwright/viewer') has a 126 KB entry chunk and emits 4.2 MB in all: mounting fetches the React viewer (~960 KB), while the barcode library (~945 KB) is fetched only for a report with barcodes; the designer (~970 KB) is not in that build. With a framework wrapper it is emitted too (5.2 MB in all), as a lazy chunk fetched only when the designer component mounts.

Keywords

reportwright

FAQs

Package last updated on 19 Oct 2026

Related posts