PHPackages                             charlielangridge/playa - 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. charlielangridge/playa

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

charlielangridge/playa
======================

Create temporary cookie-backed players for Laravel applications.

v0.3.0(1mo ago)24187[1 PRs](https://github.com/charlielangridge/playa/pulls)MITPHPPHP ^8.4CI passing

Since May 8Pushed 1mo agoCompare

[ Source](https://github.com/charlielangridge/playa)[ Packagist](https://packagist.org/packages/charlielangridge/playa)[ Docs](https://github.com/charlielangridge/playa)[ GitHub Sponsors]()[ RSS](/packages/charlielangridge-playa/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (3)Dependencies (24)Versions (7)Used By (0)

Playa
=====

[](#playa)

Playa gives a Laravel app a lightweight `Player` model for visitors who are not necessarily logged in.

It is useful for QR-code journeys, event games, voting screens, kiosk flows, demos, and other places where you want to remember "this device came back" without making somebody create an account first.

The package stores the player in your database and keeps only the player's UUID in an HttpOnly cookie.

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

[](#installation)

Install the package with Composer:

```
composer require charlielangridge/playa
```

Publish and run the migration:

```
php artisan vendor:publish --tag="playa-migrations"
php artisan migrate
```

Publish the config if you want to change the cookie name, lifetime, table name, or user-linking behaviour:

```
php artisan vendor:publish --tag="playa-config"
```

Basic Usage
-----------

[](#basic-usage)

Add the `playa` middleware to any route that should have a temporary player.

```
use Illuminate\Support\Facades\Route;

Route::get('/join/{game}', JoinGameController::class)
    ->middleware(['web', 'playa'])
    ->name('games.join');
```

When a visitor opens that URL, Playa will:

- create a `playa_players` row if the device has no valid player cookie
- store the player's UUID in a cookie
- resolve the same player on later visits
- refresh `last_seen_at`
- refresh `expires_at` when renewal is enabled
- make the player available from the request and the facade

This works well for direct links and QR codes. The QR code can point at a normal application route such as:

```
https://example.com/join/summer-party

```

The route handles your application flow; Playa handles the device identity.

Accessing The Current Player
----------------------------

[](#accessing-the-current-player)

From a controller or route closure:

```
use Illuminate\Http\Request;

public function __invoke(Request $request)
{
    $player = $request->player();

    // $player is an instance of CharlieLangridge\Playa\Models\Player
}
```

From somewhere that does not receive the request:

```
use CharlieLangridge\Playa\Facades\Playa;

$player = Playa::player();
```

You can also resolve or create players directly:

```
use CharlieLangridge\Playa\Facades\Playa;

$player = Playa::findByUuid($uuid);

$player = Playa::create([
    'name' => 'Sam',
    'username' => 'sam-27',
]);
```

Storing Player Details
----------------------

[](#storing-player-details)

The `Player` model has first-class `name` and `username` columns, plus a `data` JSON column for application-specific details.

```
$player = request()->player();

$player->update([
    'name' => 'Sam',
    'username' => 'sam-27',
    'data' => [
        'team' => 'blue',
        'accepted_rules_at' => now()->toIso8601String(),
    ],
]);
```

Playa does not ship profile routes or forms. Build those in your application so validation, copy, and consent match the flow you are building.

Extending The Player Model
--------------------------

[](#extending-the-player-model)

If your application needs player-specific relationships, casts, scopes, or helpers, create an application model that extends Playa's model:

```
namespace App\Models;

use CharlieLangridge\Playa\Models\Player as PlayaPlayer;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Player extends PlayaPlayer
{
    public function team(): BelongsTo
    {
        return $this->belongsTo(Team::class);
    }
}
```

Then configure Playa to use it:

```
'player_model' => App\Models\Player::class,
```

The configured model must extend `CharlieLangridge\Playa\Models\Player`. Add any extra columns, such as `team_id`, in your application's own migrations.

Linking A Player To A User
--------------------------

[](#linking-a-player-to-a-user)

Players can be linked to your app's user model without becoming authenticated users themselves.

```
$player = request()->player();

$player->linkUser(auth()->user());
```

To remove the link:

```
$player->unlinkUser();
```

Automatic linking is disabled by default. If you want any authenticated request with the `playa` middleware to claim the current player, enable it in `config/playa.php`:

```
'auto_link_authenticated_user' => true,
```

Expiry And Renewal
------------------

[](#expiry-and-renewal)

By default, a player lasts for 30 days:

```
'lifetime_minutes' => 60 * 24 * 30,
```

Renewal is enabled by default:

```
'renew_on_visit' => true,
```

With renewal enabled, each visit through the `playa` middleware refreshes the database `expires_at` value and the cookie lifetime.

With renewal disabled, the player expires at the original `expires_at` time even if the device keeps visiting.

When a cookie points to an expired, missing, or invalid player, Playa creates a fresh player and sends a replacement cookie.

Pruning Expired Players
-----------------------

[](#pruning-expired-players)

Expired rows are not deleted automatically. Schedule the prune command in your application if you want to clean them up:

```
use Illuminate\Support\Facades\Schedule;

Schedule::command('playa:prune')->daily();
```

You can keep recently expired rows around for a grace period:

```
php artisan playa:prune --hours=24
```

Configuration
-------------

[](#configuration)

The published config looks like this:

```
return [
    'table_name' => 'playa_players',

    'player_model' => CharlieLangridge\Playa\Models\Player::class,

    'lifetime_minutes' => 60 * 24 * 30,

    'renew_on_visit' => true,

    'cookie' => [
        'name' => 'playa_player',
        'path' => '/',
        'domain' => null,
        'secure' => null,
        'http_only' => true,
        'same_site' => 'lax',
        'lifetime_minutes' => null,
    ],

    'user_model' => null,
    'user_id_type' => 'bigint',

    'auto_link_authenticated_user' => false,
];
```

### Table Name

[](#table-name)

The default table is `playa_players` to avoid colliding with application tables that already use `players`.

Change this before publishing/running the migration:

```
'table_name' => 'players',
```

### Cookie Options

[](#cookie-options)

The cookie stores the player UUID only. By default it is HttpOnly and SameSite Lax.

Set `secure` to `true` in production if your app always runs over HTTPS:

```
'cookie' => [
    'secure' => true,
],
```

If `cookie.lifetime_minutes` is `null`, Playa uses `lifetime_minutes` for the cookie as well.

### User Id Type

[](#user-id-type)

The migration supports the common Laravel user key types:

```
'user_id_type' => 'bigint', // bigint, uuid, ulid, or string
```

Set this before publishing/running the migration.

If `user_model` is `null`, Playa falls back to `config('auth.providers.users.model')`.

Events
------

[](#events)

Playa dispatches events for the main lifecycle points:

- `CharlieLangridge\Playa\Events\PlayerCreated`
- `CharlieLangridge\Playa\Events\PlayerResolved`
- `CharlieLangridge\Playa\Events\PlayerRenewed`
- `CharlieLangridge\Playa\Events\PlayerExpired`
- `CharlieLangridge\Playa\Events\PlayerLinkedToUser`

For example:

```
use CharlieLangridge\Playa\Events\PlayerCreated;
use Illuminate\Support\Facades\Event;

Event::listen(PlayerCreated::class, function (PlayerCreated $event) {
    // $event->player
});
```

Forgetting The Current Player
-----------------------------

[](#forgetting-the-current-player)

If your application needs to clear the device identity, call:

```
use CharlieLangridge\Playa\Facades\Playa;

Playa::forget();
```

When called during a request using the `playa` middleware, the response will clear the player cookie.

Troubleshooting
---------------

[](#troubleshooting)

### `$request->player()` is null

[](#request-player-is-null)

Make sure the route uses the `playa` middleware.

```
Route::get('/join/{game}', JoinGameController::class)
    ->middleware(['web', 'playa']);
```

### A new player is created on every visit

[](#a-new-player-is-created-on-every-visit)

Check that the browser is receiving and returning the configured cookie. Common causes are a mismatched cookie domain, a path that does not include the route being visited, or `secure` being enabled on a non-HTTPS local site.

### The cookie is visible but Laravel cannot resolve it

[](#the-cookie-is-visible-but-laravel-cannot-resolve-it)

Use the middleware on web routes so Laravel's cookie middleware can decrypt incoming cookies and encrypt outgoing ones.

### User linking is not happening

[](#user-linking-is-not-happening)

Manual linking is the default:

```
request()->player()->linkUser(auth()->user());
```

If you want automatic linking, enable `auto_link_authenticated_user` and make sure the request is already authenticated before the `playa` middleware runs.

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

Run static analysis:

```
composer analyse
```

Format the code:

```
composer format
```

Changelog
---------

[](#changelog)

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

Credits
-------

[](#credits)

- [Charlie Langridge](https://github.com/charlielangridge)

License
-------

[](#license)

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

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance91

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

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

Every ~14 days

Total

3

Last Release

49d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4c3198a248b9580414af6bd9a09b66c8307b409de14b5bbec32140974323474?d=identicon)[charlielangridge](/maintainers/charlielangridge)

---

Top Contributors

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

---

Tags

laravelCharlie Langridgeplaya

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/charlielangridge-playa/health.svg)

```
[![Health](https://phpackages.com/badges/charlielangridge-playa/health.svg)](https://phpackages.com/packages/charlielangridge-playa)
```

###  Alternatives

[spatie/laravel-pdf

Create PDFs in Laravel apps

1.0k4.8M48](/packages/spatie-laravel-pdf)[codewithdennis/filament-select-tree

The multi-level select field enables you to make single selections from a predefined list of options that are organized into multiple levels or depths.

330530.5k30](/packages/codewithdennis-filament-select-tree)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)

PHPackages © 2026

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