BookStackApp/BookStack · error · PdfExportException
Failed to create required font data at $expectedUfm, Ensure
Error message
Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server
What it means
Thrown by getUserDomPdfFontFamilies in app/Exports/PdfGenerator.php when FontLib's saveAdobeFontMetrics() cannot write the generated .ufm (Adobe Font Metrics) file for a .ttf font found in storage/fonts/dompdf. The scan runs lazily during PDF export: for each font without an existing .ufm it parses the TTF and writes metrics next to it. Failure means the target path is not writable by the PHP process, or the font failed to parse into valid metrics.
Source
Thrown at app/Exports/PdfGenerator.php:103
protected function getUserDomPdfFontFamilies(): array
{
$fontStore = storage_path('fonts/dompdf');
if (!is_dir($fontStore)) {
return [];
}
$fontFamilies = [];
$fontFiles = glob($fontStore . DIRECTORY_SEPARATOR . '*.ttf');
foreach ($fontFiles as $fontFile) {
$fontFileName = basename($fontFile, '.ttf');
$expectedUfm = $fontStore . DIRECTORY_SEPARATOR . $fontFileName . '.ufm';
if (!file_exists($expectedUfm)) {
$font = Font::load($fontFile);
$font->parse();
try {
$font->saveAdobeFontMetrics($expectedUfm);
} catch (\Exception $exception) {
throw new PdfExportException("Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server");
}
}
$nameParts = explode('-', $fontFileName);
if (count($nameParts) === 1 || $nameParts[1] === 'Regular') {
$nameParts[1] = 'Normal';
}
$family = trim(strtolower(preg_replace('/([A-Z])/', ' $1', $nameParts[0])));
$variation = Str::snake($nameParts[1]);
if (!isset($fontFamilies[$family])) {
$fontFamilies[$family] = [];
}
$fontFamilies[$family][$variation] = $fontStore . DIRECTORY_SEPARATOR . $fontFileName;
}
return $fontFamilies;View on GitHub (pinned to 18f8469a1c)
Solutions
- Grant write permission on storage/fonts/dompdf to the web server user: chown -R www-data:www-data storage/fonts/dompdf; chmod -R u+rwX storage/fonts/dompdf.
- Validate/re-upload the offending .ttf named in the message — if Font::parse() fails the metrics can never be saved; ensure it is a genuine TTF file.
- Pre-generate the .ufm manually once (e.g. with a small PHP script using FontLib) as a user with write access, so the export path never needs to write.
- Check disk space and mount flags (df -h, mount | grep storage) to rule out a full or read-only filesystem.
- If the font keeps failing, remove it from storage/fonts/dompdf to unblock PDF exports.
Example fix
// before (shell) # storage/fonts/dompdf not writable -> 'Failed to create required font data at .../DejaVuSans.ufm' // after sudo chown -R www-data:www-data storage/fonts/dompdf sudo chmod -R u+rwX storage/fonts/dompdf
Defensive patterns
Strategy: validation
Validate before calling
<?php
$fontDir = storage_path('fonts/dompdf');
$ok = is_dir($fontDir) && is_writable($fontDir);
if ($ok) {
foreach (glob($fontDir . '/*.ttf') as $ttf) {
$ufm = substr($ttf, 0, -4) . '.ufm';
if (!file_exists($ufm) && !is_writable(dirname($ufm))) { $ok = false; break; }
}
}
var_dump($ok); // false => fix permissions/fonts before exporting PDFs Try / catch
try {
$pdf = $pdfGenerator->fromHtml($html);
} catch (\BookStack\Exceptions\PdfExportException $e) {
if (str_contains($e->getMessage(), 'Failed to create required font data at')) {
// message names the exact .ufm path; repair perms for that dir or drop the bad .ttf
Log::error('Font metric generation failed', ['msg' => $e->getMessage()]);
}
throw $e;
} Prevention
- Check is_writable(storage_path('fonts/dompdf')) as a health check before enabling custom fonts.
- Only accept genuine TTF uploads (reject OTF/WOFF renamed to .ttf).
- Pre-generate .ufm files during deployment as the runtime user.
- Monitor disk space; full disks silently break saveAdobeFontMetrics.
When it happens
Trigger: Calling fromHtml() with the dompdf engine while storage/fonts/dompdf exists and contains a .ttf whose matching .ufm does not exist yet, and Font::load()/parse()/saveAdobeFontMetrics() throws — i.e. first-time metric generation for a user-uploaded custom font with an unwritable directory or an invalid font file.
Common situations: User has just uploaded a custom font via admin settings and the web server user lacks write access to storage/fonts/dompdf; the .ttf is corrupt or an unsupported format (e.g. OTF/WOFF renamed to .ttf); read-only container filesystems; SELinux denials; disk full.
Related errors
- Failed to create required font data in {$expectedPath}, Ensu
- PDF Export via command failed due to timeout at {$timeout} s
- PDF Export via command failed with exit code {$process->getE
- PDF Export via command failed, unable to read PDF output fil
- Unexpected type of key value provided
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/581c5a856312a29c.
Report an issue: GitHub.