arzcode / finisterre
A Filament plugin that adds a full task-management system to your panel
Requires
- php: ^8.3
- filament/filament: ^5.0
- filament/spatie-laravel-media-library-plugin: ^5.0
- filament/spatie-laravel-tags-plugin: ^5.0
- laravel/prompts: ^0.3
- pxlrbt/filament-spotlight: ^2.1
- relaticle/flowforge: ^4.0
- spatie/laravel-package-tools: ^1.16
- spatie/laravel-settings: ^3.0
Requires (Dev)
- larastan/larastan: ^3.9
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1|^7.10.0
- orchestra/testbench: ^10.9|^11.1
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 4.6.1
- 4.6.0
- 4.5.5
- 4.5.4
- 4.5.3
- 4.5.2
- 4.5.1
- 4.5.0
- 4.4.1
- 4.4.0
- 4.3.0
- 4.2.1
- 4.2.0
- 4.1.0
- v4.0.5
- v4.0.3
- v4.0.2
- v4.0.1
- 4.0.0
- 3.2.2
- 3.2.1
- 3.2.0
- 3.1.1
- 3.1.0
- 3.0.0
- 2.3.1
- 2.3.0
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.19
- 2.0.18
- 2.0.17
- 2.0.16
- 2.0.15
- 2.0.14
- 2.0.13
- 2.0.12
- 2.0.11
- 2.0.10
- 2.0.9
- 2.0.8
- 2.0.7
- 2.0.6
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.x-dev
- 1.21.0
- 1.20.5
- 1.20.4
- 1.20.3
- 1.20.2
- 1.20.1
- 1.20.0
- 1.19.1
- 1.19.0
- 1.18.5
- 1.18.4
- 1.18.3
- 1.18.2
- 1.18.1
- 1.18.0
- 1.17.0
- 1.16.1
- 1.16.0
- 1.15.1
- 1.15.0
- 1.14.1
- 1.14
- 1.13.1
- 1.13.0
- 1.12.0
- 1.11.1
- 1.10.1
- 1.10.0
- 1.9.18
- 1.9.17
- 1.9.16
- 1.9.15
- 1.9.14
- 1.9.13
- 1.9.12
- 1.9.11
- 1.9.10
- 1.9.9
- 1.9.8
- 1.9.7
- 1.9.6
- 1.9.5
- 1.9.4
- 1.9.3
- 1.9.2
- 1.9.1
- 1.9.0
- 1.8.4
- 1.8.3
- 1.8.2
- 1.8.1
- 1.8.0
- 1.7.3
- 1.7.2
- 1.7.1
- 1.7.0
- 1.6.8
- 1.6.7
- 1.6.6
- 1.6.5
- 1.6.4
- 1.6.3
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.1
- 1.5.0
- 1.4.0
- 1.3.0
- 1.2.1
- 1.2.0
- 1.1.0
- 1.0.0
- 0.5.3
- 0.5.2
- 0.5.1
- 0.5.0
- 0.4.1
- 0.4.0
- 0.3.0
- 0.2.0
- 0.1
- dev-claude/finisterre-meetings-feature-7q3klg
This package is auto-updated.
Last update: 2026-09-08 19:31:06 UTC
README
A Filament plugin that adds a full task-management system to your panel: a Kanban board, task assignments, threaded and schedulable comments, email and SMS notifications, issue reporting from any of your own models, and an in-app settings page. Works with Filament v4/v5 — for Filament 3, use the v1 branch.
Installation
Quick install (recommended)
composer require arzcode/finisterre php artisan finisterre:install
The installation command does everything wiring-related in one shot: publishes the config and migrations — including
the tags and media migrations of the spatie packages tasks lean on, when your application doesn't have those tables
yet — asks to run them, enables Finisterre (active in every environment by default — manage it from the settings page),
runs php artisan filament:assets, injects FinisterrePlugin::make() into every
app/Providers/Filament/*PanelProvider.php, adds use FinisterreUserTrait; to app/Models/User.php, offers to create
a Filament theme (php artisan make:filament-theme) for every panel that has none, appends the Tailwind @source lines
to every theme file, and finishes with npm run build.
For each step the command falls back to a printed instruction if your project doesn't match the expected shape
(different panel directory, custom User location, etc.). A panel left without a theme is called out again at the end:
without one the task board renders unstyled, however many times npm run build runs.
For Filament 3, use the v1 branch:
composer require arzcode/finisterre:^1.0
Manual installation
If you'd rather wire things by hand, the package ships the usual "publishables". Start with:
composer require arzcode/finisterre
php artisan vendor:publish --tag="finisterre-config"
Publishing Filament assets is required for the kanban board's JavaScript:
php artisan filament:assets
Without this step, the kanban board will load but drag-and-drop will not work due to missing JavaScript assets.
By default, the package is active in every environment. To restrict it to specific environments, set a comma-separated
list in your .env file (or edit it later from the settings page):
FINISTERRE_ENVIRONMENTS=local,production
Important: If your Filament panel uses a different ID than admin, add this to your .env:
FINISTERRE_PANEL_SLUG=your-panel-id
To find your panel ID, check your PanelProvider.php for ->id('...'). For example, if your panel uses
->id('filament'), set FINISTERRE_PANEL_SLUG=filament.
You can change the name of the table in the config file You need to publish and run the migrations with:
php artisan vendor:publish --tag="finisterre-migrations"
php artisan migrate
If you don't already have the spatie tags package, publish the migrations
php artisan vendor:publish --provider="Spatie\Tags\TagsServiceProvider" --tag="tags-migrations" php artisan migrate
Same for spatie media package
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations" php artisan migrate
The tasks are linked to users, but the project can have a different model for users. You can change the model in the config file and the name attribute column Also, there is a trait to be included in the user's model
use Arzcode\Finisterre\Traits\FinisterreUserTrait;
Optionally, you can publish the views using
php artisan vendor:publish --tag="finisterre-views"
Upgrading
After composer update arzcode/finisterre, run:
php artisan finisterre:update
It lists every migration the package ships — and the tags and media migrations of the spatie packages it builds on —
next to the file you published for it and whether that migration has run, so you can see at a glance what a new version
added. Then it offers, one confirmation at a time, to publish the missing migrations (the spatie ones included, when
their tables are missing and nothing is published to create them), run them, seed the settings this version added,
re-publish the Filament assets and run npm run build. It also reports config keys that were added or dropped (your
published config/finisterre.php is deep-merged over the package defaults, so copying new keys over is optional), warns
when a theme file lost its @source lines, and when a panel has no theme at all — both count as outstanding under
--check, and php artisan finisterre:install fixes both.
Nothing is written without your confirmation, and publishing is idempotent — an already-published migration keeps the file name it got the first time instead of being copied again under a new timestamp.
It also understands squashed migrations. If your application ran php artisan schema:dump --prune, the Finisterre
migrations it swallowed have no file left in database/migrations, only a row in the migrations table and the INSERT
that recreates it inside database/schema. Those are reported as squashed instead of missing, so the command never
publishes a fresh copy for migrate to run a second time against tables that already exist — and if such a copy is
already sitting in database/migrations, it offers to delete it.
To only report what is outstanding — useful in CI, where it exits with a non-zero status when anything is pending:
php artisan finisterre:update --check
Uninstalling
php artisan finisterre:uninstall
It reverses the install: removes FinisterrePlugin::make() from your panel providers, use FinisterreUserTrait; from
app/Models/User.php, the @source lines from your theme files, the Finisterre entries from .env and the settings
rows, and then asks separately before dropping the database tables, deleting the published migrations and deleting
config/finisterre.php. It finishes by running composer remove arzcode/finisterre and npm run build.
Filament Theme CSS
Required. Following Filament v5 guidance, this package ships raw CSS only — it no longer compiles a Tailwind stylesheet of its own. The utility classes used in its Blade views are compiled by your application's Filament theme. Without the
@sourcelines below the kanban board and task views will render unstyled.
You need a custom Filament theme (
php artisan make:filament-theme). Add the following lines to your theme's CSS file (e.g.
resources/css/filament/admin/theme.css) so Tailwind scans the package views for classes, then rebuild your theme (
npm run build):
@source '../../../../vendor/arzcode/finisterre/resources/views'; @source '../../../../vendor/relaticle/flowforge/resources/views';
The installer adds these lines automatically to each detected theme file.
The package comes with a default policy for the tasks that can be overridden in the config file and set your own policy
'model_policy' => Arzcode\Finisterre\Policies\FinisterreTaskPolicy::class,
Usage
Add the plugin to your panel provider. By default every authenticated user can view all tasks — no closure needed:
use Arzcode\Finisterre\FinisterrePlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ FinisterrePlugin::make(), ]); }
To restrict access by role, pass closures to the corresponding user… setters:
FinisterrePlugin::make() ->userCanViewAllTasks(fn() => auth()->user()?->hasRole(RoleEnum::Admin)) ->userCanViewOnlyTheirTasks(fn() => auth()->user()?->hasAnyRole([RoleEnum::Editor, RoleEnum::Manager])) ->userCanScheduleComments(fn() => auth()->user()?->hasRole(RoleEnum::Admin)) ->userCanConfigureFinisterre(fn() => auth()->user()?->hasRole(RoleEnum::Admin)),
The task page
Clicking a card on the board (or a row in the task list) opens the task page rather than a form. It shows the title as heading, a strip of badges for status, priority, assignee, due date, tags and attachment count, the description with its attachments, the subtasks panel and the comments below.
Every badge is a quick action for users allowed to update the task: status, priority and assignee change with one click from a dropdown; tags, due date and attachments open a small modal (new tags can be created on the spot, and the attachments modal uploads new files and removes existing ones). The page updates in place after each change. Image attachments open in a lightbox; other files open in a new tab or download. The Edit button leads to the form, which only keeps the long-form fields: title, description and attachments.
Settings page
Most configuration can be managed at runtime from an in-app settings page instead of editing the config file. It is
opened from a header action (⚙️) on the Kanban board — it is intentionally hidden from the navigation menu — and is
gated by userCanConfigureFinisterre() (allowed for everyone by default; see the closure above).
Values are stored in the database and take precedence over config/finisterre.php at runtime, so admins can change them
without a deploy. The page covers:
- General — active environments, the Filament panel slug, and whether tasks are excluded from global search.
- Tasks — statuses to hide from the board, and the fallback user notified when a task has no assignee.
- Assignable users filter — column/value used to limit who can be assigned tasks (e.g.
role=admin). - Subtasks — whether to notify the assignee of checklist changes, and how long to group them for.
- Comments — whether to show avatars, and the heroicons used for the comment actions.
- SMS — enable/disable and credentials for SMS notifications (see SMS notifications).
Global search
The package's Filament resources stay out of the panel's global search, so tasks don't dilute the results of the host
application's own resources. If you want tasks to show up there, untick Exclude tasks from global search in the
settings page, or set the config key to false:
// config/finisterre.php 'exclude_from_global_search' => false,
Subtask notifications
When somebody other than the assignee adds, renames, ticks or deletes one of a task's subtasks, the assignee gets an email and an in-app notification. Edits are grouped: the first change starts a five-minute window, and everything done inside it arrives as a single digest rather than one message per subtask.
The digest reports the net change over the window, not each keystroke, because it works by diffing a snapshot taken when the window opened against the checklist as it stands when the digest is built. So a subtask that was added and then deleted, a tick that was undone, and a rename that was reverted are all simply absent — there is nothing to report.
Both settings live on the settings page, and in config/finisterre.php:
'subtasks' => [ 'notify' => true, 'notification_delay_minutes' => 5, ],
No extra tables are involved: the snapshot rides along in the queued job's payload.
Grouping needs a queue worker and a shared, lock-capable cache store. The window is held open by a unique-job lock on the default cache store, and the digest itself is a delayed job:
| Host setup | Result |
|---|---|
redis, memcached, database or dynamodb cache, plus queue:work |
Grouping works as described |
file cache on a single server |
Works; on a multi-server deploy each server keeps its own window |
CACHE_STORE=array |
The lock is per-PHP-process, so grouping silently does nothing |
QUEUE_CONNECTION=sync |
The delay is ignored and the job runs inline — one email per change |
Two things worth knowing. Only a non-assignee edit opens a window; once one is open, the
diff covers the whole checklist, so an edit the assignee makes themselves during that window
is reported to them along with the rest. And seeders or bulk imports that create subtasks
would otherwise trigger digests — set finisterre.subtasks.notify to false, or wrap the
import in FinisterreSubtask::withoutEvents(fn () => ...).
Displaying a user's full name
finisterre.authenticatable_attribute accepts either a single column (default 'name') or an array of columns to
concatenate for display:
'authenticatable_attribute' => ['name', 'lastname'],
With an array, the package shows "John Doe" in every user-facing select (task assignee, filter, kanban, comment notify
list) and uses CONCAT_WS(' ', ...) for SQL-level selects. Columns must exist on the users table.
Kanban ordering
The kanban board (powered by flowforge) stores each
card's position in the order_column, which is an integer. Cards within a column are kept as
10, 20, 30, ….
Flowforge's default algorithm computes positions as the decimal midpoint between two cards plus
random jitter, which fills order_column with long decimals like 63821.3847291500. To keep the
value a clean integer, TasksKanbanBoard overrides flowforge's calculateAndUpdatePosition() and
renumbers the whole target column sequentially on every move. No vendor files are patched, so the
behavior survives composer update.
If you are upgrading an existing install whose order_column is still a decimal, publish and run
the conversion migration. It renumbers each status column to clean 10, 20, 30, … integers and
changes the column type to unsignedInteger:
php artisan vendor:publish --tag="finisterre-migrations"
php artisan migrate
Notifications follow the move semantics:
- Reordering a card within the same column only changes
order_columnand does not notify the assignee. - Moving a card to a different column changes its status and does notify the assignee (unless the new status is "Done").
SMS notifications
Using smsarena.es as provider. By default, only notifies tasks on creation with priority TaskPriorityEnum::Urgent That can be changed in the config file
Add to your .env file the following:
# Finisterre
FINISTERRE_SMS_ENABLED=false
FINISTERRE_SMS_AUTH_KEY=CHANGE
FINISTERRE_SMS_SENDER=CHANGE
FINISTERRE_SMS_NOTIFY_TO=CHANGE
Reporting issues from your own models
Any model in your app can let users open a Finisterre task against a specific record. The task stores a polymorphic
subject pointing back to that record, and the task form shows a link to it.
First publish and run the migration that adds the subject columns to the tasks table:
php artisan vendor:publish --tag="finisterre-migrations"
php artisan migrate
Make the model implement FinisterreReportable. The InteractsWithFinisterreReports trait provides sensible defaults (
a ClassName (#id) label and a link inferred from the model's Filament resource edit/view page):
use Arzcode\Finisterre\Contracts\FinisterreReportable; use Arzcode\Finisterre\Traits\InteractsWithFinisterreReports; class Order extends Model implements FinisterreReportable { use InteractsWithFinisterreReports; }
Override either method to customise the label or link:
public function getFinisterreReportLabel(): string { return "Order {$this->reference}"; } public function getFinisterreReportUrl(): ?string { return route('orders.show', $this); }
Then add ReportIssueAction wherever the record is available (a resource page, table row, infolist, etc.). It opens a
modal asking for a title, description and attachments (images, PDF, and videos up to 3 MB), and associates the
created task with the record:
use Arzcode\Finisterre\Filament\Actions\ReportIssueAction; ReportIssueAction::make();
Role restriction for Task Reports
TODO
Development
The package ships raw CSS (resources/css/app.css) with no Tailwind build — utility classes in the views are compiled
by the host application's theme via the @source lines above. There is no JavaScript build step. Run
php artisan filament:assets in the host application to publish the package's assets.
Testing
composer test
Pre-push hook
composer ci:check runs everything CI runs: Pint in check mode, PHPStan and the test suite. A tracked
.githooks/pre-push runs it before every push so a red build never leaves your machine, and composer install
points this clone at it (core.hooksPath is per clone and cannot be committed, so it is set from
.githooks/install.php).
composer ci:check # run the checks yourself git config --get core.hooksPath # should print .githooks git push --no-verify # skip the hook in an emergency
Pushing from a Git GUI rather than a terminal? It starts the hook without your shell profile, so if composer
is somewhere unusual, add that location to the PATH line at the top of .githooks/pre-push.
