PHPackages                             alajusticia/laravel-expirable - 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. [Database &amp; ORM](/categories/database)
4. /
5. alajusticia/laravel-expirable

ActiveLibrary[Database &amp; ORM](/categories/database)

alajusticia/laravel-expirable
=============================

Make Eloquent models expirable

v2.5.0(2mo ago)2193.4k↓17.3%84MITPHPPHP ^8.2

Since Aug 27Pushed 2mo ago2 watchersCompare

[ Source](https://github.com/alajusticia/laravel-expirable)[ Packagist](https://packagist.org/packages/alajusticia/laravel-expirable)[ Fund](https://ko-fi.com/alajusticia)[ RSS](/packages/alajusticia-laravel-expirable/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (14)Versions (27)Used By (4)

Laravel Expirable 📅
===================

[](#laravel-expirable-)

Inspired by the SoftDeletes trait, this package provides a trait to make Eloquent models expirable.

It relies on an additional attribute (named `expires_at` by default) that contains the date of expiration (or `null` to make the model eternal).

When the expiration date is reached, the model will automatically disappear from all the Eloquent query results (but still remain in the database).

- [Compatibility](#compatibility)
- [Installation](#installation)
    - [Prepare your model](#prepare-your-model)
        - [Change the default name of the expiration attribute](#change-the-default-name-of-the-expiration-attribute)
        - [Set a default period of validity](#set-a-default-period-of-validity)
    - [Prepare your migration](#prepare-your-migration)
- [Usage](#usage)
    - [Retrieving models](#retrieving-models)
        - [Retrieving valid models](#retrieving-valid-models)
        - [Retrieving all models](#retrieving-all-models)
        - [Retrieving only expired models](#retrieving-only-expired-models)
        - [Retrieving only eternal models](#retrieving-only-eternal-models)
        - [Retrieving expired models since](#retrieving-expired-models-since)
    - [Get the expiration date](#get-the-expiration-date)
    - [Set the expiration date manually](#set-the-expiration-date-manually)
        - [The basic way](#the-basic-way)
        - [Using expiresAt()](#using-expiresat)
        - [Using lifetime()](#using-lifetime)
    - [Make existing models expire](#make-existing-models-expire)
        - [Expire models by key](#expire-models-by-key)
        - [Expire models by query](#expire-models-by-query)
    - [Revive expired models](#revive-expired-models)
    - [Make existing models eternal](#make-existing-models-eternal)
    - [Extend model lifetime](#extend-model-lifetime)
    - [Shorten model lifetime](#shorten-model-lifetime)
    - [Reset the expiration date to default](#reset-the-expiration-date-to-default)
    - [Get the status of a model](#get-the-status-of-a-model)
    - [Purge expired records](#purge-expired-records)
- [License](#license)

Compatibility
-------------

[](#compatibility)

Laravel Expirable package versionSupported Laravel framework versionsv210 to 12[v1](https://github.com/alajusticia/laravel-expirable/tree/v1)5.8 to 9You're reading the documentation for the latest version (v2) of this package.

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

[](#installation)

Install the package via composer using this command:

```
composer require alajusticia/laravel-expirable
```

You can publish the configuration file with:

```
php artisan vendor:publish --provider="ALajusticia\Expirable\ExpirableServiceProvider"
```

### Prepare your model

[](#prepare-your-model)

To make a model expirable, add the Expirable trait provided by this package:

```
use ALajusticia\Expirable\Traits\Expirable;
use Illuminate\Database\Eloquent\Model;

class Subscription extends Model
{
    use Expirable;
```

This trait will automatically add the expiration attribute in the list of attributes that should be mutated to dates.

#### Default name of the expiration attribute

[](#default-name-of-the-expiration-attribute)

By default the package adds an attribute named `expires_at` on your model. You can change this name by setting the `EXPIRES_AT` constant (don't forget to set the same name for the column in the migration, [see below](#prepare-your-migration)).

For example, let's say that we have a `Subscription` model and we want the attribute to be `ends_at`:

```
use ALajusticia\Expirable\Traits\Expirable;
use Illuminate\Database\Eloquent\Model;

class Subscription extends Model
{
    use Expirable;

    const EXPIRES_AT = 'ends_at';
```

You can also change the attribute name globally for all your expirable models by using the `attribute_name` option in the `expirable.php` configuration file (the constant prevails). If you do change the name globally in the configuration file, you don't have to set the name in the migration as it will be populated automatically.

#### Set a default period of validity

[](#set-a-default-period-of-validity)

You can set a default period of validity with the `defaultExpiresAt` public static function.

This method must return a date object or `null`. This way, on saving the model the date of expiration will be automatically added unless you explicitly provide a date.

An example to set a default period of validity of six months:

```
use ALajusticia\Expirable\Traits\Expirable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;

class Subscription extends Model
{
    use Expirable;

    // ...

    public static function defaultExpiresAt()
    {
        return Carbon::now()->addMonths(6);
    }
```

```
// Create a new subscription which will expire in six months (using default expiration date)
$subscription = new Subscription;
$subscription->save();

// Create a new subscription which will expire in one year (overwrite the default expiration date)
 $subscription = new Subscription;
 $subscription->expiresAt(Carbon::now()->addYear());
 $subscription->save();
```

### Prepare your migration

[](#prepare-your-migration)

The package requires that you add the expirable column in your migration. For convenience, the package provides the `expirable()` and `dropExpirable()` blueprint macros ready to use in your migration files:

```
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up(): void
    {
        Schema::table('subscriptions', function (Blueprint $table) {
            $table->expirable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down(): void
    {
        Schema::table('subscriptions', function (Blueprint $table) {
            $table->dropExpirable();
        });
    }
};
```

By default the name of the database column, like the model attribute, will be `expires_at` or the one in the configuration file. If you modified the default name of the attribute on your model with the `EXPIRES_AT` constant, you need to set the same custom name for the column in your migration, by giving the macro a parameter with the name.

To continue with our subscription example:

```
    public function up()
    {
        Schema::table('subscriptions', function (Blueprint $table) {
            $table->expirable('ends_at');
        });
    }

    public function down()
    {
        Schema::table('subscriptions', function (Blueprint $table) {
            $table->dropExpirable('ends_at');
        });
    }
```

Usage
-----

[](#usage)

### Retrieving models

[](#retrieving-models)

#### Retrieving valid models

[](#retrieving-valid-models)

Do your stuff as usual. By default, when the date of expiration is reached, the model will automatically be excluded of the results.

For example:

```
// Get only valid and current subscriptions and exclude expired ones
$subscriptions = Subscription::all();
```

This, and all the next examples, work as well with relationships:

```
$user = App\User::find(1);
$subscriptions = $user->subscriptions();
```

#### Retrieving all models

[](#retrieving-all-models)

To disable the default filtering and retrieve all the models, ignoring their expiration date:

```
// Get all subscriptions
$subscriptions = Subscription::withExpired()->get();
```

#### Retrieving only expired models

[](#retrieving-only-expired-models)

Use the onlyExpired scope:

```
// Get expired subscriptions
$subscriptions = Subscription::onlyExpired()->get();
```

#### Retrieving only eternal models

[](#retrieving-only-eternal-models)

To get only the models which do not expire (with expiration date attribute to `null`), use the onlyEternal scope:

```
// Get unlimited subscriptions
$subscriptions = Subscription::onlyEternal()->get();
```

#### Retrieving expired models since

[](#retrieving-expired-models-since)

This package provides a query scope to retrieve only models that have expired for at least a given period of time. Use the `expiredSince` scope and give it a parameter representing the desired period of time.

The parameter must be a string and the syntax is the same as the syntax accepted by the Carbon `sub` method (see documentation here: ).

For example, let's say that you want to definitively delete from the database the models expired since at least one year, the query will be:

```
// Delete expired models since one year or more
Subscription::expiredSince('1 year')->delete();
```

### Get the expiration date

[](#get-the-expiration-date)

To get the expiration date without having to know the name of its attribute, use the getExpirationDate method:

```
$subscription->getExpirationDate(); // Returns a date object
```

### Set the expiration date manually

[](#set-the-expiration-date-manually)

#### The basic way

[](#the-basic-way)

If you know the name of the expiration date attribute, you can simply populate this attribute with a date object (or `null` for eternal):

```
// Create a new subscription valid for one month
$subscription = new Subscription();
$subscription->ends_at = Carbon::now()->addMonth();
$subscription->save();
```

Of course it also works with mass assignment, but don't forget to add the attribute you intend to mass assign (here `ends_at`) in the `$fillable` property of your model:

```
// Create a new subscription valid for one month
$subscription = Subscription::create([
    'plan' => 'premium',
    'ends_at' => Carbon::now()->addMonth(),
]);
```

#### Using expiresAt()

[](#using-expiresat)

Use the `expiresAt` method with a date object in parameter (or `null` for eternal) to set an expiration date manually. On an Eloquent query the changes will be directly saved in the database. On a single model you still need to use the `save` method:

```
// Create a new subscription valid for one month
$subscription = new Subscription();
$subscription->expiresAt(Carbon::now()->addMonth());
$subscription->save();
```

```
// Set multiple subscriptions valid for one year
Subscription::whereKey([1, 2, 3])->expiresAt(Carbon::now()->addYear());
```

#### Using lifetime()

[](#using-lifetime)

The `lifetime` method provides you a more human readable way to set the period of validity with a string. The parameter must be a string and the syntax is the same as the syntax accepted by the Carbon `add` method (see documentation here: ).

```
// Create a new subscription valid for one month
$subscription = new Subscription();
$subscription->lifetime('1 month');
$subscription->save();
```

### Make existing models expire

[](#make-existing-models-expire)

If you want a model to expire, use the `expire` method on a model instance. This will set the expiration date at the current timestamp.

```
// Make a model expire
$subscription->expire();
```

#### Expire models by key

[](#expire-models-by-key)

If you know the primary key of the model, you may make it expire without retrieving it by calling the `expireByKey` method. In addition to a single primary key as its argument, the `expireByKey` method will accept multiple primary keys, an array of primary keys, or a collection of primary keys:

```
Subscription::expireByKey(1);

Subscription::expireByKey(1, 2, 3);

Subscription::expireByKey([1, 2, 3]);

Subscription::expireByKey(collect([1, 2, 3]));
```

#### Expire models by query

[](#expire-models-by-query)

You can also run an expire statement on a set of models:

```
Subscription::where('plan', 'basic')->expire();
```

### Revive expired models

[](#revive-expired-models)

After a model has expired, you can make it valid again using `revive()` method. It accepts an optional parameter which can be a date object or `null` for the new period of validity. Without parameter it resets to the default expiration date or set the expiration attribute to `null` if no default expiration date is set (making the model eternal).

```
// Reset validity with the default expiration date or set validity for unlimited period
$subscription->revive();

// Set the model to expire in one month
$subscription->revive(Carbon::now()->addMonth());
```

Sure, it also works with queries:

```
// Revive by query
Subscription::where('plan', 'plus')->revive();
```

### Make existing models eternal

[](#make-existing-models-eternal)

If you want a model never to expire, you just have to set the expiration attribute to `null`. You can do that manually or for existing models you can use the provided shortcut method `makeEternal()`:

```
// Make a model eternal
$subscription->makeEternal();
```

```
// Make eternal by query
Subscription::where('plan', 'business')->makeEternal();
```

### Extend model lifetime

[](#extend-model-lifetime)

With the `extendLifetimeBy`, you can extend the model lifetime by a human readable period (using the same syntax as the `lifetime` method):

```
$subscription->extendLifetimeBy('1 month')->save();
```

In the same way, you have the ability to shorten the model lifetime with the `shortenLifetimeBy` method:

### Shorten model lifetime

[](#shorten-model-lifetime)

```
$subscription->shortenLifetimeBy('3 days')->save();
```

### Reset the expiration date to default

[](#reset-the-expiration-date-to-default)

You can reset the expiration date to its default value (`null` or the date returned by the `defaultExpiresAt` static function):

```
$subscription->resetExpiration()->save();
```

### Get the status of a model

[](#get-the-status-of-a-model)

You can call the `isExpired()` and `isEternal()` methods on an expirable model instance. For example:

```
if ($subscription->isExpired()) {
    $user->notify(new RenewalProposal($subscription));
}
```

### Purge expired records

[](#purge-expired-records)

This package comes with a command to delete expired records from the database:

```
php artisan expirable:purge

```

You have two ways to indicate which models should be purged:

- add their class to the `purge` array of the configuration file:

```
    'purge' => [
        \App\Models\Subscription::class,
    ],
```

- pass one or several classes in argument of the purge command:

```
php artisan expirable:purge "App\Models\Subscription" "App\Models\Coupon"

```

Models passed as arguments take precedence (the `purge` array in the configuration file will be ignored).

You can also specify a period of time to delete models expired since that given period, using the `since` option (the value of this option is passed to the [expiredSince query scope](#retrieving-expired-models-since)):

```
php artisan expirable:purge "App\Models\Subscription" --since="2 months"

```

By default, this command will use force deletion to purge the models. If needed, you can specify the `mode` option with the value `soft` to perform a soft delete:

```
php artisan expirable:purge --mode=soft

```

⚠️ Be aware that if you try to purge models depending on tables with foreign key constraints using the default `hard` mode, you will have to specify the desired action for the "on delete" property of the constraint (for example using the onDelete('cascade'), cascadeOnDelete() or nullOnDelete() modifiers on the foreign key in your migrations: ) or purge the child models first (ordering the command arguments or the array in the config file to start with the children) to avoid SQL errors.

License
-------

[](#license)

Open source, licensed under the [MIT license](LICENSE).

###  Health Score

62

—

FairBetter than 99% of packages

Maintenance86

Actively maintained with recent releases

Popularity42

Moderate usage in the ecosystem

Community22

Small or concentrated contributor base

Maturity81

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 93% 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 ~103 days

Recently: every ~185 days

Total

24

Last Release

70d ago

Major Versions

v0.1 → v1.02019-08-27

v1.6 → v2.02023-03-07

v1.7 → v2.12023-07-14

v1.8 → v2.32024-02-28

v1.x-dev → v2.42025-03-01

PHP version history (5 changes)v0.1PHP ^7.1.3

v1.3PHP ^7.2.5

v1.4PHP ^7.2.5|^8.0

v2.0PHP ^8.1

v2.4PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/9f22fbac3be34391e07c95c8400fa6ffca40fcec6792bbd5efb1cfb12050a3f8?d=identicon)[alajusticia](/maintainers/alajusticia)

---

Top Contributors

[![alajusticia](https://avatars.githubusercontent.com/u/17909018?v=4)](https://github.com/alajusticia "alajusticia (40 commits)")[![alebejarano](https://avatars.githubusercontent.com/u/74239211?v=4)](https://github.com/alebejarano "alebejarano (1 commits)")[![sync667](https://avatars.githubusercontent.com/u/1151335?v=4)](https://github.com/sync667 "sync667 (1 commits)")[![zmorris](https://avatars.githubusercontent.com/u/1406101?v=4)](https://github.com/zmorris "zmorris (1 commits)")

---

Tags

eloquentlaravelsoft-delete

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/alajusticia-laravel-expirable/health.svg)

```
[![Health](https://phpackages.com/badges/alajusticia-laravel-expirable/health.svg)](https://phpackages.com/packages/alajusticia-laravel-expirable)
```

###  Alternatives

[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[ryangjchandler/orbit

A flat-file database driver for Eloquent.

922256.2k5](/packages/ryangjchandler-orbit)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

423715.4k1](/packages/clickbar-laravel-magellan)[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)[jerome/filterable

Streamline dynamic Eloquent query filtering with seamless API request integration and advanced caching strategies.

19226.1k](/packages/jerome-filterable)[baril/bonsai

An implementation of the Closure Tables pattern for Eloquent.

3593.5k](/packages/baril-bonsai)

PHPackages © 2026

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