---
title: "Lifecycle & status — Advertisements for Laravel | Roundly"
description: "Publish now or on a schedule, unpublish, expire, archive and soft-delete — each transition fires an event; status is recomputed on every read."
url: https://roundly-consulting.com/open-source/docs/advertisements-for-laravel/lifecycle
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Advertisements for Laravel](https://roundly-consulting.com/open-source/docs/advertisements-for-laravel.md)

# Lifecycle & status

Every transition is a facade call (or the matching action or model method) that saves the ad and dispatches an event:

```php
use RoundlyConsulting\Advertisements\Facades\Advertisements;

Advertisements::publish($ad);                     // live now → AdvertisementPublished
Advertisements::publish($ad, now()->addWeek());   // scheduled → AdvertisementPublished
Advertisements::unpublish($ad);                   // back to draft → AdvertisementUnpublished
Advertisements::expire($ad);                      // expired now → AdvertisementExpired
Advertisements::expire($ad, now()->addMonth());   // expires later → AdvertisementExpired
Advertisements::archive($ad);                     // → AdvertisementArchived
Advertisements::delete($ad);                      // soft delete → AdvertisementDeleted
```

## Status

Every advertisement has a status of type RoundlyConsulting\\Advertisements\\Enums\\AdvertisementStatus:

| Case | Value | Meaning |
| --- | --- | --- |
| `Draft` | `draft` | Created, not published. |
| `Scheduled` | `scheduled` | published\_at is in the future. |
| `Published` | `published` | Live now and not expired. |
| `Expired` | `expired` | expires\_at is in the past. |
| `Archived` | `archived` | Explicitly archived. |

Read the status through the status accessor (or the is\*() helpers and the query scopes). It’s recomputed on every read: an ad you publish() or archive() reports its new status on the same instance, and an ad crossing its published\_at or expires\_at reads as Published / Expired the moment the clock passes it — even in a long-lived worker and without a re-save.

```php
$ad->status;          // AdvertisementStatus::Draft
$ad->publish();
$ad->status;          // AdvertisementStatus::Published — same instance, no refresh needed
$ad->isPublished();   // true
```

```php
use RoundlyConsulting\Advertisements\Enums\AdvertisementStatus;

$ad->status;                                    // AdvertisementStatus::Published
$ad->status === AdvertisementStatus::Expired;   // true once expires_at passes — no re-save needed
$ad->status->label();                           // "Published"
```

## The status column

- The status column holds a snapshot: create(), update() and every lifecycle action write the status the dates give at that moment, and only Archived overrides the dates.
- Archiving sticks through update() and expire() — an archived ad gets the expiry date but stays archived. Only publish() and unpublish() take an ad out of the archive.
- Time moves on without rewriting the column, so a scheduled ad’s column still says scheduled after it goes live. Query by state with the scopes (published(), active(), expired(), …), which compare the dates, rather than with where('status', …); archived() is the one that reads the column.

## Timing

- publish($ad, $at) with a future instant stores Scheduled; the ad goes live by itself once published\_at passes — no job or command needed.
- expire($ad, $at) with a future instant only sets expires\_at; the ad reads as Expired once it passes. AdvertisementExpired fires at call time either way — time-based expiry itself fires no event.
- unpublish() clears published\_at and returns the ad to Draft.
- archive() takes the ad out of the published, active, scheduled, expired and draft scopes without deleting it.
- delete() is a soft delete that keeps the creatives for a restore; forceDelete() removes them too, and expired ads are removed for good by pruning (see Artisan commands).

## Convenience methods

Ask an ad about its state, or transition it directly — the model methods are sugar over the same manager, so they run the same actions, fire the same events and are recorded by Advertisements::fake():

```php
$ad->isPublished(); $ad->isActive(); $ad->isExpired(); $ad->isScheduled(); $ad->isArchived();

$ad->publish();                   // sugar for Advertisements::publish($ad) — same manager, same event, seen by the fake
$ad->publish(now()->addDay());    // schedule
$ad->expire(now()->addMonth());   // expire later
$ad->unpublish(); $ad->archive(); $ad->delete();
$ad->renderCreative('sidebar');   // sugar for Advertisements::render($ad, 'sidebar')
```

isActive() is true for a published ad whose expires\_at is empty or in the future. The model’s delete() routes through the manager and DeleteAdvertisement, so the soft delete and AdvertisementDeleted fire whichever way you call it.

## Enum helpers

AdvertisementStatus and AdvertisementEventType use the shared Helpers trait from enums-for-laravel — labels, select options and a validation rule without hand-rolled code:

```php
use RoundlyConsulting\Advertisements\Enums\AdvertisementEventType;
use RoundlyConsulting\Advertisements\Enums\AdvertisementStatus;

AdvertisementStatus::toArray();         // ['draft' => 'Draft', 'scheduled' => 'Scheduled', …]
AdvertisementStatus::options();         // {value, label, name} option DTOs for JS selects
AdvertisementStatus::validationRule();  // 'in:draft,scheduled,published,expired,archived'

AdvertisementEventType::Click->counterColumn();   // 'clicks_count'

$request->validate(['status' => ['required', AdvertisementStatus::validationRule()]]);
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/advertisements-for-laravel.md)
