Skip to content
Blume
Esc
↑↓navigate↵open⌘Jpreview
Guides

Writing

Offer PDF and EPUB downloads of your docs pages

Readers can save any docs page as a PDF or an EPUB, and you know what each format keeps, from long code blocks and tabs to images and diagrams.

By 9 min read

Readers can save a docs page for offline use once you turn on Blume's export option. Every docs page then gets an Export action under its table of contents, with Export to PDF and Export to EPUB. PDF goes through the browser's print dialog, with a print stylesheet that strips the site down to the article. EPUB is generated in the reader's browser and downloads as a .epub file for Apple Books, calibre, or an e-reader. Neither needs a server, so a static site stays static.

Both formats export the one page the reader is on, not your documentation as a book. If you need a whole manual with a cover and chapters, that's a publishing pipeline Blume doesn't provide. For PDF alone, you may not need the option: the print stylesheet ships on every Blume site, so a reader who presses Ctrl+P (Cmd+P on a Mac) already gets the stripped-down page. export adds a menu entry that opens the print dialog, plus the EPUB generator.

This guide turns the action on, builds a test page with the content that exports worst, checks both files against a checklist, and fixes what doesn't carry over.

Turn on the Export action

Add export to your config. true turns on both formats:

import { defineConfig } from "blume";

export default defineConfig({
  title: "Acme Docs",
  export: true,
});

To offer one format, pass an object instead. A format you leave out stays off, so this shows only the PDF entry:

export: { pdf: true },

The action sits below the table of contents. Where that is depends on the window and the page:

  • In a window at least 1,280 pixels wide, it's in the right-hand rail. In a narrower window or on a phone, it's at the bottom of the On this page menu above the content, and its PDF and EPUB entries open right under it.
  • Pages whose layout mode drops the table of contents (wide, center, frame, and custom) have no rail and no menu, and API operation pages have neither.
  • Turning the outline off with toc: false keeps both, so the action stays. The menu then holds only the page actions.

Build a test page

Exports go wrong on particular content, so test with a page that has all of it: a long command, a titled code block, a collapsed code block, tabs, a diagram, an image, a callout, and an accordion. Save this as docs/export-test.mdx, and put any screenshot at docs/images/dashboard.png:

---
title: Send your first message
description: Install the Acme SDK, send a test SMS, and check that it arrived.
---

Install the SDK with the package manager your project uses:

```package-install
npm i @acme/messages
```

## Set your API key

<Tabs>
  <Tab title="macOS and Linux">
    On macOS or Linux, export the key in your shell:

    ```bash
    export ACME_KEY="sk_test_123"
    ```

  </Tab>
  <Tab title="Windows">
    On Windows, set the key in PowerShell:

    ```powershell
    $env:ACME_KEY = "sk_test_123"
    ```

  </Tab>
</Tabs>

## Send a message

Send a test SMS from the command line:

```bash
curl -X POST https://api.acme.example/v1/messages -H "Authorization: Bearer $ACME_KEY" -H "Content-Type: application/json" -d '{"to":"+15550100","channel":"sms","template":"welcome"}'
```

Or from your code:

```ts send.ts
import { Acme } from "@acme/messages";

const acme = new Acme({ apiKey: process.env.ACME_KEY });
const message = await acme.messages.send({
  to: "+15550100",
  channel: "sms",
  template: "welcome",
});
console.log(message.id, message.status);
```

The API answers with the new message:

```json response.json expandable
{
  "id": "msg_01J9ZKQ4",
  "object": "message",
  "channel": "sms",
  "to": "+15550100",
  "template": "welcome",
  "status": "queued",
  "created_at": "2026-09-27T10:00:00Z",
  "updated_at": "2026-09-27T10:00:00Z",
  "segments": 1,
  "error": null,
  "metadata": {
    "source": "quickstart"
  },
  "links": {
    "self": "https://api.acme.example/v1/messages/msg_01J9ZKQ4"
  }
}
```

## Check delivery status

```mermaid
sequenceDiagram
  participant App
  participant API as Acme API
  participant Phone
  App->>API: POST /messages
  API-->>App: 202, status queued
  API->>Phone: SMS
  App->>API: GET /messages/msg_01J9ZKQ4
  API-->>App: status delivered
```

![The Acme dashboard with the test message marked Delivered](./images/dashboard.png)

| Status      | Meaning                        |
| ----------- | ------------------------------ |
| `queued`    | Accepted, not yet sent         |
| `sent`      | Handed to the carrier          |
| `delivered` | The carrier confirmed delivery |
| `failed`    | See the `error` field          |

:::warning
Test keys only send to phone numbers you've verified in the dashboard.
:::

<Accordion>
  <AccordionItem title="Why is my message still queued?">
    A message to a new number can wait a few minutes for carrier checks.
    Resend it from [Send a message](#send-a-message), or go back to
    [the overview](/).
  </AccordionItem>
</Accordion>

Run npx blume dev and open /export-test. Wait for the diagram to draw before you export. Mermaid renders in the browser after the page loads, and a diagram that hasn't finished prints as a blank space.

Check the PDF

You can export from either theme. When a reader prints from the dark theme, Blume switches the page to the light theme for the printout and back again afterward, so the PDF has dark text on white paper.

Choose Export, then Export to PDF. In Chrome, pick Save as PDF as the destination, open More settings, and turn on Background graphics so the callout keeps its fill. In Firefox, the destination is Save to PDF and the option is Print backgrounds. Other browsers word it differently.

The header, sidebar, rail, and breadcrumbs are gone, and so is everything after the article: feedback, "Last updated", previous and next links, and the site footer. Code blocks print without their buttons, and response.json prints in full even though it's collapsed on screen. Links stay clickable, and in Chrome they point at full URLs on your site. Compare the rest against the checklist below. On this page, expect two problems: the curl command is cut off at the right margin, and the accordion prints only its title.

Check the EPUB

Choose Export, then Export to EPUB. The entry reads Generating… while Blume loads the generator and downloads the page's images, then the browser saves export-test.epub. The file is named after the page's path, with slashes turned into hyphens, so /guides/setup becomes guides-setup.epub.

None of the site's CSS or JavaScript goes into the file. Blume rewrites the article as plain HTML with a small built-in e-reader stylesheet, and the result is one chapter:

  • The book's title is the page's title.
  • It opens on a contents page with a single entry, then the page.
  • The author and publisher are your site's title, and the language is the page's language, so a translated page exports as a book in that language.
  • Links to other pages become full URLs on your site, since the book holds only this page.

Open the file in Apple Books and in calibre's E-book viewer, and on an e-reader if your readers use one. Reading apps lay out the same file differently, so check each one against the checklist.

Export checklist

What happens to each part of the test page. The PDF column comes from Chrome's print output, and the EPUB column from the file's contents.

On the pageIn the PDFIn the EPUB
Header, sidebar, rail, breadcrumbsRemovedNot included
Feedback, "Last updated", previous and next links, site footerRemovedNot included
Code highlightingKeptPlain monospace
Long code linesCut off at the marginWrapped
Code block titles, like send.tsPrintedDropped
expandable code blocksPrinted in fullComplete
Copy buttons and iconsButtons removed, icons printedDropped
Tabs, including package installsEvery panel, under the tab stripEvery panel, with no tab labels
AccordionsTitle only, unless open when printedTitle and body; the reader decides how to show them
Mermaid diagramsPrinted, once renderedDropped
ImagesPrintedDownloaded into the file
Links to other pagesFull URLs to your siteFull URLs to your site

Two EPUB rows need a closer look in each app. Blume converts colocated images to WebP, which EPUB only made a core image format in version 3.3, so check that images show on the oldest reader you care about. And an image without alt text gets the alt text "image-placeholder" in the file.

Adjust the page for readable exports

Most fixes are to the content, and they make the web page clearer too.

Wrap long code lines

Paper can't scroll, so wrap long lines. Append wrap to one fence (```bash wrap), or wrap every block on the site:

markdown: {
  code: { wrap: true },
},

Both wrap on screen too. The print rules further down wrap only on paper. See Long lines and long blocks for the other fence options.

Say in the text what the chrome said

The EPUB drops code block titles and tab labels, so don't let them carry information alone. Name the file in the sentence before the block ("Or from your code, in send.ts:"), and name the platform inside each tab panel. The test page's tabs already do this, which is why they still read correctly without labels.

Give every diagram a text fallback

A Mermaid diagram draws itself with JavaScript, so it never reaches the EPUB. Add a sentence after it that states what it shows. When a diagram has to survive in both formats, render it to an image and reference that instead. Move the diagram source into diagrams/delivery.mmd, outside your content folder, then run:

npx -y -p @mermaid-js/mermaid-cli@12.0.0 mmdc -i diagrams/delivery.mmd -o docs/images/delivery.svg

Replace the fence with ![How a message moves from your app to a phone](./images/delivery.svg). The image doesn't follow the site's dark theme the way a live diagram does, so save this for diagrams readers need offline. The CLI also draws with Mermaid's own defaults, not Blume's dagre layout and classic look; to match the site, start the .mmd file with a front matter block that sets layout: dagre and look: classic under config:. The Mermaid guide covers writing diagrams in the first place.

Keep must-read content out of collapsed places

An accordion prints only its title unless the reader opened it before printing. Use accordions for optional detail, and keep a step or a required value where it prints. expandable code blocks don't have this problem: they print in full.

Host images next to the page

Reference images by relative path, as the test page does, and give each one alt text. The EPUB export downloads every image on the page, and a browser can only download an image from another host when that host allows it with CORS headers. An image it can't download is left out of the book.

Tidy the printout with theme.css

Blume's print stylesheet already removes the page chrome and opens expandable blocks. What it leaves alone is long lines, which print cut off. To wrap them on paper only, add a print rule to a theme.css in your project root:

@media print {
  .prose pre,
  .prose pre code {
    white-space: pre-wrap !important;
    overflow-wrap: anywhere;
  }
}

Troubleshooting

There's no Export action

Check that export is set in blume.config.ts and that the page uses the default layout mode. In a window narrower than 1,280 pixels, open the On this page menu above the content: the action is at the bottom of it. API operation pages never show it.

An image from another host is missing from the EPUB

Open the browser console. A warning that starts with [blume] EPUB export left out an image it couldn't fetch names the image the browser couldn't download, usually one on another host without CORS headers. The rest of the book still exports. Download the image into your content folder and reference it by relative path.

An image is missing from an EPUB made in dev

In blume dev, optimized images come from Astro's image endpoint, a URL with no file extension, and the EPUB records them without a media type. Do your final EPUB check on a production build:

npx blume build
npx blume preview

A diagram prints in dark colors

The page prints in the light theme, but a Mermaid diagram redraws itself for a theme change in the background, and a print that starts before the redraw finishes keeps the dark drawing. Switch the site to the light theme, wait for the diagram to redraw, and export again.

A diagram is blank in the PDF

It was printed before Mermaid finished drawing it. Wait until the diagram shows on screen, then print again.

Next step

Turn on page export

Add export: true to blume.config.ts, then export your longest page in both formats and check it against the checklist.

Read the export docs

A step here not working for you? Report a broken step.

Keep going.More guides.

Upgrade your docs with Blume.

Install today and ship a production-grade docs site in minutes. Free and open source, forever.

npx blume init