A flexible, performant, and developer-friendly schedule management system for Laravel

Installs: 52 722

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1 398

Watchers: 16

Forks: 99

Open Issues: 2

pkg:composer/laraveljutsu/zap

v1.13.1 2026-02-15 21:59 UTC

This package is auto-updated.

Last update: 2026-02-15 22:04:08 UTC


README

Zap Logo

Flexible schedule management for modern Laravel applications

PHP Version Laravel Version License Total Downloads Why PHP

Website β€’ Documentation β€’ Support

Table of contents

🎯 What is Zap?

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.

πŸ“¦ Installation

Requirements: PHP β‰₯8.5 β€’ Laravel β‰₯12.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 migrate

Make a model schedulable: add the HasSchedules trait.

use Zap\Models\Concerns\HasSchedules;

class Doctor extends Model
{
    use HasSchedules;
}

🧩 Core concepts

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

πŸš€ Quick start

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)->...

πŸ“… Schedule patterns

Recurrence at a glance

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()

Recurrence examples

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);

Date ranges

$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

Time periods

$schedule->addPeriod('09:00', '17:00');
$schedule->addPeriod('09:00', '12:00');
$schedule->addPeriod('14:00', '17:00');

πŸ” Query & check availability

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. Prefer isBookableAt(), isBookableAtTime(), and getBookableSlots().

πŸ’Ό Real-world examples

Doctor

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);

Meeting room

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();

Employee (with vacation)

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();

βš™οΈ Configuration

Publish assets:

php artisan vendor:publish --tag=zap-migrations
php artisan vendor:publish --tag=zap-config

Important keys in config/zap.php: time_slots.buffer_minutes, default_rules.no_overlap, conflict_detection, validation.

πŸ›‘οΈ Advanced features

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().

Custom model support (UUIDs)

If your app uses UUIDs/ULIDs for primary keys:

  1. Models β€” Extend Zap\Models\Schedule and Zap\Models\SchedulePeriod, add Laravel’s HasUuids trait. Add HasUuids to your schedulable model (e.g. Doctor) as well.
  2. Config β€” In config/zap.php, set models.schedule and models.schedule_period to your extended classes.
  3. Migrations β€” After publishing, change id() to uuid('id')->primary(), morphs('schedulable') to uuidMorphs('schedulable'), and foreignId('schedule_id') to foreignUuid('schedule_id') in the schedules and schedule_periods tables.

Do this before running migrations.

🧠 AI agent support

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.

🀝 Contributing

Contributions are welcome. Use PSR-12 and add tests.

git clone https://github.com/ludoguenet/laravel-zap.git
cd laravel-zap
composer install
composer pest

πŸ“„ License

MIT License.

πŸ”’ Security

Report issues to ludo@epekta.com (not the public issue tracker).

Made with πŸ’› by Ludovic GuΓ©net for the Laravel community