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

Filament Business Hours Laravel Package

laboiteacode/filament-business-hours

Filament field for managing business opening hours on a drag-and-drop weekly 24-hour grid, with exceptional closures, special hours, vacation periods, and timezones. Stores data natively in spatie/opening-hours format.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

The package is a thin, well-behaved adapter between a Filament panel and spatie/opening-hours, and that shape is what makes it defensible. It owns exactly one concern — the editing surface — and delegates the domain model entirely to a mature, widely deployed Spatie library. Concretely:

  • Single table, single row per business. business_hours with key, timezone, overflow, hours (JSON), exceptions (JSON). Nothing else touches your schema. No polymorphic relations, no side tables, no migrations colliding with application models.
  • The read path does not depend on the package. Because the columns are stored in Spatie's native definition shape, OpeningHours::create() can be built from a raw DB::table() query, a seeder, an import job, or a queue worker without ever loading the Filament page or the package model. This is the single most important architectural property: the UI is replaceable, the data is not.
  • Panel-native integration. Navigation label/icon/group/sort, cluster support, slug, canAccessUsing() closure or config gate, per-panel ->record() key so multiple panels can manage distinct hour sets, and a fluent API that overrides a publishable config file. It behaves like a first-class Filament citizen rather than bolting a custom page on.
  • Validation is real, not cosmetic. The save path instantiates Spatie\OpeningHours\OpeningHours before persisting, so a stored record is queryable by construction. Combined with range normalization (sort, merge overlaps/touching), duplicate-coverage rejection across exceptions and vacation periods, and hard payload caps (100 ranges/day, 500 exception entries, 1100 days per vacation, 255-char labels) to defend against crafted Livewire state, the correctness story is stronger than the 8-star count suggests.

The main architectural limitation: there is no multi-tenant scoping, no audit trail, and no API. Multi-location means one plugin instance per panel per record key — workable for a handful of sites, awkward at dozens. Any headless/storefront exposure is your code, not theirs.

Integration feasibility

On the right stack, this is a sub-day integration:

  1. composer require laboiteacode/filament-business-hours
  2. Publish + run the migration (one table)
  3. ->plugin(FilamentBusinessHoursPlugin::make()) in the panel provider
  4. Read via BusinessHours::openingHours($key) or ->toOpeningHoursArray()

That is genuinely it. The friction is entirely in the compatibility gate.

Constraint Supported
PHP 8.2 – 8.5
Laravel 12, 13
Filament 4, 5

Those are the only supported versions. Laravel 13 and Filament 5 are leading-edge targets. Any estate still on Laravel 10/11 or Filament 3 — which is most of the installed base in 2025 — is hard-blocked, and nothing in the package can be backported cheaply because the page is built on Filament 4 form/schema primitives.

Technical risk

The risk is concentrated in maintenance and ecosystem, not in code quality.

  • Bus factor 1. Published by La Boîte à Code (an agency) with a named maintainer. No co-maintainers visible, no dependents, 8 stars. Assume no external bug-fix pressure.
  • Version treadmill. Filament majors have historically been breaking for plugin authors. A Filament 6 release plausibly strands this package for months. The v1.0.1 changelog shows CI pinned to commit SHAs, a SECURITY.md, and Dependabot with a cooldown — that is unusually disciplined supply-chain hygiene for a package this size, and it materially reduces (but does not eliminate) the "abandoned package" tail risk.
  • Whole-page save semantics. The Save action writes grid + exceptional dates + vacations + settings as one row. Two admins editing concurrently get last-write-wins with no optimistic concurrency check and no audit of who changed what. For a regulated or multi-editor setting, that is a real gap.
  • Preserved-but-unrepresentable exceptions. Hand-written m-d to m-d recurring periods and structured data payloads survive UI edits verbatim. Good for data integrity, dangerous for operators: the admin sees a saved state that the grid cannot fully express, so "the page looks right" is not proof the record is right.
  • Timezone/DST correctness is inherited, not solved. The package stores an IANA identifier and passes it through; correctness of DST transitions and post-midnight ranges rests on Spatie plus your choice of overflow vs. two-range modelling. This needs an explicit decision, documented, not left to the editor.

Key questions to raise with the vendor/maintainer (or answer ourselves)

  1. What is the support and release commitment across Filament 6 / Laravel 14? Is there a stated compatibility SLA?
  2. Is multi-tenancy on the roadmap (tenant-scoped records, or one record key per tenant without one page per panel)?
  3. Any plan for an audit log or optimistic locking on save?
  4. Which spatie/opening-hours major is pinned, and how are its majors tracked?
  5. What is the realistic fork cost if the package stalls? (Our estimate: one Livewire page, one model, one migration, ~2–4 engineer-days to own — low enough to make this an acceptable bet.)
  6. Does the CI test suite cover the validation/normalization edge cases (DST, overlapping vacations, overflow mode), or only happy-path grid CRUD?

Integration Approach

Stack fit

Good fit if and only if the target panel is already on Filament 4/5 with Laravel 12/13 and PHP 8.2+. This is an additive settings page, not a platform dependency — it does not touch authentication, routing, queueing, or storage beyond one table. It slots in beside existing Filament resources with no architectural negotiation.

For a Filament 3 / Laravel 10–11 estate: do not attempt. Either defer until the panel upgrade lands, or budget the 2–4 days to lift the pattern (the grid UX is the work; the Spatie persistence contract is 30 lines).

Migration path

There is no incumbent to displace — the package is net-new. The migration work is data, not code:

  • If hours currently live as ad-hoc columns (opens_at, closes_at, per-day booleans) or a bespoke table, write a one-off artisan command or seeder that assembles the Spatie definition array and writes rows via BusinessHours::create(['key' => ..., 'timezone' => ..., 'hours' => [...], 'exceptions' => [...]]). The page picks them up on next load.
  • Exit path is trivially cheap by design: the JSON columns are the Spatie contract. Dropping the package later means deleting the Composer dependency and the plugin registration; every consumer reading via OpeningHours::create() keeps working. Zero data lock-in. This is the strongest argument for adopting.
  • Rehearse the reverse (raw-SQL read) in a test as a guardrail so nobody accidentally couples application logic to the package model.

Compatibility

  • Pin the package to an exact minor in composer.json; treat Filament major bumps as a scheduled evaluation, not a routine composer update.
  • Verify the transitive spatie/opening-hours constraint resolves against whatever version the rest of the estate already uses — a duplicate major is a nuisance but not a blocker (the definition array is stable across Spatie majors, the query API less so).
  • Confirm PHP 8.2 is the floor in CI; if any environment is on 8.1, that's a hard stop.
  • Non-English operators are covered (EN/FR/ES shipped), but labels typed into exceptions are free text and will not be translated — decide whether that's acceptable or whether labels must be constrained to enum values.

Sequencing

  1. Confirm the gate — pin down Filament and Laravel versions across target panels. If the gate fails, stop; do not prototype.
  2. Spike in a staging panel (half a day) — install, publish migration, register with a non-default record key, drag a few ranges, save, and round-trip through OpeningHours::create() in tinker. Validate the overflow-mode decision explicitly.
  3. Decide the overnight model — two ranges (20:00-24:00 Fri + 00:00-02:00 Sat) vs. overflow mode. Write it down; it determines how every future editor uses the grid.
  4. Build the read-side service — a single OpeningHoursService::for(string $key): ?OpeningHours wrapping BusinessHours::resolve() with a cache layer. All application code depends on this, never on the package model.
  5. Add cache invalidation — a model observer on BusinessHours busting the business-hours:{key} cache key on save. Pair with a short TTL (1 h) as a backstop.
  6. Lock down authorization — a manageBusinessHours gate (or ->canAccessUsing()), not the default "anyone with panel access."
  7. Backfill from legacy source via command/seeder; spot-check the grid renders what operations expects.
  8. Expose downstream — storefront "open until" badge, API endpoint, availability checks — all reading through the service in step 4.
  9. Document a runbook: how to edit hours, what the overnight convention is, who to escalate to when the page breaks after a Filament upgrade, and the fork contingency.

Operational Impact

Maintenance

The maintenance surface is small and well-defined: one migration, one config file, one plugin registration line, one page. The codebase is not something you maintain — the compatibility envelope is what you maintain.

  • Realistic annual cost: a half-day per Filament major release to verify the page still renders and saves, plus a dependency-bump review. Well under a sprint per year.
  • Fork cost if abandoned: 2–4 engineer-days for a competent Filament developer to own the page outright against the stable Spatie contract. This is the number that makes the 8-star / 0-dependent profile tolerable rather than disqualifying.
  • No lock-in to justify. If the package dies, nothing migrates. Delete the plugin line.

Support

MIT, single-maintainer agency, no commercial support tier, no dependents to piggyback on. Assume self-support with upstream GitHub issues as best-effort. Mitigations already present in the repo (pinned CI actions, SECURITY.md with private disclosure and response timelines, Dependabot with cooldown) are a positive signal on maintainer discipline — but they are process hygiene, not a support commitment. Plan for the maintainer to be unreachable.

Operationally this means: whoever on the team owns Filament owns this package. Name them.

Scaling

  • Read throughput: OpeningHours::create() parses the full definition on every call. On a storefront header rendered per request, that is a measurable per-request cost. Cache the definition array (the README's own guidance) with a 1-hour TTL plus observer invalidation. After that, the read path is a cache hit and the table is touched almost never.
  • Write throughput: trivially low — admin saves are human-paced, and each save rewrites one row. No contention concern except the last-write-wins caveat.
  • Data growth: bounded and tiny. One row per business; exceptions/vacations are JSON in-row, capped at 500 entries by validation. This will never be a storage or query-plan problem.
  • Multi-location: the honest scaling limit. Ten shops is fine (one record key each). Fifty shops means fifty records managed through one page per panel per key, which does not scale as an admin UX. Plan a bespoke resource layer before you reach that point.

Failure modes

Failure Impact Detection Mitigation
Filament major upgrade breaks the page Editors cannot change hours; existing data unaffected Staging smoke test on panel upgrade Pin version; half-day verification per Filament major
Cache not invalidated after save Storefront shows stale hours ("closed" while open) Operator report; hours look right in panel but not on site Observer on save + short TTL backstop
Overnight/DST edge handled wrong Storefront reports wrong open state around DST or at 1 a.m. Scheduled assertion tests against the definition Explicit overflow-vs-two-range decision, documented; DST tests in CI
Two editors save concurrently One editor's changes silently lost Editor report ("my hours reverted") Operational: single owner of hours; or add updated_at check before save in a custom wrapper
Hand-written exception survives but is unrepresentable in UI Record is semantically richer/different than the grid implies Hard to detect visually Read the raw JSON in reviews; restrict who writes rows directly
Package abandoned pre-Filament-6 Frozen on current Filament major — Fork contingency (2–4 days); low trigger threshold given exit cost is near zero
Malformed/large Livewire payload Page render degrades or caps silently truncate Validation message on save Caps are in place (100 ranges/day, 500 exceptions); document them for heavy users

Ramp-up

  • Filament-fluent developer: under a day. The integration is four lines; the honest learning curve is spatie/opening-hours' query API (isOpenAt, nextOpen, forDate, exception precedence, overflow) — budget half a day there, because that is what application code will actually call.
  • Filament-naive Laravel developer: ~2 days, mostly Filament 4 form/schema conventions.
  • Non-technical operations editor: the drag grid is genuinely usable, but needs 15 minutes of onboarding plus a one-page guide covering: drag to create, click/right-click to edit precisely, copy-a-day, exceptional dates vs. vacation periods (and why overlapping them is rejected), the configured snap step, and the overnight convention. Publish the guide where editors will find it — the drag interaction is discoverable, but the semantics of exceptions vs. vacations are not.
  • Documentation depth: README is unusually strong for a package of this size (query examples, raw-SQL escape hatch, caching guidance, DB schema, limits). There is no separate docs site, no video, no recipe collection — fine at this scope, thin if you need to onboard a rotating ops team.

TPM recommendation: Adopt in a Filament 4/5 panel where hours are a peripheral settings concern, with three non-negotiables — a read-side service abstraction so the app never touches the package model, a cached definition with observer invalidation, and a named internal owner plus a documented fork trigger. Do not adopt as a critical-path multi-location or multi-tenant scheduling system, and do not adopt on a Filament 3 estate. The vendor risk is real but hedged by the Spatie-native storage: the cost of being wrong is measured in days, not quarters.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log