Open source July 2026
Wireschedule

Wireschedule

Wireschedule

Wireschedule is a self-hosted booking scheduler for Laravel. Availability is declared in config, bookings land in your own table, and the UI is a Livewire component that inherits the wire* theme.

Requirements: PHP 8.2+, Laravel 11+, Livewire 3.

Installation

composer require edulazaro/wireschedule
php artisan migrate

The service provider registers its own migration, so there is nothing to publish before running it. The migration creates the single wireschedule_bookings table.

The CSS is not automatic: import it into your bundle or the widget renders unstyled:

@import '../../vendor/edulazaro/wireschedule/resources/css/wireschedule.css';

There is no separate JavaScript build step. The Livewire view ships a small inline Alpine x-data and leans on the Livewire and Alpine already on the page.

Usage

One Blade tag. The event you pass is the key of an event type defined in config:

<x-wireschedule event="demo" />

The component renders the date buttons, then the free time slots for the chosen date, then a short name and email form, and saves the booking on confirm.

Theming is the shared wire* attribute, with eleven themes from studio and minimal to brutalist, glass, neon and synthwave:

<html data-wire-theme="studio">

Configuring availability

php artisan vendor:publish --tag=wireschedule-config

Each event is a weekly schedule plus rules about how bookings are allowed. The availability map is keyed by ISO weekday (1 is Monday, 7 is Sunday), and each day is a list of HH:MM ranges.

'timezone' => 'Europe/Madrid',      // availability is read in this zone
'events' => [
    'demo' => [
        'label' => 'Demo',
        'duration' => 30,   // minutes per meeting
        'buffer' => 0,      // minutes between meetings
        'days_ahead' => 21, // how far out people can book
        'min_notice' => 120,// minimum minutes of notice
        'availability' => [
            1 => [['10:00', '14:00'], ['16:00', '18:00']],
            5 => [['10:00', '14:00']],
        ],
    ],
],

The free slots a visitor sees are that availability minus the bookings already in the database.

The event prop is locked. On mount the component runs abort_unless against the event config, so passing a key that is not defined in config makes the component 404 rather than rendering an empty widget. Define the event first.

Timezones

Availability is written in the organiser's zone, set once as timezone in config. The visitor's browser zone is detected client side through Intl.DateTimeFormat().resolvedOptions().timeZone, and slots are displayed in their zone.

Bookings persist in UTC with the visitor's timezone kept on the row, so reading a booking in either zone is a conversion rather than a guess.

Double-booking protection

Two layers: a unique (event_type, starts_at) index on the table, and a re-check of slot availability at confirm time.

Reading bookings back

use EduLazaro\WireSchedule\Models\Booking;

Booking::ofType('demo')->confirmed()->upcoming()->orderBy('starts_at')->get();

If the person was logged in, Wireschedule associates them through a polymorphic booker relation:

$booking->booker; // the authenticated user, or null for a guest

Front-end hook

When a booking lands the component dispatches a wireschedule-booked browser event carrying the new booking id.

built and maintained by Edu Lazaro · MIT license