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 Date Range Field Laravel Package

marshmallow/nova-date-range-field

marshmallow/nova-date-range-field is a Laravel Nova field that adds a date range picker for selecting start and end dates. It helps manage date ranges in Nova resources, useful for admin forms and filters.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package alongside Nova (Nova itself must already be installed and configured):

    composer require marshmallow/nova-date-range-field
    
  2. Check the Nova constraint — this is now much clearer than before. Release 5.0.0 explicitly adds Nova 5 support (PR #5), and the package versioning now jumps to track the Nova major (1.0.6 → 5.0.0). So:

    • On Nova 5, require ^5.0.
    • On Nova 4, stay on the 1.0.x line.
    • Do not assume composer require picked the right major. Read vendor/marshmallow/nova-date-range-field/composer.json and confirm the laravel/nova constraint matches your installed major. The 5.0.0 release is a major bump, so Composer may refuse to upgrade an existing ^1.0 install without an explicit change.
  3. Look at the README and the field class first. Because the package is small, the field's public API is the source of truth. Open vendor/marshmallow/nova-date-range-field/src/ and confirm:

    • the field class namespace (likely Marshmallow\NovaDateRangeField\...),
    • whether it stores two columns (e.g. start/end) or a single JSON column,
    • the fluent setter names for binding endpoints (commonly ->startColumn() / ->endColumn(), but verify).
  4. Your first real use case is almost always a resource with a bounded period — a booking, rental, subscription, campaign, or reporting window. Add the field to a resource's fields():

    use Marshmallow\NovaDateRangeField\DateRange; // confirm namespace
    
    DateRange::make('Booking period')
        ->startColumn('starts_at')
        ->endColumn('ends_at')
        ->rules('required', 'date'),
    

    Ensure starts_at and ends_at are $fillable and cast ('date' or 'datetime') on the model, then save a record in the panel and inspect the DB row to confirm how the field actually wrote the values. Do this before writing more code — it eliminates all ambiguity about the storage model.

  5. If the package ships uncompiled assets, run php artisan nova:publish or build the package's JS (npm install && npm run prod inside the package directory) and confirm the picker renders. If assets are precompiled, no build step is needed.

Implementation Patterns

Two-column binding is the happy path. Treat the field as a UI wrapper around a start column and an end column. Keep the model simple:

protected $casts = [
    'starts_at' => 'datetime',
    'ends_at'   => 'datetime',
];

protected $fillable = ['name', 'starts_at', 'ends_at'];

Validate the range, not just the endpoints. Field-level ->rules() often only applies to the whole field, not to each endpoint separately. Add a FormRequest (or ->rules() on a companion field) enforcing ends_at >= starts_at:

'starts_at' => ['required', 'date'],
'ends_at'   => ['required', 'date', 'after_or_equal:starts_at'],

If the package's rules() doesn't reach both columns, this is the reliable guard.

Reuse via a base resource or helper method. Because the field is niche, avoid scattering configuration. Define a private method on a base Resource:

protected function periodField(string $label = 'Period'): DateRange
{
    return DateRange::make($label)
        ->startColumn('starts_at')
        ->endColumn('ends_at')
        ->rules('required', 'date');
}

Then call $this->periodField() in each resource. This isolates any API change to one place if the package is ever replaced — and it also makes a Nova 4 → 5 upgrade a single-file touch point.

Separate the index/detail display from the form. If the field's resolveForDisplay doesn't format the range nicely, use onlyOnForms() for the date-range field and add two plain Date fields for index/detail (or a computed Text field):

$this->periodField()->onlyOnForms(),
Date::make('Starts')->onlyOnIndex(),
Date::make('Ends')->onlyOnIndex(),

For filters, prefer a hand-rolled NovaFilter. If the field can't bind to filter inputs cleanly, implement a filter with two Date inputs (from/to) and a whereBetween on the start column — this is typically more reliable than stretching a form field into a filter.

Normalize timezones on write. Use a model observer or saving hook to convert picker output to UTC (or your app's chosen storage timezone) before persisting:

static::saving(function ($model) {
    $model->starts_at = $model->starts_at?->utc();
    $model->ends_at   = $model->ends_at?->utc();
});

Extension pattern. If you need different storage (e.g. a single JSON column), subclass the field and override fill() / resolve() rather than patching vendor code.

Gotchas and Tips

  • Storage ambiguity is the #1 trap. Some date-range fields write a JSON blob into one column; others write two columns. The fillable/casts setup and SQL queries differ completely. Log a saved row on day one and design around what you actually see.
  • Null endpoints — now partially fixed, still guard. 5.0.0 includes [BUGFIX] Undefined array key issue (PR #2), which is very likely the null/partial-range resolution crash. That is good news, but don't assume every partial case is covered: if starts_at is set and ends_at is null (or vice versa), still guard with ->displayUsing(fn () => $this->starts_at && $this->ends_at ? ... : '—') or make the field ->nullable() and show a dash. Re-test the exact null scenario from your old version against 5.0.0 before deleting any workaround.
  • JSON storage breaks indexing and SQL filtering. If the field writes JSON, you cannot efficiently whereBetween in a filter. For any resource you'll query by date, insist on two real date columns.
  • Version line now tracks the Nova major — plan the upgrade deliberately. Going from 1.0.6 to 5.0.0 is a major jump, not a patch. An existing ^1.0 constraint will not pull in Nova 5 support. Pin explicitly per environment: "marshmallow/nova-date-range-field": "^1.0" on Nova 4, "^5.0" on Nova 5. Smoke-test after every Nova upgrade, and treat the package's own major version as a signal to re-read the changelog rather than assuming API stability.
  • Asset build order matters. If the picker doesn't appear after upgrading to 5.0.0, verify compiled assets exist under vendor/.../dist (or that the package's NovaServiceProvider/asset registration runs). Nova 5 asset registration may differ from Nova 4 — rebuild and re-publish (php artisan nova:publish) after composer update, and hard-refresh the browser cache.
  • ->rules() may not bind to both columns. Don't trust field rules for cross-column validation — put after_or_equal in a FormRequest or add an explicit saving check.
  • Debugging save payloads: open the browser network tab and inspect the POST/PUT /nova-api/... body to see exactly what the picker submits (keys, format, timezone). This is faster than reading the package source — and is the quickest way to confirm the Nova 5 upgrade didn't change the payload shape.
  • Timezone skew. The picker emits local time; if you store DATETIME without normalizing, records created from different timezones will be inconsistent. Normalize in an observer and consistently display in the panel's expected zone.
  • Don't mix with a stock Date field on the same attribute. A single-column Date::make('starts_at') and this range field both writing starts_at will conflict on forms. Use onlyOnForms() / hideFrom*() to keep them mutually exclusive.
  • Low-adoption risk management: the package now has Nova 5 support, a bugfix, and two new contributors — a genuine improvement in maintenance signal. But it still has near-zero dependents. Keep it isolated behind a helper method (see above) and write a single feature test that saves a record from the panel (or via a model factory + Nova:: request), so a future swap is a one-file change. Also check the package license — small packages sometimes ship without one, which is a procurement issue.
  • Read release notes, not release dates. The substantive signal in 5.0.0 is the Nova 5 PR and the null-key bugfix. Verify both against your own install (constraint + a saved partial-range record) rather than trusting the version number alone — the jump to 5.0.0 is a compatibility declaration, not a stability guarantee.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route