Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Calendar Laravel Package

wdelfuego/nova-calendar

A Laravel Nova calendar tool that displays your Eloquent models as events on a monthly calendar, with customizable event rendering, filtering, badges, and click-through actions—all configurable from a single Nova resource.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm fit before you write code. This is a Nova Tool, so it is worthless outside Nova and shouldn't influence your admin framework choice. Check composer.json constraints against your Nova major, Laravel, and PHP versions — Nova tools routinely break across majors, and with zero dependents you are the first person on your exact combination. Also read the actual LICENSE file: GitHub reports NOASSERTION, which means nobody has told you what you may do with this, and that's a legal question before it's an engineering one.

Install and register on a branch:

composer require wdelfuego/nova-calendar
php artisan nova-calendar:install   # publishes config, possibly model/migration stubs — read the diff
php artisan nova:publish            # Nova tool assets; blank calendar panels usually trace back to this
// app/Providers/NovaServiceProvider.php
public function tools(): array
{
    return [
        new \Wdelfuego\NovaCalendar\NovaCalendar(),
    ];
}

Where to look first, in this order:

  1. config/nova-calendar.php — the entire integration surface. The eventSources array is what you edit day to day; everything else is set-and-forget.
  2. vendor/wdelfuego/nova-calendar/src — specifically the Event class and the event-source base classes (NovaEventSource, CustomEventSource). The fluent event API and the stub you extend are the most accurate documentation this package has.
  3. Whatever the installer scaffolded in app/. Decide immediately whether you're adopting its schema or copying its shape into your own tables. Default to copying the shape — keep the package a presentation layer so you can delete it in one commit later.
  4. resources/views/vendor/nova-calendar only if you need to change rendering. That's the road to a fork; go there last.

First use case — exactly one thing. Point the calendar at an existing, trusted, non-sensitive Eloquent model in read-only mode:

namespace App\Nova\Calendar;

use App\Nova\Booking;
use Wdelfuego\NovaCalendar\EventSource\NovaEventSource;

class BookingEventSource extends NovaEventSource
{
    // Mirror the stub the installer published — property names shift between releases.
    public $model = Booking::class;
    public $dateAttributes = ['starts_at', 'ends_at'];
}
// config/nova-calendar.php
'eventSources' => [
    \App\Nova\Calendar\BookingEventSource::class,
],
php artisan config:clear

Open the tool, navigate to a month you know has bookings, and verify titles, start/end placement, and click-through. That's an hour-to-a-day spike. Timebox it there — do not let it drift into recurrence, permissions design, or CRUD.

Implementation Patterns

One event source per Nova resource. This is how you get a colour-coded mixed calendar (bookings + shifts + editorial deadlines + support windows) without a polymorphic events table. Each source class is a thin adapter: map attributes, pick a colour, link the resource. If a source grows past ~40 lines, the logic belongs on the model or in a service.

Same model, multiple date attributes. If Booking has both created_at and starts_at and both matter, register two source classes over the same model with different $dateAttributes. Cheaper and clearer than inventing a synthetic view.

Synthetic events for non-rows. Holidays, billing cutoffs, on-call rotations, external deadlines — anything that isn't an Eloquent row — belongs in a CustomEventSource where you build Event objects yourself:

class HolidayEventSource extends CustomEventSource
{
    public function nonNovaEvents(): array
    {
        return collect(config('company.holidays'))
            ->map(fn ($h) => Event::make($h['label'])
                ->start(Carbon::parse($h['date'], config('app.timezone')))
                ->allDay()
                ->color('#94a3b8'))
            ->all();
    }
}

Authorization is yours, not the package's. The calendar renders every row the query returns. A Nova policy governs the click-through; it does not filter the grid payload. Scope at the source — a global scope on the model, an explicit whereIn('team_id', $visibleTeamIds), or a query method on the source class. This is the one mistake that's an incident rather than a bug. Then, only emit a resource URL when the user can actually view the record:

$url = $user->can('view', $model)
    ? "/resources/{$model->resourceKey()}/{$model->getKey()}"
    : null;

A link that 403s is worse than no link.

Keep name/colour resolution pure. Don't call auth(), DB::, or relationship queries inside the per-event accessors — every call multiplies by the number of events in the rendered window. Use plain attributes, eager loads configured on the source, or a precomputed column. Your model should stay usable outside Nova, and the calendar should stay fast.

Centralise status → colour. One map (config array or a static method on the model) reused by all sources. Hex codes scattered across three adapter classes rot within a sprint.

Time zones: store UTC, convert at the boundary, set allDay() explicitly. DST transitions are the source of nearly every "the calendar is wrong by a day" report. Test a date that falls inside a DST shift.

Recurrence is your expansion, not the package's. It renders events; it does not solve RRULE. Expand occurrences server-side into concrete rows for the visible window, cap the horizon (e.g. 12 months), cache the expansion. Never recurse unbounded inside an accessor.

Wrap the page defensively. Consider a thin sub-class of the tool or a small middleware so an exception in one event source yields an empty calendar plus a logged error, rather than a 500 that takes down the Nova shell mid-demo.

Test the provider, not the UI. Assert on your event source's output for a fixed window against a seeded dataset — "March returns 14 events, one spanning 3 days, two all-day" — rather than driving the Vue layer through a browser. Faster, stable, and it documents the date semantics you settled on.

Gotchas and Tips

  • config:clear after every eventSources edit. This is overwhelmingly the #1 cause of "my new source doesn't appear." Bake it into your muscle memory.
  • nova:publish after every Nova or package upgrade. Stale compiled assets are the #1 cause of a blank panel. Do it in the deploy script, not manually.
  • Silent date-attribute mismatch. A typo'd column name doesn't produce a friendly warning — it produces a blank calendar or a stack trace deep inside the provider. Triple-check names against the migration.
  • Verify end-date semantics early. Check whether the end is inclusive or exclusive and how zero-length events render (some views hide them). Test a one-day and a three-day event explicitly before you tell anyone it works.
  • Multi-day and all-day events are what stakeholders test first and what looks subtly wrong first. Inspect the banner row on a real month view, not just the day view.
  • Empty-calendar debugging ladder: tool registered in tools()? source listed in config? config cache cleared? source actually returns rows for the rendered range? attribute names match DB columns exactly? timezone pushing events outside the window? Temporarily log the returned event count inside the source instead of guessing.
  • Scaling limit is query shape, not request volume. Index starts_at/ends_at, filter by visible range inside the source, and watch long-window agenda views — payload size and render time blow up before CPU does.
  • The installer's schema is optional. If it scaffolds a model and migration, copy the column shape into your own tables and use the package purely as a view. That preserves the exit path: remove the tool registration, delete the source classes, config:clear — no data migration.
  • Nova upgrades will break it, and nobody upstream will have your fix. Pin the version, name an owner, and budget a recurring maintenance slot. Assume a fork may be necessary before you commit engineering hours.
  • Tool conflicts. If you already have custom Nova tools, validate asset publishing in staging — two tools contending over the Vue build is a lost afternoon.
  • Localization is supported but not automatic. Publish translations and set app.locale if your audience isn't English; otherwise month and weekday names will surprise people.
  • Extension points, in order of preference: custom event source → custom data provider → published views → fork. Work down that list, never up.
  • Document the rollback in the PR description. "Delete the tool registration and the source classes; no data migration" is what makes the adoption decision easy to approve — and it's true only if you kept event data in app-owned tables.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource