NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Permissions for Laravel

Translatable descriptions

The description on both Role and Permission is a real translatable attribute, backed by translatable-for-laravel. It is stored as a per-locale JSON map (jsonb) and resolves to a plain string for the current app locale. There is nothing to wire — the trait is applied for you:

use Illuminate\Support\Facades\App;

$permission->update(['description' => ['en' => 'View users', 'sk' => 'Zobraziť používateľov']]);

App::setLocale('sk');
$permission->description;              // "Zobraziť používateľov"  (current locale)

App::setLocale('en');
$permission->description;              // "View users"

$permission->getTranslations('description'); // ['en' => 'View users', 'sk' => 'Zobraziť …']
$permission->getTranslation('description', 'sk'); // "Zobraziť používateľov"

Write a per-locale array to set several locales at once, or a bare string to set only the current locale:

App::setLocale('sk');

$role->description = 'Správca';   // a bare string writes the current locale only
$role->save();

$role->getTranslations('description'); // ['sk' => 'Správca']

$role->setTranslation('description', 'en', 'Administrator')->save();

toArray(), toJson() and API Resources emit the resolved locale string, matching property access — not the raw JSON map.

Fallback modes

When the current locale has no value, permissions.description_fallback (env PERMISSIONS_DESCRIPTION_FALLBACK) decides how far the read reaches:

ModeChainNotes
Fallbackcurrent locale → app fallback_locale → nullThe default. A description you have not translated for a locale never surfaces content from an unrelated language.
Nonecurrent locale → nullExact locale only.
Anycurrent locale → app fallback_locale → first available localeDescriptions never render blank, but an untranslated value can surface in another locale. Opt in deliberately.

Descriptions are developer- and admin-facing labels, not personal data, so Any is a reasonable opt-in — it is simply not the default, because silently reaching into an unrelated locale is a surprise:

# Opt into first-available fallback (descriptions never render blank):
PERMISSIONS_DESCRIPTION_FALLBACK=any

To set the mode for one model, declare $translatableFallbackMode on your own subclass and register it in permissions.models.role or permissions.models.permission. The config fills the property only when it is null, so an explicit per-model mode always wins over description_fallback:

use RoundlyConsulting\Permissions\Models\Role;
use RoundlyConsulting\Translatable\Enums\FallbackMode;

final class AppRole extends Role
{
    protected ?FallbackMode $translatableFallbackMode = FallbackMode::None;
}

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

By donating, you agree to our donation terms.

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.