---
title: "Commands, events & scheduling — Money for Laravel | Roundly"
description: "Artisan commands for currencies and rates, the scheduled ECB refresh, queued refreshes, refreshing and pruning from code, and refresh events."
url: https://roundly-consulting.com/open-source/docs/money-for-laravel/commands
language: en
---

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

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

# Commands, events & scheduling

| Command | Purpose |
| --- | --- |
| `money:currencies {--iso} {--custom} {--code=*}` | List the registry: code, numeric code, exponent, symbol, name and origin. |
| `money:rates:refresh {source=ecb} {--feed=daily} {--from=} {--to=} {--queue}` | Fetch rates into the table. --feed is daily, recent or history; --queue dispatches a unique job; supports --isolated. |
| `money:rates:prune {--before=} {--keep-days=} {--include-manual} {--pretend}` | Delete old rates. One bound is required; manual rows stay unless --include-manual; --pretend only counts. |
| `money:convert {amount} {from} {to} {--date=} {--driver=}` | Convert an amount and print the rate, source, date and result. |

```bash
php artisan money:currencies --custom

php artisan money:rates:refresh                     # ECB daily feed into the rates table
php artisan money:rates:refresh ecb --feed=recent   # the last 90 days
php artisan money:rates:refresh ecb --feed=history --from=2020-01-01 --to=2020-12-31
php artisan money:rates:refresh --queue             # dispatch a unique queued job instead
php artisan money:rates:refresh --isolated          # keep overlapping runs out

php artisan money:rates:prune --keep-days=365 --pretend   # only count
php artisan money:rates:prune --before=2024-01-01 --include-manual

php artisan money:convert 19.99 EUR USD
php artisan money:convert 19.99 EUR USD --date=2026-09-01 --driver=database
```

## Scheduled refresh

With MONEY\_EXCHANGE\_SCHEDULE=true the provider schedules money:rates:refresh on exchange.refresh.cron — 30 16 \* \* 1-5 in Europe/Berlin, just after the ECB publishes around 16:00 CET — with withoutOverlapping() and onOneServer(). Your app’s scheduler must be running. It’s off by default.

## Queued refresh

```php
use RoundlyConsulting\Money\Enums\EcbFeed;
use RoundlyConsulting\Money\Facades\Exchange;
use RoundlyConsulting\Money\Jobs\RefreshExchangeRates;

Exchange::rates()->refreshLater(feed: EcbFeed::Recent);   // queue the refresh

// the same job, dispatched yourself: source, feed, ?from, ?to (Y-m-d)
dispatch(new RefreshExchangeRates('ecb', 'recent'));
```

refreshLater() dispatches the RefreshExchangeRates job, which is unique per source and feed for ten minutes and holds scalars only.

## Refreshing and pruning from code

```php
use RoundlyConsulting\Money\Enums\EcbFeed;
use RoundlyConsulting\Money\Facades\Exchange;

$result = Exchange::rates()->refresh('ecb', EcbFeed::Recent);   // RefreshResult
$result->stored;                   // rows written
$result->skippedManual;            // manual rows left untouched
$result->skippedUnknownCurrency;   // feed currencies the registry does not know
$result->latestDate;               // ?CarbonImmutable

$deleted = Exchange::rates()->prune(now()->subYear());          // int
```

A refresh defaults to the last 7 days (daily feed), 90 days (recent) or everything since 1999 (history). A failed refresh never deletes rows. Prune returns the number of rows deleted — or counted, when pretending. The actions behind these calls are under DI and actions.

## Events

```php
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Money\Events\ExchangeRatesRefreshed;
use RoundlyConsulting\Money\Events\ExchangeRatesRefreshFailed;

Event::listen(function (ExchangeRatesRefreshed $event) {
    logger()->info('Exchange rates refreshed', [
        'source' => $event->result->source,
        'stored' => $event->result->stored,
        'latest' => $event->result->latestDate?->toDateString(),
    ]);
});

Event::listen(function (ExchangeRatesRefreshFailed $event) {
    // alert your team: $event->source, $event->message — existing rows are untouched
});
```

- ExchangeRatesRefreshed — RefreshResult $result: source, stored, skippedManual, skippedUnknownCurrency, latestDate.
- ExchangeRatesRefreshFailed — string $source and string $message, fired before the exception is rethrown. Hook your alerting here.

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