- What is Zap?
- Installation
- Core concepts
- Quick start
- Schedule patterns
- Query & availability
- Real-world examples
- Configuration
- Advanced
- AI agent support
- Contributing
Zap is a calendar and scheduling package for Laravel. Define availabilities, appointments, blocked times, and custom schedules for any resource (doctors, rooms, employees, etc.).
Use cases: appointment booking, healthcare resources, employee shifts, shared space booking.
Requirements: PHP โฅ8.5 โข Laravel โฅ13.0
composer require laraveljutsu/zap
php artisan vendor:publish --provider="Zap\ZapServiceProvider"UUIDs/ULIDs: If your app uses non-integer primary keys, read Custom model support before migrating. You may need to change migrations and config.
php artisan migrateMake a model schedulable: add the HasSchedules trait.
use Zap\Models\Concerns\HasSchedules;
class Doctor extends Model
{
use HasSchedules;
}| Type | Purpose | Overlaps |
|---|---|---|
| Availability | When a resource can be booked | Allowed |
| Appointment | Bookings / scheduled events | Exclusive |
| Blocked | When booking is forbidden | Exclusive |
| Custom | Your rules (overlap, etc.) | You define |
use Zap\Facades\Zap;
// 1. Working hours
Zap::for($doctor)
->named('Office Hours')
->availability()
->forYear(2025)
->addPeriod('09:00', '12:00')
->addPeriod('14:00', '17:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
->save();
// 2. Block lunch
Zap::for($doctor)
->named('Lunch Break')
->blocked()
->forYear(2025)
->addPeriod('12:00', '13:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
->save();
// 3. Create an appointment
Zap::for($doctor)
->named('Patient A - Consultation')
->appointment()
->from('2025-01-15')
->addPeriod('10:00', '11:00')
->withMetadata(['patient_id' => 1, 'type' => 'consultation'])
->save();
// 4. Get bookable slots (60 min, 15 min buffer)
$slots = $doctor->getBookableSlots('2025-01-15', 60, 15);
// 5. Next available slot
$nextSlot = $doctor->getNextBookableSlot('2025-01-15', 60, 15);๐ก Use the
zap()helper instead of the facade when you prefer:zap()->for($doctor)->...
| Pattern | Method / example |
|---|---|
| Daily | daily() |
| Weekly (days) | weekly(['monday', 'friday']) |
| Weekly + period | weekDays(['monday', 'friday'], '09:00', '17:00') |
| Odd/even weeks | weeklyOdd(), weeklyEven() (+ weekOddDays / weekEvenDays) |
| Bi-weekly | biweekly(['tuesday'], $startsOn?) |
| Monthly (dates) | monthly(['days_of_month' => [1, 15]]) |
| Bi-monthly / quarter / semi / annual | bimonthly(), quarterly(), semiannually(), annually() + config |
| Ordinal weekday | firstWednesdayOfMonth(), secondFridayOfMonth(), lastMondayOfMonth() |
| Every N weeks | everyThreeWeeks(), โฆ everyFiftyTwoWeeks() |
| Every N months | everyFourMonths(), โฆ everyElevenMonths() |
Daily & weekly
$schedule->daily()->from('2025-01-01')->to('2025-12-31');
$schedule->weekly(['monday', 'wednesday', 'friday'])->forYear(2025);
$schedule->weekDays(['monday', 'wednesday', 'friday'], '09:00', '17:00')->forYear(2025);
$schedule->weeklyOdd(['monday', 'wednesday', 'friday'])->forYear(2025);
$schedule->weeklyEven(['monday', 'wednesday', 'friday'])->forYear(2025);
$schedule->biweekly(['tuesday', 'thursday'], '2025-01-07')->from('2025-01-07')->to('2025-03-31');Monthly (by day of month)
$schedule->monthly(['days_of_month' => [1, 15]])->forYear(2025);
$schedule->bimonthly(['days_of_month' => [5, 20], 'start_month' => 2])->from('2025-01-05')->to('2025-06-30');
$schedule->quarterly(['days_of_month' => [7, 21], 'start_month' => 2])->from('2025-02-15')->to('2025-11-15');
$schedule->semiannually(['days_of_month' => [10], 'start_month' => 3])->from('2025-03-10')->to('2025-12-10');
$schedule->annually(['days_of_month' => [1, 15], 'start_month' => 4])->from('2025-04-01')->to('2026-04-01');Monthly ordinal weekday (1st, 2nd, 3rd, 4th, or last weekday of the month)
$schedule->firstWednesdayOfMonth()->forYear(2025); // Every 1st Wednesday
$schedule->secondFridayOfMonth()->forYear(2025); // Every 2nd Friday
$schedule->lastMondayOfMonth()->forYear(2025); // Every last Monday
// Also: thirdTuesdayOfMonth(), fourthSaturdayOfMonth(), lastSundayOfMonth(), etc.Dynamic intervals
$schedule->everyThreeWeeks(['monday', 'friday'])->from('2025-01-06')->to('2025-12-31');
$schedule->everyFourWeeks(['tuesday'], '2025-01-06')->from('2025-01-13');
$schedule->everyFourMonths(['day_of_month' => 15])->forYear(2025);
$schedule->everyFiveMonths(['days_of_month' => [1, 15], 'start_month' => 2])->forYear(2025);$schedule->from('2025-01-15'); // Start
$schedule->on('2025-01-15'); // Alias for from()
$schedule->from('2025-01-01')->to('2025-12-31'); // Range
$schedule->between('2025-01-01', '2025-12-31'); // Same
$schedule->forYear(2025); // Full year$schedule->addPeriod('09:00', '17:00');
$schedule->addPeriod('09:00', '12:00');
$schedule->addPeriod('14:00', '17:00');| Need | Method |
|---|---|
| Any bookable slot today? | $model->isBookableAt('2025-01-15', 60) |
| Time range bookable? | $model->isBookableAtTime('2025-01-15', '09:00', '09:30') |
| List bookable slots | $model->getBookableSlots('2025-01-15', 60, 15) |
| Next bookable slot | $model->getNextBookableSlot('2025-01-15', 60, 15) |
| Conflicts for a schedule | Zap::findConflicts($schedule) / Zap::hasConflicts($schedule) |
| Schedules on a date | $model->schedulesForDate('2025-01-15')->get() |
| Schedules in range | $model->schedulesForDateRange('2025-01-01', '2025-01-31')->get() |
| By type | $model->appointmentSchedules(), availabilitySchedules(), blockedSchedules() |
| Schedule type checks | $schedule->isAvailability(), isAppointment(), isBlocked() |
โ ๏ธ isAvailableAt()is deprecated. PreferisBookableAt(),isBookableAtTime(), andgetBookableSlots().
Zap::for($doctor)->named('Office Hours')->availability()->forYear(2025)
->addPeriod('09:00', '12:00')->addPeriod('14:00', '17:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])->save();
Zap::for($doctor)->named('Lunch Break')->blocked()->forYear(2025)
->addPeriod('12:00', '13:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])->save();
Zap::for($doctor)->named('Patient A - Checkup')->appointment()
->from('2025-01-15')->addPeriod('10:00', '11:00')->withMetadata(['patient_id' => 1])->save();
$slots = $doctor->getBookableSlots('2025-01-15', 60, 15);Zap::for($room)->named('Conference Room A')->availability()
->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '08:00', '18:00')
->forYear(2025)->save();
Zap::for($room)->named('Board Meeting')->appointment()
->from('2025-03-15')->addPeriod('09:00', '11:00')
->withMetadata(['organizer' => 'john@company.com'])->save();Zap::for($employee)->named('Regular Shift')->availability()
->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '09:00', '17:00')
->forYear(2025)->save();
Zap::for($employee)->named('Vacation Leave')->blocked()
->between('2025-06-01', '2025-06-15')->addPeriod('00:00', '23:59')->save();Publish assets:
php artisan vendor:publish --tag=zap-migrations
php artisan vendor:publish --tag=zap-configImportant keys in config/zap.php: time_slots.buffer_minutes, default_rules.no_overlap, conflict_detection, validation.
Custom schedules & rules
Zap::for($user)->named('Custom Event')->custom()
->from('2025-01-15')->addPeriod('15:00', '16:00')->noOverlap()->save();Metadata
->withMetadata(['patient_id' => 1, 'type' => 'consultation', 'notes' => 'Follow-up'])Validation rules: noOverlap(), allowOverlap(), workingHoursOnly('09:00', '17:00'), maxDuration(120), noWeekends().
If your app uses UUIDs/ULIDs for primary keys:
- Models โ Extend
Zap\Models\ScheduleandZap\Models\SchedulePeriod, add LaravelโsHasUuidstrait. AddHasUuidsto your schedulable model (e.g.Doctor) as well. - Config โ In
config/zap.php, setmodels.scheduleandmodels.schedule_periodto your extended classes. - Migrations โ After publishing, change
id()touuid('id')->primary(),morphs('schedulable')touuidMorphs('schedulable'), andforeignId('schedule_id')toforeignUuid('schedule_id')in the schedules and schedule_periods tables.
Do this before running migrations.
Zap provides Laravel Boost 2.0 skills. With Boost installed, agents get accurate knowledge of the API.
| Skill | Contents |
|---|---|
zap-schedules |
Types, builder API, validation, conflict detection |
zap-availability |
Bookable slots, availability checks, querying |
zap-recurrence |
All recurrence patterns (daily, weekly, odd/even, monthly, ordinal weekday, dynamic) |
No extra configuration.
Contributions are welcome. Use PSR-12 and add tests.
git clone https://github.com/ludoguenet/laravel-zap.git
cd laravel-zap
composer install
composer pestReport issues to ludo@epekta.com (not the public issue tracker).
Made with ๐ by Ludovic Guรฉnet for the Laravel community
