---
name: package
description: "fpdf2 package guide for Python PDF generation with setup, fonts, layout defaults, and output handling"
metadata:
  languages: "python"
  versions: "2.8.7"
  revision: 1
  updated-on: "2026-03-12"
  source: maintainer
  tags: "fpdf2,pdf,python,fonts,unicode,html"
---

# fpdf2 Python Package Guide

## Golden Rule

Install `fpdf2`, but import from `fpdf`:

```bash
python -m pip install "fpdf2==2.8.7"
```

```python
from fpdf import FPDF
```

Create a fresh `FPDF` instance per document, call `add_page()`, set a font before writing text, and use `bytes(pdf.output())` when you need the PDF in memory for an HTTP response or file upload.

## Install

### pip

```bash
python -m pip install "fpdf2==2.8.7"
```

### uv

```bash
uv add "fpdf2==2.8.7"
```

### Poetry

```bash
poetry add "fpdf2==2.8.7"
```

If you are migrating from older PyFPDF-era code, verify the environment only has `fpdf2` installed. The modern package name is `fpdf2`, but the import path remains `fpdf`.

## Initialize A Document

`FPDF()` defaults to portrait, millimeters, and A4. Start by creating a document, enabling automatic page breaks, adding a page, and choosing a font:

```python
from fpdf import FPDF

pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", size=12)

pdf.cell(text="Hello from fpdf2")
pdf.ln(10)
pdf.multi_cell(text="This PDF was generated from Python.")

pdf.output("hello.pdf")
```

Useful constructor overrides:

```python
from fpdf import FPDF

pdf = FPDF(
    orientation="landscape",
    unit="pt",
    format=(612, 792),
)
```

- `orientation`: `"portrait"` or `"landscape"`
- `unit`: `"pt"`, `"mm"`, `"cm"`, or `"in"`
- `format`: named page size such as `"A4"` or a `(width, height)` tuple

## Core Usage

### Draw text and simple layout

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=12)

pdf.cell(text="Invoice #1234", new_x="LMARGIN", new_y="NEXT")
pdf.set_font_size(10)
pdf.multi_cell(
    text="Generated with fpdf2. Use multi_cell for wrapped paragraphs instead of trying to pack long text into cell()."
)

pdf.output("invoice.pdf")
```

`cell()` is best for single-line content. `multi_cell()` is the normal choice for wrapped paragraphs or user-provided text.

### Use a Unicode font for non-Latin text

Built-in PDF core fonts cover Latin text, but not arbitrary Unicode. Register a `.ttf`, `.otf`, `.ttc`, or `.otc` font file before writing multilingual text:

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()

pdf.add_font("DejaVuSans", fname="fonts/DejaVuSans.ttf")
pdf.set_font("DejaVuSans", size=14)
pdf.multi_cell(text="Hello, world\\nGreek: Γειά σου\\nArabic: مرحبا\\nChinese: 你好")

pdf.output("unicode.pdf")
```

If you only set a core font such as `Helvetica`, `Times`, or `Courier`, non-Latin characters will fail or render incorrectly.

### Insert images

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=12)
pdf.cell(text="Chart output", new_x="LMARGIN", new_y="NEXT")
pdf.image("artifacts/chart.png", x=10, y=25, w=120)
pdf.output("report.pdf")
```

Pass either `w` or `h` and let `fpdf2` preserve aspect ratio unless you intentionally want to distort the image.

### Write basic HTML fragments

`write_html()` is useful for simple rich text, lists, links, and tables generated by your app:

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=11)
pdf.write_html(
    """
    <h1>Status</h1>
    <p><b>Build:</b> green</p>
    <ul>
      <li>PDF generated with <a href="https://py-pdf.github.io/fpdf2/">fpdf2</a></li>
      <li>HTML support is intentionally limited</li>
    </ul>
    """
)
pdf.output("html.pdf")
```

Use this for controlled snippets, not as a full HTML-to-PDF browser engine.

### Return PDF bytes from a web API

`output()` returns a `bytearray` when you do not pass a filename. Convert it to `bytes` for frameworks that expect immutable byte strings:

```python
from flask import Response
from fpdf import FPDF

def download_pdf() -> Response:
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Helvetica", size=12)
    pdf.cell(text="Generated on demand")

    payload = bytes(pdf.output())
    return Response(
        payload,
        mimetype="application/pdf",
        headers={"Content-Disposition": "inline; filename=example.pdf"},
    )
```

## Configuration Notes

### Fonts and file paths

- `add_font()` expects a font file that your process can read at runtime.
- As of `2.8.6`, `fpdf2` supports `.woff` and `.woff2` in addition to `.ttf`, `.otf`, `.ttc`, and `.otc`.
- Keep font files in a stable application path and reference them with absolute or application-root-relative paths in production.

### Page geometry and breaks

- `set_auto_page_break(auto=True, margin=15)` is the safest default for text-heavy reports.
- `set_margins(left, top, right)` controls content bounds across the document.
- For custom sizes such as labels or receipts, pass an explicit `(width, height)` tuple to `format`.

### Output lifecycle

- `pdf.output("file.pdf")` writes to disk.
- `pdf.output()` returns the document as a `bytearray`.
- The API docs warn that content cannot be added after `output()` finalizes the document. Build the whole document first, then export once.

## Common Pitfalls

- Package name and import name differ. Install `fpdf2`, import from `fpdf`.
- Always call `add_page()` before writing visible content.
- Always set a font before calling `cell()`, `multi_cell()`, or `write_html()`.
- Core fonts are not a substitute for Unicode fonts. Use `add_font()` for multilingual output or symbols outside Latin coverage.
- `write_html()` is intentionally limited. It is not a browser engine and will not match arbitrary CSS-heavy templates.
- Reuse patterns from old `pyfpdf` examples carefully. Some deprecated compatibility parameters were removed in `2.8.5`.
- Generate a new `FPDF` object per document or request. Mutating and reusing an already-output document object is error-prone.

## Version-Sensitive Notes For 2.8.x

- `2.8.5` removed remaining traces of deprecated parameters from APIs and docs. If older examples pass `dest=` to `output()` or `uni=` to `add_font()`, update them.
- `2.8.6` added support for `.woff` and `.woff2` fonts, plus improvements around transforms and text regions.
- `2.8.7` is a bugfix release. If you depend on image metadata internals or type-checking behavior, prefer `2.8.7` examples over early `2.8.x` blog posts.

## Official Sources Used

- Docs root: `https://py-pdf.github.io/fpdf2/`
- Tutorial: `https://py-pdf.github.io/fpdf2/Tutorial.html`
- Text styling and Unicode fonts: `https://py-pdf.github.io/fpdf2/TextStyling.html`
- Output API reference: `https://py-pdf.github.io/fpdf2/fpdf/fpdf.html`
- Web API usage guide: `https://py-pdf.github.io/fpdf2/UsageInWebAPI.html`
- Project history and compatibility context: `https://py-pdf.github.io/fpdf2/History.html`
- Changelog: `https://py-pdf.github.io/fpdf2/Changelog.html`
- PyPI release page: `https://pypi.org/project/fpdf2/`
