PHPackages                             bernskiold/laravel-currency-converter - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. bernskiold/laravel-currency-converter

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

bernskiold/laravel-currency-converter
=====================================

A driver-based currency conversion package for Laravel with caching.

1.0.0(1mo ago)040↓71.4%[1 PRs](https://github.com/bernskiold/laravel-currency-converter/pulls)MITPHPPHP ^8.3CI passing

Since Jun 5Pushed 1mo agoCompare

[ Source](https://github.com/bernskiold/laravel-currency-converter)[ Packagist](https://packagist.org/packages/bernskiold/laravel-currency-converter)[ Docs](https://github.com/bernskiold/laravel-currency-converter)[ RSS](/packages/bernskiold-laravel-currency-converter/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (9)Versions (3)Used By (0)

Convert currencies in Laravel, the easy way
===========================================

[](#convert-currencies-in-laravel-the-easy-way)

[![Latest Version on Packagist](https://camo.githubusercontent.com/16beffc56e869df07b6b61ea0e1731dd9f8f0d1f2f9d2c5cef21b495db0a29f1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6265726e736b696f6c642f6c61726176656c2d63757272656e63792d636f6e7665727465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bernskiold/laravel-currency-converter)[![GitHub Tests Action Status](https://camo.githubusercontent.com/b9da5570c4e215d01250404ba0ef7e92838f47995f0ba40490390c69e12179f5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6265726e736b696f6c642f6c61726176656c2d63757272656e63792d636f6e7665727465722f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/bernskiold/laravel-currency-converter/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/42a77d8dd754c348eab19e012cc4a9470fc8ee52c8a0897092eda4fed8575ab0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6265726e736b696f6c642f6c61726176656c2d63757272656e63792d636f6e7665727465722f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/bernskiold/laravel-currency-converter/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/4025aba3f334525e2e70812abaa690e4771c1a4b490744ae5cffed67eac80d9f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6265726e736b696f6c642f6c61726176656c2d63757272656e63792d636f6e7665727465722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bernskiold/laravel-currency-converter)

Working with money in more than one currency shouldn't be painful. This package gives you a clean, expressive way to convert between currencies, look up exchange rates, and keep a base-currency copy of your model's amounts in sync — all backed by pluggable providers and sensible caching.

```
use Bernskiold\LaravelCurrencyConverter\Facades\CurrencyConverter;

CurrencyConverter::convert(100, 'USD', 'SEK'); // 1050.0
CurrencyConverter::rate('USD', 'SEK');         // 10.5
CurrencyConverter::toBase(100, 'USD');         // into your app's base currency
```

It ships with a free, keyless provider out of the box, so you can be up and running in a minute — and when you're ready for something else, swapping providers is a one-line change.

Why you'll like it
------------------

[](#why-youll-like-it)

- **Batteries included.** [Frankfurter](https://frankfurter.dev) (free, keyless, European Central Bank reference rates) works without any setup.
- **Driver based.** Frankfurter, exchangerate.host, and a fixed/static driver come built in — and adding your own takes a single method.
- **Cached by default.** Rates are cached per provider and currency pair (a day by default), so you're not hitting an API on every conversion.
- **Forgiving.** Every failure mode throws a single, catchable `CurrencyConversionException`, so you decide how to degrade — never the library.
- **Model friendly.** A small trait keeps a base-currency column in sync automatically, with display helpers for your views.

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

[](#installation)

You can install the package via Composer:

```
composer require bernskiold/laravel-currency-converter
```

That's it — the free Frankfurter driver is active by default. If you'd like to tweak the providers, caching, base currency, or number formatting, publish the config:

```
php artisan vendor:publish --tag=currency-converter-config
```

Usage
-----

[](#usage)

Reach for the facade anywhere in your app:

```
use Bernskiold\LaravelCurrencyConverter\Facades\CurrencyConverter;

// Convert a value (rounded to the configured number of decimals).
CurrencyConverter::convert(100, 'USD', 'SEK'); // 1050.0

// Just the rate, please.
CurrencyConverter::rate('USD', 'SEK'); // 10.5

// Convert into — or out of — your configured base currency.
CurrencyConverter::toBase(100, 'USD');   // USD -> base_currency
CurrencyConverter::fromBase(100, 'USD'); // base_currency -> USD

// Need a specific provider for a single call? Say so.
CurrencyConverter::convert(100, 'USD', 'SEK', driver: 'exchangerate_host');
```

Prefer dependency injection over facades? Resolve the class straight from the container — same methods, no facade required:

```
app(\Bernskiold\LaravelCurrencyConverter\CurrencyConverter::class)->convert(100, 'USD', 'SEK');
```

### Formatting amounts

[](#formatting-amounts)

When it's time to show a number to a human, `format()` uses your configured formatting (US conventions by default):

```
CurrencyConverter::format(1234.5);        // "1,234.50"
CurrencyConverter::format(1234.5, 'USD'); // "1,234.50 USD"
```

### Automatic currency conversion on your models

[](#automatic-currency-conversion-on-your-models)

Often you'll store an amount in whatever currency the record was created in, but you also want a copy of that amount in your reporting currency so totals and comparisons are easy. The `ConvertsCurrencies` trait keeps that copy in sync for you — every time the model is saved.

Add the trait and tell it which columns to convert with a `$currencyConversions` map. Each entry maps a **source column** (the amount in the record's own currency) to a **target column** (where the base-currency value should be stored):

```
use Bernskiold\LaravelCurrencyConverter\Concerns\ConvertsCurrencies;

class Expense extends Model
{
    use ConvertsCurrencies;

    protected static array $currencyConversions = [
        'amount' => 'amount_sek',
    ];
}
```

You can convert as many columns as you like — just add more entries to the map.

#### What your model needs

[](#what-your-model-needs)

The trait makes a few small assumptions:

- **A currency column.** It reads the record's currency from a `currency` attribute by default (an ISO code such as `USD`).
- **The source and target columns exist.** Both the amount column and its base-currency counterpart must be real database columns. A migration for the example above would look like:

    ```
    $table->string('currency', 3)->default('SEK');
    $table->decimal('amount', 12, 2)->nullable();
    $table->decimal('amount_sek', 12, 2)->nullable();
    ```

That's it — no other configuration is required on the model.

#### Using a different currency column

[](#using-a-different-currency-column)

If your currency lives somewhere other than a `currency` column, override `currencyColumn()`:

```
protected function currencyColumn(): string
{
    return 'currency_code';
}
```

Need the currency from somewhere that isn't a plain column — a relationship, say? Override `currencyCode()` instead, which is what the trait actually calls (and which is public, so it's handy in your own code too):

```
public function currencyCode(): ?string
{
    return $this->billingAccount->currency;
}
```

#### How it behaves

[](#how-it-behaves)

- On **create and update**, each target column is filled using `toBase()` (on update, only when the amount or currency actually changed).
- If the record is already in the **base currency**, the amount is copied across as-is — no API call.
- If a conversion ever **fails**, it's logged rather than thrown, so the save always goes through. You can fill in any gaps later:

    ```
    $expense->recalculateCurrencyConversions();
    ```

#### Display helpers

[](#display-helpers)

The trait also gives your views a couple of friendly helpers, both formatted with your configured [number formatting](#formatting-amounts):

```
$expense->amountWithCurrency('amount');    // "1,234.56 USD"
$expense->amountInBaseCurrency('amount');  // "12,962.88 SEK"
```

Choosing a provider
-------------------

[](#choosing-a-provider)

Set your default provider in `config/currency-converter.php` (or via the `CURRENCY_CONVERTER_DRIVER` environment variable):

DriverKey requiredNotes`frankfurter`NoECB mid-market reference rates, updated daily. The default.`exchangerate_api`OptionalBroad coverage. Uses your key if set (`EXCHANGERATE_API_KEY`), otherwise the free keyless endpoint.`exchangerate_host`YesSet `EXCHANGERATE_HOST_KEY`.`open_exchange_rates`YesSet `OPEN_EXCHANGE_RATES_APP_ID`. Free plan is USD-base only.`fixer`YesSet `FIXER_ACCESS_KEY`. Free plan is EUR-base only.`database`NoRead rates from a table you manage. See below.`fixed`NoStatic rates from config — great for tests.#### The database driver

[](#the-database-driver)

The `database` driver reads rates from a table you control — handy when you need pinned, auditable rates rather than live market data. Publish and run the migration:

```
php artisan vendor:publish --tag=currency-converter-migrations
php artisan migrate
```

This creates an `exchange_rates` table (`from_currency`, `to_currency`, `rate`). The table and column names are configurable under `currency-converter.drivers.database`.

A convenience `ExchangeRate` model is included for managing the rates — from a scheduled job, an importer, or by hand:

```
use Bernskiold\LaravelCurrencyConverter\Models\ExchangeRate;

ExchangeRate::setRate('USD', 'SEK', 10.42); // creates or updates the pair

ExchangeRate::forPair('USD', 'SEK')->value('rate'); // 10.42
```

The model reads its table, connection, and column names from the same config, so it stays in step with the driver.

### Bringing your own provider

[](#bringing-your-own-provider)

Need rates from somewhere we don't support yet? Write a class that implements the `ExchangeRateProvider` contract:

```
namespace App\CurrencyConverter;

use Bernskiold\LaravelCurrencyConverter\Contracts\ExchangeRateProvider;
use Illuminate\Support\Facades\Http;

class AcmeBankDriver implements ExchangeRateProvider
{
    public function getRate(string $from, string $to): float
    {
        return (float) Http::acmeBank()
            ->get("/rates/{$from}/{$to}")
            ->json('rate');
    }
}
```

Then register it — usually in a service provider's `boot()` method — and select it via config (`currency-converter.default`) or per call (`driver: 'acme-bank'`):

```
use Bernskiold\LaravelCurrencyConverter\Facades\CurrencyConverter;
use App\CurrencyConverter\AcmeBankDriver;

CurrencyConverter::extend('acme-bank', fn () => new AcmeBankDriver);
```

The closure receives the container, so feel free to resolve any dependencies your driver needs.

Testing
-------

[](#testing)

The easiest way to test code that converts currencies is to fake the converter. `CurrencyConverter::fake()` swaps in a fake that returns predictable rates and never touches the network — and records every conversion so you can assert against it:

```
use Bernskiold\LaravelCurrencyConverter\Facades\CurrencyConverter;

CurrencyConverter::fake(['USD' => ['SEK' => 10.0]]);

// ... exercise your code ...

CurrencyConverter::assertConverted('USD', 'SEK');
```

Any currency pair you don't define converts 1:1, so a bare `CurrencyConverter::fake()` is enough when you only care that conversion happened. The fake also drives the `ConvertsCurrencies` trait, so your models behave exactly as they would in production — without an HTTP call in sight.

A few assertions are available on the fake:

```
CurrencyConverter::assertConverted('USD', 'SEK');                 // a USD -> SEK conversion happened
CurrencyConverter::assertConverted();                            // any conversion happened
CurrencyConverter::assertConverted(fn ($value, $from, $to) => $value === 100.0);
CurrencyConverter::assertConvertedTimes(2, 'USD', 'SEK');
CurrencyConverter::assertNothingConverted();
```

Prefer to exercise the real conversion path? Reach for the `fixed` driver (or `Http::fake()` with the HTTP providers) to stay fast and offline:

```
config()->set('currency-converter.default', 'fixed');
config()->set('currency-converter.drivers.fixed.rates', ['USD' => ['SEK' => 10.0]]);

expect(CurrencyConverter::convert(100, 'USD', 'SEK'))->toBe(1000.0);
```

You can run the package's own test suite with:

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](SECURITY.md) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Erik Bernskiöld](https://bernskiold.com)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance91

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 80% 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

Unknown

Total

1

Last Release

49d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/8409819?v=4)[Bernskiold](/maintainers/bernskiold)[@bernskiold](https://github.com/bernskiold)

---

Top Contributors

[![ErikBernskiold](https://avatars.githubusercontent.com/u/1166728?v=4)](https://github.com/ErikBernskiold "ErikBernskiold (4 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

laravelmoneycurrencyconversionexchange ratesbernskiold

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/bernskiold-laravel-currency-converter/health.svg)

```
[![Health](https://phpackages.com/badges/bernskiold-laravel-currency-converter/health.svg)](https://phpackages.com/packages/bernskiold-laravel-currency-converter)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77922.3M186](/packages/laravel-mcp)[spatie/laravel-export

Create a static site bundle from a Laravel app

674146.0k6](/packages/spatie-laravel-export)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

813336.8k3](/packages/defstudio-telegraph)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

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