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 Timezone Field Laravel Package

tapp/filament-timezone-field

A Filament form select field for picking timezones, with options to filter by country or region, switch display language, and use GMT instead of UTC. Install via Composer and add TimezoneSelect::make('timezone') to your resource schema. Compatible with Filament 3.x, 4.x and 5.x.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require tapp/filament-timezone-field:"^3.0"

There is nothing to publish, no migration, no asset build and no install command. Once Composer has pulled it in you can reference the classes straight away.

Pick the right major first. The package version tracks the Filament version, not its own features:

Filament Package
3.x / 4.x / 5.x ^3.0
2.x ^2.0 (separate branch)

If you are on Filament 3, ^3.0 is still the correct constraint — the 3.x line was retro-fitted for Filament 4 and 5 support (see v3.0.10 and v3.0.12). Pin at least ^3.0.13; see Gotchas.

Where to look first: src/Forms/Components/TimezoneSelect.php (the field and all its -> modifiers), src/Enums/Region.php (region constants for ->byRegion()), then src/Tables/Columns/TimezoneColumn.php.

First use case — a timezone column on users, defaulting to UTC. Migrate:

$table->string('timezone')->default('UTC');

Then in your UserResource:

use Tapp\FilamentTimezoneField\Forms\Components\TimezoneSelect;

public static function form(Form $form): Form
{
    return $form->schema([
        TimezoneSelect::make('timezone')
            ->searchable()
            ->required(),
    ]);
}

That's it — the field is a subclass of Filament's Select, so the stored value is the plain IANA identifier (Europe/Lisbon, America/New_York) while the visible label carries the offset and human-readable name. Nothing else in your app has to change to consume the value; Carbon::now($user->timezone) just works.

Implementation Patterns

Keep field, column and filter in sync

The common failure mode is showing UTC in the form and GMT+0 in the table (or vice-versa). Centralise the display config:

use Tapp\FilamentTimezoneField\Forms\Components\TimezoneSelect;
use Tapp\FilamentTimezoneField\Tables\Columns\TimezoneColumn;
use Tapp\FilamentTimezoneField\Tables\Filters\TimezoneSelectFilter;

// form
TimezoneSelect::make('timezone')->timezoneType('GMT')->searchable()->required(),

// table
TimezoneColumn::make('timezone')->timezoneType('GMT')->formattedOffsetAndTimezone(),

// filter
TimezoneSelectFilter::make('timezone')->timezoneType('GMT'),

The filter accepts the same display modifiers as the field (added in v3.0.3), so a single helper method returning the configured component keeps all three aligned:

protected static function timezoneField(): TimezoneSelect
{
    return TimezoneSelect::make('timezone')
        ->timezoneType('GMT')
        ->searchable()
        ->required();
}

Narrow the option list for regional apps

The full IANA list is ~400 entries. If your tenants are Australian or US-only, don't make admins scroll:

TimezoneSelect::make('timezone')
    ->byCountry(['US', 'AU'])
    ->searchable(),

// or
use Tapp\FilamentTimezoneField\Enums\Region;

TimezoneSelect::make('timezone')
    ->byRegion([Region::Australia, Region::America]),

Arrays are supported on both byCountry() and byRegion() since v3.0.6; a bare string or single DateTimeZone::AUSTRALIA constant also works. Prefer the Region enum over raw DateTimeZone integers for readability — both are accepted.

Localisation without touching stored data

->language('es') only changes the rendered labels via Symfony Intl. The value written to the database is still the IANA identifier, so you can safely switch locale per panel or per user without a data migration:

TimezoneSelect::make('timezone')->language(app()->getLocale())

Browser hydration on profile / onboarding forms

TimezoneSelect::make('timezone')
    ->getTimezoneFromBrowser()
    ->required(),

This reads the browser's resolved timezone and fills the field only when it is empty. That makes it ideal for a "create account" or "new tenant" form: the user gets a sensible default they can override, and existing records are never silently rewritten on edit. Don't reach for it as a "recompute" button — it won't fire on a populated field.

Validation

The package ships no validation rule, so add one. ->rules(['timezone']) is the cheap PHP-native check; if you narrow the list with byCountry/byRegion, tighten it to match so a crafted request can't post an out-of-scope zone:

use Illuminate\Validation\Rule;

TimezoneSelect::make('timezone')
    ->byCountry('US')
    ->rules([Rule::in(array_keys(...))]) // your narrowed set
    ->required(),

Rendering in the app, not just the panel

Because the value is an identifier, the column is the only place you should use the formatted output. Everywhere else (emails, invoices, API responses) convert explicitly:

$user->timezone; // 'Europe/Lisbon'
now($user->timezone)->format('H:i');

Gotchas and Tips

Pin a recent patch. Several early-3.x bugs bite in normal usage:

  • v3.0.5 — TimezoneColumn threw on empty/null values. Any nullable timezone column needs this.
  • v3.0.11 — options were not loaded when the field was initialised, so selects could render empty inside modals, repeaters, or relationship forms.
  • v3.0.13 — getTimezoneFromBrowser() caused a "Component not found" error on auth/SPA pages.
  • v3.1.0 — relaxes symfony/intl to ^8.0, which matters if you're on a newer Laravel/Symfony stack.

There is a lot of Filament-major churn in the changelog (v3.0.10 Filament 4, v3.0.12 Filament 5). Constrain Composer and re-test the field after any Filament upgrade, especially the column formatters.

symfony/intl and the intl PHP extension are hard requirements. Every label is built through Intl, so a production container missing ext-intl will fail at render time rather than at boot. Add it to your Dockerfile and your composer.json platform requirements.

Never store or parse the label. The offset shown is computed at render time and shifts with DST — the same record can read GMT+1 in January and GMT+2 in July. Store only the identifier; if you need to display an offset elsewhere, derive it with Carbon at that moment.

hideNames() alone is dangerous. With names hidden, every zone sharing an offset collapses to identical-looking options (UTC+01:00 several times over). It's only usable combined with byCountry()/byRegion() or searchable(). hideOffset() is the safer of the two to use alone.

timezoneType('GMT') is cosmetic. It swaps the UTC prefix for GMT in labels only. It does not change the stored value, so mismatched settings between field and column produce confusing but harmless screens — the trap is assuming it's a data concern.

The column accepts a DateTimeZone instance (added v3.0.4) as well as a string, which is handy if you're returning hydrated objects from a cast. Accessors returning null are safe as of v3.0.5, but accessors returning an empty string are worth testing explicitly.

Extension points are thin. There's no config file, no publishable views, and no documented customisation hook. If you need different option text you subclass TimezoneSelect and override the option-building method, or simply fall back to a plain Filament Select with your own options() — this package is a convenience wrapper around DateTimeZone::listIdentifiers() plus Intl formatting, and for a single hardcoded list you may not need the dependency at all.

It's a small, single-maintainer package (~57 stars). Fine for admin panels and internal tools where a broken upgrade is a nuisance; think twice before making it a hard dependency of a user-facing critical path, or wrap it in your own base field class so a swap is a one-file change.

Testing tip: assert on the stored identifier (assertDatabaseHas(['timezone' => 'Europe/Lisbon'])), not the rendered label. If you do snapshot column output, expect to regenerate fixtures around DST boundaries and symfony/intl version bumps.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style