PHPackages                             xavicabot/filament-appointments - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. xavicabot/filament-appointments

ActiveLibrary

xavicabot/filament-appointments
===============================

FilamentPHP 4/5 Appointment Picker (Tucalendi-style) + Admin resources + Google Calendar availability sync

v2.0.5(1w ago)066MITPHPPHP ^8.2CI passing

Since Feb 17Pushed 1w agoCompare

[ Source](https://github.com/xavicabot/filament-appointments)[ Packagist](https://packagist.org/packages/xavicabot/filament-appointments)[ RSS](/packages/xavicabot-filament-appointments/feed)WikiDiscussions main Synced today

READMEChangelog (7)Dependencies (6)Versions (9)Used By (0)

Filament Appointments
=====================

[](#filament-appointments)

A complete appointment booking system for [FilamentPHP](https://filamentphp.com). Includes a visual time slot picker, weekly schedule management, manual blocks, booking with double-booking prevention, email confirmations, Google Calendar busy-time sync, and automatic Google Meet link generation.

> **v2.x** — Requires PHP 8.2+, Laravel 11.28+/12, and FilamentPHP 4 or 5.
>
> For FilamentPHP 3 support, use [v1.x](https://github.com/xavicabot/filament-appointments/tree/1.x) (`composer require xavicabot/filament-appointments:^1.0`).

---

Installation
------------

[](#installation)

### 1. Install the package

[](#1-install-the-package)

```
composer require xavicabot/filament-appointments
```

### 2. Publish and run the migrations

[](#2-publish-and-run-the-migrations)

```
php artisan vendor:publish --tag=filament-appointments-migrations
php artisan migrate
```

This creates the following tables: `fa_rules`, `fa_blocks`, `fa_bookings`, `fa_connections`, `fa_sources`, `fa_busy_cache`.

### 3. Publish the config file

[](#3-publish-the-config-file)

```
php artisan vendor:publish --tag=filament-appointments-config
```

This creates `config/filament-appointments.php`.

---

Setting Up Your Panel
---------------------

[](#setting-up-your-panel)

### Register the Plugin

[](#register-the-plugin)

Add the plugin to your Filament `PanelProvider`:

```
use XaviCabot\FilamentAppointments\FilamentAppointmentsPlugin;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->plugins([
                FilamentAppointmentsPlugin::make(),
            ]);
    }
}
```

This adds three resources to your sidebar under the **"Appointments"** navigation group:

- **Appointment Rules** — Define weekly availability schedules
- **Appointment Blocks** — Block specific dates/times (holidays, vacations, etc.)
- **Calendar Connections** — Manage Google Calendar integrations

### Prepare Your User Model

[](#prepare-your-user-model)

Add the `HasAppointments` trait to the model that **provides** appointments (e.g., advisors, doctors, professionals):

```
use XaviCabot\FilamentAppointments\Support\HasAppointments;
use XaviCabot\FilamentAppointments\Support\HasBookings;

class User extends Authenticatable
{
    use HasAppointments, HasBookings;
}
```

- **`HasAppointments`** — Marks the model as a schedule owner. Provides `User::withAppointments()` scope (users that have active rules) and `$user->toSlotOwner()`.
- **`HasBookings`** — Allows the model to book appointments. Provides `$user->bookings()` relationship and email methods for confirmations.

> If the people who book appointments are different from those who provide them (e.g., Patient books with Doctor), add `HasAppointments` to Doctor and `HasBookings` to Patient.

### Set the Owner Model in Config

[](#set-the-owner-model-in-config)

Open `config/filament-appointments.php` and set your owner model:

```
'owner_model' => \App\Models\User::class,
'owner_label' => 'name',
```

This allows the admin resources to show a dropdown of users when creating rules and blocks, instead of raw numeric IDs.

---

Creating Availability Schedules
-------------------------------

[](#creating-availability-schedules)

Go to **Appointment Rules** in your Filament panel and create rules for each day of the week. Each rule defines:

FieldDescriptionExampleOwnerThe professional providing appointmentsDr. SmithWeekdayDay of the week (Monday–Sunday)MondayStart timeWhen the schedule starts09:00End timeWhen the schedule ends13:00IntervalMinutes between each slot30ActiveEnable/disable the ruleYes**Example:** A rule for Monday, 09:00–13:00 with 30-min intervals generates: 09:00, 09:30, 10:00, 10:30, 11:00, 11:30, 12:00, 12:30.

You can create multiple rules for the same day (e.g., morning 09:00–13:00 + afternoon 15:00–18:00).

---

Blocking Dates
--------------

[](#blocking-dates)

Go to **Appointment Blocks** to manually mark dates or time ranges as unavailable:

- **All-day block:** Set the date only (leave start/end time empty) — blocks the entire day
- **Partial block:** Set date + start/end time — blocks only that time range
- **Reason:** Optional note (e.g., "Public holiday", "Vacation")

---

Using the Appointment Picker
----------------------------

[](#using-the-appointment-picker)

The `AppointmentPicker` is a Filament form field that shows a date input and a dropdown grid of available time slots.

### Basic Example

[](#basic-example)

```
use XaviCabot\FilamentAppointments\Forms\Components\AppointmentPicker;
use XaviCabot\FilamentAppointments\Support\SlotOwner;

AppointmentPicker::make('time_slot')
    ->label('Pick a time')
    ->owner(fn () => SlotOwner::forUser(auth()->user()))
    ->minDate(now())
    ->maxDate(now()->addDays(30))
    ->required()
```

### With an Advisor Selector

[](#with-an-advisor-selector)

When users need to choose who they're booking with:

```
use Filament\Forms\Get;
use XaviCabot\FilamentAppointments\Forms\Components\AppointmentPicker;
use XaviCabot\FilamentAppointments\Support\SlotOwner;

// Advisor dropdown
Forms\Components\Select::make('advisor_id')
    ->label('Advisor')
    ->options(User::withAppointments()->pluck('name', 'id'))
    ->required()
    ->live(),

// Time slot picker — reacts to selected advisor
AppointmentPicker::make('time_slot')
    ->label('Pick a time')
    ->owner(fn (Get $get) => $get('advisor_id')
        ? new SlotOwner('user', (int) $get('advisor_id'))
        : null
    )
    ->minDate(now())
    ->maxDate(now()->addDays(30))
    ->required()
    ->visible(fn (Get $get) => filled($get('advisor_id'))),
```

### Field Value

[](#field-value)

The selected value is stored as a string in the format `"YYYY-MM-DD HH:MM"` (e.g., `"2026-02-16 09:30"`).

To parse it:

```
$parts = explode(' ', $data['time_slot'], 2);
$date = $parts[0];  // "2026-02-16"
$time = $parts[1];  // "09:30"
```

### Available Methods

[](#available-methods)

MethodDescription`owner(callable)`Closure returning a `SlotOwner`. Determines whose schedule is shown.`minDate(string|DateTime|Closure)`Earliest selectable date.`maxDate(string|DateTime|Closure)`Latest selectable date.---

Creating Bookings
-----------------

[](#creating-bookings)

Use `AppointmentService` to create bookings with double-booking protection:

```
use XaviCabot\FilamentAppointments\Services\AppointmentService;
use XaviCabot\FilamentAppointments\Support\SlotOwner;

$service = app(AppointmentService::class);

$booking = $service->createBooking(
    owner: new SlotOwner('user', $advisorId),
    date: '2026-02-20',
    startTime: '10:00',
    client: auth()->user(),
);
```

This automatically:

1. Determines the slot duration from the matching rule
2. Prevents double-booking (database-level lock)
3. Creates a Google Meet link if the owner has a connected Google account
4. Sends a confirmation email to the client
5. Returns an `AppointmentBooking` model

### Full Page Example

[](#full-page-example)

Here's a complete Filament page that lets users book appointments:

```
use XaviCabot\FilamentAppointments\Forms\Components\AppointmentPicker;
use XaviCabot\FilamentAppointments\Services\AppointmentService;
use XaviCabot\FilamentAppointments\Support\SlotOwner;

// In your form method:
$schema = [
    Forms\Components\Select::make('advisor_id')
        ->label('Advisor')
        ->options(User::withAppointments()->pluck('name', 'id'))
        ->required()
        ->live(),

    AppointmentPicker::make('time_slot')
        ->label('Pick a time')
        ->owner(fn (Get $get) => $get('advisor_id')
            ? new SlotOwner('user', (int) $get('advisor_id'))
            : null
        )
        ->minDate(now())
        ->maxDate(now()->addDays(30))
        ->required()
        ->visible(fn (Get $get) => filled($get('advisor_id'))),

    Forms\Components\Textarea::make('notes')
        ->label('Notes (optional)')
        ->rows(2),
];

// In your submit method:
public function submit(): void
{
    $data = $this->form->getState();

    $parts = explode(' ', $data['time_slot'], 2);
    $date = $parts[0];
    $time = $parts[1] ?? '00:00';

    $owner = new SlotOwner('user', (int) $data['advisor_id']);

    $booking = app(AppointmentService::class)
        ->createBooking($owner, $date, $time, auth()->user());

    Notification::make()
        ->title('Appointment booked!')
        ->success()
        ->send();
}
```

---

Email Confirmations
-------------------

[](#email-confirmations)

By default, bookings are created with status `confirmed`. You can require email confirmation first.

### Enable Confirmation Flow

[](#enable-confirmation-flow)

In `config/filament-appointments.php`:

```
'bookings' => [
    'require_confirmation' => true,
    'confirmation_ttl_hours' => 24,
],
```

### How It Works

[](#how-it-works)

1. Booking is created with status **`pending`**
2. Client receives an email with a **signed URL** button
3. Client clicks the button — booking status changes to **`confirmed`**
4. If the link expires (after `confirmation_ttl_hours`), the booking can be auto-cancelled

The email includes the appointment date/time, a confirmation button, the expiration notice, and a Google Meet link (if available).

### Auto-Cancel Expired Bookings

[](#auto-cancel-expired-bookings)

Schedule the artisan command to automatically cancel unconfirmed bookings:

```
// Laravel 11+ (routes/console.php)
Schedule::command('fa:expire-appointments')->hourly();

// Laravel 10 (app/Console/Kernel.php)
$schedule->command('fa:expire-appointments')->hourly();
```

Or run manually:

```
php artisan fa:expire-appointments
```

---

Google Calendar Integration
---------------------------

[](#google-calendar-integration)

The package can sync with Google Calendar to:

- **Mark slots as unavailable** when the owner has events in Google Calendar (freeBusy API)
- **Create Google Calendar events with Meet links** when bookings are made

### Step 1: Create Google OAuth Credentials

[](#step-1-create-google-oauth-credentials)

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a project (or use an existing one)
3. Enable the **Google Calendar API**
4. Go to **Credentials** &gt; **Create Credentials** &gt; **OAuth 2.0 Client ID**
5. Application type: **Web application**
6. Add an authorized redirect URI: ```
    https://your-domain.com/filament-appointments/google/callback

    ```

### Step 2: Add Environment Variables

[](#step-2-add-environment-variables)

Add to your `.env`:

```
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=https://your-domain.com/filament-appointments/google/callback
```

> The redirect URI must match **exactly** what you configured in Google Cloud Console.

### Step 3: Connect an Account from Filament

[](#step-3-connect-an-account-from-filament)

1. Go to **Calendar Connections** in your Filament panel
2. Click **"Connect Google"**
3. Select the user to connect
4. Complete the Google OAuth consent screen
5. Once connected, click **"Sync"** to fetch the list of Google Calendars
6. Toggle **"Included"** for each calendar you want to check for busy times

### Two-Way Sync

[](#two-way-sync)

The integration works in **both directions**:

#### Google Calendar → App (busy-time detection)

[](#google-calendar--app-busy-time-detection)

When slots are loaded for a date, the package automatically checks the owner's Google Calendar for existing events:

1. Fetches the owner's connected calendars (only those marked as "Included")
2. Calls the Google Calendar **freeBusy** API for that date
3. Any slot that overlaps with a Google Calendar event is marked as unavailable
4. Results are cached to avoid excessive API calls

So if the owner has a meeting in Google Calendar from 10:00 to 11:00, the slots at 10:00 and 10:30 will automatically appear as unavailable in the appointment picker.

The cache TTL is configurable:

```
// config/filament-appointments.php
'google' => [
    'cache_ttl_seconds' => 120, // Cache busy windows for 2 minutes
],
```

> Sync is near real-time: there is a maximum delay equal to the cache TTL (2 minutes by default). After that, any change in Google Calendar is reflected in the available slots.

#### App → Google Calendar (event creation + Meet link)

[](#app--google-calendar-event-creation--meet-link)

When a booking is created and the owner has a connected Google account:

1. A **Google Calendar event** is automatically created on the owner's primary calendar — this blocks that time slot in Google Calendar too
2. A **Google Meet conference link** is generated for the event
3. The Meet URL is stored in `$booking->metadata['meet_link']`
4. The Meet link is included in the confirmation email sent to the client

This means the owner's Google Calendar stays in sync: if someone books an appointment through your app, it appears as a calendar event with a Meet link, and that time is blocked for future Google Calendar scheduling.

If no Google connection exists, the booking proceeds normally without a calendar event or Meet link.

---

Configuration Reference
-----------------------

[](#configuration-reference)

Full `config/filament-appointments.php`:

```
return [
    // URL prefix for all package routes (slots endpoint, Google OAuth, etc.)
    'route_prefix' => 'filament-appointments',

    // Auto-register admin resources (Rules, Blocks, Calendar Connections)
    // Set to false if you want to register them manually or not at all
    'register_resources' => true,

    // The Eloquent model that owns schedules
    // Used in admin dropdowns to list available owners
    'owner_model' => \App\Models\User::class,

    // Which attribute to display in owner dropdowns
    'owner_label' => 'name',

    // Timezone for slot generation
    'timezone' => 'Europe/Madrid',

    // Custom slot resolver class (advanced)
    'resolver' => \XaviCabot\FilamentAppointments\Support\SlotResolver::class,

    // Booking behavior
    'bookings' => [
        // If true, bookings start as "pending" and require email confirmation
        'require_confirmation' => false,

        // Hours before unconfirmed bookings are auto-cancelled
        'confirmation_ttl_hours' => 24,
    ],

    // Google Calendar settings
    'google' => [
        // How long to cache busy windows per owner/date (seconds)
        'cache_ttl_seconds' => 120,
    ],
];
```

---

Customizing Views
-----------------

[](#customizing-views)

Publish the views to customize the look of the appointment picker, emails, and confirmation page:

```
php artisan vendor:publish --tag=filament-appointments-views
```

This copies the views to `resources/views/vendor/filament-appointments/`:

ViewDescription`forms/components/appointment-picker.blade.php`The slot picker (Alpine.js + Tailwind)`emails/appointment-confirmed.blade.php`Email sent when a booking is confirmed`emails/appointment-pending.blade.php`Email sent when a booking requires confirmation`bookings/confirmed.blade.php`Page shown after clicking the confirmation link---

Translations
------------

[](#translations)

The package includes translations in 5 languages: **English**, **Spanish**, **French**, **German**, and **Italian**.

The language is determined by your Laravel app locale (`config/app.php` &gt; `locale`).

To override translations, publish them:

```
php artisan vendor:publish --tag=filament-appointments-translations
```

Or add your own files in:

```
resources/lang/vendor/filament-appointments/{locale}/messages.php

```

---

Disabling Admin Resources
-------------------------

[](#disabling-admin-resources)

If you don't want the package to register its admin resources automatically (e.g., you want to manage rules via your own UI):

```
// config/filament-appointments.php
'register_resources' => false,
```

---

License
-------

[](#license)

MIT

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance98

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

8

Last Release

12d ago

Major Versions

1.x-dev → v2.0.02026-03-06

PHP version history (2 changes)v1.0.0PHP ^8.1

v2.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/c306544de15f8dabd17b623e794ab75e5905b7d89c2a4b56e7fec629d2a02347?d=identicon)[xavicabot](/maintainers/xavicabot)

---

Top Contributors

[![xavicabot](https://avatars.githubusercontent.com/u/25738574?v=4)](https://github.com/xavicabot "xavicabot (10 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/xavicabot-filament-appointments/health.svg)

```
[![Health](https://phpackages.com/badges/xavicabot-filament-appointments/health.svg)](https://phpackages.com/packages/xavicabot-filament-appointments)
```

###  Alternatives

[bezhansalleh/filament-language-switch

Zero config Language Switch(Changer/Localizer) plugin for filamentphp admin

339951.2k13](/packages/bezhansalleh-filament-language-switch)[nikaia/translation-sheet

Laravel Translation via Google Spreadsheet

294599.8k](/packages/nikaia-translation-sheet)[spatie/laravel-mix-preload

Add preload and prefetch links based your Mix manifest

169175.3k2](/packages/spatie-laravel-mix-preload)[schulzefelix/laravel-search-console

A Laravel package to retrieve data from Google Search Console

5037.7k1](/packages/schulzefelix-laravel-search-console)[danilopolani/filament-plausible-widget

Plausible widget for Filament admin dashboard

203.9k](/packages/danilopolani-filament-plausible-widget)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
