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

mokhosh/filament-jalali

Filament plugin adding full Jalali (Solar Hijri/Persian) calendar support to admin panels: Jalali date picker inputs, date/time table columns, filters, and formatting/conversion helpers so Persian dates display and save correctly in resources, forms, and tables.

View on GitHub
Deep Wiki
Context7

Getting Started

mokhosh/filament-jalali swaps Filament's Gregorian date pickers for Jalali (Shamsi) ones while keeping Gregorian values in your database. v6.0.0 is a major bump whose only substantive change is the platform matrix: Filament 5 + Laravel 13. There are no new field APIs in the changelog, so the day-to-day workflow is unchanged — but the install path now splits by Filament version.

Version selection first:

# Filament 5 / Laravel 13
composer require mokhosh/filament-jalali:^6.0

# Filament 4 / Laravel 12 — stay put
composer require mokhosh/filament-jalali:^5.1

Do not run composer update mokhosh/filament-jalali in a Filament 4 app and assume you're fine; Composer will resolve you to 5.1.x, which looks like a successful upgrade in CI logs. Pin the constraint explicitly.

Install and register:

composer require mokhosh/filament-jalali
php artisan filament:upgrade
php artisan filament:assets
// app/Providers/Filament/AdminPanelProvider.php
use Mokhosh\FilamentJalali\FilamentJalali;

public function panel(Panel $panel): Panel
{
    return $panel
        ->locale('fa')
        ->plugin(FilamentJalali::make());
}

First use case: pick the one resource where a Persian-speaking admin actually enters dates — an order, an invoice, a subscription — and swap a single field:

use Mokhosh\FilamentJalali\JalaliDatePicker;

JalaliDatePicker::make('due_at')
    ->label('مهلت پرداخت')
    ->required();

Then verify the round trip: type a Jalali date in the picker, save, and confirm the due_at column holds the correct Gregorian Y-m-d. If that one field behaves, the rest is mechanical.

Implementation Patterns

Swap at the field level, not the model level. The package's contract is: picker renders Jalali, state stays Gregorian, Eloquent casts stay date / datetime. Keep it that way.

// migration / model: untouched
protected function casts(): array
{
    return ['due_at' => 'date'];
}

Centralise the choice with a factory helper so you never have two sources of truth for "which picker does this panel use":

namespace App\Support;

use Mokhosh\FilamentJalali\JalaliDatePicker;
use Mokhosh\FilamentJalali\JalaliDateTimePicker;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\DateTimePicker;

final class DateField
{
    public static function date(string $name): DatePicker
    {
        return app()->getLocale() === 'fa'
            ? JalaliDatePicker::make($name)
            : DatePicker::make($name);
    }

    public static function dateTime(string $name): DateTimePicker
    {
        return app()->getLocale() === 'fa'
            ? JalaliDateTimePicker::make($name)
            : DateTimePicker::make($name);
    }
}

Return type is the base Filament class so callers can still chain ->required(), ->minDate(), ->seconds(false) without caring which branch ran. This is also the seam that makes a future swap to a different Jalali package a one-file change.

Keep conversions at the boundary only. If you're also importing/exporting or generating PDFs, convert once at ingest using a Jalali library (Morilog/Jalali or hekmatinasser/verta) and hand Filament a Gregorian Carbon. Double conversion — a Jalali value parsed by Carbon on the way out and re-parsed on the way in — is the classic silent off-by-one-day / off-by-one-month bug and it will not throw.

Validation runs against the underlying value. ->minDate(now()), ->after('start_at'), ->rules(['date']) all operate on the Gregorian state. That's usually what you want; just don't write custom rules assuming a Jalali string lands in $state.

Multi-panel apps need the plugin per panel. Registering in one provider does not leak into UserPanelProvider. If you have a shared base provider, put it there — but note the ->locale('fa') decision is per panel, and the plugin should follow the same scope.

Testing: assert against the database, not the rendered picker. Wire-level assertions on Jalali strings are exactly what PR #36 in this release had to refactor away from — they're brittle.

livewire(CreateOrder::class)
    ->fillForm(['due_at' => '2025-03-21']) // Gregorian; or convert a Jalali fixture first
    ->call('create')
    ->assertHasNoFormErrors();

$this->assertDatabaseHas('orders', ['due_at' => '2025-03-21 00:00:00']);

If you need to feed Jalali input in tests, convert with the same library the picker uses, so the fixture and the component agree.

Table integration changed scope in this release. PR #36 moved the test suite to a real Filament table configuration. That's a good signal for you: after upgrading, re-check DatePicker filters (->form(fn (Filter $filter) => [...])), TextColumn::dateTime()->format(), and any ->summarize() / group-by-date on Jalali-facing columns. Filters are the highest-risk surface because they rebuild a form schema inline.

Gotchas and Tips

v6 is not a cosmetic bump. Filament 5 drags in its own Livewire, PHP, and asset-pipeline requirements. Before bumping, run composer why-not mokhosh/filament-jalali ^6.0 in the app to see what's actually blocking you — it's usually a Filament plugin that hasn't shipped a v5-compatible release yet, not this package.

Locale timing is bug #1. app()->getLocale() inside a field closure evaluates at render; ->locale('fa') on the panel is set by Filament's middleware. If your helper factory runs in a service provider or a config file, it will read the default locale, not the panel's. Keep the locale check inside a method that runs during form rendering.

Timezone double-shift. Jalali conversion is date-based, but DateTimePicker is instant-based. If your app stores UTC and shows Tehran time, convert timezone first, then format Jalali — not the other way round. A one-hour DST-adjacent shift turns into a wrong day at midnight.

minDate / maxDate / disabledDates take Gregorian. Passing '1404-01-01' to ->minDate() doesn't throw; it becomes an invalid Carbon and the constraint quietly disappears. Convert with Jalali::fromFormat('Y-m-d', '1404-01-01')->toCarbon().

After upgrading, clear the cached assets. Filament 5 ships a new asset pipeline; a stale public/js/filament build is the most common "picker renders blank / no calendar" report. Run php artisan filament:assets && php artisan view:clear, and in production re-run filament:optimize (or whatever your deploy script uses) as part of the release.

Don't add a Jalali cast to the model. It fights the field, breaks whereDate queries, and breaks Filament's sorting on the column. If you need Jalali display in a table separate from the form, format at the column level.

Bulk actions and imports bypass the picker. ImportAction, CreateAction with ->mutateFormDataUsing(), and seeded data all write raw state. Validate/convert there too — the field's Jalali rendering gives you no protection outside the form.

Extension points worth knowing: the package is a thin layer, so if a specific picker needs behavior it doesn't expose, subclass the field, override the view, and register ->view() on your subclass rather than monkey-patching vendor code. For per-panel tweaks (e.g. week-start, format string), a small config publish + ->format() on the field is cleaner than a fork.

Regression checklist for the v6 upgrade: (1) one date field round-trips correctly, (2) table date range filter still filters, (3) TextColumn date formatting under fa renders Jalali, (4) timezone-shifted DateTime field at 23:00 local still stores the right UTC day, (5) CI uses an explicit ^5.1 / ^6.0 constraint rather than floating.

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