FreeGridv1.6.0한국어

Print and PDF

Print or save a PDF with column headers and chosen rows repeated on every page, group rows, totals, conditional formats, a title, a footer and page numbers.

Live demoPrinting & PDF

Print or save a PDF on the paper you choose, with header rows repeated on every page, group rows, totals, a title and page numbers.

Code for this demo
import { createGrid, type ColumnDef, type PrintOptions } from '@freegridjs/core';
import { HEADERS, makeRows } from '../data';
import { button, select, separator, text, toggle, tr, type DemoContext } from '../ui';

export default function mount(ctx: DemoContext) {
  const { lang } = ctx;
  const h = (en: string, ko: string) => tr(lang, { en, ko });
  const t = HEADERS[lang];
  const columns: ColumnDef[] = [
    { field: 'name', header: t.name, width: 150 },
    { field: 'country', header: t.country, width: 130 },
    { field: 'company', header: t.company, width: 130 },
    { header: t.revenue, children: [
      { field: 'q1', header: t.q1, type: 'number', format: '#,##0', width: 100, aggFunc: 'sum' },
      { field: 'q2', header: t.q2, type: 'number', format: '#,##0', width: 100, aggFunc: 'sum' },
    ] },
    { field: 'growth', header: t.growth, type: 'number', format: '0.0%', width: 90,
      cellClass: p => (typeof p.value === 'number' && p.value < 0 ? 'neg' : 'pos') },
    { field: 'active', header: t.active, type: 'boolean', width: 70 },
    { field: 'joined', header: t.joined, type: 'date', width: 110 },
  ];
  const grid = createGrid(ctx.host, {
    columns, data: makeRows(60), locale: lang, rowNumbers: true, groupBy: ['country'],
    summary: { rows: [{ label: h('Total', '합계'), columns: { q1: 'sum', q2: 'sum' } }] },
  });

  // what the buttons below change
  const opts: PrintOptions = {
    title: h('Sales, first half of 2026', '2026년 상반기 매출 현황'),
    subtitle: h('By country · Q1 and Q2', '국가별 · 1~2분기'),
    footer: h('ACME Inc. · Internal use only', '(주)에이크미 · 대외비'),
    paper: 'A4',
    orientation: 'auto',
  };

  // the page print() would print, shown under the grid
  const preview = document.createElement('iframe');
  preview.className = 'print-preview';
  preview.title = h('Print preview', '인쇄 미리보기');
  preview.setAttribute('sandbox', '');
  ctx.host.after(preview);
  // a little padding stands in for the paper margins
  const refresh = () => { preview.srcdoc = grid.getPrintHtml(opts).replace('<body>', '<body style="padding:24px 28px">'); };
  grid.on('sortChange', refresh);
  grid.on('filterChange', refresh);
  grid.on('groupChange', refresh);
  refresh();

  text(ctx, h('Group rows, totals, colors and row numbers print as they are; the preview below updates as you change the options. Right-click → Print… works too.',
    '그룹 행·합계·색·행 번호가 그대로 인쇄됩니다. 옵션을 바꾸면 아래 미리보기가 바로 바뀌고, 우클릭 → 인쇄…로도 인쇄할 수 있습니다.'));
  separator(ctx);
  button(ctx, h('Print', '인쇄하기'), () => grid.print(opts));
  select(ctx, h('Paper', '용지'), [['A4', 'A4'], ['A3', 'A3'], ['B4', 'B4'], ['letter', 'Letter']], 'A4', v => { opts.paper = v; refresh(); });
  select(ctx, h('Direction', '방향'), [['auto', h('Auto', '자동')], ['portrait', h('Portrait', '세로')], ['landscape', h('Landscape', '가로')]], 'auto',
    v => { opts.orientation = v; refresh(); });
  separator(ctx);
  toggle(ctx, h('Group rows', '그룹 행'), true, on => { opts.groups = on; refresh(); });
  toggle(ctx, h('Totals', '합계 행'), true, on => { opts.summary = on; refresh(); });
  toggle(ctx, h('Row numbers', '행 번호'), true, on => { opts.rowNumbers = on; refresh(); });
  toggle(ctx, h('Stripes', '줄무늬'), true, on => { opts.striped = on; refresh(); });
  toggle(ctx, h('Page numbers', '페이지 번호'), true, on => { opts.pageNumbers = on; refresh(); });
  separator(ctx);
  // headers print on every page; repeatRows adds the first rows too, repeatTitle the title block
  toggle(ctx, h('Header on every page', '페이지마다 헤더'), true, on => { opts.repeatHeader = on; refresh(); });
  select(ctx, h('Repeat rows', '반복할 행'), [['0', h('None', '없음')], ['1', '1'], ['2', '2'], ['3', '3']], '0', v => { opts.repeatRows = Number(v); refresh(); });
  toggle(ctx, h('Title on every page', '페이지마다 제목'), false, on => { opts.repeatTitle = on; refresh(); });
  separator(ctx);
  button(ctx, h('Save as PDF', 'PDF로 저장'), () => {
    grid.exportPdf(h('sales.pdf', '매출현황.pdf'), opts);
    ctx.log(h('exportPdf → sales.pdf', 'exportPdf → 매출현황.pdf'));
  });
  return grid;
}

print() prints the rows as they are on screen (sort and filters included), and exportPdf() saves the same pages as a PDF file. Column groups, group rows (collapsed ones too, with counts and totals), summary rows, cellClass colours and conditional formats (colours, bars, icons) all print; columns keep their proportions on the page, and wide grids turn the page sideways. The right-click menu has "Print…" and "Export PDF" too.

Rows repeated on every page

Long tables repeat the column headers on every page (repeatHeader, on by default). Like a spreadsheet's "rows to repeat at top", repeatRows repeats the first rows as well, and repeatTitle: true repeats the title. The same settings apply to PDF and to Excel files (print titles).

grid.print({
  title: 'Sales, first half of 2026',
  subtitle: 'By country · Q1 and Q2',
  footer: 'ACME Inc. · Internal use only',
  paper: 'A4',              // A3, A5, B4, B5, letter, legal
  orientation: 'auto',      // sideways when the columns are wide
  repeatHeader: true,       // header rows on every page (default)
  repeatRows: 1,            // and the first row (e.g. last year's total) too
  repeatTitle: false,       // the title on the first page only
});
const html = grid.getPrintHtml({ title: 'Preview' });   // the HTML that prints (for a preview)

Save as PDF

The PDF is made in the browser without a library. Each page is drawn the way the screen draws it, so Korean, Japanese, Chinese and every other script print exactly as shown; the text in the PDF cannot be selected or searched. resolution (default 2) sets the sharpness and quality the file size.

grid.exportPdf('sales.pdf', { title: 'Sales, first half of 2026', repeatRows: 1 });

// or the bytes, to upload or attach to an email
const bytes = grid.getPdf({ paper: 'A3', resolution: 3 });   // Uint8Array (null without a canvas)
await fetch('/api/reports', { method: 'POST', body: bytes, headers: { 'Content-Type': 'application/pdf' } });

Excel print titles

// Excel files repeat the header rows on every printed page too (print titles)
grid.exportXlsx('sales.xlsx', { repeatRows: 1 });    // headers + the first row
grid.exportXlsx('sales.xlsx', { repeatHeader: false });

Options

NameTypeDescription
title / subtitle / footerstringHeading on top, a line under it, text at the bottom left of every page.
paper / orientation'A4' | 'A3' | 'A5' | 'B4' | 'B5' | 'letter' | 'legal' / 'auto' | 'portrait' | 'landscape'Default A4 and 'auto' (sideways when the columns are too wide).
repeatHeaderbooleanColumn headers on every page (default true). Excel exports get the same print titles.
repeatRowsnumberAlso repeat the first n rows on every page, under the headers.
repeatTitlebooleanThe title block on every page, not only the first.
fitToWidth / groups / summary / striped / pageNumbers / printedAt / rowNumbersbooleanScale columns to the page, group rows with totals, summary rows, stripes, "1 / 3", print time, a # column.
resolution / qualitynumberPDF only: page image sharpness (default 2) and JPEG quality (default 0.92).

Questions about this page

Can FreeGrid export a PDF?

Yes. grid.exportPdf("file.pdf", options) saves the print layout as a PDF in the browser, with any language, and getPdf() returns the bytes.

How do I repeat the header row on every printed page?

Headers repeat by default (repeatHeader). repeatRows: n also repeats the first n rows, and repeatTitle repeats the title; Excel exports get the same print titles.