# Configuration reference

> Every config key Skyline adds on top of the stock config/horizon.php.

Source: https://boring-observability.dev/skyline/docs/configuration
Section: Getting started — Skyline for Laravel documentation
Updated: 2026-07-13

---

Skyline reads the same `config/horizon.php` as Horizon. Every upstream option — `path`, `prefix`, `middleware`, `waits`, `silenced`, `environments` and the rest — behaves exactly as documented in the [Horizon documentation](https://laravel.com/docs/horizon). This page covers only the keys Skyline *adds*.

All of them have working defaults. A Horizon config file copied across unchanged is a valid Skyline config file.

## Reference

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `log_channel` | `HORIZON_LOG_CHANNEL` | `null` | Log channel that receives [job lifecycle events](https://boring-observability.dev/skyline/docs/job-lifecycle-logging). `null` uses the application default channel. |
| `worker_output` | `HORIZON_WORKER_OUTPUT` | `'cli'` | `'cli'` or `'json'`. See [JSON worker output](https://boring-observability.dev/skyline/docs/json-worker-output). |
| `attempt_exceptions` | — | `1` | How many previous-attempt failure reasons to retain per job. `0` disables recording. |
| `trim.delayed` | — | `10080` | Minutes to retain delayed-job tracking data (7 days). |
| `metrics.ema_alpha` | — | `0.05` | Smoothing factor for the runtime and wait-time moving averages. Between 0 and 1. |
| `metrics.snapshot_lock` | — | `300` | Seconds a `horizon:snapshot` lock is held, to de-duplicate concurrent snapshots. Not written to the published file. |
| `trends.interval` | — | `15` | Minutes per trend bucket, and the workload sampling cadence. |
| `trends.retention` | — | `24` | Hours of trend history to keep and display. |
| `environments.*.*.queueWeights` | — | `[]` | Per-supervisor map of queue name to weight. Only valid when `balance` is `false`. |

## log_channel

Routes Skyline's job lifecycle log lines to a dedicated channel, so a full end-to-end job trace does not drown your application log. Leave it `null` to use the default channel.

```php
'log_channel' => env('HORIZON_LOG_CHANNEL'),
```

Because each event is emitted at a level appropriate to its severity, the channel's own log level is the volume dial: point it at a channel with level `error` to see only failures, or `debug` for a full trace of every job. The full event list is on the [Job lifecycle logging](https://boring-observability.dev/skyline/docs/job-lifecycle-logging) page.

## worker_output

Each worker writes one line per job to its output. The default `'cli'` format is the human-readable table you see in the terminal; `'json'` emits one structured object per line for a log pipeline to ingest.

```php
'worker_output' => env('HORIZON_WORKER_OUTPUT', 'cli'),
```

> **Laravel 11+**
>
> JSON output relies on the framework's structured worker output, which exists on Laravel 11 and later. On earlier versions the setting is simply never consulted and output stays in CLI format — no error is raised.

## attempt_exceptions

When a job throws or times out but still has retries left, Skyline records why that attempt failed and shows the history under a **Previous Attempts** panel on the job's dashboard page. Because each entry can carry a full stack trace, the number retained per job is capped.

```php
'attempt_exceptions' => 1,
```

The default of `1` keeps only the most recent failure reason. Raise it to retain more history — the oldest entries beyond the limit are dropped — or set it to `0` to turn the feature off entirely.

## trim.delayed

Skyline indexes delayed jobs so the **Scheduled** and **Retries** views can show them with their real next-run time. That index is trimmed on the same principle as Horizon's other retention settings:

```php
'trim' => [
    'recent' => 60,
    'pending' => 60,
    'completed' => 60,
    'recent_failed' => 10080,
    'failed' => 10080,
    'monitored' => 10080,
    'delayed' => 10080,
],
```

`delayed` is the Skyline addition; the rest are upstream. The value is in minutes, and the default of `10080` is seven days. Trimming runs periodically from the `php artisan horizon` master process, so it requires no separate scheduled command.

## metrics

```php
'metrics' => [
    'trim_snapshots' => [
        'job' => 24,
        'queue' => 24,
    ],

    'ema_alpha' => 0.05,
],
```

`ema_alpha` is the smoothing factor of the exponential moving average behind Skyline's runtime and wait-time estimates. It must sit between 0 and 1. Lower values produce a stable average that shrugs off outliers; higher values react faster to a genuine change in job duration. The default of `0.05` is deliberately conservative — a single pathological job should not move the estimate much.

## trends

Configures the time-series behind the dashboard's workload, wait-time and failure trend charts.

```php
'trends' => [
    'interval' => 15,
    'retention' => 24,
],
```

`interval` is the size of each bucket in minutes, and doubles as the sampling cadence for the workload and wait series. `retention` is how many hours of history to keep and display. Together they set the number of points on the chart: the defaults give 96 buckets across 24 hours. Shortening the interval gives a finer chart at the cost of more Redis keys and a busier render.

> **Trends need the master process**
>
> Workload and wait samples are taken from the `php artisan horizon` master process loop, not from a scheduled command. Failure counts are recorded as jobs fail. If the master process is not running, no workload samples are recorded for that period — but nothing else breaks.

## queueWeights

A per-supervisor map that turns strict left-to-right queue priority into a proportional policy. It is only valid when that supervisor runs with `'balance' => false`.

```php
'supervisor-1' => [
    'connection' => 'redis',
    'queue' => ['high', 'default', 'low'],
    'balance' => false,
    'queueWeights' => [
        'high' => 3,
        'default' => 2,
        // 'low' is omitted, so it keeps the default weight of 1
    ],
],
```

See [Weighted queues](https://boring-observability.dev/skyline/docs/weighted-queues) for the semantics, the exception raised when it is combined with a balancing strategy, and the `block_for` notes.


## Common questions

### Do I need to change config/horizon.php to use Skyline?

No. Every key Skyline adds has a working default, and a Horizon config file copied across unchanged is a valid Skyline config file. The new keys are opt-in refinements, not requirements.

### What is ema_alpha in the Skyline metrics config?

It is the smoothing factor of the exponential moving average behind the runtime and expected-wait-time estimates, and it must lie between 0 and 1. Lower values weight history more heavily, so a single pathological job barely moves the estimate; higher values react faster to a genuine change in job duration. The default of 0.05 favours stability.
