PHPackages                             rokde/laravel-pergament - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. rokde/laravel-pergament

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

rokde/laravel-pergament
=======================

A file-based CMS package for Laravel with documentation, blog, landing pages, SEO, and PWA support.

v1.4.5(2w ago)3481↓78.2%[3 issues](https://github.com/rokde/laravel-pergament/issues)[5 PRs](https://github.com/rokde/laravel-pergament/pulls)MITPHPPHP ^8.4

Since Feb 20Pushed 6d agoCompare

[ Source](https://github.com/rokde/laravel-pergament)[ Packagist](https://packagist.org/packages/rokde/laravel-pergament)[ Fund](https://www.buymeacoffee.com/robertkummer)[ Fund](https://www.paypal.me/rok)[ RSS](/packages/rokde-laravel-pergament/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (10)Dependencies (45)Versions (28)Used By (0)

Laravel Pergament
=================

[](#laravel-pergament)

[![Latest Version on Packagist](https://camo.githubusercontent.com/d59f8074143192adb1f70f29bb9e49da8d6aa59fb19cdd5ee991ab6ed8711808/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726f6b64652f6c61726176656c2d70657267616d656e742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/rokde/laravel-pergament)[![GitHub Tests Action Status](https://camo.githubusercontent.com/e64a04a812d5272d13b916f7b1ec2af866a1b1fcc2189bb9d94e574b83ae4360/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f726f6b64652f6c61726176656c2d70657267616d656e742f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/rokde/laravel-pergament/actions/workflows/tests.yml?query=branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/555b5c0d889e868085880d989066de0033c781980a7145b296ee299145ca5408/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726f6b64652f6c61726176656c2d70657267616d656e742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/rokde/laravel-pergament)

A file-based CMS package for Laravel. Renders documentation, blog posts, and standalone pages from Markdown files with YAML front matter. Blade templates, Tailwind CSS, dark mode, server-side syntax highlighting — no database required.

Installation
------------

[](#installation)

Add the package via Composer:

```
composer require rokde/laravel-pergament
```

Publish the configuration:

```
php artisan vendor:publish --tag=pergament-config
```

Publish the views (optional, for customization):

```
php artisan vendor:publish --tag=pergament-views
```

Publish only the header component (navigation bar, search, font controls, dark mode toggle):

```
php artisan vendor:publish --tag=pergament-header
```

Publish only the footer component (copyright bar):

```
php artisan vendor:publish --tag=pergament-footer
```

Configuration
-------------

[](#configuration)

The main config file is `config/pergament.php`. Key options:

### Base URL Prefix

[](#base-url-prefix)

Control where Pergament listens. All routes are nested under this prefix:

```
'prefix' => '/',                    // Pergament owns the root
'prefix' => 'docs',                 // Pergament lives at /docs/*
'prefix' => 'landing-page/hello',   // Pergament lives at /landing-page/hello/*
```

### Content Path

[](#content-path)

Where your Markdown content lives on disk:

```
'content_path' => base_path('content'),
```

### Homepage

[](#homepage)

Configure what shows at the base URL:

```
'homepage' => [
    'type' => 'page',        // "page", "blog-index", "doc-page", or "redirect"
    'source' => 'home',      // page slug, "chapter/page", or redirect target
],
```

### Documentation

[](#documentation)

```
'docs' => [
    'enabled' => true,
    'path' => 'docs',            // subfolder under content_path
    'url_prefix' => 'docs',      // URL segment: /prefix/docs/chapter/page
    'title' => 'Documentation',
],
```

### Blog

[](#blog)

```
'blog' => [
    'enabled' => true,
    'path' => 'blog',
    'url_prefix' => 'blog',
    'title' => 'Blog',
    'per_page' => 12,
    'default_authors' => [],
    'feed' => [
        'enabled' => true,
        'type' => 'atom',        // "atom" or "rss"
        'title' => null,
        'description' => '',
        'limit' => 20,
    ],
],
```

### Colors &amp; Theming

[](#colors--theming)

Configure your brand color and page background. Both values propagate as CSS custom properties (`--p-primary`, `--p-bg`) that drive the entire UI — navigation highlights, badges, links, scrollbars, focus rings, text selection, and more:

```
'colors' => [
    'primary'    => '#3b82f6',   // any CSS color: hex, oklch, named…
    'background' => '#ffffff',
],
```

Dark mode is handled automatically: the background switches to a dark slate (`#111827`) and tints derived from `--p-primary` re-resolve against it without any extra configuration.

### Site &amp; SEO

[](#site--seo)

```
'site' => [
    'name' => env('APP_NAME', 'Pergament'),
    'url' => env('APP_URL', 'http://localhost'),
    'locale' => 'en',
    'seo' => [
        'title' => env('APP_NAME', 'Pergament'),
        'description' => '',
        'keywords' => '',
        'og_image' => '',
        'twitter_card' => 'summary_large_image',
        'robots' => 'index, follow',
    ],
],
```

### Text-to-Speech

[](#text-to-speech)

Add a play/pause button that reads page content aloud using the browser's [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis). Each content type can be enabled independently:

```
'tts' => [
    'blog'  => true,            // show on blog posts
    'docs'  => true,            // show on documentation pages
    'pages' => false,           // show on standalone pages
    'voice' => 'Samantha',      // preferred voice name (null = browser default)
    'rate'  => 1.0,             // speech rate: 0.5 (slow) to 2.0 (fast)
],
```

The `voice` value is browser- and OS-dependent. Common options:

PlatformVoicesmacOS / iOS`Samantha`, `Alex`, `Daniel`, `Karen`, `Moira`, `Thomas`Chrome`Google UK English Female`, `Google UK English Male`, `Google US English`Windows`Microsoft David`, `Microsoft Zira`, `Microsoft Mark`If the chosen voice is unavailable, the browser's default voice is used as fallback.

Content Structure
-----------------

[](#content-structure)

```
content/
├── docs/
│   ├── 0-getting-started/
│   │   ├── 01-introduction.md
│   │   └── 02-installation.md
│   └── 1-configuration/
│       └── 01-basic-setup.md
├── blog/
│   ├── 2024-01-15-hello-world/
│   │   ├── post.md
│   │   └── hero.png
│   └── 2024-03-20-new-feature/
│       └── post.md
└── pages/
    ├── home.md
    ├── about.md
    └── pricing.md

```

### Documentation

[](#documentation-1)

Documentation lives in numbered chapter directories. Each chapter contains numbered Markdown files:

- Directory format: `{order}-{chapter-slug}/` (e.g. `0-getting-started/`)
- File format: `{order}-{page-slug}.md` (e.g. `01-introduction.md`)
- The numeric prefixes control sort order and are stripped from URLs

### Blog Posts

[](#blog-posts)

Blog posts live in date-prefixed directories:

- Directory format: `{YYYY-MM-DD}-{slug}/` (e.g. `2024-01-15-hello-world/`)
- Each directory contains a `post.md` file and any associated media files
- The date is extracted from the directory name

### Standalone Pages

[](#standalone-pages)

Simple Markdown files in the `pages/` directory. The filename (without `.md`) becomes the URL slug.

Front Matter
------------

[](#front-matter)

All content files use YAML front matter delimited by `---`:

```
---
title: My Page Title
excerpt: A brief description shown on index pages
---

# My Page Title

Content goes here.
```

### Documentation Front Matter

[](#documentation-front-matter)

```
---
title: Introduction
excerpt: Getting started with Pergament
---
```

### Blog Post Front Matter

[](#blog-post-front-matter)

```
---
title: "Hello World"
excerpt: "Our very first blog post"
category: "Announcements"
tags:
  - "laravel"
  - "pergament"
author: "Jane Doe"
---
```

You can also define multiple authors with details:

```
authors:
  - name: "Jane Doe"
    email: "jane@example.com"
    url: "https://janedoe.com"
    avatar: "https://example.com/avatar.jpg"
  - name: "John Smith"
```

### Page Front Matter

[](#page-front-matter)

```
---
title: About Us
excerpt: Learn more about our company
layout: landing
allow_html: true
---
```

Set `layout: landing` to use the full-width landing page layout instead of the default centered content layout.

Set `allow_html: true` on standalone pages that include custom HTML/CSS fragments. The page content is still rendered from Markdown, but Pergament skips the default `prose` content wrapper so your HTML layout can control its own structure and styling.

### SEO Overrides

[](#seo-overrides)

Any page can override global SEO settings using dot notation in its front matter:

```
---
title: My Page
seo.title: "Custom SEO Title - My Site"
seo.description: "A custom meta description for this specific page"
seo.og_image: "https://example.com/special-og.png"
seo.robots: "noindex, nofollow"
---
```

These override the corresponding values from `config('pergament.site.seo.*')`.

Page CSS &amp; JS Assets
------------------------

[](#page-css--js-assets)

Attach page-scoped styles and scripts without any configuration. Place a file with the **same basename** as a Markdown file, using a `.css` or `.js` extension, in the **same directory**. Its contents are embedded inline in the rendered page — CSS into the document ``, JavaScript just before the closing `` tag.

Content typeMarkdown fileSidecar filesPage`content/pages/home.md``content/pages/home.css`, `home.js`Blog post`content/blog/2024-01-15-hello/post.md``post.css`, `post.js` (same directory)Doc page`content/docs/0-getting-started/0-intro.md``0-intro.css`, `0-intro.js`Both files are optional and independent — a page may have only CSS, only JS, both, or neither. You may attach at most one `.css` and one `.js` per Markdown file.

The contents are injected **inline and verbatim**: CSS inside a `` tag, JavaScript inside a `` tag. There is no separate request and no caching layer. This works the same way for live URLs and for the static site produced by `pergament:generate-static`.

Because the content is injected raw, keeping it valid is your responsibility: avoid a literal `` or `` inside the file, as it would close the surrounding tag prematurely.

GitHub-Style Alerts
-------------------

[](#github-style-alerts)

Pergament supports GitHub-style alert blocks for highlighting important information. Alerts are enabled by default.

### Syntax

[](#syntax)

```
> [!NOTE]
> Useful information that users should know, even when skimming.

> [!TIP]
> Helpful advice for doing things better or more easily.

> [!IMPORTANT]
> Key information users need to know to achieve their goal.

> [!WARNING]
> Urgent info that needs immediate user attention to avoid problems.

> [!CAUTION]
> Advises about risks or negative outcomes of certain actions.
```

### Alert Types

[](#alert-types)

TypePurposeColor`NOTE`Supplementary informationBlue`TIP`Helpful adviceGreen`IMPORTANT`Key informationPurple`WARNING`Urgent noticesAmber`CAUTION`Risk warningsRedEach alert renders as a styled `` with an icon, title, and content. Dark mode variants are included automatically.

### Configuration

[](#configuration-1)

```
'markdown' => [
    'alerts' => true,   // set to false to disable alert rendering
],
```

When disabled, alert syntax is rendered as a plain blockquote.

Downloads
---------

[](#downloads)

Pergament automatically handles file download links in blog posts and documentation pages.

### Relative download links

[](#relative-download-links)

Any relative link in a blog post or documentation page that points to a file (not an external URL, anchor, or another Markdown page) is automatically rewritten to the correct media URL:

```
[Download the guide](guide.pdf)
[Get the source archive](src.zip)
```

In a blog post, these become `/blog/media/{slug}/guide.pdf` and `/blog/media/{slug}/src.zip`. In a documentation page they resolve to the equivalent docs media path. Place the files in the same directory as your `post.md` or doc Markdown file.

External links (`http://`, `https://`), anchors (`#`), `mailto:` links, and links to other Markdown pages (`.md`) are left unchanged.

### `:::download` block directive

[](#download-block-directive)

Wrap one or more links in a `:::download` block to add the HTML `download` attribute, which instructs the browser to download the file instead of navigating to it:

```
:::download

[Download PDF](report.pdf)
[Download CSV](data.csv)

:::
```

Links inside a `:::download` block receive the `download` attribute — the browser saves the file to disk rather than opening it. The relative-path URL rewriting described above still applies. External links and anchors inside the block are not given the `download` attribute.

Footnotes
---------

[](#footnotes)

Pergament supports inline footnotes via the CommonMark `FootnoteExtension`. Footnotes are disabled by default.

### Syntax

[](#syntax-1)

```
Here is a sentence with a footnote.[^1]

You can have multiple footnotes[^2] in a single document[^3].

[^1]: This is the first footnote.
[^2]: This is the second footnote.
[^3]: Footnotes can contain **Markdown** formatting.
```

Footnote references render as superscript links (``) that anchor to the footnote list at the bottom of the page. Each footnote includes a backlink to return to the reference in the text.

### Configuration

[](#configuration-2)

```
'markdown' => [
    'footnotes' => false,   // set to true to enable footnote support
],
```

Block-Based Landing Pages
-------------------------

[](#block-based-landing-pages)

For landing pages and homepages, you can use block directives in Markdown to create structured sections. Block directives wrap content in `` elements with CSS classes for styling.

### Syntax

[](#syntax-2)

```
:::hero

# Welcome to Our Product

The best solution for your needs.

[Get Started](/docs/getting-started/introduction)

:::

:::features

## Why Choose Us

- **Fast** — Built for speed
- **Reliable** — 99.9% uptime
- **Simple** — Easy to use

:::

:::cta

## Ready to Get Started?

Sign up today and see the difference.

[Sign Up Free](/register)

:::
```

Each `:::{name}` block becomes a `` in the rendered HTML. The closing `:::` ends the block.

### Built-in Block Types

[](#built-in-block-types)

The default views include basic styles for these block types:

DirectiveCSS ClassPurpose`:::hero``pergament-block-hero`Hero sections with centered text`:::features``pergament-block-features`Feature grids and lists`:::cta``pergament-block-cta`Call-to-action sectionsYou can use any name — it maps directly to a CSS class. Custom blocks like `:::pricing`, `:::testimonials`, or `:::team` will generate `pergament-block-pricing`, `pergament-block-testimonials`, and `pergament-block-team` classes respectively.

### Styling Blocks

[](#styling-blocks)

Override the default styles by publishing the views and editing the CSS, or add your own styles targeting the generated classes:

```
.pergament-block-hero {
    padding: 6rem 2rem;
    text-align: center;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
}

.pergament-block-features {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
    padding: 4rem 2rem;
}

.pergament-block-pricing {
    /* your custom block styles */
}
```

### Full Landing Page Example

[](#full-landing-page-example)

Create `content/pages/home.md`:

```
---
title: "My Product"
layout: landing
allow_html: true
seo.title: "My Product - The Best Solution"
seo.description: "Discover the best solution for your needs"
---

:::hero

# My Product

Build something amazing with our platform.

[Get Started](/docs/getting-started/introduction) [Learn More](#features)

:::

:::features

## Features

### Lightning Fast
Our platform is optimized for performance at every level.

### Fully Extensible
Plugin system lets you customize everything.

### Dark Mode
Beautiful light and dark themes out of the box.

:::

:::cta

## Start Building Today

Join thousands of developers who trust our platform.

[Sign Up Free](/register)

:::
```

Then set the homepage config to use it:

```
'homepage' => [
    'type' => 'page',
    'source' => 'home',
],
```

Dark Mode
---------

[](#dark-mode)

The package supports class-based dark mode. Add the `dark` class to your `` element to activate dark mode. All views include `dark:` Tailwind variants.

### Themed Images

[](#themed-images)

Documentation images can have dark/light variants that automatically switch based on the active theme. Place variant files alongside the original:

```
content/docs/0-getting-started/
├── 01-introduction.md
├── dashboard.png           # referenced in markdown
├── dashboard.dark.png      # shown in dark mode
└── dashboard.light.png     # shown in light mode (optional)

```

The variant resolution works as follows:

Dark variant existsLight variant existsLight mode showsDark mode showsNoNo`dashboard.png``dashboard.png`YesNo`dashboard.png``dashboard.dark.png`NoYes`dashboard.light.png``dashboard.png`YesYes`dashboard.light.png``dashboard.dark.png`Command Palette Search
----------------------

[](#command-palette-search)

When search is enabled, a command palette is available on every page. Open it with `Cmd+K` (macOS) or `Ctrl+K` (other platforms), or by clicking the search input in the navigation bar.

- **Real-time results** — results appear as you type, fetched from the search endpoint as JSON (no page reload)
- **Type badges** — each result is labelled **Doc**, **Post**, or **Page**
- **Keyboard navigation** — `↑`/`↓` to move between results, `Enter` to open, `Escape` to close
- **Mouse navigation** — click any result to navigate
- **Excerpt preview** — a short excerpt is shown below each title; falls back to the first 160 characters of content when no explicit excerpt is set in front matter
- **No-JS fallback** — the nav search form submits to `/search?q=…` as before when JavaScript is unavailable

Search covers all three content types:

TypeSourceDocDocumentation pagesPostBlog postsPageStandalone pagesBlade Components
----------------

[](#blade-components)

The header and footer are extracted as anonymous Blade components so you can publish and customise them independently without overriding the entire view set.

### Header (``)

[](#header-x-pergamentheader-)

The header component renders the sticky navigation bar and includes:

- Site name / logo link
- Documentation and Blog navigation links (shown when the respective feature is enabled)
- Search input (shown when search is enabled)
- Font-size controls (A− / A+ / OpenDyslexic toggle)
- Dark mode toggle
- Mobile hamburger menu with all of the above
- Command palette overlay (shown when search is enabled)

Publish just the header to customise it:

```
php artisan vendor:publish --tag=pergament-header
```

This publishes `resources/views/vendor/pergament/components/header.blade.php` into your application. Laravel's view resolution automatically prefers the published file over the package default.

### Footer (``)

[](#footer-x-pergamentfooter-)

The footer component renders the bottom bar containing the copyright notice.

Publish just the footer to customise it:

```
php artisan vendor:publish --tag=pergament-footer
```

This publishes `resources/views/vendor/pergament/components/footer.blade.php` into your application.

> Both components are also included when you run `php artisan vendor:publish --tag=pergament-views`.

Artisan Commands
----------------

[](#artisan-commands)

### Create a documentation page

[](#create-a-documentation-page)

We have an interactive docs creation command. All arguments are optional, you will be guided through all necessary things.

```
php artisan pergament:make:doc

# Or with arguments
php artisan pergament:make:doc --chapter=getting-started --title="Installation Guide" --order=02
```

### Create a blog post

[](#create-a-blog-post)

```
php artisan pergament:make:post

# Or with arguments
php artisan pergament:make:post \
    --title="My First Post" \
    --category="Tutorials" \
    --tags="laravel, php" \
    --author="Jane Doe" \
    --date=2024-06-15
```

Both commands prompt for any missing arguments interactively.

### Export a static site

[](#export-a-static-site)

Render your whole site to a folder of plain HTML files — no PHP, no database, no server required:

```
php artisan pergament:generate-static dist
```

The export is **fully self-contained and portable**. All internal links are relative and end in `.html`, and the CSS, JS, and fonts are bundled into an `assets/` folder. You can copy the output anywhere — a USB stick, Netlify, GitHub Pages, an S3 bucket — open any `.html` file directly from disk (`file://`), and navigate the entire site offline.

```
dist/
├── index.html              # homepage
├── assets/                 # bundled css, js, fonts (relative urls)
│   ├── pergament.css
│   ├── pergament.js
│   └── fonts/
├── docs/
│   ├── index.html          # redirects to the first doc page
│   └── getting-started/
│       ├── introduction.html
│       └── introduction.md # token-safe Markdown sidecar
├── blog/
│   ├── index.html
│   ├── page/2.html         # pagination
│   ├── my-post.html
│   ├── my-post.md
│   └── feed.xml
├── about.html
├── about.md
├── sitemap.xml
├── robots.txt
└── llms.txt

```

#### Markdown sidecars

[](#markdown-sidecars)

Every content page (docs, posts, standalone pages) is also written as a `.md` file next to its `.html`. The Markdown is clean — front matter stripped, a `# Title` heading, and internal links rewritten to relative `.md` paths — so an LLM (or any tool) can fetch the `.md` version of a page for a token-safe, prose-only copy of the content.

#### Options

[](#options)

OptionDescription`--content-path=`Source content directory for this export (defaults to the configured `content_path`). Useful for generating from a content folder that lives outside your app.`--prefix=`Override the URL prefix for this export (e.g. `--prefix=/` to export at the root).`--base-url=`Override the site URL used in `sitemap.xml` and the feed.`--clean`Remove the output directory before generating.```
php artisan pergament:generate-static dist \
    --content-path=/path/to/content \
    --base-url=https://example.com \
    --clean
```

> **Note:** Static exports use a bundled client-side search index (`search.json`) and the command palette/search forms work without a server. Broken internal content links are reported as warnings during generation (the export still succeeds).

Standalone Static Export
------------------------

[](#standalone-static-export)

Pergament ships a standalone CLI for generating static HTML without a host Laravel application:

```
vendor/bin/pergament generate-static public --content-path=content --base-url="https://example.com"
```

The command accepts the same options as the Laravel Artisan command:

- `--content-path=` overrides the content directory.
- `--prefix=` overrides the generated Pergament route prefix.
- `--base-url=` sets the absolute site URL used by canonical URLs, feeds, and sitemap output.
- `--clean` removes the output directory before generating.

### GitHub Pages

[](#github-pages)

Use the standalone CLI in GitHub Actions to publish generated files to GitHub Pages:

User/org pages (`https://OWNER.github.io`) and custom domains usually only need `--base-url`. Project pages hosted below `/REPOSITORY` should keep the owner domain in `--base-url` and pass the repository path with `--prefix=/REPOSITORY`.

```
name: Deploy Pergament site

on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
      - run: composer install --no-dev --prefer-dist --no-interaction
      - run: vendor/bin/pergament generate-static public --content-path=content --base-url="https://OWNER.github.io" --prefix=/REPOSITORY
      - uses: actions/upload-pages-artifact@v3
        with:
          path: public

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4
```

Routes
------

[](#routes)

All routes are nested under the configured `prefix`. With the default `/` prefix:

RouteDescription`/`Homepage`/docs`Documentation index (redirects to first page)`/docs/{chapter}/{page}`Documentation page`/blog`Blog index`/blog/{slug}`Blog post`/blog/category/{category}`Posts by category`/blog/tag/{tag}`Posts by tag`/blog/author/{author}`Posts by author`/blog/feed`RSS/Atom feed`/search?q=query`Search`/{slug}`Standalone page`/sitemap.xml`XML sitemap`/robots.txt`Robots.txt`/llms.txt`LLMs.txtWith `prefix` set to `docs`, all routes become `/docs/...`, `/docs/blog/...`, etc.

Analytics
---------

[](#analytics)

Pergament includes a privacy-first, server-side analytics system. It records only the URL path, a timestamp, and whether the request came from a bot — no IP addresses, no cookies, no personal data, no third-party services. No cookie banner required.

Data is written as newline-delimited JSON (NDJSON) to one file per day:

```
storage/pergament/analytics/YYYY-MM-DD.ndjson

```

Each line is a JSON object:

```
{"url":"/blog/my-post","timestamp":"2026-03-29T10:23:45+00:00","is_bot":false}
```

### Enabling analytics

[](#enabling-analytics)

Analytics is disabled by default. Enable it in your published config:

```
'analytics' => [
    'enabled' => true,
    'storage_path' => null,  // defaults to storage/pergament/analytics
],
```

### Viewing analytics locally

[](#viewing-analytics-locally)

```
# Today's page views (URL + type + count)
php artisan pergament:analytics

# A specific date
php artisan pergament:analytics --date=2026-03-01

# Multi-day summary
php artisan pergament:analytics --summary

# Configurable window
php artisan pergament:analytics --summary --days=7
```

Both modes display a `Users` and `Bots` breakdown. Bots are identified by their user-agent and always recorded — never silently dropped.

### Remote download endpoint

[](#remote-download-endpoint)

For production environments without shell access, you can expose a secure download URL that streams the raw NDJSON log for a given date.

**The endpoint is disabled by default.** A developer must explicitly enable it and set a secret token:

```
'analytics' => [
    'enabled' => true,
    'download' => [
        'enabled' => true,
        'token'   => env('PERGAMENT_ANALYTICS_TOKEN'),
    ],
],
```

Generate a token:

```
php -r "echo bin2hex(random_bytes(32));"
```

Once enabled, the endpoint is available at:

```
GET {prefix}/analytics/download?date=YYYY-MM-DD&token=

```

The `date` parameter defaults to today if omitted. The response is an `application/x-ndjson` file download.

ResponseCondition`404`Download not enabled in config`403`Token missing, wrong, or not configured`400`Invalid date format`404`No data recorded for that date`200`NDJSON file download### Fetching analytics from a remote site

[](#fetching-analytics-from-a-remote-site)

The `pergament:analytics` command can pull data from a remote site's download endpoint and display it locally — useful when you cannot SSH into production:

```
# Today's detail from remote
php artisan pergament:analytics \
    --remote=https://mysite.com \
    --token=

# Specific date
php artisan pergament:analytics \
    --remote=https://mysite.com \
    --token= \
    --date=2026-03-15

# 7-day summary from remote
php artisan pergament:analytics \
    --remote=https://mysite.com \
    --token= \
    --summary \
    --days=7
```

The command fetches the NDJSON files from the remote endpoint and renders the same tables as local mode.

### Syncing remote data to local storage

[](#syncing-remote-data-to-local-storage)

If your production environment has no persistent file storage (ephemeral/serverless deployments), analytics data is lost on every deploy. Use `--sync` to download all recorded dates from the remote and merge them into your local storage before they disappear:

```
php artisan pergament:analytics \
    --remote=https://mysite.com \
    --token= \
    --sync
```

The command:

1. Calls `GET /analytics/dates?token=…` to discover which dates have data on the remote
2. Downloads each date's NDJSON file
3. Merges entries into local storage — **duplicates are skipped** (matched by `url + timestamp`), so running `--sync` multiple times is safe
4. Displays today's stats from the now-merged local data

Combine with `--summary` or `--date` to control what is displayed after the sync:

```
# Sync everything, then show a 7-day summary
php artisan pergament:analytics \
    --remote=https://mysite.com \
    --token= \
    --sync \
    --summary \
    --days=7
```

**Typical workflow for ephemeral environments:**

```
# 1. Before deploying a new version, sync production data locally
php artisan pergament:analytics --remote=https://mysite.com --token=… --sync

# 2. Deploy — production storage is wiped

# 3. View historical stats locally at any time (no remote needed)
php artisan pergament:analytics --summary --days=90
```

Markdown Responses for AI &amp; LLMs
------------------------------------

[](#markdown-responses-for-ai--llms)

All content pages (documentation, blog posts, standalone pages, and the homepage) can be served as plain Markdown instead of HTML. This is configurable in the exports section of the configuration.

A markdown response is returned when any of the following is true:

TriggerExample`Accept: text/markdown` request header`curl -H "Accept: text/markdown" /docs/getting-started/installation`Known AI / LLM user-agentRequests from ChatGPT, Claude, Perplexity, etc.`.md` URL suffix`/blog/my-post.md`Media files, feeds, sitemaps, and search results are excluded — only rendered HTML content pages are converted.

Features
--------

[](#features)

- **File-based content** — Markdown + YAML front matter, no database
- **Documentation** — Numbered chapters/pages, sidebar navigation, TOC scrollspy, heading anchor links, themed images
- **Blog** — Categories, tags, multiple authors, date-prefixed directories, pagination
- **RSS/Atom feeds** — Configurable feed type and limits
- **SEO** — Meta tags, Open Graph, Twitter Cards, per-page overrides via dot notation
- **Sitemap** — Auto-generated XML sitemap
- **robots.txt / llms.txt** — Auto-generated or custom content
- **Markdown responses** — All content pages served as plain Markdown via `Accept: text/markdown`, `.md` suffix, or known AI user-agents (powered by spatie/laravel-markdown-response)
- **Command palette search** — `Cmd+K`/`Ctrl+K` opens a live search dialog across docs, posts, and pages; keyboard navigable; no-JS form fallback
- **PWA** — Optional manifest.json and service worker
- **GitHub-style alerts** — `> [!NOTE]`, `> [!TIP]`, `> [!IMPORTANT]`, `> [!WARNING]`, `> [!CAUTION]` blocks with icons and color coding; dark mode included
- **Footnotes** — Inline `[^ref]` syntax with superscript links and back-references; opt-in via config
- **Downloads** — Relative file links in blog posts and docs are auto-rewritten to media URLs; `:::download` block directive adds the HTML `download` attribute to prompt browser saves
- **Landing pages** — Block-based content with `:::directive` syntax
- **Dark mode** — Class-based toggle with system preference detection; dark-mode syntax highlighting
- **Syntax highlighting** — Server-side via tempest/highlight, light and dark themes included
- **Theming** — Configure `colors.primary` and `colors.background`; the entire UI (nav, links, badges, scrollbars, focus rings, text selection) derives from these two values via CSS custom properties
- **Zoomable images** — Click any image to enlarge it in a lightbox; Escape or click outside to close
- **Copy code** — Hover a code block to reveal a Copy button; switches to "Copied" on success
- **Text-to-speech** — Optional play/pause button that reads content aloud using the browser Speech Synthesis API; configurable per content type, with selectable voice and speech rate
- **Analytics** — Privacy-first page view tracking (URL + timestamp + bot flag), NDJSON storage, Artisan command with local and remote display, optional token-protected download endpoint
- **Configurable prefix** — Mount the CMS at any URL path

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](./.github/CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Robert Kummer](https://github.com/rokde)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance77

Regular maintenance activity

Popularity19

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 91.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

21

Last Release

19d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4946056?v=4)[Robert Kummer](/maintainers/rokde)[@rokde](https://github.com/rokde)

---

Top Contributors

[![rokde](https://avatars.githubusercontent.com/u/4946056?v=4)](https://github.com/rokde "rokde (83 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (7 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

laraveldocumentationmarkdowncmsblogpergament

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/rokde-laravel-pergament/health.svg)

```
[![Health](https://phpackages.com/badges/rokde-laravel-pergament/health.svg)](https://phpackages.com/packages/rokde-laravel-pergament)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M131](/packages/roots-acorn)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M132](/packages/laravel-pulse)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77022.3M151](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58171.6k14](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
