# Laravel Vouchers: redeemable codes bound to your Eloquent models

> Laravel Vouchers is a MIT licensed package from Beyond Code that attaches voucher codes to specific Eloquent models, so a user can redeem a code and gain access to one particular record. It handles code generation, expiry, arbitrary attached data, validation and the pivot table that records who redeemed what.

**beyondcode/laravel-vouchers** — Allow users to redeem vouchers that are bound to models.

- Repository: https://github.com/beyondcode/laravel-vouchers
- Stars: 706 · Forks: 100
- Language: PHP
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/beyondcode-laravel-vouchers

## The problem: giving one person access to one record

The package description is one sentence, and it is precise: allow users to redeem vouchers that are bound to models. The README expands on why you would want that. This package can associate vouchers with your Eloquent models, which might come in handy if you need to associate voucher codes with content stored in your Eloquent models. The worked example is a video course application, where a voucher gives a user access to one specific video course. That is the whole use case, and it is narrower than coupon systems you may be used to. There is no percentage discount, no minimum spend, no expiry window applied to a cart. A voucher is a token that points at a single model, and redeeming it records the association between a user and that model. The primary language is PHP, the licence is MIT, and the package is published on Packagist as beyondcode/laravel-vouchers. The default branch is master and the last push was on 2026-10-02, the same day as the 2.4.0 release tag.

## Two traits do the work, one on each side

The mechanism is two Eloquent traits. Any model that should be redeemable, in the sense of being what a voucher grants access to, gets the HasVouchers trait, and the model that represents the redeemer, normally your User, gets the CanRedeemVouchers trait. The README shows the user model using CanRedeemVouchers and the video course model using HasVouchers. The naming in the prose is slightly inconsistent, since the installation section refers to the trait as BeyondCode\Vouchers\Traits\HasVouchers for all models while the code samples import CanRedeemVouchers for the user, but the two names and their split are clear enough to work with. The CanRedeemVouchers side is what stores the voucher and user association in the database, in a pivot table that the config file names as user_voucher. The HasVouchers side is what gives the model the createVoucher and createVouchers factory methods. The Vouchers facade is the third entry point, and it is what the validation methods hang off.

## Installing the package and creating the vouchers table

Installation is a Composer require, and the package registers itself automatically, so there is no service provider to add by hand.

```bash
php artisan vendor:publish --provider="BeyondCode\Vouchers\VouchersServiceProvider" --tag="migrations"
```

```bash
php artisan migrate
```

The config file and the translation files are published the same way, with the tags config and translations. The config is where the shape of a code is defined: the table name vouchers, the pivot table user_voucher, the character set used for generation, which deliberately omits the letters that are easy to confuse when read aloud, a prefix and a suffix that default to null, a mask of four characters and four characters separated by a hyphen, and the separator itself. The user_model key points at the model that belongs to vouchers, and the README shows it set to App\User::class.

```bash
composer require beyondcode/laravel-vouchers
```

The database side needs two more steps. The migration has to be published first, and it is published with a tag rather than published wholesale, which keeps the other publishable assets separate.

## Creating vouchers, with data and with an expiry

Vouchers can be created from the model, from the facade, or one at a time. On the model, createVoucher returns a single Voucher instance and createVouchers returns an array of them, the count being the first argument. The facade form is Vouchers::create($videoCourse, 5), and the README notes that the return value is an array containing all generated Voucher models. Every Voucher has a code property holding the generated code, formatted according to the mask, prefix, suffix and separator in the config, so the example in the config comments shows a generated code shaped like foo-AGXF-1NH8. Two optional arguments extend this. The second is an array of arbitrary data attached to the voucher, which the README motivates with a personal message from whoever created it, and it is read back through the voucher instance with a data accessor. The third is a Carbon instance used as an expiry date, after which the code can no longer be redeemed.

## Redeeming, and the event your business logic should hang on

Redemption is one method on the user model. redeemCode takes the string a user typed and returns the voucher model if the code is valid, while redeemVoucher takes an existing Voucher instance, which is the form to use when you already have the record. The README is explicit about the intended pattern: after a user successfully redeems a voucher, the package fires a BeyondCode\Vouchers\Events\VoucherRedeemed event containing the user instance and the voucher instance, and you should listen for that event to perform the business logic of your application. That is a deliberate separation. The package records the redemption, and your application decides what the redemption means, whether that is granting access to a course, setting a subscription end date or enabling a feature. From the redeemed voucher you can also reach the associated model through the Voucher's model relation.

## Validation and the exceptions you have to catch

Before you let a user type a code, the facade offers isValidCode and isValidVoucher, both returning a boolean, so a form can check a code without an exception. When you go straight to redeeming, the package throws instead, and the README documents the two cases. If a user tries to redeem an invalid code, the package throws BeyondCode\Vouchers\Exceptions\VoucherIsInvalid. The second case is a voucher that has already been redeemed. These are the exceptions you will want to catch and react to in your application, and the README says so in as many words. The package also ships translations that you can publish for further customisation, and the README shows that they are reachable through the usual key path, for example the vouchers::validation.code_invalid key, so you can localise the message a user sees when a code does not work.

## Where the package stops, and the alternative to weigh

The boundary is the single model binding. A voucher here grants access to one record of one model type, and the model type is fixed when the voucher is created. That is the wrong shape if you want a discount code applied to a basket, a coupon that can be used a fixed number of times across a category, or a promotion that selects a subset of rows at checkout. In those cases the design to compare against is a plain coupons table with a rules engine on top, where the redemption records a discount rather than a model reference, and where the same code can be redeemed against many models. The difference in approach is the point: Laravel Vouchers uses the voucher as a pointer and lets the relation carry the meaning, while a coupon system uses the voucher as a value and lets the rules engine decide what it applies to. The trade-off in the other direction is real too, and it favours this package. Because the voucher is a first class model with its own table, its own code format, expiry, attached data and a redemption event, the access-granting case is handled without you writing any of that scaffolding, and the database side is one published migration and one pivot table.

## Conclusion

Adopt Laravel Vouchers when your application needs to hand out access to individual database records, for example a paid course or a subscription period, and you want the voucher to be a first class model with its own code, expiry and data rather than a string in a column. Do not adopt it if you need multi-use coupons, a redemption percentage, or codes that apply to a whole catalogue, because the package is built around one voucher pointing at one model. Before you wire it in, publish the migration and the config, confirm the characters and mask in the config match the format your support team will read out, and decide which model carries the HasVouchers trait, since that model type is fixed at redemption time.

## FAQ

### What does Laravel Vouchers do?

The package associates vouchers with your Eloquent models, so a voucher code gives a user access to one specific record. The README gives the example of an application that sells online video courses, where a voucher grants access to one particular video course.

### How do I install Laravel Vouchers and create the vouchers table?

Run composer require beyondcode/laravel-vouchers, publish the migration with the migrations tag, then run php artisan migrate. The config file and the translation files are published separately with the config and translations tags.

### Which traits do I need to add to my models?

Models that a voucher should grant access to get the HasVouchers trait, and the model of the person redeeming, usually your user model, gets the CanRedeemVouchers trait. The README notes that the package then takes care of storing the voucher and user association in the database.

### How do I know when a voucher was redeemed?

After a successful redemption the package fires a BeyondCode\Vouchers\Events\VoucherRedeemed event that contains the user instance and the voucher instance. The README says you should listen to this event to perform the business logic of your application when a user redeems a voucher.

### Can a voucher expire and carry extra data?

Yes. The createVouchers method accepts a Carbon instance as a third parameter, and a user cannot redeem the code after that date. The second parameter takes an array of arbitrary data, such as a personal message from the creator, which is read back from the voucher instance with a data accessor.

## Sources

- [beyondcode/laravel-vouchers on GitHub](https://github.com/beyondcode/laravel-vouchers)
- [Issues](https://github.com/beyondcode/laravel-vouchers/issues)
- [License: MIT](https://github.com/beyondcode/laravel-vouchers/blob/master/LICENSE)
- [README](https://github.com/beyondcode/laravel-vouchers/blob/master/README.md)
- [Releases](https://github.com/beyondcode/laravel-vouchers/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/beyondcode-laravel-vouchers
