Dart PDF
last modified October 18, 2026
In this article we show how to create PDF documents in Dart language.
Portable Document Format (PDF) is a versatile file format created by Adobe that gives people an easy, reliable way to present and exchange documents.
$ dart pub add pdf
To produce PDF files in Dart, we use the pdf package. It is
written in pure Dart and works in command-line programs, Flutter applications,
and on the web. The widgets resemble the Flutter widgets, but they are
different classes; therefore, we import the library with the pw
prefix.
Dart PDF simple example
The following is a simple Dart example which produces a PDF document.
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
void main() async {
final pdf = pw.Document();
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Center(
child: pw.Text('An old falcon'),
);
},
));
final file = File('simple.pdf');
await file.writeAsBytes(await pdf.save());
}
The program creates a new PDF file and writes it to the disk. It contains
centered text. The program prints nothing; the result is the
simple.pdf file.
import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw;
We import the required packages. The pdf.dart library contains
low-level types such as PdfPageFormat and PdfColors;
the widgets are in widgets.dart.
final pdf = pw.Document();
A new empty document is created.
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Center(
child: pw.Text('An old falcon'),
);
},
));
A page is added with addPage. We provide the page format and the
content. The content consists of a Text widget which is centered
on the page with Center. A pw.Page holds exactly one
page; for content that may span several pages, we use MultiPage.
final file = File('simple.pdf');
await file.writeAsBytes(await pdf.save());
The save method returns the document as bytes, which we write to a
file.
Dart PDF metadata
The Document constructor accepts metadata such as the title and the
author. They are shown by PDF viewers in the document properties.
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
void main() async {
final pdf = pw.Document(
title: 'Falcons',
author: 'Jan Bodnar',
subject: 'Birds of prey',
);
pdf.addPage(pw.Page(
build: (context) => pw.Center(
child: pw.Text(
'The peregrine falcon',
style: pw.TextStyle(
fontSize: 28,
fontWeight: pw.FontWeight.bold,
color: PdfColors.indigo,
),
),
),
));
await File('meta.pdf').writeAsBytes(await pdf.save());
}
The text is styled with TextStyle: we set its size, weight, and
colour. When no pageFormat is given, the A4 format is used.
Dart PDF header
A document header is created with Header.
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
void main() async {
const txt = '''
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.
''';
final pdf = pw.Document();
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Column(children: [
pw.Header(level: 0, text: 'The Battle of Thermopylae'),
pw.Paragraph(text: txt),
]);
},
));
await File('header.pdf').writeAsBytes(await pdf.save());
}
The document contains a header and a paragraph of text.
return pw.Column(children: [ pw.Header(level: 0, text: 'The Battle of Thermopylae'), pw.Paragraph(text: txt), ]);
The header and the paragraph are added to a column widget. We specify the level and text for the header and the text for the paragraph. The level 0 is the largest header.
Dart PDF bullets
A bullet is created with the Bullet widget.
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
void main() async {
const languages = ['Dart', 'F#', 'Clojure', 'Go', 'Groovy', 'Raku', 'Python'];
final pdf = pw.Document();
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Column(children: [
pw.Header(level: 1, text: 'Programming languages'),
for (final lang in languages) pw.Bullet(text: lang),
]);
},
));
await File('bullets.pdf').writeAsBytes(await pdf.save());
}
We have a header and a list of programming languages. Each language is displayed
as a bullet. A collection for creates the widgets from the list.
Dart PDF rectangles
A rectangle is created with Rectangle.
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
void main() async {
final colors = [
PdfColors.grey,
PdfColors.amber300,
PdfColors.green500,
PdfColors.blue600,
];
final pdf = pw.Document();
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
for (final color in colors)
pw.SizedBox(
width: 80,
height: 80,
child: pw.Rectangle(fillColor: color),
),
],
);
},
));
await File('rectangles.pdf').writeAsBytes(await pdf.save());
}
We create a row of SizedBox widgets. In the boxes, we place
rectangle widgets. The spaceBetween alignment distributes the free
space between the boxes, which replaces the Spacer widgets.
pw.SizedBox( width: 80, height: 80, child: pw.Rectangle(fillColor: color), ),
For each rectangle, we define a fill colour.
Dart PDF footer
In the next example, we add a footer to our pages.
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
void main() async {
final pdf = pw.Document();
final title = pw.LoremText();
pdf.addPage(
pw.MultiPage(
footer: _buildFooter,
build: (context) => [
pw.Header(level: 1, text: title.sentence(1)),
for (final n in [50, 70, 150, 250, 350, 450]) ...[
pw.SizedBox(height: 20),
pw.Lorem(length: n),
],
],
),
);
await File('footer.pdf').writeAsBytes(await pdf.save());
}
pw.Widget _buildFooter(pw.Context context) {
return pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
pw.Text(
'Page ${context.pageNumber}/${context.pagesCount}',
style: const pw.TextStyle(
fontSize: 12,
color: PdfColors.blue700,
),
),
],
);
}
The footer consists of a row that contains the current and the total page numbers.
final title = pw.LoremText();
The lorem classes provide some filler text for the document. This is a common practice in desktop publishing when testing.
pdf.addPage(
pw.MultiPage(
footer: _buildFooter,
build: (context) => [
...
],
),
);
We have a multi-page document. The build function returns a list
of widgets, which the MultiPage flows across as many pages as
needed. The creation of the footer is delegated to the
_buildFooter function.
pw.SizedBox(height: 20), pw.Lorem(length: n),
We add some empty space with SizedBox. The Lorem
widget adds n words of Latin filler text.
pw.Text(
'Page ${context.pageNumber}/${context.pagesCount}',
style: const pw.TextStyle(
fontSize: 12,
color: PdfColors.blue700,
),
),
The footer is a row with a Text widget. It contains the current
page number and the count of all pages. Also, we style the text: we define its
size and colour.
Dart PDF table
A table is created with TableHelper.fromTextArray. It is a complex
helper with many options. (In older versions of the package, the same
functionality was provided by the Table.fromTextArray constructor,
which is now deprecated.)
import 'dart:io';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
const users = [
('John Doe', 'gardener'),
('Roger Roe', 'driver'),
('Joe Smith', 'teacher'),
];
void main() async {
final pdf = pw.Document();
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: genTable,
));
await File('table.pdf').writeAsBytes(await pdf.save());
}
pw.Widget genTable(pw.Context ctx) {
return pw.TableHelper.fromTextArray(
border: null,
cellAlignment: pw.Alignment.centerLeft,
headerDecoration: const pw.BoxDecoration(
borderRadius: pw.BorderRadius.all(pw.Radius.circular(1)),
color: PdfColors.blue500,
),
headerHeight: 25,
cellHeight: 40,
headerStyle: pw.TextStyle(
color: PdfColors.white,
fontSize: 10,
fontWeight: pw.FontWeight.bold,
),
cellStyle: const pw.TextStyle(
color: PdfColors.blueGrey800,
fontSize: 10,
),
rowDecoration: const pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.blueGrey900,
width: .5,
),
),
),
headers: ['Name', 'Occupation'],
data: [
for (final (name, occupation) in users) [name, occupation],
],
);
}
We display users in a table. The users are stored in a list of records.
const users = [
('John Doe', 'gardener'),
('Roger Roe', 'driver'),
('Joe Smith', 'teacher'),
];
The data source for the table is a list of records. This replaces a dedicated class with an index-based accessor, which was needed in the older version of this example.
headerHeight: 25, cellHeight: 40, cellAlignment: pw.Alignment.centerLeft,
We have options that define the look of the table. Here we define the header and cell heights and the alignment of the cells.
rowDecoration: const pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.blueGrey900,
width: .5,
),
),
),
We define table bottom borders. We set the border colour and size.
headers: ['Name', 'Occupation'], data: [ for (final (name, occupation) in users) [name, occupation], ],
We set the headers for the table and fill it with data. Each row is a list of
strings; the record pattern in the for loop extracts the two
fields.
Source
Dart pdf library - documentation
In this article we have shown how to generate PDF documents in Dart.
Author
List all Dart tutorials.