Entries & Revisions

Entries are the actual content records inside a model. Every save creates an immutable revision so editors can audit, diff, and restore.

Entry routes

Entries are scoped under their content model's slug.

RouteMethodPurpose
/admin/content/{model:slug}/entriesGETList entries (search, filter, sort, paginate)
/admin/content/{model:slug}/entries/createGETNew entry form
/admin/content/{model:slug}/entriesPOSTCreate entry
/admin/content/{model:slug}/entries/{entry}GETEdit entry
/admin/content/{model:slug}/entries/{entry}PUTUpdate entry
/admin/content/{model:slug}/entries/{entry}DELETESoft delete
/admin/content/{model:slug}/singleGETShortcut for single-type models

Entry status

StatusMeaning
draftHidden from the public API.
publishedVisible to the public API.
scheduledAuto-publishes at scheduled_at via a scheduler job.
archivedHidden from listings but kept for reference.

Field data shape

Field values are stored in the data JSON column, keyed by field slug:

{
  "title":   "Hello world",
  "slug":    "hello-world",
  "excerpt": "First post.",
  "body":    "<p>Long form rich text…</p>",
  "tags":    ["intro", "release"],
  "hero_image":  "f3c2…media-uuid…",
  "related_posts": ["uuid-1", "uuid-2"]
}

Localization

When the model has is_localized = true, entries are scoped per locale. The unique constraint on (content_model_id, slug, locale) allows the same slug to exist across languages. Editors switch locale from the entry list header.

SEO subform

If has_seo is enabled on the model, the entry form renders an SEO panel writing to the seo JSON column:

{
  "title":        "Meta title",
  "description":  "Meta description, ~160 chars",
  "og_image":     "media-uuid",
  "no_index":     false,
  "canonical":    "https://example.com/posts/hello"
}

Revisions

Every store and update creates a content_revisions row containing a complete snapshot of the entry's data, seo, and metadata at that point in time.

RouteMethodPurpose
…/entries/{entry}/revisionsGETList revisions (newest first, with author + diff summary)
…/entries/{entry}/revisions/{revision}GETShow a single revision (full snapshot)
…/entries/{entry}/revisions/{revision}/restorePOSTRestore — applies the snapshot and creates a new revision representing the restore
How restore worksRestore does not destroy newer revisions. It replays the snapshot onto the entry and writes a fresh revision, so the timeline stays intact.

Activity logging

An observer writes a row to activity_logs on every create / update / delete, capturing the actor, IP, and a JSON diff of changed fields.

Validation

Per-field validation is computed dynamically from the model's content_fields + validation JSON. Required fields, unique fields, numeric ranges, and pattern matches are all rolled into Laravel rule strings on form submission.