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

Filament Easy Footer Laravel Package

devonab/filament-easy-footer

Filament EasyFooter adds a flexible, customizable footer to FilamentPHP admin panels (v3–v5). Seamlessly integrate a polished footer and configure enable/disable, position, custom sentence, GitHub/installed versions, load time, logo links, and custom themes via simple options.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (match the Filament major version first):

FilamentPHP Package version
v3.x ^1.0
v4.x / v5.x ^2.0
composer require devonab/filament-easy-footer:^2.0
php artisan vendor:publish --tag="filament-easy-footer-config"

Register in the panel provider — this is the only mandatory step:

use Devonab\FilamentEasyFooter\EasyFooterPlugin;

->plugins([
    EasyFooterPlugin::make(),
])

Where to look first:

  1. config/filament-easy-footer.php — only three things live here: app_name, github.*, and (later versions) versioning.local_fallback_config_key. Everything else is fluent chained off the plugin.
  2. The panel provider's ->plugins([...]) block — the whole API is a builder: withSentence(), withLinks(), withLogo(), withFooterPosition(), etc.
  3. The published views (--tag="filament-easy-footer-views") if you need markup changes beyond what the fluent API exposes.

First use case — the 10-minute win: a branded footer with copyright, three legal links, and a border, without touching Blade:

EasyFooterPlugin::make()
    ->withSentence('© ' . date('Y') . ' Acme Inc.')
    ->withLinks([
        ['title' => 'About',   'url' => 'https://acme.test/about'],
        ['title' => 'CGV',     'url' => 'https://acme.test/cgv'],
        ['title' => 'Privacy', 'url' => 'https://acme.test/privacy'],
    ])
    ->withBorder(),

Because the plugin hooks into panels::footer (or a sidebar hook), the footer appears on every panel page — including login/register — the moment you register it. That is usually what you want for an internal admin, and usually not what you want for a public-facing auth screen (see Gotchas).

Implementation Patterns

Compose the footer as a chain in the panel provider, not in config. The config file is deliberately thin; almost all behaviour is opt-in fluent calls. Keep the chain in a dedicated private method if the provider gets noisy:

protected function footer(): EasyFooterPlugin
{
    return EasyFooterPlugin::make()
        ->withFooterPosition('footer')
        ->withSentence(new HtmlString(
            '<img src="/img/logo.svg" width="18" height="18" style="margin-right:.4rem"> Acme'
        ))
        ->withLogo('/img/acme-mark.svg', 'https://acme.test', 'Powered by Acme', 24)
        ->withLinks([...])
        ->withBorder()
        ->hiddenFromPagesEnabled();
}

Pick the position by intent, not aesthetics:

  • footer (default) → global app footer under the content area. Best for legal links and version stamps.
  • sidebar (panels::sidebar.nav.end) → footer glued under the nav items; good for a compact "build info" chip on desktop.
  • sidebar.footer (panels::sidebar.footer) → pinned to the bottom of the sidebar, visually separated. Best for "v2.3.1 · up to date" style badges.

Remember these map to render hooks, so on mobile (collapsed sidebar) the sidebar variants may be invisible. If the footer contains compliance-critical links, use footer.

Version stamping is the killer feature for ops. Two independent concepts:

// Zero network calls, reads the Composer-resolved version
->withShowInstalledVersion()

// Adds latest GitHub tag + an "update available" badge (needs github config)
->withGithub(showLogo: true, showUrl: true)
->withShowInstalledVersion(showLatest: true, showUpdatable: true)

showLatest / showUpdatable are silently inert without ->withGithub() — chain both or neither. Configure the repository once in the config file:

'github' => [
    'repository' => 'acme/admin',
    'token'      => env('GITHUB_TOKEN'), // required for private repos, recommended otherwise
    'cache_ttl'  => 3600,
],

Hide auth pages as a default, then add exceptions. hiddenFromPagesEnabled() alone removes the footer from admin/login, admin/forgot-password, admin/register. Note that passing an explicit array replaces that default set entirely:

->hiddenFromPagesEnabled()
->hiddenFromPages(['settings', 'admin/login', 'admin/register']) // forgot-password now shows it

Load time is a novelty, use it in staging. ->withLoadTime('This page loaded in') reads well on a dev/staging panel and looks amateur on a customer-facing admin. Gate it:

->withLoadTime(app()->isLocal() ? 'Local render time' : null)

(or conditionally register the whole plugin in local/staging only.)

Multi-panel apps: the plugin is per-panel. If you run AdminPanel and AppPanel, register it in each and vary the chain — e.g. full legal footer on the customer panel, version badge on the admin panel, hiddenFromPagesEnabled() only on the public one.

Customising markup: publish views only when the fluent API runs out (logo/sentence/links cover ~90% of needs). Published views live in resources/views/vendor/filament-easy-footer/ and become yours to maintain across package upgrades — treat it as a fork.

Gotchas and Tips

The custom-theme requirement is not optional on Filament v4/v5. Tailwind 4 won't scan the vendor views, so footer classes get purged and the footer renders unstyled or invisible. Add to your theme.css before npm run build:

@source '../../../../vendor/devonab/filament-easy-footer/resources/views/**/*';

The number of ../ depends on where your theme CSS sits (resources/css/filament/admin/theme.css needs more levels than resources/css/theme.css). If the footer looks broken, this is the first thing to check — not the plugin config.

withSentence() overrides app_name. Precedence is roughly: withSentence() → github/versioning config → app_name config → config('app.name'). Setting a sentence and then wondering why the app name vanished is the most common confusion.

HtmlString output is filtered to a whitelist: <strong> <img> <a> <em> <span> <b> <i> <small>. Anything else is stripped. Inline style attributes survive (the README's own examples use them), but don't plan on <svg> or Tailwind classes — use the whitelist tags with inline styles.

Links are capped at three. The withLinks() array is documented as 3 max; a fourth entry may render or may not, depending on version. Keep it at three and put the rest in a custom view if you truly need more.

GitHub API calls fail quietly and look like "no version". Symptoms: badge missing, showLatest blank. Causes: wrong repository format (must be owner/name, no URL), missing token on a private repo, token without Contents: read-only permission, or a stale cache_ttl after you push a new tag. Debug by dropping cache_ttl to 60 locally, and remember the value is cached — clearing config/application cache alone won't refresh it.

Installed-version resolution depends on the deploy artifact. If Composer can't resolve the package version (common with VCS-less deploys or archive builds), the badge disappears. Set the fallback:

'versioning' => ['local_fallback_config_key' => 'app.version'],

and populate app.version from your CI (APP_VERSION=$(git describe --tags)). This is also the cleaner long-term pattern — deterministic across environments.

hiddenFromPages() matches paths, so mind the prefix. Values are relative to the panel ('settings', not '/admin/settings') and must match what the router reports — no leading slash, no query string. When a page won't hide, Log::info(request()->path()) on that page and copy the exact string.

Sidebar positions and collapsed sidebars. sidebar / sidebar.footer render inside the sidebar DOM; on mobile the sidebar is off-canvas, so the footer disappears. Don't put legally required content there.

footerEnabled() is a runtime toggle, not a kill switch. The footer is enabled by default; ->footerEnabled() with no argument is a no-op. If you conditionally disable, you need the boolean form (->footerEnabled(false)) — easy to misread from the README example, which shows // true by default next to a bare call.

Maturity reality check: 39 stars, 0 dependents, single maintainer. That's fine for a cosmetic plugin — the blast radius of a breaking change is a footer — but budget for it: pin the version (^2.0), avoid publishing views unless necessary (published views are the main upgrade-friction point), and don't build compliance-critical UX on top of it. The art/ screenshots in the README are effectively the visual spec; there's no long-form docs beyond it.

Extension points, in order of cost: fluent API → config file → published views → your own Blade partial in a panel render hook (if you outgrow the package entirely, ->renderHook(PanelsRenderHook::FOOTER, fn () => view('partials.footer')) replaces it in one line).

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin