laraveljutsu / zap
A flexible, performant, and developer-friendly schedule management system for Laravel
Installs: 29 556
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1 319
Watchers: 15
Forks: 86
Open Issues: 3
pkg:composer/laraveljutsu/zap
Requires
- php: >=8.2 <8.6
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.20
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
README
Flexible schedule management for modern Laravel applications
Website • Documentation • Support
🎯 What is Zap?
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 • resource scheduling • shift management • calendar applications
📦 Installation
Requirements: PHP ≤8.5 • Laravel ≤12.0
composer require laraveljutsu/zap php artisan vendor:publish --tag=zap-migrations php artisan migrate
Add the trait to your schedulable models:
use Zap\Models\Concerns\HasSchedules; class Doctor extends Model { use HasSchedules; }
🧩 Core Concepts
| 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 |
🚀 Quick Start
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)
📅 Schedule Patterns
Recurrence
// 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 - combines weekly() and addPeriod()) $schedule->weekDays(['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');
Date Ranges
$schedule->from('2025-01-15'); // Single date $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 shortcut
Time Periods
// Single period $schedule->addPeriod('09:00', '17:00'); // Multiple periods (split shifts) $schedule->addPeriod('09:00', '12:00'); $schedule->addPeriod('14:00', '17:00');
🔍 Query & Check
// Check if there is at least one bookable slot on the day $isBookable = $doctor->isBookableAt('2025-01-15', 60); // 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();
isAvailableAt()is deprecated in favor ofisBookableAt()andgetBookableSlots(). Use the bookable APIs for all new code.
💼 Real-World Examples
🏥 Doctor Appointment System
// 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);
🏢 Meeting Room Booking
// 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();
👔 Employee Shift Management
// 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();
⚙️ Configuration
Publish and customize:
php artisan vendor:publish --tag=zap-config
Key settings in config/zap.php:
'time_slots' => [ 'buffer_minutes' => 0, // Default buffer between slots ], 'default_rules' => [ 'no_overlap' => [ 'enabled' => true, 'applies_to' => ['appointment', 'blocked'], ], ],
🛡️ Advanced Features
Custom Schedules with Explicit Rules
Zap::for($user)->named('Custom Event')->custom() ->from('2025-01-15')->addPeriod('15:00', '16:00') ->noOverlap() // Explicitly prevent overlaps ->save();
Metadata Support
->withMetadata([ 'patient_id' => 1, 'type' => 'consultation', 'notes' => 'Follow-up required' ])
🤝 Contributing
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/pest
📄 License
Open-source software licensed under the MIT License.
🔒 Security
Report vulnerabilities to ludo@epekta.com (please don't use the issue tracker).
Made with 💛 by Ludovic Guénet for the Laravel community