Skip to content

Start building your next app or website — with AI, right in your browser.

Visit devdojo.com
Docs
Changelog

Apps (Coming Soon) 30 / 32

Changelog

A drop-in product changelog for Laravel with per-user read tracking — the Changelog model, a HasChangelogs trait, a mark-as-read endpoint, and an optional Filament admin — so you can show a "what's new" indicator.

devdojo/changelog adds a product changelog (release notes) to a Laravel app, with per-user "read" tracking so you can show a "what's new" badge that clears once a user has seen the latest entry. It ships the Changelog model, a HasChangelogs trait for your User, a single POST /changelog/read endpoint, and an optional Filament admin resource. Like blog, it is front-end agnostic — you render the public changelog page in whatever theme you use.

It is one of the feature packages bundled by foundation, where it self-gates on a feature flag, and it works standalone in any Laravel 10/11/12/13 app.

Requirements

  • PHP 8.2+ and Laravel 10 – 13 (only illuminate/support and illuminate/database are required).
  • Filament v4 — optional, and only if you want the bundled admin resource; the model, trait, and migrations have no hard dependency on it.

Overview

  • A Changelog is a release note with a title, a short description, and a full HTML body.
  • Each user's read state is tracked through a changelog_user pivot — a row means "this user has read this entry."
  • The HasChangelogs trait adds a changelogs() relation and a hasChangelogNotifications() helper that returns true when the newest entry is unread, driving a "what's new" indicator.
  • POST /changelog/read marks every unread entry as read for the signed-in user.

Installation

composer require devdojo/changelog

Publish the migrations and config, then migrate:

php artisan vendor:publish --tag=changelog:migrations
php artisan vendor:publish --tag=changelog:config
php artisan migrate

If your User model isn't discoverable from config('auth.providers.users.model'), set it explicitly:

CHANGELOG_USER_MODEL="App\\Models\\User"

Then wire the trait onto your User model and, optionally, register the Filament admin.

Note — migrations are publish-only. Run vendor:publish --tag=changelog:migrations before migrate, or the changelogs and changelog_user tables won't exist.

Configuration

Publishing changelog:config writes config/devdojo/changelog/settings.php under the config key devdojo.changelog.settings:

return [
    // The host User model used for read-tracking.
    // Null → falls back to auth.providers.users.model.
    'user_model' => env('CHANGELOG_USER_MODEL'),
];
Key Default Purpose
user_model env('CHANGELOG_USER_MODEL')null Host User model used for the read-tracking pivot. When null, resolved from auth.providers.users.model.

Publish tags

Tag Publishes to
changelog:config config/devdojo/changelog/settings.php
changelog:migrations database/migrations

Wiring your User model

Add the HasChangelogs trait to your User:

use Devdojo\Changelog\Traits\HasChangelogs;

class User extends Authenticatable
{
    use HasChangelogs;
}

This adds:

$user->changelogs;                    // BelongsToMany — entries the user has read
$user->hasChangelogNotifications();   // bool — is the latest entry unread?

The data model

changelogs

Column Type Notes
id increments
title string(191)
description string(191) short summary shown in the notification
body text full HTML body
created_at / updated_at timestamps

changelog_user (read-tracking pivot)

Column Type Notes
changelog_id unsigned int FK → changelogs.id, onDelete('cascade')
user_id unsigned big int FK → users.id, onDelete('cascade')
primary key (changelog_id, user_id) a row = "this user has read this entry"

The Changelog model is mass-assignable for title, description, and body (via $fillable).

Usage

Creating entries

use Devdojo\Changelog\Models\Changelog;

Changelog::create([
    'title'       => 'v3.0 Released',
    'description' => 'A big update with new features and improvements.',
    'body'        => '<p>Here is everything that changed…</p>',
]);

Most teams author entries from the Filament admin instead.

Read tracking and the "what's new" indicator

Show an indicator when the signed-in user has an unread latest entry (route('changelog') here is the app-defined page from the Folio example below — the package only registers the read endpoint):

@auth
    @if (auth()->user()->hasChangelogNotifications())
        <a href="{{ route('changelog') }}" class="badge">What's new</a>
    @endif
@endauth

When the user views or dismisses the changelog, mark everything as read by POSTing to the package's endpoint:

Method URI Name Middleware
POST /changelog/read changelog.read web, auth
// e.g. when the "what's new" popup is dismissed
fetch('{{ route('changelog.read') }}', {
    method: 'POST',
    headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
});

TiphasChangelogNotifications() compares only the newest entry against the user's read pivot. Adding one new entry re-lights the badge for every user, even those who read older entries.

Rendering a changelog page

The package is headless — render list and detail with the model:

use Devdojo\Changelog\Models\Changelog;

$logs = Changelog::orderByDesc('created_at')->paginate(10);  // list
$changelog = Changelog::findOrFail($id);                     // single entry

A minimal Folio example:

{{-- resources/views/pages/changelog/index.blade.php --}}
<?php
use function Laravel\Folio\name;
use Devdojo\Changelog\Models\Changelog;

name('changelog');
$logs = Changelog::orderByDesc('created_at')->paginate(10);
?>

<x-layout>
    @foreach ($logs as $log)
        <article>
            <time>{{ $log->created_at->toFormattedDateString() }}</time>
            <h2>{{ $log->title }}</h2>
            <div>{!! $log->body !!}</div>
        </article>
    @endforeach
    {{ $logs->links() }}
</x-layout>

How it's wired

ChangelogServiceProvider merges the settings config in register(). In boot() it registers the two publish groups (console only), then — gated on config('foundation.features.changelog', true) — loads the package's routes/web.php, which defines the single changelog.read route behind the web and auth middleware. The default-on true fallback means the route is registered even when no Foundation config is present.

The endpoint itself is ChangelogController@read: it finds every changelog the current user hasn't read (whereDoesntHave('users', …)) and attaches those IDs to the user through the changelog_user pivot, after which hasChangelogNotifications() returns false. The Changelog::users() relation resolves the pivot's other side from devdojo.changelog.settings.user_model, falling back to the auth config.

Filament admin

Register the plugin to get a Changelogs resource:

use Devdojo\Changelog\Filament\ChangelogPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(ChangelogPlugin::make());
}

The resource manages title, description, and a rich-text body. Filament is a suggested (optional) dependency and the resource is built for Filament v4 — the model, trait, and migrations work without it.

Using with Foundation

When foundation is installed, the changelog self-gates on its feature flag:

// config/foundation.php
'features' => [
    'changelog' => true, // toggle at /foundation/setup to disable
],

Disabling changelog skips registration of the changelog/read route and the Filament resource; the model, trait, and migrations remain in place, so toggling is lossless. Standalone, with no Foundation present, the flag is absent and the changelog defaults to on.

Extension points

  • Swap the pivot's User model — set CHANGELOG_USER_MODEL / devdojo.changelog.settings.user_model.
  • Custom read semantics — the trait's hasChangelogNotifications() is intentionally simple (latest-entry only); override it in your User for per-entry unread counts by querying the changelog_user pivot directly.
  • Edit the schema — the published migrations are yours; add columns such as a version or category.
  • Own the front-end — build the changelog page and "what's new" popup in your own theme against the Changelog model.

The changelog pairs naturally with blog for long-form content and notifications for in-app alerts.

© 2026 DevDojo Edit this page