{"record":{"id":"97b199112f89d4df","repo":"BookStackApp/BookStack","slug":"failed-to-create-required-font-data-in-expectedp","errorCode":null,"errorMessage":"Failed to create required font data in {$expectedPath}, Ensure all content in this location is writable by the web server","messagePattern":"Failed to create required font data in (.+?), Ensure all content in this location is writable by the web server","errorType":"exception","errorClass":"PdfExportException","httpStatus":null,"severity":"error","filePath":"app/Exports/PdfGenerator.php","lineNumber":72,"sourceCode":"        }\n\n        return $wkhtmlBinaryPath ?: '';\n    }\n\n    protected function renderUsingDomPdf(string $html): string\n    {\n        $options = config('exports.dompdf');\n        $domPdf = new Dompdf($options);\n        $domPdf->setBasePath(base_path('public'));\n\n        $fontMetrics = $domPdf->getFontMetrics();\n        $userFontfamilies = $this->getUserDomPdfFontFamilies();\n        foreach ($userFontfamilies as $fontFamily => $fonts) {\n            try {\n                $fontMetrics->setFontFamily($fontFamily, $fonts);\n            } catch (\\Exception $exception) {\n                $expectedPath = storage_path('fonts/dompdf');\n                throw new PdfExportException(\"Failed to create required font data in {$expectedPath}, Ensure all content in this location is writable by the web server\");\n            }\n        }\n\n        $domPdf->loadHTML($this->convertEntities($html));\n        $domPdf->render();\n\n        return (string) $domPdf->output();\n    }\n\n    /**\n     * @return array<string, array<string, string>>\n     */\n    protected function getUserDomPdfFontFamilies(): array\n    {\n        $fontStore = storage_path('fonts/dompdf');\n        if (!is_dir($fontStore)) {\n            return [];\n        }","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Exports/PdfGenerator.php#L54-L90","documentation":"Thrown by renderUsingDomPdf in app/Exports/PdfGenerator.php when Dompdf's FontMetrics::setFontFamily() fails while registering user-provided custom fonts from storage/fonts/dompdf. The library wraps any exception from font registration into this PdfExportException pointing at the dompdf font directory. It almost always indicates the web-server user cannot write the .ufm/.fm font metric files into that directory (or the font files themselves are corrupt/unreadable).","triggerScenarios":"Calling fromHtml() with the dompdf engine active (no pdf_command configured, no wkhtml binary) while storage/fonts/dompdf contains .ttf files, and FontMetrics::setFontFamily() throws — typically because it cannot write font metric/cache files, or a previously generated .ufm is stale/corrupt from a different dompdf/FontLib version.","commonSituations":"Deployments where storage/fonts was created by root or a different user than PHP-FPM/www-data; read-only storage mounts (immutable containers, read-only volumes); SELinux/AppArmor blocking writes to storage; upgrading dompdf so cached font metrics become incompatible; a corrupted uploaded .ttf that FontLib cannot parse so the metric registration blows up.","solutions":["Make storage/fonts/dompdf (and everything under storage/) writable by the web server user: chown -R www-data:www-data storage/fonts && chmod -R u+rwX storage/fonts.","Clear stale/corrupt font data: delete all generated .ufm files (and any broken uploads) in storage/fonts/dompdf so they are regenerated on next export.","Verify the .ttf files in storage/fonts/dompdf are valid, readable fonts (re-upload them); a corrupt font makes setFontFamily throw even with correct permissions.","If running in a container, ensure the storage volume is writable at runtime, or pre-generate font metrics at build time as the runtime user.","As a workaround, temporarily remove the custom fonts (empty storage/fonts/dompdf) to confirm the error is font-related rather than a general dompdf failure."],"exampleFix":"// before (shell, as root on server)\nls -l storage/fonts/dompdf  # owned by root, web server cannot write\n\n// after\nchown -R www-data:www-data storage/fonts\nclearstatcache && rm -f storage/fonts/dompdf/*.ufm","handlingStrategy":"try-catch","validationCode":"<?php\n$fontDir = storage_path('fonts/dompdf');\n$isReady = !is_dir($fontDir) || (\n    is_dir($fontDir) && is_writable($fontDir)\n    && collect(glob($fontDir . '/*.ttf'))->every(fn($f) => is_readable($f))\n    // all .ufm files that exist must be writable too\n    && collect(glob($fontDir . '/*.ufm'))->every(fn($f) => is_writable($f))\n);","typeGuard":null,"tryCatchPattern":"try {\n    $pdf = $pdfGenerator->fromHtml($html);\n} catch (\\BookStack\\Exceptions\\PdfExportException $e) {\n    if (str_contains($e->getMessage(), 'Failed to create required font data in')) {\n        // fix storage/fonts/dompdf permissions or remove custom fonts, then retry once\n        Log::warning('Dompdf font registration failed', ['error' => $e->getMessage()]);\n    }\n    throw $e;\n}","preventionTips":["Add storage/fonts/dompdf to your deployment permission script (chown www-data, u+rwX).","After every font upload or dependency upgrade, delete stale *.ufm files so metrics regenerate.","Validate uploaded fonts are real TTFs before accepting them into storage/fonts/dompdf.","In containers, run as the same user that owns storage/ or pre-chown in the entrypoint."],"tags":["php","dompdf","filesystem-permissions","pdf-export","fonts"],"backgroundTag":"storage-directory-not-writable","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}