Content lifecycle hooks
Action hooks fired when content entries are created, updated, or deleted — pages, posts, and custom content types.
These action hooks fire at key points in the content entry lifecycle. All hooks fire for pages, posts, and every custom content type — the Content model and its type are identical regardless of which edit screen the user came from.
contensio/content/created
Type: Action
Since: 1.4.0
Source: ContentController — storePage, storePost, storeContent
Fired after a new content entry has been saved to the database and all translations, term associations, and field values have been written.
Arguments
| # | Name | Type | Description |
|---|---|---|---|
| 1 | $content |
Content |
The newly created entry, with translations, author, terms, and featuredImage eager-loaded. |
Example
use Contensio\Models\Content;
add_action('contensio/content/created', function (Content $content) {
// Notify an external search index
SearchIndex::push([
'id' => $content->id,
'title' => $content->translations->first()?->title,
'status' => $content->status,
]);
});
Notes
- The entry's
statusmay bedraft,published, orscheduled— check$content->statusif your callback should only run for published entries. - Fires after all database writes complete. Safe to read related data without extra queries.
- If the callback throws, the exception is reported and the save still completes successfully.
contensio/content/updated
Type: Action
Since: 1.4.0
Source: ContentController — updatePage, updatePost, updateContent
Fired after an existing content entry has been updated — status, translations, term associations, and field values are all written before this hook fires.
Arguments
| # | Name | Type | Description |
|---|---|---|---|
| 1 | $content |
Content |
The updated entry, reflecting the new state after the save. |
Example
add_action('contensio/content/updated', function (Content $content) {
Cache::forget("content:{$content->id}");
});
Notes
- If the status changed during this update,
contensio/content/status-changedfires immediately after this hook. - The
$contentmodel reflects the new state —$content->statusis the status after the save.
contensio/content/status-changed
Type: Action
Since: 1.4.0
Source: ContentController — updatePage, updatePost, updateContent
Fired when a content entry's status transitions during a save. Does not fire on create — only on update when the old and new status differ.
Arguments
| # | Name | Type | Description |
|---|---|---|---|
| 1 | $content |
Content |
The entry after the status change. |
| 2 | $from |
string |
The previous status (draft, published, scheduled). |
| 3 | $to |
string |
The new status (draft, published, scheduled). |
Example
add_action('contensio/content/status-changed', function (Content $content, string $from, string $to) {
if ($to === 'published') {
Slack::notify("Published: {$content->translations->first()?->title}");
}
if ($from === 'published' && $to === 'draft') {
SearchIndex::remove($content->id);
}
});
Common transitions
$from |
$to |
Meaning |
|---|---|---|
draft |
published |
User clicked Publish |
draft |
scheduled |
User set a future publish date |
scheduled |
published |
Scheduled publish time arrived (fired by contensio:publish-scheduled) |
published |
draft |
User unpublished |
published |
scheduled |
User changed from published to scheduled |
Notes
- Fires after
contensio/content/updatedon the same request. - Only fires when
$from !== $to. If the user saves without changing status, this hook does not fire.
contensio/content/published
Type: Action
Since: 1.5.0
Source: PublishScheduledContent artisan command
Fired when a scheduled entry goes live automatically via the contensio:publish-scheduled scheduler command. This is a distinct hook from contensio/content/status-changed and is specifically for automation triggered by the scheduler, not by a user action.
Arguments
| # | Name | Type | Description |
|---|---|---|---|
| 1 | $content |
Content |
The entry that just went live. Status is published when this hook fires. |
Example
use Contensio\Models\Content;
add_action('contensio/content/published', function (Content $content) {
// Auto-post to social media when scheduled content goes live
Twitter::tweet(
"New post: {$content->translations->first()?->title} — " .
route('contensio.post', $content->slug)
);
// Ping search engines
Http::get("https://www.google.com/ping?sitemap=" . route('contensio.sitemap'));
});
Notes
- Also fires
contensio/content/updatedandcontensio/content/status-changed(scheduled→published) on the same entry in the same command run —publishedfires first. - Does not fire when an admin manually changes a scheduled post to published via the edit screen; use
contensio/content/status-changedfor that case. - The command is designed to run every minute via the Laravel scheduler. Ensure it is registered in your
routes/console.phporKernel.php.
contensio/content/deleting
Type: Action
Since: 1.4.0
Source: ContentController — destroyPage, destroyPost, destroyContent
Fired before the entry is deleted. The record is still in the database when this hook runs — safe to read all relations.
Arguments
| # | Name | Type | Description |
|---|---|---|---|
| 1 | $content |
Content |
The entry about to be deleted, with relations available. |
Example
add_action('contensio/content/deleting', function (Content $content) {
// Remove from search index while we still have the data
SearchIndex::remove($content->id);
// Clean up any plugin-owned related records
MyPluginMeta::where('content_id', $content->id)->delete();
});
Notes
- Use this hook (not
contensio/content/deleted) whenever you need data from the entry — title, relations, translations — because after deletion those are gone. - The database delete happens immediately after this hook returns.
contensio/content/deleted
Type: Action
Since: 1.4.0
Source: ContentController — destroyPage, destroyPost, destroyContent
Fired after the entry has been deleted from the database. Only the ID and type string are available — the record no longer exists.
Arguments
| # | Name | Type | Description |
|---|---|---|---|
| 1 | $id |
int |
The ID of the deleted entry. |
| 2 | $type |
string |
The content type name: page, post, or the custom type slug. |
Example
add_action('contensio/content/deleted', function (int $id, string $type) {
AuditLog::record("content.{$type}.deleted", $id);
});
Notes
- The database record is already gone. Do not attempt to fetch
Content::find($id)— it will returnnull. - If you need the entry's data (title, translations, etc.) use
contensio/content/deletinginstead.