aryehraber/statamic-uuid
A Statamic addon that adds UUID support to entries, automatically generating unique identifiers and providing a UUID fieldtype. Useful for stable references in APIs, imports, integrations, and content syncs.
aryehraber/statamic-uuid is a Statamic addon that registers a uuid fieldtype so you can attach stable, globally unique identifiers to entries (and other content types) without hand-rolling generation logic in every project.
Install
composer require aryehraber/statamic-uuid
Version v2.4.0 adds Statamic v6 support (the only change in this release). The addon auto-discovers via Statamic's addon manifest — no service provider registration needed. If you are on Statamic 4/5, v2.3.0 remains a valid pin; v2.4.0 is a safe drop-in for those versions too.
Where to look first
vendor/aryehraber/statamic-uuid/src/ — the fieldtype class is the whole API surface; it is small enough to read end-to-end in five minutes.config/statamic/uuid.php) — controls the default UUID version used when a field doesn't specify one.Publish config if you need to change the default version:
php artisan vendor:publish --tag=statamic-uuid-config
First use case
Add a UUID field to a collection blueprint so every entry carries an immutable external ID, then expose it in your API resource / JSON output. That gives downstream consumers (mobile apps, webhooks, third-party integrations, data warehouse extracts) a key that never changes even if the slug, title, or internal Statamic id semantics shift.
title: Posts
sections:
main:
fields:
- handle: uuid
field:
type: uuid
display: UUID
version: 4 # optional; falls back to config default
Blueprint-first, generate-on-save. The idiomatic use is a hidden or read-only uuid field on the blueprint. The fieldtype fills the value when the entry is saved, so you never call a generator manually. Keep it out of the publish form UI (visibility: hidden or a read-only display) so editors can't clobber it.
Cross-system identity mapping. Use the UUID as the join key between Statamic content and an external database, search index (Algolia/Meilisearch/Typesense), or CRM. Store the UUID rather than the entry id or slug in the external system — it survives slug renames, collection moves, and (with care) environment rebuilds.
API resources and Antlers output. Augment your API resource or Antlers template with the field:
// app/Http/Resources/PostResource.php
return [
'id' => $this->uuid, // external, stable
'title' => $this->title,
'slug' => $this->slug,
];
{{ uuid }}
Idempotency and cache keys. Because the value is stable per entry, it works well as a cache-key segment or an idempotency token for content-sync jobs (sync:post:{uuid}), so re-running an import doesn't duplicate rows.
Backfilling existing content. Entries created before the field existed will have null. Write a one-off Artisan command that iterates the collection, assigns a UUID via the same generator the fieldtype uses, and saves — do this once, guarded by whereNull('uuid'), rather than relying on editors to re-save every entry.
Version selection per field. If you need deterministic, reproducible IDs (e.g. derived from a known namespace for a fixed dataset), use v3/v5 and pass the namespace/name the fieldtype expects. For everything else — and for anything that must be unguessable — use v4.
Environment parity in tests. In Pest/PHPUnit, assert the field is populated after save and that it is a valid UUID; assert it does not change on subsequent saves. This catches accidental regeneration if you later add a custom save hook.
Generated on save, not on read. A field added to a blueprint does not retroactively populate existing entries. Expect null until each entry is saved (or your backfill command runs). Check this before shipping an API that assumes the key exists — add a whereNotNull guard or a fallback.
Changing version does not regenerate existing values. The option only affects future generation. Switching a field from v4 to v5 will leave a mixed set of values behind; if you need consistency, backfill deliberately.
Uniqueness is not enforced for you. The fieldtype generates values but does not add a database unique index or Statamic validation rule. If your integration genuinely requires uniqueness (it usually does), enforce it yourself — a unique index on the UUID column in your own projection table, or a validation rule — and be aware that low-probability collisions are a theoretical concern for v4 and a practical one for v3/v5 if you feed them identical deterministic inputs.
v3/v5 are deterministic, not random. Same namespace + name yields the same UUID. That is a feature for reproducible identifiers and a footgun if you assume every save produces a fresh value.
Never treat a UUID as a secret. v4 is unguessable-ish but is an identifier, not an authorization token. Don't use it in place of signed URLs or policy checks.
Multisite / localization. Decide early whether the UUID should be shared across sites in a multisite install or localized per site. If your integration expects one ID per piece of content, make the field non-localizable; otherwise you'll get a different ID per site and subtly break cross-site lookups.
Config caching in production. After publishing or editing the config, run php artisan config:clear (and re-cache) — a stale cached config is the most common reason a changed default version appears to be ignored.
Upgrading to v2.4.0. The release is purely additive compatibility for Statamic v6. If you're moving the app to Statamic 6, bump the addon and then smoke-test the fieldtype's save path on one collection — Statamic major versions occasionally shift fieldtype lifecycle hooks, so verify new entries get a value and existing entries keep theirs. No migration is implied by the changelog.
Extension point. If you outgrow the fieldtype (custom namespacing, prefixed IDs, ULID instead of UUID), the fieldtype class is the place to subclass or copy from; there's no event system to hook into, so a small custom fieldtype built on the same generator is usually cleaner than fighting the addon's defaults.
How can I help you explore Laravel packages today?