Building Better Scheduling Experiences in Filament
Why I built an open-source date and time slot picker for Filament — with working hours, availability, blocked slots, booking constraints, timezones, RTL, and more.
Scheduling looks simple until you actually have to build it.
The first version of the requirement is usually something like:
“Let the user choose a date and time.”
That sounds like a job for a standard DateTimePicker.
And sometimes, it is.
But the moment that date and time represents a real appointment, reservation, meeting, consultation, or service booking, the requirements start to grow.
Users should only be able to select times during working hours. Some slots are already booked. Others might not be available at all. Appointments may need to be booked at least an hour in advance.
Past time slots should disappear even when today is still a valid booking date.
Availability might come dynamically from your database or an external API.
And if your application serves users in different countries, timezone and localization suddenly matter too.
At that point, you’re no longer solving a date-time input problem.
You’re solving a scheduling problem.
That is why I built Filament Date Time Slots, an open-source form field for Filament focused specifically on this use case.
A scheduling field, not a booking system
There are two directions you can take when solving scheduling.
One is to build a complete booking system.
That system might introduce appointment models, availability tables, employees, resources, calendars, booking statuses, notifications, and integrations with external calendars.
That’s useful when you actually need a booking platform.
But it wasn’t what I wanted from a reusable Filament component.
I wanted something smaller and more composable:
A form field that understands scheduling without owning the application’s scheduling architecture.
So this:
DateTimeSlotPicker::make('scheduled_at')is still fundamentally a Filament form field.
It doesn’t require you to create a particular appointments table.
It doesn’t introduce its own booking model.
It doesn’t decide how your application stores reservations.
It simply helps the user choose a valid date and time according to the rules your application provides.
The resulting value remains a normal datetime that can fit naturally into your existing model.
That makes the same component useful for appointments, reservations, consultations, CRM follow-ups, meetings, service bookings, tasks, delivery windows, interviews, and many other scheduling workflows.
Getting started
Installation is intentionally simple.
composer require wooserv/filament-date-time-slotsThen add the field to your Filament form:
use WooServ\FilamentDateTimeSlots\Forms\Components\DateTimeSlotPicker;DateTimeSlotPicker::make('scheduled_at')That’s it.
The package ships with its compiled frontend assets, so applications using it don’t need to install the package’s JavaScript dependencies or run an additional frontend build just to use the field.
Installing a Filament field should feel like installing a Filament field.
From a datetime picker to actual scheduling
The interesting part begins when we start adding real scheduling rules.
Working hours
Imagine a business accepting appointments between 9 AM and 5 PM on weekdays.
Instead of letting users select arbitrary times, we can define the schedule:
DateTimeSlotPicker::make('scheduled_at')
->workingHours([
'monday' => ['09:00', '17:00'],
'tuesday' => ['09:00', '17:00'],
'wednesday' => ['09:00', '17:00'],
'thursday' => ['09:00', '17:00'],
'friday' => ['09:00', '17:00'],
])
->slotInterval(30);Now the interface can generate 30-minute slots within those working hours.
Instead of asking the user:
“What time do you want?”
We’re asking:
“Which of these available times works for you?”
That is a much better abstraction for scheduling.
When your backend already knows the available slots
Working hours aren’t always enough.
Consider a doctor, consultant, salesperson, meeting room, hotel service, or any other bookable resource.
Availability might depend on existing reservations, an employee’s schedule, a resource calendar, business rules, or even an external API.
In these situations, your application may already know exactly which slots should be offered.
You can provide them directly:
DateTimeSlotPicker::make('scheduled_at')
->availableSlots([
'2026-09-08' => ['09:00', '09:30', '11:00', '14:30'],
'2026-09-09' => ['10:00', '10:30', '13:00'],
]);More importantly, the configuration can be dynamic, allowing availability to come from your application’s own data and logic.
That was an important design decision.
A scheduling component shouldn’t try to own your availability logic.
Your application knows its business rules better than the component ever could.
The field should take those rules and turn them into a clean scheduling experience.
Available and blocked are different concepts
There is another distinction that becomes useful in real applications: the difference between an available schedule and a blocked slot.
Suppose a consultant normally has these slots:
09:00
09:30
10:00
10:30
11:00
But somebody has already booked 10:00.
That time belongs to the schedule, but it should no longer be selectable.
DateTimeSlotPicker::make('scheduled_at')
->blockedSlots([
'2026-09-08' => ['10:00'],
]);Depending on the experience you want, blocked slots can disappear from the available choices or remain visible as disabled slots.
That distinction matters.
Showing 10:00 as unavailable tells the user that the time exists but has already been taken.
For many scheduling interfaces, that’s more informative than simply hiding it.
Scheduling isn’t only about availability
There is another class of rules that isn’t about whether a slot exists.
It’s about whether the user is allowed to book it.
Suppose your business requires appointments to be booked at least two hours in advance.
A slot could technically be free while still being too close to the current time.
That’s where minimum lead time becomes useful:
DateTimeSlotPicker::make('scheduled_at')
->minimumLeadTime(120)You can also control allowed date ranges and disable specific dates.
These constraints allow the scheduling interface to reflect the same rules your application expects rather than relying on users to manually choose a valid datetime.
Today is an interesting edge case
Past dates are easy.
Yesterday shouldn’t be selectable.
But today is more complicated.
Imagine it’s currently 2:15 PM.
Today is still a perfectly valid booking date.
But 9:00 AM, 11:30 AM, 1:00 PM, and 2:00 PM aren’t valid slots anymore.
Meanwhile, 2:30 PM, 3:00 PM, and 4:00 PM might still be valid.
Then add a minimum lead time of two hours.
Suddenly, 2:30 PM and 3:00 PM aren’t valid either.
A normal date picker mostly thinks in dates.
A normal time picker mostly thinks in times.
Scheduling requires both pieces of information to interact.
Get Hamada Habib’s stories in your inbox
Join Medium for free to get updates from this writer.
These small edge cases are where a scheduling-specific component starts becoming useful.
Timezones are part of the data lifecycle
Timezone support can easily become superficial.
Displaying a timezone next to a datetime doesn’t necessarily mean the underlying scheduling problem has been solved.
Imagine your application stores datetimes according to its configured application timezone while the person scheduling the appointment operates in another timezone.
The picker can be configured accordingly:
DateTimeSlotPicker::make('scheduled_at')
->timezone('Asia/Riyadh')The important part is that timezone handling participates in the field’s datetime lifecycle rather than simply changing a label in the interface.
The user can interact with the datetime in the appropriate timezone while the application continues following its own storage conventions.
For scheduling software, timezone handling isn’t decoration.
It’s part of correctness.
Localization matters too
Dates and times are highly localized pieces of UI.
Some users expect:
14:30
Others expect:
2:30 PM
Some applications are left-to-right.
Others are right-to-left.
Calendar labels also need to match the language of the surrounding interface.
Filament Date Time Slots supports locale configuration, 12/24-hour time display, RTL interfaces, and dark mode.
For example:
DateTimeSlotPicker::make('scheduled_at')
->locale('ar')
->timezone('Africa/Cairo');This was particularly important because a reusable component shouldn’t assume that every Filament application is English and LTR.
A scheduling component might be used in Cairo, Riyadh, London, New York, or anywhere else.
The component should adapt to the application rather than requiring the application to adapt to it.
Why I didn’t build an appointment system
This is probably the most important design decision behind the package.
It would have been easy to keep adding features.
Appointments.
Employees.
Resources.
Availability models.
Reservation tables.
Statuses.
Recurring schedules.
Calendar synchronization.
Eventually, the package would stop being a form component and become an entire booking platform.
That’s a valid product to build.
But it solves a different problem.
For Filament Date Time Slots, I wanted the boundary to remain clear:
The package owns the scheduling input. Your application owns the scheduling business.
Your models remain yours.
Your database remains yours.
Your availability queries remain yours.
Your reservation logic remains yours.
And the component remains reusable.
I think that separation is particularly valuable in the Laravel ecosystem, where small composable packages can often be more useful than packages that attempt to own the entire application architecture.
Compatibility should be tested, not just declared
Before releasing the package, I also wanted compatibility to mean more than putting broad version constraints in composer.json.
Filament Date Time Slots currently targets:
Filament 3, 4, and 5
Laravel 11, 12, and 13
PHP 8.2 and newer versions supported by those framework combinations
But declaring something like:
"filament/filament": "^3.2|^4.0|^5.0"doesn’t prove that the package actually works across those generations.
So the repository uses a GitHub Actions compatibility matrix to test representative combinations of Laravel, Filament, and PHP.
It’s a small infrastructure detail, but an important one.
For an open-source package, I’d rather compatibility mean:
“We test this.”
than:
“Composer lets you install this.”
Keeping installation boring
There’s another principle behind the package that probably won’t appear in a screenshot:
Installing it shouldn’t require a frontend adventure.
The component uses JavaScript and CSS, but its compiled assets are distributed with the package and registered with Filament.
For the developer consuming it, installation remains:
composer require wooserv/filament-date-time-slotsNo package-specific npm installation.
No separate asset compilation.
No extra frontend setup just to render the field.
For a self-contained Filament component, I think that’s exactly how installation should feel.
Boring installation is good installation.
One field, many workflows
Although appointments are an obvious use case, time-slot selection is a more general primitive than it first appears.
The same component can represent a medical appointment:
DateTimeSlotPicker::make('appointment_at')A CRM follow-up:
DateTimeSlotPicker::make('follow_up_at')A consultation:
DateTimeSlotPicker::make('consultation_at')A delivery:
DateTimeSlotPicker::make('delivery_at')Or a reservation:
DateTimeSlotPicker::make('reserved_at')The surrounding business logic can be completely different.
But the interaction remains essentially the same:
Choose a valid date → choose an available time → store a datetime.
That’s the level of abstraction I wanted the package to provide.
Open source
Filament Date Time Slots is open source and released under the MIT license.
Install it with:
composer require wooserv/filament-date-time-slotsLive demo
https://booking.wooserv.com
GitHub
https://github.com/wooserv/filament-date-time-slots
Filament Marketplace
https://filamentphp.com/plugins/wooserv-date-time-slots
Packagist
https://packagist.org/packages/wooserv/filament-date-time-slots
The project is developed by WooServ, and contributions, bug reports, feature discussions, and feedback are welcome.
If you’re building something with Filament involving appointments, reservations, availability, or scheduling, I’d be interested to hear about the scheduling rules you’ve had to solve.
Because there are always more edge cases hiding behind one seemingly simple requirement:
“Just let the user choose a date and time.”







