Kit settings under /admin
What the installer asked — plus a handful of things you previously could only change by editing a file — now lives at /admin/configuracoes-da-aplicacao — the panel menu labels the screen Configurações da aplicação, because once installed the kit is the provenance, not the product —, in six tabs. No .env, no deploy.
Every field lives in a section with an icon, collapsible through its header; the browser remembers the state of each section (closed or open, including the ones that start closed) in localStorage, per browser. Each tab loads its content only when opened, and a tab with a validation error opens automatically.
Each tab has a fixed key in the URL — ?tab=identidade, ?tab=email, ?tab=tabelas, ?tab=registro,
?tab=login, ?tab=aplicacao —, stable for links and bookmarks. Up to v0.49 the key was generated by
Filament (?tab=…::data::tab); an old link in that format does not break, it just opens on the first tab.
| Tab | What you change | Sections |
|---|---|---|
| Identidade (identity) | application name, system version, primary colour (the Filament palette or a free hex value), brand logo, favicon, the artwork on the authentication screens and the panel header (the composed brand and the user block) | Nome e versão (name and version); Cor primária (primary colour); Logo, favicon e arte (logo, favicon and artwork); Cabeçalho dos painéis (panel header) |
transport (log, array, smtp), host, port, encryption, username, password and sender |
Remetente (transport and sender); Servidor SMTP (connection and credentials) | |
| Tabelas (tables) | rows per page, striped rows, recall of the user’s filter/search/sort, draggable columns, table loading (pagination, infinite scroll or a “Load more” button), saved views and their sharing — the defaults for every table in all three panels (details) | Paginação e aparência (pagination and appearance); Carregamento e visões (loading and views) |
| Registro (sign-up) | registration without an invitation on /app, manual approval and e-mail verification (details) |
Cadastro sem convite (registration and approval); E-mail verificado (e-mail verification) |
| Login | the single login page at /login (details), the four social login providers, each with its switch, allowed panels, Client ID and encrypted Client Secret, plus the login screen footer (details) |
Página única de login (single login page); Login social (with a section per provider); Proteção anti-robô (bot protection); Rodapé da tela de login (login footer) |
| Aplicação (application) | card navigation hub, unsaved-changes alert, layout density, whether the kit version shows in the footer, the dynamic dashboard — and which panels it applies to —, the shortcut dock (panels, default layout and style), the AI agents (the usage switch and the panels of the Assistant chat), the guided tutorial (checklist, tours and authoring), what your business calls each organisation (singular and plural), and hiding the selector when only one is reachable | Comportamento dos painéis (panel behaviour); Dashboard dinâmico (dynamic dashboard); Dock de atalhos (shortcut dock); Agentes de IA (AI agents); Tutorial guiado (guided tutorial); Organizações (organisations) |
Everything is stored by spatie/laravel-settings in the settings table, with the screen coming from filament/spatie-laravel-settings-plugin — both were already installed in the kit and unused until this version.
The footer: signature for everyone, version only once you are in
The footer is composed in one place (App\Support\AssinaturaDoRodape) and appears on every
screen of all three panels and on the authentication screens. What differs between them is not
the format — it is who sees what:
| Part | Visitor | Signed in |
|---|---|---|
© {year} {Application name} |
✅ | ✅ |
v{system version} |
❌ | ✅ |
kit {kit version} |
❌ | ✅, if the switch is on |
The version is hidden from visitors, and that is deliberate: showing the exact installed
version on the login screen hands any passer-by the list of known vulnerabilities that apply to it.
The name and the © are public anyway — they already appear at the top of the panel and in the
browser tab title.
The year is the current one, computed on every render: there is no field for it, and it rolls over on its own.
Your system’s version comes from the Versão do sistema field in the Nome e versão section on the Identidade tab, seeded
by APP_VERSION in .env. The field shows a fixed v on its left: it is display only, it
marks the convention and it is not stored — type 1.2.3 and the footer shows v1.2.3. Type the
v as well and you will see vv1.2.3, which is the screen telling you one is spare.
These are two different versions, and conflating them is the mistake this section exists to prevent:
| What it is | Where you edit it | |
|---|---|---|
config('app.version') |
your product’s version | the screen, or APP_VERSION in .env |
config('kit.version') |
the starter kit version the project was born from | nobody: kit:update writes it itself |
The second is an internal kit metric — kit:update uses it to know which version to diff from. It
does not appear in the footer by default; to show it next to yours, enable the show kit
version switch in the Comportamento dos painéis section on the Aplicação tab. php artisan kit:info always prints both, regardless
of the switch.
Empty field, no version of YOURS in the footer. A project that does not version itself need not
pretend to — the © {year} {Name} signature stays, just without the number.
The login screen notice
The Rodapé da tela de login section on the Login tab has a free-text field that is rendered inside the login form card, below the buttons
(social login ones included), and only on the login screens (/admin/login, /app/login, /infra/login and /login). It
accepts Markdown — bold, italic and links — and discards raw HTML, because the login screen is
public.
The registration and password reset screens get the signature only, not the notice.
If the switch is on and the field is empty, the footer shows the signature and, beside it, the kit version, labelled
(kit 0.52.0). It is never presented as if it were your product’s: the label is precisely what
prevents that reading, and it is a kit requirement, not a screen detail.
APP_VERSION seeds ONCE, at install time. After that, the screen wins.
This is the same rule as every other key on this page — the database wins at runtime; .env seeds
and is the fallback — and here it has a consequence worth spelling out: on an already-installed
project, editing APP_VERSION in .env does not change the footer. The stored value wins,
including when it is blank. If the field on the screen is empty, the footer shows no version of
yours even with APP_VERSION=2.4.1 in the file.
So: APP_VERSION exists so a fresh install is born versioned. To change the version afterwards, use
the field on the screen.
The kit does not read the git tag or branch name at runtime — a production image usually has no
.git, and reading from there would create a second source of truth diverging from what the screen
shows. A deploy that checks out a release/2.4 branch does not update the footer by itself.
The version never shows to signed-out visitors. The footer also renders on the login, registration and password-reset screens, and there it stays empty on purpose: an installation’s exact version is the map of vulnerabilities that apply to it.
Warning before you lose what you typed
In the Comportamento dos painéis section on the Aplicação tab, the unsaved-changes switch enables Filament’s native alert: leaving a form with pending changes makes the browser ask for confirmation before discarding them.
It applies to every create and edit screen across all three panels, including those coming from third-party plugins — the panel decides, not the resource, so there is nothing to wire up screen by screen.
It ships enabled: the previous behaviour was losing the input silently, and silence is not the default worth preserving. Turning it off is one click, no deploy.
It is an alert, not a draft: confirming the exit still loses what you typed. The kit evaluated two draft/autosave packages and adopted neither — the reasons are in
wikis/pacotes-candidatos.md.
Compact layout: a scale, not a switch
In the Comportamento dos painéis section on the Aplicação tab, Densidade do layout tightens the stat cards, the tables, the sidebar and the buttons of all three panels at once. There are three steps:
| Level | --spacing |
Table row | 10-row table | Sidebar width |
|---|---|---|---|---|
| Confortável (default) | .25rem, Filament’s own |
56.0 px | 612 px | 320 px |
| Compacto | 0.2rem |
46.4 px (−17.1%) | 509.2 px (−16.8%) | 272 px (−15.0%) |
| Denso | 0.175rem |
42.9 px (−23.4%) | 471.8 px (−22.9%) | 264 px (−17.5%) |
| Confortável (default) | Compacto | Denso |
|---|---|---|
All three are the same screen, and they are produced by the same suite that proves the option works — they are not a mock-up. Look at the actions column on the right: on confortável it is clipped to “Edi…”, and on denso it fits whole. Tightening is not only about height; it stops hiding content.

These numbers were measured on the kit itself, with a real browser reading computed style on
/admin/users at 1600×1000 — not estimated.
It ships confortável, on purpose: density is taste, and updating the kit must not change your project’s appearance on its own. At that level the kit emits no style at all — the HTML is byte for byte what it always was.
It takes effect on the next refresh. The kit emits a single CSS declaration per request, from a
render hook in Filament’s base layout: no Vite theme, no npm run build, no deploy. Saving on the
screen is enough.
The price, and it shows at the densest step. Filament 5 derives every spacing from a single variable, so shrinking the variable shrinks things that are not space: icons go from 24 px to 19.2 px at compacto and 16.8 px at denso, and the text input becomes shorter than the button beside it (4 px apart at compacto, 6 px at denso). Checkboxes shrink too, and that is the one that matters, because it is a click target: the checkbox square is
calc(var(--spacing) * 4), so it goes from 16 px to 12.8 px at compacto and 11.2 px at denso. That is exactly why the option is a scale and not an on/off — anyone bothered by the distortion, or who needs the full click target, stays on the middle step, which still delivers two thirds of the gain.The sidebar tightens on both axes, and the width needed its own mechanism: it comes from
--sidebar-width, not from--spacing. The widths in the table above are the measured minimum — the kit’s longest label is “Configurações da aplicação”, and it sets the threshold. If your project adds a menu item with a longer label, it gets an ellipsis, which is Filament’s normal behaviour.Three things do not tighten, and none of them is a defect: the top bar, whose height is fixed; the
/infra/pulsecards, which ship their own CSS (the Pulse tables tighten normally); and the collapsed sidebar rail, left out on purpose — it is icon-only, its width is the click target, and the icon inside it already shrinks. All three are on the kit roadmap.There is an official paid compact theme that reaches −22.5% without distorting icons or fonts. It does not ship with the kit because its licence is per project and a starter kit is distributed — the study, with the numbers, is in the
layout-compactwiki. The denso step reaches practically the same gain, with the distortion as the trade-off.
The dynamic dashboard is a switch, not a migration
Still on the Aplicação tab, the Dashboard dinâmico section swaps the panels’ landing screen: instead
of the classic dashboard, a grid the user builds, moves and resizes for themselves
(mddev31/filament-dynamic-dashboard). It ships off — KIT_DASHBOARD_DINAMICO=false in
.env.example — and Painéis onde vale restricts which panels it applies to: empty means all of
them.
Two decisions are what make it a switch rather than a one-way door:
- Both pages are always registered. The panel is built in the provider’s
register()and the database value only arrives inboot(); a conditional->pages([...])would read the config before it exists. What decides which page answers isApp\Support\DashboardDinamico, per request — saving here takes effect on the next F5, with no cache clear and no restart. - Turning it off deletes nothing. The grids people built stay in the
dashboardsanddashboard_widgetstables and come back untouched when you turn it on again.
Viewing is not building: dragging and saving the grid belongs to whoever has Shield’s
Manage:Dashboard permission — everyone else sees the same screen without being able to edit it.
The shortcut dock: three options, no starting panel
Still in the Aplicação tab, the Dock de atalhos section configures the floating button with each user’s favourites (how it works):
| Option | What it sets | Born |
|---|---|---|
| Panels where the dock appears | the panels (admin, app, infra) where the dock appears | none |
| Default layout | Leque (speed dial) or Barra; each user can change their own |
Leque (speed dial) |
| Style | Nativo, Contorno, Contraste or Tingido; applies to everyone, the user cannot change it |
Nativo |
The panel list is seeded by the KIT_DOCK_PAINEIS variable (comma-separated ids, in
config/kit.php → dock.paineis), empty in .env.example. Empty means no panel — the
opposite of the dynamic dashboard’s Panels where it applies, just above, where blank means all.
Layout and style have no variable: this screen is what changes them. As in the rest of the tab,
the database wins over .env and saving takes effect on the next F5. Who sees the dock is still
decided by the Usar:Dock permission, not by this section.
The AI agents: one switch and the chat panels
Still in the Aplicação tab, the Agentes de IA section turns the kit’s AI layer on and decides where the Assistant chat appears:
| Option | What it sets | Born |
|---|---|---|
| Usar agentes de IA (use AI agents) | on, the Agentes de IA catalog is back in the /admin menu and the chat may appear; off, the catalog leaves the menu, the hub, the ⌘K search and the dock, the /admin/agentes-ia URL answers 404 (even for someone with the permission), the Agentes de IA widget group leaves the /admin dashboard and the chat appears in no panel |
off |
| Painéis onde o chat do Assistente aparece (panels where the Assistant chat appears) | the panels (admin, app, infra) where the floating chat button is mounted; only applies with the switch on | none |
Both are seeded by KIT_AGENTES_IA and KIT_AGENTES_IA_PAINEIS (comma-separated ids, in
config/kit.php → agentes_ia), off and empty in .env.example. Empty means no panel, as in
the dock. As in the rest of the tab, the database wins over .env and saving takes effect on the
next F5: nothing hits the database to decide, and nothing is deleted when you turn it off — agents,
conversations and favourites stay stored and come back when you turn it on again. Up to v0.50.0 the
chat was fixed on /app; now it can live in any panel, and a chat already open when usage is turned
off refuses the next message with 404. Who opens the catalog is still decided by the
ViewAny:AgenteIa permission: the switch is one more condition, not a replacement for it.
The guided tutorial: seeding and enabling are separate choices
Still on the Aplicação tab, the Tutorial guiado section enables the getting-started journeys across all three panels (how it works):
| Option | What it defines | Ships as |
|---|---|---|
| Usar o tutorial guiado (use the guided tutorial) | on, shows the floating checklist, tours, Primeiros passos page and authoring screens; off, the Onboarding and Condições menus disappear from /admin and their URLs return 404 (even for users with the permission), the Jornadas de onboarding concluídas widget disappears from the /admin dashboard, and the checklist, tours and Primeiros passos page appear in no panel |
off |
The switch is seeded by KIT_ONBOARDING (config/kit.php → onboarding.habilitado), set to
false in .env.example. As in the rest of the tab, the database wins over .env. There is no
panel list: each journey declares its own. Turning it off deletes nothing; turning it back on
restores progress. A checklist already open refuses the next action while usage is off, saving
nothing.
Seeding does not turn the switch on. Run php artisan db:seed --class=OnboardingSeeder (or use
kit:install --onboarding during installation) to create the content, and enable usage on this
screen. The two can be done in either order. Seeing the tutorial still requires the
View:PrimeirosPassos permission; authoring follows ViewAny:OnboardingFlow and
ViewAny:OnboardingCondition.
A selector for people with nothing to switch to
With multi-organization enabled, the Organizações section on the Aplicação tab has Ocultar o seletor quando houver uma
organização só. On, the whole block at the top of the /app sidebar — avatar, label and name —
disappears for anyone who can reach one active organization; with nothing to switch to, it
just repeats the name the header already shows. With two or more, the selector stays.
The count comes from the very list that feeds the selector: the user’s active organizations —
and, for master_global, all of them. A link to an inactive organization does not count.
It is looks only: canAccessTenant(), the /app/{tenant} address and direct links do not change.
It ships off and takes effect on the next F5 — the decision is read per request, not when the
panel boots.

| The switch on the Aplicação tab |
|---|
Who wins: the database or .env?
This is the question that decides whether the screen is useful or decorative, and there is a single answer:
The database wins at runtime.
.envseeds the first write and is the fallback.
flowchart LR %% DG-14 accTitle: Where configuration comes from accDescr: .env seeds config/*.php at boot; the database overrides mapaDeConfiguracao's keys when the settings table exists, and the settings screen writes to it; a key outside the map, such as KIT_TENANCY, has only .env as its source. env_file[".env"] --> config_php["config/*.php"] settings_banco["Settings in DB"] -->|"overrides, mapped keys"| config_php tela_config["Settings screen"] -->|"writes"| settings_banco env_file -.->|"only .env: KIT_TENANCY and other unmapped keys"| chave_fora_do_mapa["Key outside mapaDeConfiguracao"]
ConfiguracoesDoKit::aplicarNaConfig() runs the second arrow
(app/Settings/ConfiguracoesDoKit.php:aplicarNaConfig:640), called at boot by
KitServiceProvider::configureSettingsDoKit()
(app/Providers/KitServiceProvider.php:configureSettingsDoKit:467); the overridden keys are exactly
the ones in mapaDeConfiguracao()
(app/Settings/ConfiguracoesDoKit.php:mapaDeConfiguracao:469) — KIT_TENANCY
(config/kit.php:KIT_TENANCY:414) is not in that map, so the database never overrides it.
How that works without any consumer knowing the settings exist:
- The
database/settings/*_create_kit_settings.phpmigration seeds each property with the value fromconfig(...), which comes from.env. On a fresh install, the colour and name you picked duringkit:installreach the database on their own —migrateruns after the installer wrote the file. App\Providers\KitServiceProvider::configureSettingsDoKit()overlays the process configuration with what the database holds, once per request and per artisan command.App\Support\CorPrimaria, the threePanelProviders, the global table configuration and Laravel’s ownMailManagerall keep readingconfig(). None of them changed.
What happens in each situation:
| Situation | Who wins |
|---|---|
| the property has a row in the database | the database |
| the property has no row (you added one and didn’t migrate) | .env, with a warning in the log |
the settings table does not exist (before the first migrate) |
.env, silently |
| the database is unreachable | .env, with a warning |
kit:install on a fresh install |
.env → the migration carries the values into the database |
kit:install --force |
drops the database, rewrites .env and re-migrates → the database is born matching the new .env |
kit:install --custom on an installed project |
rewrites .env and writes to the settings — both sources end up equal |
There is no switch for “use the settings or not”, and that is a decision, not an omission: a flag would be a third source of truth, which is exactly the problem the rule above solves. To turn it off, php artisan migrate:rollback on the settings migration — with no rows in the table the overlay is a no-op and .env is the only source again.
Colour: closed list and free colour
The Cor primária section on the Identidade tab has two fields, with a declared precedence:
valid hex → palette name → Filament default.
Hex wins because it is the more specific field: someone typing #7c3aed chose that colour, whereas the list selector has a default value and may never have been touched. A value outside the format (#abcd, blue, #gggggg) is ignored and resolution falls back to the name — the same tolerance the kit already had for an invalid colour name, and for the same reason: this runs in every panel’s boot, and an exception there would take down every page in the project, not one screen.
Inside /app/{organisation}, the organisation’s colour still beats both.
Brand logo: one, or two by theme
The Logo, favicon e arte section on the Identidade tab has a switch, One logo only, on both themes — and it ships ON, because an existing installation must not change its look through a kit update. With it on, the same logo is used on the light and dark themes, as it always was.
Turned off, the brand splits into two images:
- Brand logo — shown on the light theme, and in the top brand of all three panels. A light background is recommended.
- Logo for the dark theme — shown only on the dark theme. A transparent background is recommended: with a white background the logo becomes a solid block in the dark.
Both fields become required of each other in the split mode: saving the light one without the dark one (or the other way around) is refused. With both empty, the panel falls back to the name in text, with no error.


The browser swaps the image, through the dark class on <html> — the server cannot know the
theme when it is “System”, so both <img> tags ship in the same HTML and Filament’s CSS shows the
right one. The top of the business panel and the lock screen use the same pair, according to the
installation’s and the organisation’s switches.
The organisation has the same switch on its own form: One logo for both themes. On (the default),
the uploaded logo covers light and dark and the company does not have to send a second image; off, the
dark-variant field appears. Organisations that already had a logo_dark uploaded when the feature
shipped start with the switch off — an existing split is not undone by an update.
With unifica_logo on and the organisation’s light logo available, it serves both themes;
an old dark variant of the organisation is not used. If the light logo was not uploaded or its
file no longer exists, the installation’s complete pair is used, respecting the global switch.
With the organisation’s unification off, fallback applies per variant: its light logo falls back
to the installation’s light logo; its dark logo falls back to the installation’s dark logo and,
without that, the browser displays the resolved light logo. The dark variant only applies when
global unification is also off; with it on, the resolved light logo serves both themes, regardless
of the organisation’s switch.
Panel header: what to show at the top
The Identidade tab closes with the Cabeçalho dos painéis (panel header) section: four switches and one select, all born off — with nothing on, the three panels look exactly as they always did (the uploaded logo or, without one, the name as text). Same rule as the logo by theme: an existing installation does not change its face with a kit update.
The first three compose the top brand, in this order, with a separator between each present segment:
- Nome do projeto na marca do topo (project name) — the application name, as text.
- Nome do painel na marca do topo (panel name) —
Administração,Infraestruturaor, in the business panel, the name of the open organisation. Without an open organisation, the business panel omits the segment when it would repeat the project name next to it (there, the panel label is the application name). - Logo da marca ao fim da composição (brand logo) — the logo from the previous section (the light one and, with a split brand, the dark one, swapped by the browser). Without an uploaded logo nothing shows in that spot — and if no segment resolves, the brand goes back to the usual one; it is never empty.
The composition replaces the brand wherever Filament draws it: in the topbar and, on a narrow
screen with the sidebar open, at the top of the sidebar (there the texts shrink and truncate with an
ellipsis to fit on one line). Authentication screens (login, password reset,
two-factor, e-mail verification) keep the usual brand: the header belongs to the
panels’ inner screens. In the business panel (/app), with an
open organisation, the composition’s logo (and the plain brand, with the composition off) is the
organisation’s, with unification and fallback as described in the previous section; the image’s
alt is the organisation’s name when the logo is hers. In the
administration and infrastructure panels it is the installation’s; on authentication screens (login,
password reset) too, and the lock screen uses the session’s organisation, by the same rule.
The fourth switch, Nome do usuário ao lado do avatar (user name next to the avatar), shows who
is signed in to the left of the avatar, with a line below chosen in Abaixo do nome do usuário:
the profile (the role in the current panel, with the same label as the avatar menu —
Administrador Geral in any panel; someone with no role in the panel gets the name alone) or the
e-mail. The block disappears below 768 px of width; the avatar menu keeps showing name,
e-mail and role. The select only appears with the block on.
The five options have .env counterparts, which seed the first write and are the fallback — like
every other option on this screen (Who wins):
# KIT_CABECALHO_NOME_DO_PROJETO=false# KIT_CABECALHO_NOME_DO_PAINEL=false# KIT_CABECALHO_LOGO_DA_MARCA=false# KIT_CABECALHO_USUARIO=false# KIT_CABECALHO_DETALHE_DO_USUARIO=perfil # or email; anything else falls back to perfilPermission
Just one: View:ConfiguracoesDoKit, generated by ShieldPermissionsSeeder and handed to the admin role by PapeisSeeder — with no list to edit, because a role’s matrix is the whole panel’s. master_global gets in through Gate::before; infra and panel_user do not receive it.
It is one permission for opening and saving, on purpose. The plugin’s canEdit() disables the form but does not hide values — the package’s own README says so in writing — and this screen holds the SMTP password. A “read-only” role here would be a role that reads a credential.
Upload ceiling: 10 MB, and where to change it
Every upload in the kit — this screen’s logo, favicon and login artwork, the organisation logo in
/admin/organizacoes, and Projeto attachments — accepts files up to 10 MB, and refuses SVG.
The number is one key, in .env:
# In MEGABYTES. Empty, 0 or absent = 10.KIT_UPLOAD_MAXIMO_MB=10It feeds config('kit.uploads.maximo_em_kb') — the config holds kilobytes, because that is
the unit Filament’s ->maxSize() and Livewire’s temporary-upload rule receive. The multiplication
by 1024 lives in exactly one place, in config/kit.php, and App\Support\TetoDeUpload is what
reads the key. There is deliberately no field for this on the screen: it is an installation
decision, not a day-to-day one.
An upload crosses four limits, and the smallest one wins. They do not refuse in the same way, and that is what makes a mismatch expensive:
| Layer | Where | Value in the kit | How the error shows up |
|---|---|---|---|
| nginx | docker/nginx/nginx.conf |
client_max_body_size 60M |
network failure in the console |
| PHP | docker/php/uploads.ini |
upload_max_filesize=52M, post_max_size=60M |
same |
| Livewire (temporary upload) | aligned to the kit key by KitServiceProvider, with 1 MB of headroom |
11 MB | 422 on the XHR, generic error |
Filament (->maxSize()) |
the kit key | 10 MB | a proper message, on the field |
Only the last one refuses with a clear message — which is why the kit aligns Livewire to the key instead of leaving its default (12 MB) looser than the screen.
To raise the ceiling substantially, change these together:
KIT_UPLOAD_MAXIMO_MB— covers the screen and Livewire in one go;- above 52 MB,
docker/php/uploads.ini(upload_max_filesizeandpost_max_size); - above 60 MB,
docker/nginx/nginx.conf(client_max_body_size).
⚠️ Outside the kit’s Docker setup, PHP usually ships with upload_max_filesize=2M. There the
real ceiling is 2 MB, not the key’s — and the error shows up as a network failure that never
mentions size. Check with php -i | grep upload_max_filesize before blaming the kit.
Why SVG is refused
SVG is XML, and XML accepts <script>. The logo, the favicon and the login artwork are served
from the application’s same origin, with public visibility: opening an uploaded SVG’s URL
would run the script with access to the session cookie — stored XSS. The uploader is admin, who
already has full access, so this is insider escalation rather than an anonymous door; in a starter
kit it is worth closing anyway.
The barrier is Laravel’s mimes rule (not Filament’s ->image(), which is a different thing
and accepts image/*, SVG included), with the format list in
ConfiguracoesDoKit::FORMATOS_DE_IMAGEM: jpg, jpeg, png, gif, bmp, webp, avif, heic, heif, ico,
tif and tiff. SVG is the only image format left out, and it is the only one that carries script.
And it does not look at the extension: the MIME type comes from the file’s content on the
temporary disk, so renaming logo.svg to logo.png does not get through. On Projeto attachments,
where an allow-list would close the field to PDFs and spreadsheets, the rule refuses only
image/svg+xml.
Change trail
Every change shows up in /infra/audits, with who changed it, when, the property name and the old and new values. One row per changed property; saving without changing anything creates no record.
The mail password is encrypted in the settings table and enters the trail masked (••••••): the record says the secret changed, never what it is.
Two details worth knowing before touching this:
- The trail does not come from the
App\Traits\AuditsFillablestrait. A spatie settings class is not an Eloquent model, and pointing its repository at a model with the trait would audit only creation — changing an existing property goes throughupsert(), which fires no Eloquent event. The trail comes from aSavingSettingslistener, the only point in the package carrying old and new values together. - The recorded event is
settings-updated, notupdated, so the trail’s “restore” button does not appear: it wouldfill(['nome_da_aplicacao' => …])into a row whose columns aregroup/name/payload.
This is not an organisation’s settings
A tenant’s visual identity (per-organisation colour and logo) is still plain CRUD at /admin/organizacoes, in the cor_primaria (free hexadecimal), cor_primaria_nome (the same Filament palette as this settings screen — the hexadecimal wins when filled) and logo columns of the Tenant model, and it beats the kit’s inside /app/{slug}. Nothing was moved here.
What was left out, and why
| Item | Why |
|---|---|
| database driver, host and name | changing it after migrate is not a config rewrite, it is a different installation |
| turning multi-tenancy on/off | the permission tables only get the context column if permission.teams is active before migrate; the path is php artisan kit:tenancy |
| admin e-mail and password | UsuarioAdminSeeder does not sync, on purpose (it runs on every db:seed, and updating the password there would silently revert a change made in the profile screen). A field that does not change the credential is worse than no field — the path is the profile screen |
| organisations CRUD slug | it is read at route registration, not at render, and the URL is a permanent identifier |
| panel languages | the kit’s internationalisation is not done: turning on a second language today switches half the screen. See the idiomas block in config/kit.php |
| trail retention | not an installation question; it stays in .env, where zero has documented semantics |
Performance
The overlay costs one query per boot (the whole group comes from a single read). If that bothers you, SETTINGS_CACHE_ENABLED=true in .env — bearing in mind that with the cache on, saving through the screen requires php artisan settings:clear-cache.
Adding a property
Three places, always, and tests/Kit/ConfiguracoesDoKitTest.php fails if you forget one:
- the typed property in
app/Settings/ConfiguracoesDoKit.php; - the line in
ConfiguracoesDoKit::mapaDeConfiguracao()(property →config()key); - the
add()/deleteIfExists()pair in a new migration underdatabase/settings/.
Plus the field on the right tab of app/Filament/Admin/Pages/ConfiguracoesDoKit.php.