Updating a project born from the kit
The kit is a starting point, not a dependency. After create-project the project is yours: you rename panels, change canAccessPanel(), edit seeders. That’s why there is no kit:update that overwrites files — it would rewrite exactly what you customized, and a starter kit that ruins the user’s project is worth nothing.
What changes splits into three layers, and each one has its own path:
| Layer | What it is | How to update |
|---|---|---|
| Dependencies | Filament, plugins, Laravel | composer update — it’s most of the improvements and it arrives on its own |
| The kit’s glue | providers, traits, widgets, error views | manual diff against the new tag (below) |
| Your business | everything you wrote | never touched |
The easy way: php artisan kit:update
The command automates the entire git step and applies nothing without your approval:
php artisan kit:update --dry-run # only shows what changedphp artisan kit:update # review and apply, file by fileWhat it does, in order:
-
Checks the ground — requires a git repository with a clean tree. Without that there would be no way back, so it refuses to run (showing the commands to put the project under version control).
-
Links the kit temporarily — adds the
kitremote with push blocked and fetches the tags into a namespace of their own (kit-v*), so they don’t collide with your project’s versions. -
Compares — from the version in
config('kit.version')up to the chosen tag, restricted to the paths that belong to the kit. Your business code never enters the equation. -
Offers a temporary branch (
kit-update/v0.16.0) so yours doesn’t get dirty. -
Asks file by file — see the diff, apply, skip or stop. You can change your mind halfway and apply the rest in bulk. A file removed from the kit is never deleted automatically: it only warns you.
-
Unlinks — removes the remote and the
kit-*tags on the way out, even if you interrupt it halfway. The project isn’t left with anything third-party hanging around. -
Marks the applied version in
config/kit.php— only that line, without touching the rest of the file. It’s the starting point for the next comparison.
Two details that show up in practice:
config/kit.phpalways shows up as “modified” (it carries the version mark). Applying it brings the kit’s new keys, but replaces the whole file — if you changed seeder credentials or added your own keys there, read the diff and copy only what matters instead of applying.kit:updateupdates itself. Since PHP already loaded the class into memory, the new behavior (and the new messages) only take effect on the following run. The command tells you when that happens. The path list that filters the diff is read from the target version (since v0.30.1), so a directory only the new version covers arrives in the same run — the “run the command again” notice only appears when that read failed. An installation older than v0.30.1 still runs the old list on its first pass: run the second one with the command the notice prints. The known case is v0.22.x → v0.23.0 or later, which leftView [svg.arte-do-login] not foundbetween the two runs; the second run fixes it, or copyresources/views/svg/arte-do-login.blade.phpfrom the kit repository.
A new kit dependency: composer.json is never applied
kit:update does not overwrite your composer.json — it carries YOUR project’s dependencies,
and applying it would wipe out everything you installed after the kit. Instead the command
reports what changed there (new package, new script) and you copy it by hand:
git diff kit-v0.35.0 kit-v0.36.0 -- composer.jsoncomposer updatephp artisan filament:assets # required whenever the new package publishes CSS/JSThe report only shows up when the command knows where you started from. It reads
config('kit.version'); if that marker does not match a kit tag, pass--from=vX.Y.Z.
In v0.36.0 this applies to mortalkiller/filament-page-header, which brings the rich header on
record screens and publishes its own CSS and JS. Without composer require + filament:assets,
the View/Edit screens for users and organizations still answer — just without the header.
A new screen: reseed both seeders
The “next steps” the command prints mention filament:assets and the tests, not the seeders —
and a new kit screen usually brings a new permission, which lands ownerless in your database:
php artisan db:seed --class=Database\Seeders\ShieldPermissionsSeederphp artisan db:seed --class=Database\Seeders\PapeisSeederBoth are idempotent — running them again duplicates nothing.
In v0.36.0 specifically they are a no-op — and it is worth knowing why, so you don’t go hunting
for a defect that isn’t there. The ViewUser screen that version brings consumes the View:User
permission, and that permission was already generated and handed out all along: view is in
config('filament-shield.policies.methods'), so ShieldPermissionsSeeder always created it and
PapeisSeeder always gave it to the roles. Between v0.35.0 and v0.36.0 neither the seeders nor
config/filament-shield.php changed a line (git diff v0.35.0 v0.36.0 -- database/seeders config/filament-shield.php comes back empty). What was missing was the screen, not the
permission: the checkbox in /admin/shield/roles existed and decided nothing.
Run both anyway. The habit costs two idempotent commands and pays off on the version that does bring a genuinely new Resource or Page — there the permission really is born ownerless in your database, and the symptom is a screen nobody can see.
At the end nothing is committed: you review with git diff, run php artisan migrate if a new migration arrived (from v0.31.0 on the command also delivers database/settings/, and the settings screen breaks while the new property has no row in the database), run composer test:kit (the foundation) and commit. Went wrong? git checkout -- . undoes it, or delete the branch and go back to yours.
- The settings screen’s URL changed in v0.32.0. It now answers at
/admin/configuracoes-da-aplicacao, the name it already showed in the menu. The old slug returns a 301 to the new one, so old bookmarks and links still arrive. If your project wrote the old address by hand somewhere — a test, a link in one of your views, a bookmarklet —, update it; the permission (View:ConfiguracoesDoKit) and the class did not change, only the slug. You don’t have to approve 30 files one by one. During the review the menu offers “Apply all NEW files from here on” and “Apply EVERYTHING from here on” — one confirmation covers the set. And you can start in bulk already:
php artisan kit:update --only-new # only what doesn't exist in the project yetphp artisan kit:update --all # everything, including what overwritesThe distinction is the point: a new file has nothing to overwrite, so applying those in bulk is safe — that’s the case for the widgets, the Spotlight, the concerns, the kit’s CSS (resources/css/filament/ and public/css/kit/, delivered from v0.30.0 on) and the Settings migrations (database/settings/, from v0.31.0 on). The .env.example is delivered too (from v0.32.2 on): it is a suggestion file, it is where the kit documents every new key, and your .env is never touched — but it arrives as modified, so review the diff if you added keys of your own to it. A modified one replaces the current content, and if you edited that file your version is lost (recoverable with git checkout -- <file>, since nothing is committed). That’s why --only-new is the recommended bulk for a first pass, leaving the modified ones to review calmly.
| Option | What for |
|---|---|
--only-new |
applies all the new files at once (overwrites nothing) |
--all |
applies everything at once, with a single confirmation for the set |
--dry-run |
report only, changes nothing |
--tag=v0.16.0 |
compare against a specific version |
--from=v0.15.0 |
tell it which version the project started from (when config/kit.php doesn’t know) |
--branch=name |
choose the temporary branch’s name |
--no-branch |
apply on the current branch |
--keep-remote |
keep the kit’s remote and tags at the end |
--repo=URL |
compare against another kit repository (a fork, for instance); the default is config('kit.repository'), which reads KIT_REPOSITORY from .env |
With no terminal (CI, --no-interaction) the command becomes a report and changes nothing — unless you pass --only-new or --all, which are the approval, given on the command line.
The manual way
If you’d rather control every step — or understand what the command does under the hood:
Add the kit as a second remote, once. Your origin stays your project; kit is just a read source:
git remote add kit https://github.com/gsferro/filament-starter-kit-easy.git
# the kit's remote is read-only: it prevents an accidental `git push kit main`# from sending YOUR project into the kit's repositorygit remote set-url --push kit no_pushThe kit’s tags go into a namespace of their own (kit-v*). That matters: a git fetch kit --tags would bring v0.15.0, v0.16.0… into your project and collide with your versions later.
git fetch --no-tags kit 'refs/tags/*:refs/tags/kit-*'git tag -l 'kit-*' # kit-v0.15.0, kit-v0.16.0, ...Then, at each version, see what changed and bring over only what matters:
# 1. overview between your version and the new onegit diff kit-v0.15.0..kit-v0.16.0 --stat
# 2. the diff of the kit's "glue" (ignore what you already rewrote)git diff kit-v0.15.0..kit-v0.16.0 -- app/Providers app/Filament/Concerns \ app/Filament/Spotlight app/Traits resources/views/errors config/kit.php
# 3. bring it over file by file, reviewinggit checkout kit-v0.16.0 -- resources/views/errorsgit checkout kit-v0.16.0 -- app/Filament/Concerns/BadgeContagemNavegacao.phpDo this on a branch (git switch -c update-kit) and run composer test before merging. Files you rewrote: read the diff and apply by hand — it’s the only safe path.
💡 TODO / where the project is heading: extract the “glue” into a Composer package of its own (
gsferro/kit-core) with the providers, traits, widgets and infra pages. Then the middle layer becomescomposer update gsferro/kit-coreand the skeleton stays minimal — only what really is a starting point. It’s this kit’s natural evolution.