PHPackages                             goldnead/statamic-identity-contracts - 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. [PSR &amp; Standards](/categories/psr-standards)
4. /
5. goldnead/statamic-identity-contracts

ActiveLibrary[PSR &amp; Standards](/categories/psr-standards)

goldnead/statamic-identity-contracts
====================================

Identity foundation for Statamic addons: a stable Identity value object and resolver contracts so addons never depend on a concrete App\\Models\\User.

v1.1.0(today)003MITPHPPHP ^8.2CI passing

Since Jul 26Pushed todayCompare

[ Source](https://github.com/goldnead/statamic-identity-contracts)[ Packagist](https://packagist.org/packages/goldnead/statamic-identity-contracts)[ Docs](https://github.com/goldnead/statamic-identity-contracts)[ RSS](/packages/goldnead-statamic-identity-contracts/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (6)Versions (3)Used By (3)

Statamic Identity Contracts
===========================

[](#statamic-identity-contracts)

Identity foundation for the Statamic addon family. It answers one question in a stable way — **who did this?** — so that addons never have to depend on a concrete `App\Models\User`.

This package owns no data: no migrations, no models, no Control Panel screens. It ships a value object, four contracts and inert defaults.

This is a library, not an addon
-------------------------------

[](#this-is-a-library-not-an-addon)

Despite the name, there is nothing Statamic-specific in here. It is a plain Laravel package: it does not require `statamic/cms`, references no Statamic class, registers no Control Panel screen and adds nothing a site owner can see or click. It is not listed on the Statamic Marketplace and should not be.

Install it if you are **building** an addon or an application that needs to record or notify an actor without reaching for the host application's user model. If you are running a Statamic site, you will get this package pulled in as a dependency of something else, and you never need to think about it.

Requirements
------------

[](#requirements)

PHP8.2+ (8.3+ when running Laravel 13)Laravel12.x or 13.xStatamicnot required, and not usedLaravel 11 is **not** supported: every `laravel/framework` v11 release is covered by security advisories, so Composer declines to install the line under its default policy.

Why it exists
-------------

[](#why-it-exists)

Addon extraction has a recurring blocker: an addon needs to record or notify an actor, and reaches for the host application's user model. That single reference makes the addon unshippable. `Identity` breaks the dependency in both directions — the addon asks for an identity, the application decides what one is.

Install
-------

[](#install)

```
composer require goldnead/statamic-identity-contracts
```

Nothing to configure for the default behaviour. Publish the config if you need to change it:

```
php artisan vendor:publish --tag=identity-contracts-config
```

The `Identity` value object
---------------------------

[](#the-identity-value-object)

A readonly bag of scalars, safe to persist and to put on a queue:

FieldMeaning`type``user`, `contact`, `system`, `anonymous`, or an app-defined type`id`stringified identifier within that type`userId`join key into the host application's user table`contactUuid`join key into the CRM contact record`email`, `name`convenience copies, personal data`anonymousId`pseudonymous visitor id for pre-identification activity`meta`free-form, application-defined```
use Goldnead\IdentityContracts\Identity;

Identity::user(42, 'a@example.com', 'Adrian');
Identity::contact('c-uuid', 'a@example.com');
Identity::system('importer');
Identity::anonymous('anon-1');
```

Copies rather than mutation: `withContactUuid()`, `withEmail()`, `withAnonymousId()`, `withMeta()`.

`pseudonymised()` drops `email`, `name` and `meta` while keeping the join keys — this is what consumers call to honour retention rules without losing the ability to count what happened.

`toArray()` / `fromArray()` round-trip losslessly; the array keys are snake\_case and match the column names a persisting consumer will want.

Resolving
---------

[](#resolving)

```
use Goldnead\IdentityContracts\Facades\IdentityContext;

IdentityContext::current();                 // actor behind this execution context
IdentityContext::resolve($user);            // any subject → Identity
IdentityContext::actingAs($actor, fn () => …); // pin an actor for a job or import
```

`resolve()` accepts an `Identity`, anything implementing `ProvidesIdentity`, any `Authenticatable`, or an email string. Resolution order:

1. registered custom resolvers (last registered wins)
2. `ProvidesIdentity::toIdentity()`
3. `Authenticatable` → `Identity::user(...)`, enriched with the CRM join key
4. email string → contact lookup, else a contact-shaped identity without a uuid
5. fallback

**Unrecognised subjects never throw.** Identity is metadata; a ledger write must not fail because an actor could not be classified. The fallback is `Identity::anonymous()` in HTTP and `Identity::system()` in the console.

`current()` returns the `actingAs` identity if one is set, otherwise the authenticated user, otherwise the fallback. It never returns `null` — "nobody in particular" is itself an identity.

Extension points
----------------

[](#extension-points)

**`ProvidesIdentity`** — implement on your User model to control its representation completely:

```
class User extends Authenticatable implements ProvidesIdentity
{
    public function toIdentity(): Identity
    {
        return Identity::user($this->id, $this->email, $this->name, $this->contact_uuid);
    }
}
```

**`IdentityResolver`** — teach the manager about a subject type it has never seen:

```
IdentityContext::resolveUsing(fn ($subject) => $subject instanceof ApiClient
    ? Identity::system('api:'.$subject->handle)
    : null);
```

**`ContactLocator`** — bridges an email to a CRM contact. Default binding is a no-op, so any package may ask for a contact uuid without requiring a CRM to exist. Applications running LeadHub bind an implementation reading `leadhub_contacts`; that is the only place the join by email lives.

**`AnonymousIdResolver`** — supplies the pseudonymous visitor id. The bundled `SessionAnonymousIdResolver` stores a uuid in the existing session and deliberately sets **no cookie of its own**, so no additional consent surface is created. With `anonymous.persist` off it returns a one-way hash of the session id and writes nothing. With `anonymous.enabled` off it returns `null` forever.

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

[](#configuration)

```
'resolve_from_auth' => true,   // let current() fall back to the auth guard
'system_id' => 'system',       // actor id for schedulers, webhooks, workers
'anonymous' => [
    'enabled' => true,
    'persist' => true,
    'session_key' => 'identity_anonymous_id',
],
```

Headless applications that always pass the actor explicitly (API hubs, import pipelines) should set `resolve_from_auth` to `false`.

Privacy notes
-------------

[](#privacy-notes)

- Nothing is persisted by this package.
- An email address is never reused as an identifier — a contact without a uuid keeps `id` as `null`.
- `pseudonymised()` exists so consumers can retain behavioural records after a deletion request without keeping personal data.

Tests
-----

[](#tests)

```
composer install
composer test      # Pest
composer lint      # Pint, check only
composer analyse   # PHPStan level 8
```

Support
-------

[](#support)

Only the latest version is supported. Bugs and questions go to [GitHub issues](https://github.com/goldnead/statamic-identity-contracts/issues); security reports go to the private channel named in [SECURITY.md](SECURITY.md).

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~6 days

Total

2

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/85572d690277234a86834808cab169c4900922b4855fe9028426f8350dd74e97?d=identicon)[goldnead](/maintainers/goldnead)

---

Top Contributors

[![goldnead](https://avatars.githubusercontent.com/u/1313348?v=4)](https://github.com/goldnead "goldnead (9 commits)")

---

Tags

contractslaravelidentityfoundationstatamic

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/goldnead-statamic-identity-contracts/health.svg)

```
[![Health](https://phpackages.com/badges/goldnead-statamic-identity-contracts/health.svg)](https://phpackages.com/packages/goldnead-statamic-identity-contracts)
```

###  Alternatives

[statamic/cms

The Statamic CMS Core Package

4.9k3.8M1.1k](/packages/statamic-cms)[laravel/octane

Supercharge your Laravel application's performance.

4.0k28.5M252](/packages/laravel-octane)[unopim/unopim

UnoPim Laravel PIM

10.8k2.5k](/packages/unopim-unopim)[statamic/statamic

Statamic

829182.1k](/packages/statamic-statamic)[typicms/base

A modular multilingual CMS built with Laravel, enabling developers to manage structured content like pages, news, events, and more.

1.6k20.4k](/packages/typicms-base)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21327.3k4](/packages/ecotone-laravel)

PHPackages © 2026

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