---
title: "Configuration — Lifecycle for Laravel | Roundly"
description: "Every config key, default and env variable — key types, subjects, strict writes, actors, transactions, history, rollbacks and schedules."
url: https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel/configuration
language: en
---

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

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

# Configuration

Every key has a default, so publishing config/lifecycle.php is optional. The published file in full:

```php
return [
    'key_type' => env('LIFECYCLE_KEY_TYPE', 'bigint'),
    'actor_key_type' => env('LIFECYCLE_ACTOR_KEY_TYPE', 'bigint'),

    'models' => [
        'state' => LifecycleState::class,
        'transition' => LifecycleTransition::class,
        'schedule' => LifecycleSchedule::class,
    ],

    'resources' => [
        'actor' => TransitionActorResource::class,
    ],

    'subjects' => [],

    'strict_writes' => env('LIFECYCLE_STRICT_WRITES', true),

    'actor' => [
        'from_auth' => env('LIFECYCLE_ACTOR_FROM_AUTH', true),
        'guard' => env('LIFECYCLE_ACTOR_GUARD'),
    ],

    'transactions' => [
        'attempts' => 3,
        'mysql_read_committed' => env('LIFECYCLE_MYSQL_READ_COMMITTED', true),
    ],

    'history' => [
        'store_payload' => env('LIFECYCLE_HISTORY_STORE_PAYLOAD', true),
        'max_context_bytes' => 16384,
        'reason_max_length' => 1000,
        'purge_on_force_delete' => env('LIFECYCLE_HISTORY_PURGE_ON_FORCE_DELETE', true),
        'prune_after_days' => env('LIFECYCLE_HISTORY_PRUNE_AFTER_DAYS'),
    ],

    'rollback' => [
        'default_window' => env('LIFECYCLE_ROLLBACK_DEFAULT_WINDOW'),
        'max_steps' => 50,
    ],

    'schedules' => [
        'batch_size' => 500,
        'max_per_run' => 10000,
        'max_attempts' => 5,
        'retry_after' => '5 minutes',
        'queue' => [
            'enabled' => env('LIFECYCLE_QUEUE_SWEEPS', false),
            'connection' => env('LIFECYCLE_QUEUE_CONNECTION'),
            'name' => env('LIFECYCLE_QUEUE'),
        ],
        'prune_after_days' => env('LIFECYCLE_SCHEDULES_PRUNE_AFTER_DAYS', 30),
    ],

    'rate_limits' => [
        'prefix' => 'lifecycle',
    ],

    'graph' => [
        'default_format' => 'mermaid',
    ],
];
```

## Every key

| Key | Default | Env | Purpose |
| --- | --- | --- | --- |
| `key_type` | bigint | `LIFECYCLE_KEY_TYPE` | Id type of the subject columns the migrations create: bigint, uuid or ulid (case-insensitive). Set before migrating; any other value throws. |
| `actor_key_type` | bigint | `LIFECYCLE_ACTOR_KEY_TYPE` | Id type of the actor columns (who transitioned, froze, scheduled): bigint, uuid or ulid; any other value throws. |
| `models.state` | LifecycleState | — | Swap the state-record model for your subclass (it must extend LifecycleState). |
| `models.transition` | LifecycleTransition | — | Swap the history model for your subclass (it must extend LifecycleTransition). |
| `models.schedule` | LifecycleSchedule | — | Swap the schedule model for your subclass (it must extend LifecycleSchedule). |
| `resources.actor` | TransitionActorResource | — | Renders a history row’s actor as {id, type}, type being the actor model’s class basename. Swap it for your own JsonResource subclass, which receives the TransitionRecord; blank or null is the packaged resource. |
| `subjects` | \[\] | — | Your models with a lifecycle. lifecycle:validate without arguments checks every lifecycle of every listed model, including the columns its definition names. Models work without being listed. |
| `strict_writes` | true | `LIFECYCLE_STRICT_WRITES` | Saving a directly changed lifecycle attribute throws DirectStateWriteException. |
| `actor.from_auth` | true | `LIFECYCLE_ACTOR_FROM_AUTH` | With no by(), the authenticated user is the actor. |
| `actor.guard` | null | `LIFECYCLE_ACTOR_GUARD` | The auth guard for from\_auth (null = the default guard). |
| `transactions.attempts` | 3 | — | How often a transaction is retried after a deadlock (1–10) — only when the package opened the outermost transaction. |
| `transactions.mysql_read_committed` | true | `LIFECYCLE_MYSQL_READ_COMMITTED` | MySQL/MariaDB: a transaction that counts a quota runs at READ COMMITTED. Off: it keeps your isolation and the quota count takes locking reads. |
| `history.store_payload` | true | `LIFECYCLE_HISTORY_STORE_PAYLOAD` | Keep the validated payload (minus sensitive() keys) in the history row. |
| `history.max_context_bytes` | 16384 | — | JSON size cap of the stored context (1024–1048576); larger is refused as invalid\_payload. |
| `history.reason_max_length` | 1000 | — | Longer reasons are refused as reason\_too\_long — transitions, schedules and rollbacks; a longer freeze reason throws InvalidLifecycleUsageException (1–10000). |
| `history.purge_on_force_delete` | true | `LIFECYCLE_HISTORY_PURGE_ON_FORCE_DELETE` | Force-deleting a subject deletes its records, history and schedules. |
| `history.prune_after_days` | null | `LIFECYCLE_HISTORY_PRUNE_AFTER_DAYS` | lifecycle:prune default for history rows (1–36500; null or blank = never). Limits survive pruning; older rollback points and idempotency keys do not. |
| `rollback.default_window` | null | `LIFECYCLE_ROLLBACK_DEFAULT_WINDOW` | How long a transition stays reversible when it declares no window (“7 days”; null or blank = unlimited). |
| `rollback.max_steps` | 50 | — | Most rows one rollbackTo() may revert (1–1000). |
| `schedules.batch_size` | 500 | — | Rows per sweep batch (1–10000). |
| `schedules.max_per_run` | 10000 | — | Most schedules one sweep run handles (1–1000000). |
| `schedules.max_attempts` | 5 | — | Attempts before a retryable scheduled transition is marked failed (1–100). |
| `schedules.retry_after` | 5 minutes | — | Delay before a refused, errored or frozen scheduled transition is tried again. A blank value is not set → 5 minutes. |
| `schedules.queue.enabled` | false | `LIFECYCLE_QUEUE_SWEEPS` | The sweep dispatches one job per due schedule instead of running it inline. |
| `schedules.queue.connection` | null | `LIFECYCLE_QUEUE_CONNECTION` | Queue connection of those jobs. |
| `schedules.queue.name` | null | `LIFECYCLE_QUEUE` | Queue name of those jobs. |
| `schedules.prune_after_days` | 30 | `LIFECYCLE_SCHEDULES_PRUNE_AFTER_DAYS` | lifecycle:prune default for finished schedule rows (1–36500; null or blank = never). |
| `rate_limits.prefix` | lifecycle | — | Prefix of the rate-limiter keys of rateLimit() transitions; it counts towards the key’s 250-character limit. |
| `graph.default_format` | mermaid | — | Exactly mermaid or dot — the graph format when none is given. |

## Strict values

Every value is validated when it is read, so a typo never silently falls back to the default:

- A blank value (LIFECYCLE\_X= or whitespace only) is not set: the key’s default applies, and a nullable key (LIFECYCLE\_HISTORY\_PRUNE\_AFTER\_DAYS=, LIFECYCLE\_SCHEDULES\_PRUNE\_AFTER\_DAYS=, LIFECYCLE\_ROLLBACK\_DEFAULT\_WINDOW=) reads as unset (null).
- Booleans accept true/false/1/0/yes/no/on/off, case-insensitive (a blank value takes the default, so LIFECYCLE\_STRICT\_WRITES= keeps strict writes on); any other value throws InvalidLifecycleConfigurationException naming the key and the value.
- key\_type and actor\_key\_type accept only bigint, uuid or ulid (case-insensitive; blank reads as bigint); anything else throws the toolkit’s InvalidConfigurationException — not a LifecycleException, so catch (LifecycleException) does not cover it — during migrate and about.
- Numbers must be canonical integers: '5.5', '+5' and '1e3' throw. A number or interval outside its range throws InvalidLifecycleConfigurationException.
- graph.default\_format must be exactly mermaid or dot. A swapped model (models.\*) must be an existing class that extends the package model, else InvalidLifecycleConfigurationException — it never falls back to the package model.
- resources.actor must be an instantiable JsonResource subclass (blank or null is the packaged one), else InvalidLifecycleConfigurationException (notAResource) when the first history row with an actor renders.
- A value throws when something first reads it. php artisan about reads the key types, most booleans, the models and the subjects, so run it after every configuration change to surface a typo at once.

## Register your subjects

List your models with a lifecycle in subjects, so lifecycle:validate has something to check in CI. Models work without being listed; with nothing listed, lifecycle:validate --strict fails instead of passing vacuously:

```php
// config/lifecycle.php
'subjects' => [
    App\Models\Listing::class,
    App\Models\Order::class,
],
```

## Environment

Every env variable the config reads, with its default:

```bash
LIFECYCLE_KEY_TYPE=bigint
LIFECYCLE_ACTOR_KEY_TYPE=bigint
LIFECYCLE_STRICT_WRITES=true
LIFECYCLE_ACTOR_FROM_AUTH=true
LIFECYCLE_ACTOR_GUARD=
LIFECYCLE_MYSQL_READ_COMMITTED=true
LIFECYCLE_HISTORY_STORE_PAYLOAD=true
LIFECYCLE_HISTORY_PURGE_ON_FORCE_DELETE=true
LIFECYCLE_HISTORY_PRUNE_AFTER_DAYS=
LIFECYCLE_ROLLBACK_DEFAULT_WINDOW=
LIFECYCLE_QUEUE_SWEEPS=false
LIFECYCLE_QUEUE_CONNECTION=
LIFECYCLE_QUEUE=
LIFECYCLE_SCHEDULES_PRUNE_AFTER_DAYS=30
```

## 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/lifecycle-for-laravel.md)
