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 Astrotomic Laravel Package

doriiaan/filament-astrotomic

Filament Astrotomic integrates Astrotomic Translatable models into Filament, giving you locale-aware form fields, tabs and table columns so you can manage multilingual content in one place without custom glue code.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites. This plugin only makes sense on top of astrotomic/laravel-translatable. Confirm three things before you install: your Filament major version, your Laravel version, and the Astrotomic version — all three appear in the plugin's composer.json. With 8 stars and 0 dependents the constraint range is likely narrow, so check rather than assume.

composer require astrotomic/laravel-translatable
composer require doriiaan/filament-astrotomic

The Astrotomic side is not handled by this plugin: publish its config, create the companion *_translations tables (translated columns live there, not as a JSON column on the parent table), and set public array $translatedAttributes = ['title', 'slug']; on the model.

Where to look first. The package is small — read vendor/doriiaan/filament-astrotomic/src end to end before you use it. There is almost certainly a Concerns/Traits directory containing the form and table helpers; that source is the real documentation. Then skim the README example resource, which is likely a single PostResource with a locale-tabbed form.

First use case. One resource, two locales, a tabbed form and a table column showing the current locale's value with fallback:

  1. Model uses Astrotomic\Translatable\Translatable.
  2. Resource form wraps translated fields in per-locale tabs using the plugin's trait.
  3. Table column reads through the translation relation instead of the attribute.

Get that vertical slice working before touching slugs, search, or a third locale — the translation-storage model is the part that trips people up, and you want to see post_translations rows appear correctly on save first.

Implementation Patterns

Per-locale tabs with a shared field builder. The dominant pattern: build the translated field set once in a closure and invoke it for each locale inside Tabs, so adding a field means editing one place.

Tabs::make('Translations')->tabs(
    collect(config('app.locales'))->map(fn ($locale) => Tab::make(strtoupper($locale))
        ->schema($this->translatedFields($locale)))->all()
);

Keep locale list in exactly one source of truth (config('app.locales')) and feed both the form tabs and any locale switcher from it — the classic failure mode is the tab list and the $translatedAttributes list drifting apart.

Table columns must go through the relation. TextColumn::make('title') will not read the translation. Use the plugin's column helper if it provides one; otherwise:

TextColumn::make('title')
    ->getStateUsing(fn ($record) => $record->translate(app()->getLocale())?->title
        ?? $record->translate(config('app.fallback_locale'))?->title)

Eager load, always. Override getEloquentQuery() with ->with('translations'). Without it, a 50-row table issues one query per row per column; this is the single biggest performance trap with table-per-translation models.

Search and sort across translations. Translated attributes aren't on the parent table, so ->searchable() and ->sortable() silently do nothing useful. Use the query callback:

->searchable(query: fn ($query, $search) => $query->whereHas('translations',
    fn ($q) => $q->where('title', 'like', "%{$search}%")->where('locale', app()->getLocale())))

Sorting requires an explicit join or a withAggregate on the translations relation.

Unique slugs per locale. Astrotomic won't generate or scope slugs for you. Either mutate in mutateFormDataBeforeCreate()/mutateFormDataBeforeSave(), or validate against the translations table:

Rule::unique('post_translations', 'slug')
    ->where('locale', $locale)
    ->ignore($record?->translate($locale)?->id)

Fallback: hand-rolled relation manager. If the trait doesn't cover your case (custom layouts, permissions per locale), a RelationManager over translations() with a locale Select is a ~60-line escape hatch and often clearer than fighting a thin plugin.

Copy-from-default action. A header action that iterates locales and fills empty tabs from the fallback locale is the highest-value convenience you'll add, and users expect it.

Gotchas and Tips

Maturity risk is the headline. 8 stars, 0 dependents, one maintainer. Pin the exact version in composer.json, note it in your upgrade docs, and budget for the possibility of forking — the trait surface is small enough that a fork is a day's work, which is also why adopting it is low-risk.

Don't confuse it with Filament's native translatable support. Filament's built-in ->translatable() on fields talks to JSON-column storage (Spatie). Astrotomic stores one row per locale. Mixing the two produces fields that render but never persist.

Validation errors land on the wrong tab. A required field failing on the fr tab leaves the user staring at the en tab with no visible error. Add tab error indicators (a badge or ->badge() driven by $errors), or hook the validation failure to activate the tab owning the first error.

Mass assignment. Translated attributes must be in $translatedAttributes, and the translation model must be fillable for them. Filament's fill() path goes through the parent model, so a missing $translatedAttributes entry silently drops input rather than erroring.

createAnother / reorder actions. Actions that re-save the record can produce duplicate locale rows if you've bypassed Astrotomic's save path (e.g. attaching translation rows manually in a RelationManager while the model also uses the trait). Pick one ownership model per resource.

Bulk actions and delete() cascade to translations only if the FK is set up with onDelete('cascade') — verify the published migration; otherwise you orphan rows that break translate() lookups later.

Debugging hydration. When a field looks empty, dump $livewire->data and $record->translations->pluck('title', 'locale') side by side. The gap between what the form state holds (often an array keyed by locale) and what the model exposes (translate($locale)->title, a string) explains most "it doesn't save" reports.

Extension points. If the shipped trait is too thin, the two seams worth subclassing are the field-schema builder and the table column state resolver — both are pure functions over a locale and a record, so they're safe to wrap without touching the package.

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