jiannius/atom
jiannius/atom is a Laravel/PHP package offering reusable components and utilities to streamline common development tasks. It aims to reduce boilerplate and integrate smoothly into Laravel applications, helping teams build features faster. Early-stage with limited public documentation.
The earlier read of this package as "just a Blade component library" undersells it. The 3.38.0 release notes confirm atom also ships Artisan commands (atom:upgrade-check, atom:tiptap-migrate, atom:purge-editor-images, atom:upgrade), model traits (AtomComponent), and editor content casts (AsEditorContent, AsTiptapContent), with components rendered as <atom:…> tags rather than an <x-atom::…> namespace. Budget your first hour accordingly: commands and casts first, views second.
composer require jiannius/atom
php artisan list atom # the real surface area: commands, not just components
First action is not a component swap — it's the read-only upgrade scan, because it tells you what your app already uses:
php artisan atom:upgrade-check
It writes nothing (unless you pass --output), so it's safe on a production checkout. Read the output before touching anything else; it names the files, the traits your AtomComponent models will need for v4, the <atom: tags in use, and which published atom views need re-checking.
Then look at:
vendor/jiannius/atom/src/Console/Commands/ — the four commands above are the day-to-day workflow.vendor/jiannius/atom/src/Casts/ — AsEditorContent vs AsTiptapContent decides whether your content columns are still legacy format.v4 branch UPGRADE.md — atom:upgrade-check is generated from it, so it's the authoritative source when a finding is ambiguous.First real use case: run atom:upgrade-check --json --output=storage/app/atom-upgrade.json, commit the JSON next to your upgrade ticket, and triage the findings by severity. That single artefact replaces most of the guesswork this assessment previously recommended.
Treat atom:upgrade-check as the entry point for every upgrade, not a one-off. It runs read-only, supports --json, --output=, --path=, and --all, and every finding carries file:line, a severity (breaks at runtime / visual change / review), and whether atom:upgrade can fix it or only report it. The third column is the one that matters for planning — "report only" findings are manual work.
# CI gate: fail the build on runtime-breaking findings only
php artisan atom:upgrade-check --json --output=build/atom-upgrade.json
Filter on severity in your pipeline rather than eyeballing the console output; the human-readable report is for the ticket, the JSON is for the gate.
Scope the scan deliberately. On a large app the default run is noisy. Use --path=app/Models when you're chasing cast/trait issues, and --all only when you want the full picture:
php artisan atom:upgrade-check --path=app/Models # trait/cast surface
php artisan atom:upgrade-check --all --output=atom-full.json
Editor content migration is now a four-stage workflow, not a command. The v2 migrate command is reversible, and you should use the reversibility:
php artisan atom:tiptap-migrate # dry run — writes nothing
php artisan atom:tiptap-migrate --compare # dry run + JSONL original-vs-new review file
php artisan atom:tiptap-migrate --accept=12,48 # convert only the rows you reviewed
php artisan atom:tiptap-migrate --force # convert all clean rows, unreviewed
php artisan atom:tiptap-migrate --status --for-v4 # gate the v4 upgrade on migration completeness
Every original is written to atom_editor_backups (created on first use), so --restore and --restore=12,48 revert byte for byte. Only prune once you're satisfied:
php artisan atom:tiptap-migrate --prune-backups --confirm-prune # --confirm-prune for non-interactive
Prefer --accept=<ids> over --force in production. --force converts "clean rows" unreviewed, and "clean" is the command's judgement, not yours.
Switch the cast before migrating, not after. On 3.x, AsEditorContent columns can be migrated. The documented order is: change the cast to AsTiptapContent first, then fix any raw-HTML printing of the column that assumed the old cast's output, then migrate.
protected $casts = [
'content' => AsTiptapContent::class, // was AsEditorContent
];
Any view doing {!! $page->content !!} or {{ strip_tags($page->content) }} is the thing that breaks here — grep for raw output of the column before you run anything.
Deploy scripts need auditing, not just updating. The two behavioural changes in this release both bite unattended runs:
# WRONG after 3.38: silently does nothing in a deploy script
php artisan atom:tiptap-migrate
# RIGHT: explicit, but now can exit 1 and kill a `set -e` chain
php artisan atom:tiptap-migrate --force
If your deploy script runs under set -e, wrap the migration so an exit 1 alerts rather than aborts mid-deploy, or move it to a separately monitored step.
Component usage. Components are <atom:…> tags, not the <x-atom::…> form the previous assessment assumed:
<atom:button variant="primary" size="sm">Save changes</atom:button>
Confirm the registered prefix from the provider before writing templates — a wrong prefix produces "Unable to locate a class or view", which is not a helpful error. atom:upgrade-check also lists the <atom: tags in use, which is a faster inventory than grepping your templates.
Keep the agent-facing docs current. The scan checks whether the atom section of your CLAUDE.md / AGENTS.md is current, which means the package has an opinion about how AI assistants should be briefed on it. If you use either file, update that section as part of the upgrade — a stale section produces confidently wrong code from your assistant.
Wrap components in app-level components. Still the right call. A breaking change in atom becomes a one-file edit rather than a repo-wide grep:
{{-- resources/views/components/ui/button.blade.php --}}
@props(['variant' => 'primary'])
<atom:button {{ $attributes->merge(['variant' => $variant]) }}>
{{ $slot }}
</atom:button>
atom:tiptap-migrate without --force is now a no-op. This is the highest-risk change in the release precisely because it's silent: a deploy script that previously converted rows now does nothing and reports success. Grep your deploy config, your CI workflows, and any Makefile/composer scripts for the bare command.
--force exiting 1 will fail set -e deploys. It exits non-zero "when a write failed or a refused value remains" — both of which are legitimate states in a partially-migrated dataset. Decide deliberately: either tolerate the exit (and alert on it) or pre-migrate out-of-band and remove the command from the deploy path entirely.
atom:purge-editor-images now reads every discovered model's connection. Because it inspects backup tables on each connection, a single unreachable secondary connection stops the whole run. Don't run it during a failover, and don't assume it's safe just because it's a cleanup command. Models with no editor column on an undefined connection are warned about and skipped rather than fatal, so read the warnings — a skip may mean the purge missed references.
The --compare JSONL file is content, not metadata. It contains original and converted editor payloads, which means user data, which means it is not a CI artefact. Keep it out of build output, out of shared storage, and delete it after review. The upgrade report has the same property — it quotes matching source lines, so treat --output files like source code for access-control purposes.
--force is not "do the migration", it's "trust the classifier". It converts clean rows unreviewed. Rows it refuses stay refused, which is why the exit code is non-zero rather than a clean success. If you see a partial run with exit 1, --status tells you what's left; --accept=<ids> is how you finish it after review.
Don't prune backups until --status --for-v4 is clean. --prune-backups deletes your only byte-for-byte route back. Gate it on the v4 status check and keep a database snapshot from before the migration as a second line of defence.
Upgrade to v4 is a separate project from 3.38.0. This release adds readiness tooling; it changes nothing in components, styles, JS, or traits. Use the breathing room to run atom:upgrade-check, migrate editor content, and clear the trait work — then take v4 with a report that's already small.
Trait work is the underestimated part. The scan lists the traits each AtomComponent model will need, per model. On an app with a dozen models that's a dozen edits and a dozen test runs; plan it as its own ticket rather than a line item in the upgrade.
Pin the version. Use an exact constraint ("jiannius/atom": "3.38.0") and read the diff before bumping. The migrate command's default-mode flip is exactly the kind of change that lands in a minor release and breaks a deploy, so treat every bump as reviewable.
Debugging resolution. When a tag "doesn't exist", confirm what's registered rather than guessing:
php artisan view:clear
grep -rn "Blade::" vendor/jiannius/atom/src
php artisan atom:upgrade-check --all --json | jq '.[] | select(.severity == "breaks at runtime")'
Extension points, in order of preference. 1) Wrap components in app-level components; 2) publish and override config; 3) publish and override individual views; 4) use --accept/--restore to stay inside the migration tooling rather than hand-editing cast columns; 5) fork. Stop at step 3 in almost all cases.
Upstream what you find. The readiness tooling is generated from a real UPGRADE.md, which suggests active maintenance — but any bug you hit is still likely unreported. A minimal reproduction (one model, one column, one command invocation) plus an issue is cheaper than owning a fork.
How can I help you explore Laravel packages today?