FreeReportv0.1.0한국어

Docs

Getting started

Getting started

FreeReport is one TypeScript package with no dependencies. Install it, import the stylesheet once, and create a viewer in an element that has a height.

npm i @freereportjs/core
import { createViewer } from '@freereportjs/core';
import '@freereportjs/core/styles.css';

const definition = await fetch('/reports/sales.json').then(r => r.json());

const viewer = createViewer(document.getElementById('viewer'), {
  definition,
  data: { rows },     // detail rows; other keys are usable in expressions
  lang: 'ko',
});
// <div id="viewer" style="height: 720px"></div>

Without a build step (CDN)

Load the bundle with a script tag; it defines a global FreeReport. Pin a version (v0.1.0) in production; latest always points to the newest release.

<link rel="stylesheet" href="https://freereport.app/cdn/latest/freereport.css">
<script src="https://freereport.app/cdn/latest/freereport.min.js"></script>
<div id="viewer" style="height: 720px"></div>
<script>
  FreeReport.createViewer(document.getElementById('viewer'), { definition, data: { rows } });
</script>

Frameworks and eGovFrame

The core works in any framework: create the viewer or designer when the component mounts and destroy it when it unmounts. Ready-made components are published on npm: @freereportjs/react, @freereportjs/vue (Vue 3), @freereportjs/svelte (Svelte 5) and @freereportjs/angular, each with a viewer and a designer. Install the core alongside the wrapper, for example npm i @freereportjs/core @freereportjs/react.

  • React: <FreeReportViewer> and <FreeReportDesigner>. The viewer ref is { viewer, report }; the designer takes onChange and onSave.
  • Vue: the viewer exposes viewer and report; the designer supports v-model:definition and emits change, update:definition and save.
  • Svelte: viewer.getViewer() / getReport() and designer.getDesigner(), or onReady.
  • Angular: <free-report-viewer> and <free-report-designer> (standalone). The designer emits (definitionChange), so [(definition)] works, and (save).

definition, data, params and zoom update the viewer in place; other props recreate it. Both components default to height: 100% with a 480px minimum.

// npm i @freereportjs/core @freereportjs/react
import { useRef, useState } from 'react';
import { FreeReportDesigner, FreeReportViewer, type ReportDefinition } from '@freereportjs/react';
import '@freereportjs/core/styles.css';

export function SalesReport({ definition, rows }) {
  const ref = useRef(null);   // { viewer, report }
  return <FreeReportViewer ref={ref} definition={definition} data={{ rows }} lang="ko" style={{ height: 720 }} />;
}

export function SalesDesigner({ initial, rows, save }) {
  const [def, setDef] = useState<ReportDefinition>(initial);
  return <FreeReportDesigner definition={def} data={{ rows }} onChange={setDef} onSave={save} />;
}

eGovFrame sample project

The sample is an eGovFrame 4.3 project (Spring 5.3, MyBatis, JDK 8+) that shows report viewing, printing, PDF and Excel and an output log. The server returns only the report definition and the data as JSON; layout, printing and files are made in the browser, with no report server, ActiveX or installer. For eGovFrame 3.x, change the imports and the groupId from org.egovframe.rte to egovframework.rte.

RouteReturnsDescription
GET /report/view.do?name=salesJSPViewer page (reportView.jsp)
GET /report/designer.do?name=salesJSPDesigner page (reportDesigner.jsp)
GET /report/def/{name}.doJSONReport definition: the copy saved in REPORT_DEF, or classpath:reports/{name}.json
POST /report/def/{name}.doJSONSaves the definition from the designer
GET /report/data/sales.doJSONReport data (Service → MyBatis)
POST /report/log.doJSONRecords the viewer onOutput event in REPORT_OUTPUT_LOG
GET /report/logs.doJSPThe latest 100 output log entries

The sample project is provided on request: email [email protected].

Building a report

Report definition

A report is a JSON object: page setup, a default font, optional parameters and styles, and bands of positioned elements. The layout engine binds it to data and produces pages; the viewer, printing, PDF and Excel all draw those same pages, so they match.

{
  "name": "거래처별 매출",
  "page": { "size": "A4", "orientation": "portrait", "margins": 15 },
  "font": { "family": "Nanum Gothic", "size": 9 },
  "sort": ["customer", "date"],
  "bands": {
    "title": { "height": 14, "elements": [
      { "type": "text", "x": 0, "y": 0, "w": 180, "h": 10, "text": "거래처별 매출", "style": { "fontSize": 16, "bold": true } }
    ] },
    "columnHeader": { "height": 7, "elements": [
      { "type": "text", "x": 0, "y": 0, "w": 30, "h": 7, "text": "일자", "style": { "bold": true, "border": true } },
      { "type": "text", "x": 30, "y": 0, "w": 110, "h": 7, "text": "거래처", "style": { "bold": true, "border": true } },
      { "type": "text", "x": 140, "y": 0, "w": 40, "h": 7, "text": "금액", "style": { "bold": true, "border": true } }
    ] },
    "detail": { "height": 6, "elements": [
      { "type": "field", "x": 0, "y": 0, "w": 30, "h": 6, "expr": "date", "format": "yyyy-MM-dd", "style": { "border": true } },
      { "type": "field", "x": 30, "y": 0, "w": 110, "h": 6, "expr": "customer", "suppressRepeated": true, "style": { "border": true } },
      { "type": "field", "x": 140, "y": 0, "w": 40, "h": 6, "expr": "amount", "format": "#,##0", "style": { "border": true, "align": "right" } }
    ] },
    "summary": { "height": 8, "elements": [
      { "type": "text", "x": 0, "y": 1, "w": 180, "h": 7, "text": "합계 {sum(amount):#,##0}원 ({koreanAmount(sum(amount))})", "style": { "align": "right", "bold": true } }
    ] },
    "pageFooter": { "height": 8, "elements": [
      { "type": "text", "x": 0, "y": 2, "w": 180, "h": 5, "text": "{page} / {pages}", "style": { "align": "center" } }
    ] }
  }
}
NameTypeDescription
namestringReport name; used for file names and the output event.
pagePageSetupPaper size, orientation and margins.
font{ family, size, color }Default font of the report (default Nanum Gothic 9pt).
stylesRecord<string, Style>Named styles; elements refer to them with styleName.
paramsRecord<string, ParamDefinition>Report parameters shown in the viewer's parameter panel.
dataSourcestringWhich array in the data holds the detail rows (default "rows").
sortstring[]Sort detail rows, e.g. ["region", "-amount"] ("-" = descending).
filterExprKeep only rows where the expression is truthy.
bandsobjecttitle, pageHeader, columnHeader, groups, detail, summary, pageFooter, noData.
pageHeaderSkipFirstbooleanDo not print the page header on the first page.
fillEmptyRowsbooleanFill the rest of each page with blank detail rows (form tables).
watermarkstring | Watermark | Watermark[]Text or image marks behind (or over) the content.
embossEmboss | Emboss[]Raised seals, logos or text pressed into the paper.

All positions and sizes are millimetres from the top-left of the band. Font sizes are points.

Page setup

NameTypeDescription
size'A3' | 'A4' | 'A5' | 'B4' | 'B5' | 'Letter' | 'Legal' | [w, h]Paper name or [width, height] in mm. Default A4.
orientation'portrait' | 'landscape'Default portrait.
marginsnumber | [top, right, bottom, left]Margins in mm. Default 15.

Bands

Bands are printed in this order: title (first page), then on every page the page header and column header, the group headers, one detail band per row, group footers, the summary after the last row, and the page footer at the bottom of every page.

NameTypeDescription
titleBandFirst page only, before everything else (title, letterhead, whole forms).
pageHeaderBandTop of every page.
columnHeaderBandBelow the page header on every page with detail rows (table column titles).
groupsGroupDefinition[]Group headers and footers, outermost first.
detailBandOnce per detail row.
summaryBandAfter the last row (grand totals, charts).
pageFooterBandBottom of every page (page numbers, page subtotals).
noDataBandPrinted instead of the detail rows when there is no data.

Band properties

NameTypeDescription
heightnumberHeight in mm.
elementsReportElement[]Positioned elements; x, y, w, h in mm inside the band.
canGrowbooleanGrow when text needs more room (default true for detail and group bands).
visibleExprPrint the band only when the expression is truthy.
pageBreakBefore / pageBreakAfterbooleanStart a new page before / after the band.
backgroundstringBand background color.
altBackgroundstringDetail only: alternate row background (zebra).
columns / columnGapnumberDetail only: print rows side by side in columns (label sheets, cards); gap in mm.

Groups and subtotals

A group collects consecutive rows with the same value. Aggregates in its header and footer cover the group; in the page footer they can cover the page; anywhere you can ask for the whole report.

"groups": [{
  "by": "region", "sort": "asc", "repeatHeader": true, "keepWithRows": 2,
  "header": { "height": 7, "elements": [
    { "type": "text", "x": 0, "y": 0, "w": 180, "h": 7, "text": "■ {region} ({count()}건)", "style": { "bold": true } }
  ] },
  "footer": { "height": 7, "elements": [
    { "type": "text", "x": 0, "y": 0, "w": 140, "h": 7, "text": "{region} 소계", "style": { "align": "center" } },
    { "type": "field", "x": 140, "y": 0, "w": 40, "h": 7, "expr": "sum(amount)", "format": "#,##0", "style": { "align": "right" } }
  ] }
}],
"pageFooter": { "height": 7, "elements": [
  { "type": "text", "x": 0, "y": 1, "w": 180, "h": 6, "text": "이 페이지 소계 {sum(amount, 'page'):#,##0}원 · 누계 {sum(amount, 'report'):#,##0}원" }
] }
NameTypeDescription
byExprExpression that groups consecutive rows, e.g. "region".
sort'asc' | 'desc'Sort rows by the group expression first (otherwise rows keep their order).
header / footerBandPrinted before / after each group; aggregates cover the group.
newPagebooleanEach group starts on a new page.
repeatHeaderbooleanRepeat the group header on pages where the group continues.
keepWithRowsnumberKeep the header with at least this many rows (default 1).
resetPageNumberbooleanRestart page / pages for each group.

Elements

NameTypeDescription
type'text' | 'field' | 'line' | 'rect' | 'image' | 'barcode' | 'chart' | 'table' | 'crosstab'Element kind.
x, y, w, hnumberPosition and size inside the band, in mm.
idstringOptional id (the designer sets one).
style / styleNameStyle / stringInline style, merged over the named style.
visibleExprDraw only when truthy.
canGrowbooleanGrow downwards when the text needs room (the band grows with it).
stretchbooleanFollow the band height when it grows (default for elements touching the band bottom).
namestringColumn name in Excel "data" mode.

Element types

typePropertiesDescription
text{ text }Static or templated text: "합계 {sum(amount):#,##0}원". {{ and }} are literal braces.
field{ expr, format?, nullText?, suppressRepeated? }One expression with an optional format; suppressRepeated hides values equal to the row above.
line{ color?, width?, dash? }From (x, y) to (x + w, y + h); a horizontal line has h = 0. Width in mm.
rect{ fill?, color?, width?, radius? }Rectangle; width 0 for no stroke.
image{ src? | expr?, fit? }URL or data: URL, or an expression from data. fit: contain | cover | fill.
barcode{ expr, format?, showText?, ecc?, color? }format: 'code128' | 'ean13' | 'qr' | 'datamatrix' | 'pdf417'. ecc: QR error correction L/M/Q/H.
chart{ chartType, category, series, source?, title?, legend?, format?, showValues?, stacked? }chartType: 'bar' | 'hbar' | 'line' | 'area' | 'pie' | 'doughnut'.
table{ source, columns, rowHeight?, showHeader?, headerStyle?, footerLabel?, altBackground? }A list inside a band (master–detail); columns: { header, expr, width?, format?, align?, sum? }.
crosstab{ rowKey, columnKey, value, aggregate?, format?, totals?, corner?, source?, cellHeight?, rowHeaderWidth?, headerStyle? }Rows × columns with an aggregated value per cell and totals. aggregate: sum | count | avg | min | max.

Styles and borders

Inline styles merge over a named style (styleName), which merges over the report font. Borders take a shorthand per side, so table cells can share edges.

"styles": {
  "th": { "bold": true, "align": "center", "background": "#eef1f6", "border": "0.2 solid #8a94a6" },
  "num": { "align": "right", "border": { "bottom": "0.15 solid #b9c1cd" } }
},
"bands": { "detail": { "height": 6, "elements": [
  { "type": "field", "x": 140, "y": 0, "w": 40, "h": 6, "expr": "amount", "format": "#,##0", "styleName": "num", "style": { "bold": true } }
] } }
NameTypeDescription
fontFamily, fontSizestring, numberFont family and size in points (default 9).
bold, italic, underline, strikebooleanFont styles.
color, backgroundstringText and background color.
align, valign'left' | 'center' | 'right' | 'justify'; 'top' | 'middle' | 'bottom'Horizontal and vertical alignment.
paddingnumber | [t, r, b, l]Inner padding in mm (default 1 horizontally, 0.5 vertically).
bordertrue | "0.3 solid #333" | { top, right, bottom, left }true = 0.2mm solid black; a CSS-like string; or per side.
lineHeightnumberMultiple of the font size (default 1.25).
wrapbooleanWrap long text (default true); false cuts at the box edge.
shrinkToFitbooleanShrink the font until one line fits.
letterSpacingnumberLetter spacing in mm (spaced titles like 견 적 서).

Data and expressions

Expressions and functions

Fields, visibility rules, filters and text templates use a small, safe expression language: arithmetic, comparisons, && || ! ?? and ?:, member access (customer.name, items[0]) and the functions below. It has no access to globals and cannot assign. In text, write expressions in braces with an optional format after a colon: "{sum(amount):#,##0}원".

price * qty                         arithmetic
amount >= params.minAmount           comparison with a parameter
customer.name ?? '(미지정)'           member access and fallback
qty > 10 ? '대량' : '일반'             condition
sum(amount)                          aggregate over the band's scope
sum(amount, 'page')                  page subtotal
sum(amount) / sum(amount, 'report')  share of the grand total
format(date, 'yyyy년 M월 d일 (ddd)')  2026년 10월 15일 (목)
koreanAmount(1230000)                일금 일백이십삼만원정
bizNo('7928703276')                  792-87-03276
{page} / {pages}                     page x / y in a text template

Variables

NameTypeDescription
page, pagesnumberPage number and page count (per group with resetPageNumber).
rowNumnumberRow number within the group (or report).
rowIndexnumberRow number within the report.
groupKey, groupLevelunknown, numberCurrent group value and depth.
nowDateTime the report was laid out.
paramsobjectParameter values: params.from.
row, rows, allRows, dataobject, arrayCurrent row, rows in scope, all rows, the whole data object.

Aggregates and scopes

The scope is the second argument: 'group' (the current group), 'page' (rows on the current page — page subtotals) or 'report' (every row). Without it, aggregates use the band's natural scope: the group in group bands, the page in page bands, the whole report elsewhere.

NameTypeDescription
sum(expr, scope?)numberSum over the scope.
avg / min / max(expr, scope?)numberAverage, minimum, maximum.
count(expr?, scope?)numberRows (or non-empty values).
distinct(expr, scope?)numberNumber of different values.
first / last(expr, scope?)unknownValue of the first / last row.
runningSum(expr, scope?)numberSum up to and including the current row (default scope report).

Functions

NameTypeDescription
format(v, f)stringFormat a number or date: format(date, "yyyy-MM-dd").
koreanAmount(n) / korean(n)string1230000 → 일금 일백이십삼만원정.
bizNo(v), phone(v)string1234567890 → 123-45-67890; 0212345678 → 02-1234-5678.
round / floor / ceil(v, digits?)numberRounding to digits after the point.
abs, number, string—Conversions.
upper, lower, trim, len, left, right, substr, replace, concat, padStart, joinstringText functions.
iif(c, a, b), coalesce(...), isEmpty(v)—Conditions and fallbacks (?: and ?? work too).
date, today, now, year, month, day, addDays, dateDiff, ymd—Dates.
groupSum(rows, key, value), groupCount(rows, key)arrayTotals per key, handy as a chart source.
min2(a, b), max2(a, b)numberSmaller / larger of two values.

Formats

A field's format (or the part after the colon in a template) formats numbers with Excel-style patterns and dates with yyyy, MM, dd, HH, mm, ss patterns.

FormatValueDescription
#,##0 #,##0.00 0.0%numberExcel-style number patterns.
₩#,##0 #,##0원 #,##0,"천"numberPrefixes, suffixes and thousands scaling.
#,##0;(#,##0);-numberSections: positive; negative; zero.
yyyy-MM-dd HH:mm:ssdateDates and times.
yyyy년 M월 d일 (ddd)dateKorean dates with the weekday (월, 화…).
a h:mmdate오전/오후 (AM/PM).
korean, koreanNumbernumber일금 ○○원정 / 십이만삼천.
bizNo, upper, lowerstringBusiness number and case.

Parameters

Parameters appear in the viewer's parameter panel and in expressions as params.name. With fetchData the viewer asks your server for the rows whenever the user presses Run.

// definition.params: { from: { label: '시작일', type: 'date', required: true },
//                      region: { label: '지역', type: 'select', options: [{ value: '' , label: '전체' }, { value: '서울' }] } }
createViewer(el, {
  definition,
  paramsOpen: true,
  fetchData: params => fetch('/api/sales?' + new URLSearchParams(params)).then(r => r.json()),
});
NameTypeDescription
labelstringLabel in the parameter panel.
type'string' | 'number' | 'date' | 'boolean' | 'select'Input type.
defaultunknownInitial value.
options{ value, label? }[]Choices for select.
requiredbooleanMust be filled before 조회 (Run).

Barcodes, charts and marks

Barcodes

Barcodes are drawn as vector shapes, so they stay sharp at any zoom, in print and in the PDF. Supported: Code128, EAN-13 (12 digits; the check digit is added), QR (error correction L/M/Q/H), DataMatrix and PDF417. With a multi-column detail band (columns: 3) one row becomes one label on a label sheet. Label demo

"detail": { "height": 38, "columns": 3, "columnGap": 3, "elements": [
  { "type": "field", "x": 2, "y": 2, "w": 36, "h": 6, "expr": "name", "style": { "bold": true } },
  { "type": "barcode", "x": 40, "y": 2, "w": 18, "h": 18, "expr": "'https://example.com/a/' + assetNo", "format": "qr", "ecc": "M" },
  { "type": "barcode", "x": 2, "y": 24, "w": 56, "h": 11, "expr": "assetNo", "format": "code128" }
] }

Charts, tables, crosstabs

A chart takes rows from its scope (or any array from source), a category per row and one or more series. groupSum() turns rows into totals per key. A table element lists an array inside a band (the items of each invoice); a crosstab tabulates rows × columns with totals. Charts demo · Crosstab demo

{ "type": "chart", "x": 0, "y": 16, "w": 110, "h": 60, "chartType": "bar", "title": "지역별 매출",
  "source": "groupSum(rows, 'region', 'amount')", "category": "region",
  "series": [{ "expr": "amount", "label": "매출" }], "format": "#,##0" },
{ "type": "table", "x": 0, "y": 20, "w": 180, "h": 20, "source": "items", "altBackground": "#fafbfd",
  "columns": [{ "header": "품목", "expr": "name" }, { "header": "수량", "expr": "qty", "width": 20, "align": "right" },
              { "header": "금액", "expr": "qty * price", "width": 40, "format": "#,##0", "sum": true }] },
{ "type": "crosstab", "x": 0, "y": 0, "w": 180, "h": 50, "rowKey": "region", "columnKey": "category",
  "value": "amount", "aggregate": "sum", "format": "#,##0", "corner": "지역 / 품목군" }

Watermarks and embossing

A watermark is a string or settings, or a list of them. Because the text is a template, a security watermark can carry who printed the page and when, repeated over the page so a photo or copy still shows it.

"watermark": [
  { "text": "{params.user} · {format(now(), 'yyyy-MM-dd HH:mm')} 출력", "repeat": true, "opacity": 0.07, "fontSize": 10, "gap": 40 },
  { "text": "사 본", "fontSize": 110, "color": "#c8102e", "pages": "all" }
],
"emboss": { "text": "(주)세컨드코리아 대표이사 직인 ·", "centerText": "인증", "x": 140, "y": 240, "w": 34 }
NameTypeDescription
textstringTemplate, e.g. "{params.user} · {format(now(), 'yyyy-MM-dd HH:mm')}".
image, imageWidthstring, numberImage mark instead of (or with) text; width in mm.
repeat, gapboolean, numberTile over the page (security pattern); distance in mm (default 45).
opacity, angle, color, fontSize, bold—Default opacity 0.12, angle -35°.
pages'all' | 'first' | 'last' | 'odd' | 'even'Which pages.
layer'back' | 'front'Behind the content (default) or over it.

Web embossing

Embossing draws a raised seal, logo or text with light and shadow, like a stamp pressed into paper. It appears on screen, in print and in the PDF.

NameTypeDescription
text, centerTextstringText around a round seal and in its centre (templates allowed).
imagestringA logo turned into a relief.
x, y, w, hnumberPosition and size in mm (default a 32mm seal near the bottom right).
shape'circle' | 'rect' | 'none'Outline.
depth, color, opacity, fontSize, pages—Relief depth in mm (default 0.3), paper tone, opacity, pages.

Viewing and output

Viewer options

The viewer draws pages as SVG when they scroll into view. Keys: PageUp/PageDown, Ctrl+Home/End, Ctrl+P (print), Ctrl + wheel (zoom).

NameTypeDescription
definitionReportDefinitionThe report (JSON).
dataReportData{ rows: [...] } plus any other values used by expressions.
fetchData(params) => data | PromiseLoad data for the parameters (on start and on 조회).
paramsobjectInitial parameter values (over the defaults).
lang'ko' | 'en'Toolbar language (default ko).
toolbar{ pages, zoom, print, pdf, xlsx, xlsxData, csv, params }Buttons to show (csv is off by default).
zoomnumber | 'width' | 'page'Initial zoom (default width).
paramsOpenbooleanOpen the parameter panel at start.
fileNamestringBase name of saved files.
licenseKeystringLicense key (or setLicenseKey once).
onOutput(e) => boolean | void | PromiseBefore every print or export; return false to cancel.
pdfSecurity{ userPassword?, ownerPassword?, permissions? }PDF password and permissions (AES-256).
authorstringPDF metadata author.

Viewer methods

NameTypeDescription
refresh()Promise<void>Run the report again.
setData(data) / setParams(p) / setDefinition(def)Promise<void>Change data, parameters or the definition and re-run.
goTo(page) / setZoom(z)voidNavigate and zoom.
print()Promise<boolean>Print (false when onOutput cancelled).
reportReportThe report object below (toPdf, toXlsx…).
destroy()voidRemove the viewer.

Designer

createDesigner mounts the web designer: a toolbox, a data field list, the bands, a property panel, preview and JSON views, undo and redo. Give it sample data so the field list and the preview have something to show. Try the designer

import { createDesigner } from '@freereportjs/core';
import '@freereportjs/core/styles.css';

const designer = createDesigner(document.getElementById('designer'), {
  definition,               // leave out to start with a blank report
  data: { rows },           // sample data for the field list and the preview
  lang: 'ko',
  onChange: def => localStorage.setItem('draft', JSON.stringify(def)),
  onSave: def => fetch('/api/reports/sales', { method: 'PUT', body: JSON.stringify(def) }),
});
designer.setMode('preview');      // 'design' | 'preview' | 'json'
const json = designer.getDefinition();

Print, PDF, Excel, CSV

Printing uses the browser at the exact paper size of the report, with the same line breaks as on screen. PDF embeds a subset of the TrueType fonts, so text is searchable and copyable. Excel "form" keeps the printed layout as merged cells with borders; "data" writes one row per record for further work.

import { createReport } from '@freereportjs/core';

const report = createReport(definition, { rows });
await report.ready();                    // wait for web fonts, then lay out

await report.print();                    // browser print at the paper size
const pdf = await report.toPdf();        // Blob, fonts embedded
const form = await report.toXlsx('form'); // merged cells and borders, like the printout
const data = await report.toXlsx('data'); // one row per record
await report.download('pdf', '매출 보고서'); // or save directly
NameTypeDescription
createReport(def, data, options?)ReportBind a definition to data without a viewer.
ready()Promise<LayoutResult>Wait for web fonts, then lay out. Call before the first output in browsers.
layout()LayoutResultPages, row count and time (cached).
print({ pages? })Promise<boolean>Print through the browser at the exact paper size.
toPdf()Promise<Blob | null>PDF with embedded font subsets.
toXlsx('form' | 'data')Promise<Blob | null>"form" keeps merged cells and borders; "data" writes one row per record.
toHwpx()Promise<Blob | null>Hangul document (.hwpx, KS X 6101) laid out like the printed pages.
toCsv()Promise<Blob | null>CSV (UTF-8 with BOM, so Excel shows Korean correctly).
download('pdf' | 'xlsx' | 'xlsx-data' | 'csv' | 'hwpx', name?)Promise<boolean>Create and save the file.

Fonts

Nanum Gothic and Nanum Myeongjo are built in (SIL Open Font License): a web stylesheet for screen and print, TrueType files for PDF, loaded from the jsDelivr CDN when first needed. registerFont adds your own family; host the files yourself on an intranet.

import { registerFont } from '@freereportjs/core';

registerFont('Malgun Gothic', {
  css: '/fonts/malgun.css',          // @font-face rules for screen and print (optional)
  normal: '/fonts/malgun.ttf',       // TrueType for the PDF (URL or Uint8Array)
  bold: '/fonts/malgunbd.ttf',
});
// then: "font": { "family": "Malgun Gothic" } or "style": { "fontFamily": "Malgun Gothic" }

Server, security and operations

Server rendering

renderPdf and renderXlsx lay out and write files without a browser (Node, Deno, Bun, workers). Text is measured with the TrueType fonts that the PDF embeds, so line breaks match. Use it for batch jobs, scheduled reports and e-mail attachments.

// Node 20+, Deno or Bun — no browser needed
import { writeFile } from 'node:fs/promises';
import { loadFonts, renderPdf, renderXlsx } from '@freereportjs/core';

const fonts = await loadFonts(['Nanum Gothic']);        // TTF bytes (or read your own files)
const pdf = await renderPdf(definition, { rows }, { fonts, params: { user: 'batch' } });
await writeFile('sales.pdf', pdf);
await writeFile('sales.xlsx', await renderXlsx(definition, { rows }, { fonts, mode: 'form' }));

PDF security

pdfSecurity encrypts the PDF with AES-256 (PDF 2.0 standard security). With a user password the file asks for it when opened; permissions limit printing, copying and changes in PDF readers. A random owner password is used when you give none.

createViewer(el, {
  definition, data,
  pdfSecurity: {
    userPassword: '1234',                      // asked when the PDF is opened (omit: opens freely)
    permissions: { print: true, copy: false, modify: false },
  },
});

Output log hook

onOutput is called before every print and export with the kind, report name, pages, rows and parameters. Write it to your output log (required for personal-data systems in Korea), ask for a reason, or return false to block it. Output control demo

createViewer(el, {
  definition, data, params: { user: '홍길동' },
  // e = { kind: 'print' | 'pdf' | 'xlsx' | 'xlsx-data' | 'csv', report, pages, rows, params, fileName }
  onOutput: async e => {
    const res = await fetch('/api/output-log', { method: 'POST', body: JSON.stringify(e) });
    return res.ok;   // false cancels the print or download
  },
});

License key

Personal use needs no key. Companies get a key for their domains (FR1.…) and set it once, or pass licenseKey. Without a valid key on a public domain the library prints one notice in the developer console; nothing on screen changes and no feature is turned off. Local and private network addresses never need a key. Pricing

import { setLicenseKey } from '@freereportjs/core';

setLicenseKey('FR1.eyJkb21haW5zIjpb...');   // once, before creating viewers
// or: createViewer(el, { definition, data, licenseKey: 'FR1.eyJkb21haW5zIjpb...' })

Help

Asking an AI assistant

When you ask ChatGPT, Claude, Gemini or Cursor to write a FreeReport definition, have it read https://freereport.app/llms-full.txt first: every option, element, function and format in one file. Ask it not to invent options that are not in the file.