---
title: "Casts & encryption — Options for Laravel | Roundly"
description: "Cast values with any Laravel cast, a custom CastsAttributes or the backed-enum cast, and encrypt sensitive options at rest."
url: https://roundly-consulting.com/open-source/docs/options-for-laravel/casts-encryption
language: en
---

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

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

# Casts & encryption

castAs() decides how the stored value is read and written. It accepts any Laravel cast string, a custom CastsAttributes (class-string or instance), or a cast class-string with parameters:

```php
public function castAs(): string|CastsAttributes
{
    return 'boolean'; // integer, float, array, collection, immutable_datetime, …
}
```

Dates round-trip as Carbon instances:

```php
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use RoundlyConsulting\Options\BaseOption;

final class TrialEndsAtOption extends BaseOption
{
    public function key(): string
    {
        return 'trial-ends-at';
    }

    public function castAs(): string|CastsAttributes
    {
        return 'immutable_datetime';
    }
}

TrialEndsAtOption::for($team)->set(now()->toImmutable()->addDays(14));
TrialEndsAtOption::for($team)->value(); // CarbonImmutable
```

## Backed enums

EnumCast casts to and from a backed enum — string- or int-backed. Pass the enum class as the cast parameter:

```php
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use RoundlyConsulting\Options\BaseOption;
use RoundlyConsulting\Options\Casts\EnumCast;

enum Status: string
{
    case Active = 'active';
    case Paused = 'paused';
}

final class StatusOption extends BaseOption
{
    public function key(): string
    {
        return 'status';
    }

    public function castAs(): string|CastsAttributes
    {
        return EnumCast::class.':'.Status::class; // backed-enum cast (string- or int-backed)
    }
}

Options::set(StatusOption::class, Status::Paused);  // stores 'paused'
Options::get(StatusOption::class);                 // Status::Paused
```

It stores the enum’s backing value and returns the case. For an int-backed enum the numeric string the text column returns is coerced to an int first. set() accepts a case or its backing value (Status::Active or 'active'); anything else — a misspelt string, a case of another enum, an empty string — throws InvalidOptionPayload and stores nothing. On read, an empty stored value, or one that isn’t a case (a row written outside the package, say), reads as null instead of throwing. You can also return new EnumCast(Status::class) — but a cast instance can’t be encrypted and shows up as type custom in group definitions.

## Encryption

Return true from encrypted() to encrypt the value at rest. It’s serialized through its cast, encrypted with Laravel’s encrypter, and decrypted and cast back on read — so an encrypted integer still reads as an int:

```php
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use RoundlyConsulting\Options\BaseOption;

final class ApiTokenOption extends BaseOption
{
    public function key(): string
    {
        return 'api-token';
    }

    public function castAs(): string|CastsAttributes
    {
        return 'string'; // must be a string when encrypted
    }

    public function encrypted(): bool
    {
        return true;
    }
}

Options::set(ApiTokenOption::class, 'tok_example_123', $tenant); // ciphertext at rest
Options::get(ApiTokenOption::class, $tenant);                    // 'tok_example_123'
```

- castAs() must return a string when encrypted() is true — 'integer', 'boolean', 'collection', a cast class-string such as EnumCast::class.':'.Status::class, … A cast instance throws EncryptionNotSupported.
- Every cast round-trips: the inner cast serializes the value before encryption and rehydrates it after decryption, so integers, booleans, dates, collections and enums come back in their type.
- Encryption uses your APP\_KEY. Tampered ciphertext throws Laravel’s DecryptException on read.
- The in-request memo and the persistent cache only ever hold the ciphertext — a cache store never sees the plain value.
- Events, observers and group definitions see the plain value — mind what you log.

## Reads always return the cast type

set() stores the value the way the database would — the raw string its cast produces — and caches that, not your input. Every read casts it, including the one right after set(): setting 'active' on an enum option returns Status::Active, an integer option set to '5' returns 5, and a collection option set to an array returns a Collection. options:set, which passes a string (or decoded JSON with --json), therefore reads back in the option’s type straight away.

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