Zap is a comprehensive calendar and scheduling system for Laravel. Manage availabilities, appointments, blocked times, and custom schedules for any resourceβdoctors, meeting rooms, employees, and more.
Perfect for:
- π Appointment booking systems
- π₯ Healthcare resource management
- π Employee shift scheduling
- π’ Shared office space bookings
Requirements: PHP β€8.5 β’ Laravel β€12.0
composer require laraveljutsu/zap php artisan vendor:publish --tag=zap-migrations php artisan migrateAdd the HasSchedules trait to any Eloquent model you want to make schedulable:
use Zap\Models\Concerns\HasSchedules; class Doctor extends Model { use HasSchedules; }Zap uses four schedule types to model different scenarios:
| Type | Purpose | Overlap Behavior |
|---|---|---|
| Availability | Define when resources can be booked | β Allows overlaps |
| Appointment | Actual bookings or scheduled events | β Prevents overlaps |
| Blocked | Periods where booking is forbidden | β Prevents overlaps |
| Custom | Neutral schedules with explicit rules | βοΈ You define the rules |
Here's a complete example of setting up a doctor's schedule:
use Zap\Facades\Zap; // 1οΈβ£ Define 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 break 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 slots, 15 min buffer) $slots = $doctor->getBookableSlots('2025-01-15', 60, 15); // Returns: [['start_time' => '09:00', 'end_time' => '10:00', 'is_available' => true, ...], ...] // 5οΈβ£ Find next available slot $nextSlot = $doctor->getNextBookableSlot('2025-01-15', 60, 15);π‘ Tip: You can also use the
zap()helper function instead of the facade:zap()->for($doctor)->...(no import needed)
Zap supports various recurrence patterns for flexible scheduling:
// Daily $schedule->daily()->from('2025-01-01')->to('2025-12-31'); // Weekly (specific days) $schedule->weekly(['monday', 'wednesday', 'friday'])->forYear(2025); // Weekly with time period (convenience method) $schedule->weekDays(['monday', 'wednesday', 'friday'], '09:00', '17:00')->forYear(2025); // Weekly odd (runs only on odd-numbered weeks) $schedule->weeklyOdd(['monday', 'wednesday', 'friday'])->forYear(2025); // Weekly odd with time period (convenience method) $schedule->weekOddDays(['monday', 'wednesday', 'friday'], '09:00', '17:00')->forYear(2025); // Weekly even (runs only on even-numbered weeks) $schedule->weeklyEven(['monday', 'wednesday', 'friday'])->forYear(2025); // Weekly even with time period (convenience method) $schedule->weekEvenDays(['monday', 'wednesday', 'friday'], '09:00', '17:00')->forYear(2025); // Bi-weekly (week of the start date by default, optional anchor) $schedule->biweekly(['tuesday', 'thursday'])->from('2025-01-07')->to('2025-03-31'); // Monthly (supports multiple days) $schedule->monthly(['days_of_month' => [1, 15]])->forYear(2025); // Bi-monthly (multiple days, optional start_month anchor) $schedule->bimonthly(['days_of_month' => [5, 20], 'start_month' => 2]) ->from('2025-01-05')->to('2025-06-30'); // Quarterly (multiple days, optional start_month anchor) $schedule->quarterly(['days_of_month' => [7, 21], 'start_month' => 2]) ->from('2025-02-15')->to('2025-11-15'); // Semi-annually (multiple days, optional start_month anchor) $schedule->semiannually(['days_of_month' => [10], 'start_month' => 3]) ->from('2025-03-10')->to('2025-12-10'); // Annually (multiple days, optional start_month anchor) $schedule->annually(['days_of_month' => [1, 15], 'start_month' => 4]) ->from('2025-04-01')->to('2026-04-01');Specify when schedules are active:
$schedule->from('2025-01-15'); // Single date $schedule->on('2025-01-15'); // Alias for from() $schedule->from('2025-01-01')->to('2025-12-31'); // Date range $schedule->between('2025-01-01', '2025-12-31'); // Alternative syntax $schedule->forYear(2025); // Entire year shortcutDefine working hours and time slots:
// Single period $schedule->addPeriod('09:00', '17:00'); // Multiple periods (split shifts) $schedule->addPeriod('09:00', '12:00'); $schedule->addPeriod('14:00', '17:00');Check availability and query schedules:
// Check if there is at least one bookable slot on the day $isBookable = $doctor->isBookableAt('2025-01-15', 60); // Check if a specific time range is bookable $isBookable = $doctor->isBookableAtTime('2025-01-15', '9:00', '9:30'); // Get bookable slots $slots = $doctor->getBookableSlots('2025-01-15', 60, 15); // Find conflicts $conflicts = Zap::findConflicts($schedule); $hasConflicts = Zap::hasConflicts($schedule); // Query schedules $doctor->schedulesForDate('2025-01-15')->get(); $doctor->schedulesForDateRange('2025-01-01', '2025-01-31')->get(); // Filter by type $doctor->appointmentSchedules()->get(); $doctor->availabilitySchedules()->get(); $doctor->blockedSchedules()->get(); // Check schedule type $schedule->isAvailability(); $schedule->isAppointment(); $schedule->isBlocked();
β οΈ Note:isAvailableAt()is deprecated in favor ofisBookableAt(),isBookableAtTime(), andgetBookableSlots(). Use the bookable APIs for all new code.
// Office hours Zap::for($doctor) ->named('Office Hours') ->availability() ->forYear(2025) ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('09:00', '12:00') ->addPeriod('14:00', '17:00') ->save(); // Lunch break Zap::for($doctor) ->named('Lunch Break') ->blocked() ->forYear(2025) ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('12:00', '13:00') ->save(); // Book appointment Zap::for($doctor) ->named('Patient A - Checkup') ->appointment() ->from('2025-01-15') ->addPeriod('10:00', '11:00') ->withMetadata(['patient_id' => 1]) ->save(); // Get available slots $slots = $doctor->getBookableSlots('2025-01-15', 60, 15);// Room availability (using weekDays convenience method) Zap::for($room) ->named('Conference Room A') ->availability() ->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '08:00', '18:00') ->forYear(2025) ->save(); // Book meeting Zap::for($room) ->named('Board Meeting') ->appointment() ->from('2025-03-15') ->addPeriod('09:00', '11:00') ->withMetadata(['organizer' => 'john@company.com']) ->save();// Regular schedule (using weekDays convenience method) Zap::for($employee) ->named('Regular Shift') ->availability() ->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '09:00', '17:00') ->forYear(2025) ->save(); // Vacation Zap::for($employee) ->named('Vacation Leave') ->blocked() ->between('2025-06-01', '2025-06-15') ->addPeriod('00:00', '23:59') ->save();Publish and customize the configuration:
php artisan vendor:publish --tag=zap-configKey settings in config/zap.php:
'time_slots' => [ 'buffer_minutes' => 0, // Default buffer between slots ], 'default_rules' => [ 'no_overlap' => [ 'enabled' => true, 'applies_to' => ['appointment', 'blocked'], ], ],Create custom schedules with explicit overlap rules:
Zap::for($user) ->named('Custom Event') ->custom() ->from('2025-01-15') ->addPeriod('15:00', '16:00') ->noOverlap() // Explicitly prevent overlaps ->save();Attach custom metadata to schedules:
->withMetadata([ 'patient_id' => 1, 'type' => 'consultation', 'notes' => 'Follow-up required' ])We welcome contributions! Follow PSR-12 coding standards and include tests.
git clone https://github.com/laraveljutsu/zap.git cd zap composer install vendor/bin/pestOpen-source software licensed under the MIT License.
Report vulnerabilities to ludo@epekta.com (please don't use the issue tracker).
Made with π by Ludovic GuΓ©net for the Laravel community
