PDF exports: one PDF document from a part of the content tree
This guide teaches the PDF export pages of the administration interface (Setup > PDF export, /pdf/list): what a
PDF export is, what every part of the list and the edit form says, how to create, download, regenerate and remove
an export, and what to do when a PDF is empty or will not generate.
Warning
.htaccess slows down every request. When .htaccess files are enabled (AllowOverride anything but None), let alone
used, Apache looks for them and reads and evaluates them on every request, in every directory of the path, and
page loading times go up drastically (measured). The only real fix is to switch the feature off for
the virtual host: AllowOverride None in the HTTPS virtual host, and the rules in the virtual host's own
configuration as mod_rewrite rules. That is far faster in production speed tests and well worth the time it takes
to optimize a production configuration. Switch over right away: copy the rules of the
example HTTPS virtual host into your virtual host (with the static cache, also
staticcache_offload.conf), set AllowOverride None, run apachectl configtest, reload Apache and
test the site. The same rules for other servers: nginx, LiteSpeed,
Caddy, IIS, Velocity (built in); where virtual host rules go in a control
panel: Plesk, cPanel, DirectAdmin, ISPConfig.
On LiteSpeed or OpenLiteSpeed, the server's own page cache can also answer anonymous visitors and crawlers without PHP: Page caching with LiteSpeed and OpenLiteSpeed.
It is for administrators who hand out content as a PDF (a handbook, a product sheet, a recipe collection). Every
figure and message below was checked against the code (kernel/private/classes/views/pdf/list.php,
kernel/private/classes/views/pdf/edit.php, kernel/classes/pdfexport/*.php, kernel/classes/ezpdfexport.php)
on the demonstration server on 6 October 2026, on Apache with PHP-FPM and on FrankenPHP.
Guides · Related: Templates and design, Paging, sorting and page sizes, Safe redirects
In short
- A PDF export makes one PDF of a source node: its name as the first heading, then its attributes. A tree export adds the nodes below it, at every level, but only those of the classes you tick.
- Generated once: the PDF is written when the export is saved and again with Regenerate; the file is kept in
var/<site>/storage/pdf/<file name>and served there at a public address. Fast, but it shows the content as it was then. - Generated on the fly: the PDF is made anew for every download. Always current; a large tree takes a while every time.
- The list leads with figures, a search, filters and an order, then one card per export with Download, Regenerate and Edit. Remove selected asks first and says which stored files go.
1. Opening the page
Setup > PDF export, or /pdf/list in the admin siteaccess. Both pages need the pdf/edit policy (Administrator has
it). Downloads go through /pdf/edit/<id>/generate, which needs the same policy.
A stored PDF is public: the web server serves var/<site>/storage/pdf/<file name> to anybody, signed in or not, and
the card shows that Public address to share or link. The rule is
RewriteRule ^var/([^/]+/)?storage/pdf/[A-Za-z0-9][A-Za-z0-9._-]*\.pdf$ - [L] in .htaccess_root and
.htaccess_root_static and a location in the nginx example. Nothing else below the storage directory
becomes public: other uploads stay behind content/download and its permissions. An installation with its own
.htaccess adds that one line after the storage/images rule; a server that keeps such rules in its own
configuration (a vhost file, an nginx front, Velocity's static paths) needs the same path there. Do not give an
export a file name you would not want guessed, and use an export generated on the fly for content that is not
for everybody: it is only downloaded through /pdf/edit/<id>/generate, by users with the pdf/edit policy. Before
6.0.15 the list linked the file at that address, but the web server refused it (404).
2. The list
| Part | What it says |
|---|---|
| Figures | Exports; generated once; generated on the fly; stored files; their size together; exports that need attention |
| Unfinished drafts | New exports that were started and never saved. They are removed once content.ini [PDFExportSettings] DraftTimeout (7200 seconds) has passed and somebody starts a new export |
| Find an export | Title, file name, ID, source node or class; every word must match. The search is part of the address (/(search)/<text>) |
| Show | All, Generated once, On the fly, With a stored file, Need attention, each with its count |
| Sort by | Title, Modified, Generated (date of the stored file), File size, ID; a second click reverses |
| Per page | The sizes of admininterface.ini [PaginationSettings] (ItemsPerPage[pdf/list], ItemsPerPageList_pdf_list[]); your choice is remembered |
Each card shows:
- Badges: Generated once or On the fly; File ready or No file; Source missing; Open in the editor by a user who has the form open.
- Source node with its class and node ID, or "Node N (no longer exists)".
- Contains: the source node only, or the source node and the nodes below it of the listed classes.
- Stored file with size and date, or "not generated"; for an export on the fly the Download name.
- Front page and Modified (when and by whom).
- Warnings: no source node chosen; the source node gone; the file not generated or removed; a file name that cannot be used; a tree without classes (only the source node is exported); classes that no longer exist.
Download sends the stored file, or makes the PDF at once for an export on the fly. Regenerate writes the stored file anew from the current content and says how large it is; it is greyed out, with the reason, when the source node is gone. A new file is written under a temporary name first, so a failed run leaves the old file alone.
3. Creating or editing an export
New PDF export opens the form at /pdf/edit/<id>; until you press OK it is a draft nobody else sees in the list.
- Title: the name in the list. It is not printed in the PDF.
- Content: choose the source node with Browse (what you typed is kept). The panel shows its path, class, section and number of children, or says that the node no longer exists. Pick Node (the source only) or Tree, and for a tree tick the classes to include. A node of an unticked class is left out with everything below it, so tick the folders that hold the content as well as the articles.
- Frontpage: an optional first page with a large and a smaller line of text.
- Footer text: the line beside the page number. Empty uses the default wording; untick for no text.
- Export type and File name: Generate once or on the fly. The file name is letters, digits,
.,_and-, up to 100 characters;.pdfis added when it is missing. A stored export's name must not be used by another stored export. On the fly, it is the name the download is offered under.
OK checks the form. An error is listed at the top with a link to its field and shown under the field:
| Message | Fix |
|---|---|
| Give the export a title. | Type a title |
| Choose the source node with Browse. / The chosen source node no longer exists. | Browse and pick a node |
| Choose at least one class to include below the source node, or export the source node only. | Tick classes, or choose Node |
| The file name must be a name only, without a folder, "..", or a leading dot. / Use only letters, digits, dots, dashes and underscores in the file name. | Rename the file |
| Another stored export already writes a file of that name. | Pick another name |
When everything is right the export is stored and, if generated once, its file is written; the list then says how large it is, or why it could not be written. Changing the name, or switching to on the fly, deletes the old file.
Cancel throws the changes away and goes back to the page the form was opened from, else to the list. If somebody else has the export open, the form says who and since when; saving replaces their unsaved changes.
4. Removing exports
Tick the cards and press Remove selected. The confirmation lists each export with its source node and the file that is deleted with it (name, size, date), and says that the content is not touched. Remove removes the export, a draft somebody has open and the stored file; Cancel changes nothing. The list then names what was removed. Reloading never removes twice.
5. Problems
| Symptom | Cause and fix |
|---|---|
| The PDF has only the first page of content | A tree without classes, or the folders between the source and the articles are not ticked. Tick them |
| "Its source node no longer exists" | The node was removed or moved to the trash. Edit the export and Browse for another node, or remove it |
| "The PDF templates wrote no file" | An override of node/view/pdf.tpl or node/view/execute_pdf.tpl failed. See var/log/error.log |
| A stored PDF shows old content | Generated once keeps the file; press Regenerate, or switch to on the fly |
| A large tree times out on the fly | Switch it to Generate once and regenerate it outside busy hours |
An export with a table answers 500 on PHP 8 (TypeError in lib/ezpdf/classes/ezpdf.php) | Fixed on 2026-10-12; update to the current 6.0.15 code (PDF export) |
For developers
expPDFExportFile: file name rules (normalizeName(),isSafeName(),nameProblem()),path(),facts(),remove()andsend()(chunked, session closed, buffers dropped; the caller callscleanExit()outside any catch).expPDFExportGenerator:render()(the two templates, as before, with the same variables),generateFile(),stream(),problemOf().generatePDF()is kept and calls them.expPDFExportInfo: the card (infoOf()), figures, search, filters, order, unfinished drafts andremove().expPDFExportForm::read(): the form, with the checks that need the database passed in.- Tests without a database:
php vendor/bin/phpunit tests/tests/kernel/classes/expPDFExportTest.php. - Velocity: the classes are kernel classes; deploy with
exp:velocity deploy --kernel.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.