Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Page Bookmarks Laravel Package

jaysontemporas/page-bookmarks

A Laravel package that adds bookmarking to your app, letting users save and revisit pages or Eloquent models. Includes migrations, a Bookmark model, and simple traits and API methods to toggle, check, and list saved items, so it drops easily into any Blade or Livewire view.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose Filament plugin (10 stars, zero dependents), so treat the README as a starting hypothesis and the src/ directory as the source of truth. Budget 15 minutes to read the plugin class, the migration, and the Resource before you wire it into a real panel.

Minimal setup path:

composer require jaysontemporas/page-bookmarks
php artisan vendor:publish --tag="page-bookmarks-migrations"
php artisan migrate

Then register the plugin on the panels that should expose bookmarks — not globally, so you can keep it out of customer-facing panels:

// app/Providers/Filament/AdminPanelProvider.php
use JaysonTemporas\PageBookmarks\PageBookmarksPlugin;

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

Where to look first, in this order:

  1. src/PageBookmarksPlugin.php — every make()/->xxx() fluent method is a config knob. This is the fastest way to learn what the package author intended to be configurable.
  2. database/migrations/* — tells you the table name, whether it's polymorphic (bookmarkable_type/bookmarkable_id), and whether there's a user_id/team_id column. That single file answers 80% of your integration questions.
  3. src/Models/ — the bookmark model, its relationships, and whether it uses morphTo().
  4. src/Filament/ — Resources, Pages, and any table/header actions you can attach to your resources.
  5. resources/views/ — if views are publishable, this is your override surface.

First use case to validate: let an admin bookmark a record they're already looking at. Open a Resource you own, add the package's action to the table row or the view-page header, click it, then confirm the row lands in the bookmarks table with the right morph type and the current user's ID. If that round-trip works, the rest of the package is mostly navigation and listing.

Sanity checks before you commit:

# What does it actually ship?
find vendor/jaysontemporas/page-bookmarks -type f -name "*.php" | head -50

# Does it declare a service provider with real boot logic?
cat vendor/jaysontemporas/page-bookmarks/composer.json

Given the adoption level, read the source before you promise it to a stakeholder. It's a small enough package that a full read is cheaper than debugging it blind.

Implementation Patterns

Attach the action where the user's intent lives. The highest-value placement is on the record view/edit page header (the user is already focused on one record), with a secondary placement on table rows for bulk triage. Keeping both means two code paths, so start with the header action and only add the table action if users ask.

// Typical shape — confirm the exact namespace in src/Filament/
use JaysonTemporas\PageBookmarks\Filament\Actions\BookmarkAction;

->headerActions([
    BookmarkAction::make(),
])

Resolve the current user's bookmarks, not the global list. Whatever the package ships, wrap the listing query in your own page or widget so you control scoping:

Bookmark::query()
    ->where('user_id', auth()->id())
    ->with('bookmarkable')
    ->latest()
    ->get();

If the package doesn't scope by user out of the box, that becomes a policy-level concern — see Gotchas.

Make the bookmark list a dashboard widget, not a Resource. Admins visit bookmarks in bursts, not as a CRUD task. A Filament\Widgets\TableWidget pinned to the dashboard, sorted by created_at desc, keeps the feature discoverable without adding a sidebar entry nobody clicks. Reserve the full Resource/page for the "manage and clean up" case.

Polymorphic targets need a stable contract. If the underlying model is morphTo, every bookmarked model should implement a consistent presentation method so the bookmark list can render a label without a match statement sprawling across your views:

// Your model
public function bookmarkLabel(): string
{
    return $this->title ?? class_basename($this).' #'.$this->getKey();
}

public function bookmarkUrl(): ?string
{
    return YourResource::getUrl('view', ['record' => $this]);
}

Do this as a small interface or trait in your app rather than editing the vendor package. It's the one extension point you'll want regardless of what the package provides.

Multi-panel and multi-tenant. Register the plugin only on panels where bookmarks make sense, and if you're using Filament tenancy, verify the migration ships a tenant_id (or that you can add one). Without tenant scoping, a shared admin panel leaks bookmark titles across tenants — a subtle but real information disclosure.

Gate the feature behind a permission, not just the panel. Even if the plugin registers its own navigation, add a canViewAny policy or a Gate::define('manage-bookmarks') check. It lets you ship the feature to a pilot group first.

Upgrade strategy for a low-adoption dependency. Pin the version explicitly in composer.json ("jaysontemporas/page-bookmarks": "^1.0"), and after each upgrade diff the migration and the plugin class. With zero dependents, regressions won't be caught by anyone else — you are the ecosystem. If the package ever goes quiet, the trait + widget above mean you can replace it with ~100 lines of your own code.

Gotchas and Tips

Morph maps will break your bookmarks. If AppServiceProvider calls Relation::enforceMorphMap([...]) — common in Laravel apps — the bookmark rows will store FQCNs the package may not expect, or stored aliases will fail to resolve on morphTo. Test bookmarking immediately after install in an app that uses morph maps; if it breaks, either add your models to the map or set Relation::morphMap() without enforcement. This is the single most likely day-one failure and the error message (Class "" not found or a null bookmarkable) doesn't point at the cause.

Check the table name before you publish. If you already have a bookmarks table (browser-sync integrations, older packages), the migration will collide. Inspect Schema::create(...) and rename via a config value or your own migration — don't hand-edit the vendor file.

Uniqueness is usually not enforced. Most bookmark packages add a row on every click. Add a composite unique index on (user_id, bookmarkable_type, bookmarkable_id) and use firstOrCreate in whatever action you expose, or accept duplicate "bookmarks" that make the list look broken. Also verify the toggle actually toggles — if the package only creates, wire the delete path yourself.

Authorization is usually the developer's job. The plugin will happily expose a list page to anyone who can reach the panel. If the Resource ships without a policy, users can see other users' bookmark labels. Write a BookmarkPolicy with viewAny/view/delete scoped to $bookmark->user_id === $user->id before this leaves staging. Audit this explicitly; it's the kind of gap a 10-star package is most likely to have.

Navigation config is panel-local. If you change ->navigationGroup(), ->navigationLabel(), or ->navigationSort(), those calls live in the PanelProvider. If you register the plugin in two panels and forget one, the sidebar is inconsistent in a way that looks like a caching bug but isn't.

Livewire caches navigation and widgets aggressively. After changing plugin registration or navigation config, run php artisan filament:cache-components (if you use it) or clear it, plus php artisan optimize:clear. Otherwise you'll chase a phantom.

Testing: register the plugin in your test panel rather than the production one, and use RefreshDatabase. Because the package has zero dependents, there are no community tests to lean on — write one feature test asserting a user can create a bookmark and cannot see another user's:

it('does not leak bookmarks across users', function () {
    $a = User::factory()->create();
    $b = User::factory()->create();
    Bookmark::factory()->for($a)->create(['title' => 'secret']);

    $this->actingAs($b)
        ->get(BookmarkResource::getUrl('index'))
        ->assertOk()
        ->assertDontSee('secret');
});

Extension points to prefer, in order: the plugin's fluent config → Filament's resource/action extension hooks → publishing views and overriding the blade → forking. Avoid editing vendor/. If you find yourself needing a fork, first check whether the feature you want is ~30 lines in your own app given the model is a simple polymorphic table.

When to walk away. If the package lacks user scoping, has no policy hooks, and its migration is awkward to extend with tenancy, the cost of adopting it exceeds writing a bookmarks table, a Bookmark model with morphTo(), a toggle action, and a table widget — roughly a day's work, and you own the upgrade path. For a feature this small, that's a legitimate alternative to a 10-star dependency.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource