Tokens
This Laravel package replaces tokens in user-entered text with data from your Eloquent models. It is useful for anything built from a template, such as email templates, notifications, PDF documents, or CMS-managed content, where an editor writes the copy and your application fills in the details.
Tokens are written in the form ##attribute##, and related model data can be reached with dot notation, e.g. ##category.name##.
Requirements
- PHP:
^8.3 - Laravel:
^11.0,^12.0or^13.0
Installation
Require the package via Composer.
composer require eighteen73/laravel-tokensThere is no config file to publish and no setup required.
Resolving the Token Manager
All of the work is done by TokenManager. You can construct it directly, resolve it from the container, or use the Tokens facade; they are equivalent.
use Eighteen73\LaravelTokens\Facades\Tokens;
use Eighteen73\LaravelTokens\TokenManager;
$manager = new TokenManager();
$manager = app(TokenManager::class);
Tokens::forModel($user)->replaceTokens('Hello ##name##.');WARNING
TokenManager is stateful: forModel(), maxDepth(), and withoutRelationships() all mutate the instance. Laravel facades cache the instance they resolve, so settings applied through the Tokens facade persist for the rest of the request.
If you've called maxDepth() or withoutRelationships(), use new TokenManager() or app(TokenManager::class) for subsequent work so you start from a clean instance. Note that forModel() swaps the model but leaves those two settings as they were.
The examples below use app(TokenManager::class).
Which Attributes Become Tokens
The TokenManager works out the available tokens from the model itself, so there's nothing to register.
- For a saved model instance, its loaded attributes are used.
- For a model class name (or an unsaved instance), the model's factory definition is used if one exists, otherwise the model's
$fillableattributes are used. - Any
$appendsaccessors are included. - Anything listed in the model's
$hiddenarray is excluded, so passwords and tokens won't leak into a token list. - Relations are included to a depth of two levels (see Relation Tokens).
Listing Available Tokens
Use plainTokens() to get an array of every token a model supports. This is handy for showing editors a reference list of what they can use in a template.
use Eighteen73\LaravelTokens\TokenManager;
$tokens = app(TokenManager::class)
->forModel(App\Models\User::class)
->plainTokens();
// ['##name##', '##email##', '##category.name##', '##posts.0.title##', ...]forModel() accepts either a class name or a model instance.
Replacing Tokens
Pass your text to replaceTokens() and any recognised tokens will be swapped for the model's values.
use App\Models\User;
use Eighteen73\LaravelTokens\TokenManager;
$user = User::find(1);
echo app(TokenManager::class)
->forModel($user)
->replaceTokens('Hello ##name##, your email address is ##email##.');
// Hello Ada, your email address is ada@example.com.Tokens that aren't recognised are left in the text untouched, as are tokens whose relation no longer exists. This means a typo in an editor's template won't break the output; it just shows up as an unreplaced token.
TIP
Any relations needed by the tokens in your text are loaded automatically via loadMissing(), so you don't need to eager load them yourself.
Relation Tokens
Related model data is accessed with dot notation. BelongsTo and HasOne relations are read directly.
$user->setRelation('category', new Category(['name' => 'Administrators']));
echo app(TokenManager::class)
->forModel($user)
->replaceTokens('Category: ##category.name##');
// Category: AdministratorsHasMany and BelongsToMany relations include a zero-based index to select which record you want.
echo app(TokenManager::class)
->forModel($user)
->replaceTokens('Your latest post is "##posts.0.title##".');
// Your latest post is "Hello World".Controlling Relation Depth
Relations are traversed two levels deep by default. You can change that, or turn relation tokens off entirely.
// Only follow relations one level deep
app(TokenManager::class)
->forModel(User::class)
->maxDepth(1)
->plainTokens();
// Ignore relations completely
app(TokenManager::class)
->forModel(User::class)
->withoutRelationships()
->plainTokens();Reducing the depth is worth doing on models with a lot of relations, as each extra level multiplies the number of tokens generated.
Custom Tokens
A model can provide tokens that don't map to an attribute (such as a formatted date, a signed URL, or a calculated total) by implementing the CustomTokens contract.
The interface has two methods: getCustomTokens() returns the token names, and replaceCustomToken() returns the value for a given name.
use Eighteen73\LaravelTokens\Contracts\CustomTokens;
use Illuminate\Database\Eloquent\Model;
class User extends Model implements CustomTokens
{
public function getCustomTokens(): array
{
return [
'full_name',
'reset_url',
];
}
public function replaceCustomToken(string $token): string
{
return match ($token) {
'full_name' => "{$this->first_name} {$this->last_name}",
'reset_url' => route('password.reset', $this),
};
}
}These are then used just like any other token, and appear in plainTokens() alongside the model's attributes.
echo app(TokenManager::class)
->forModel($user)
->replaceTokens('Hi ##full_name##, reset your password at ##reset_url##.');Custom tokens work on related models too, so a ##category.custom_token## will resolve if Category implements the contract.