Content event triggers
Content event functions run your run(payload) code after a content item is created or updated, and immediately before one is deleted. They run in the background, so editors and API clients never wait for them. Use them to send notifications, call other services or keep related data in step. This page covers when the events fire, the payload you receive, and what happens when something goes wrong.
A working example
Create a function with the trigger Content item created, mark it active, and save this code. It emails you when a comments item is created.
// Trigger: Content item created. Emails you when a "comments" item is created.
function escapeHtml(s) {
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
}
function run(payload) {
if (payload.ContentType.DeveloperName !== "comments") {
return; // this trigger fires for every content type
}
var comment = payload.PublishedContent; // raw stored values: strings, numbers, booleans
var message = EmailMessage.From(
"New comment",
"<p>" + escapeHtml(comment.body) + "</p><p>Item id: " + payload.Id + "</p>",
"[email protected]",
CurrentOrganization.SmtpDefaultFromAddress,
CurrentOrganization.SmtpDefaultFromName
);
Emailer.SendEmail(message);
}
The function has to check the content type itself. Raytha runs every active function with this trigger for every content item that is created, whatever its type.
When each event fires
| Trigger | Fires after | When it runs |
|---|---|---|
content_item_created | A content item is created in the admin, through the REST API, in a batch, or by API_V1.CreateContentItem. | After the item is saved. |
content_item_updated | An item's content is saved, its settings (template or route path) are saved, or its template is reassigned in bulk. | After the change is saved. |
content_item_deleted | An item is moved to the trash, singly or in bulk. | Immediately before the delete is saved, so the item and its template still exist. |
No event fires for unpublish, discard draft, set as home page, restore from the trash, or revert. Saving as a draft still counts as an update.
Payload
payload is the content item as a plain JavaScript object. Its keys are PascalCase, unlike the REST API, and ids are strings. This is an abridged example:
{
"Id": "1vVCcIYeeE-dMQjbYGq_ng",
"IsPublished": true,
"IsDraft": false,
"ContentTypeId": "2nWDdJZffF-eNRkcHhr_oh",
"ContentType": { "DeveloperName": "comments", "LabelSingular": "Comment", "LabelPlural": "Comments" },
"WebTemplateId": "3oXEeKaggG-fOSldIis_pi",
"RouteId": "4pYFfLbhhH-gPTmeJjt_qj",
"RoutePath": "comments/1vvccciyeeedmqjbygqng",
"PrimaryField": "",
"PublishedContent": { "title": "Great post", "body": "Thanks for <writing> this" },
"DraftContent": { "title": "Great post", "body": "Thanks for <writing> this" },
"CreatorUser": { "Id": "5qZGgMciiI-hQUnfKku_rk", "FirstName": "Ada", "LastName": "Lovelace", "EmailAddress": "[email protected]", "FullName": "Ada Lovelace" },
"CreationTime": "2026-10-02T16:58:50.394254",
"LastModifierUser": null,
"LastModificationTime": null
}
| Key | Meaning |
|---|---|
Id, ContentTypeId, WebTemplateId, RouteId | Ids, as strings. |
ContentType | The content type: DeveloperName, LabelSingular, LabelPlural, its fields and more. |
WebTemplate | The template object, including its source. Large, and rarely needed. |
IsPublished, IsDraft | Publish state. |
RoutePath | The item's public path, without a leading slash. |
PublishedContent, DraftContent | The stored field values keyed by field developer name. |
CreatorUser, LastModifierUser | Who created and last changed the item, or null. |
CreationTime, LastModificationTime | UTC timestamps. |
PrimaryField | The event path does not fill it in, so expect an empty string. Read the title from PublishedContent instead. |
PublishedContent holds the raw stored values, such as {"title": "Hello", "rank": 5}. This differs from API_V1 and the REST API, which wrap each value as {value, text, hasValue}. Dates are ISO strings, multiple select values are arrays, and a relationship is the related item's id.
Background execution
- Raytha stores a background task for each matching function. A worker picks it up within a second or so. The number of workers is
NUM_BACKGROUND_WORKERS, 4 by default. - The function runs
run(payload). Its return value is ignored. - The function shares the
RAYTHA_FUNCTIONS_MAX_ACTIVEpool with HTTP functions. It waits up toRAYTHA_FUNCTIONS_QUEUE_TIMEOUTfor a slot and is stopped afterRAYTHA_FUNCTIONS_TIMEOUT. - A failure does not affect the save that caused it. Raytha records
Error running Raytha Functionand the error on the background task, and does not retry. - There is no web request.
CurrentUseris not authenticated, and you cannot return anything to anyone. UseCurrentOrganization,EmailerandHttpClientas normal.
If you need confirmation that a handler ran, write a result somewhere you can read: send an email, call a service, or create a content item.
Loops
Raytha has no recursion guard. A function on content_item_updated that edits an item through API_V1.EditContentItem raises content_item_updated again, which runs the function again. Two functions can also trigger each other through different content types. If you write back to content, compare the new value with the stored one first, and skip the write when nothing would change. Prefer writes that go to a different content type than the one that triggered you, and check the content type at the top of the function.
Gotchas
- Filter by content type. The trigger fires for every type. Start the function with
if (payload.ContentType.DeveloperName !== "...") return;. - Calls that look up an item by id may fail here.
GetContentItemById,EditContentItem,EditContentItemSettings,UnpublishContentItem,DeleteContentItemandGetContentItemswith a view id check the item against the current web request's route. In a background task there is no request, and from reading the source these calls are expected to throw a null reference error. That has not been run against a live site.CreateContentItemand list calls without a view id do not use the check. Test the call you need before you build on it. - Raw values. Do not expect
.Valueor.TextonPublishedContentfields in the payload. - Deleted is raised before the delete is saved, but your function runs later, when the item is already in the trash. Use the payload, not a lookup.
- Bulk operations queue one task per item. Deleting 500 items queues 500 runs. Keep the function cheap.
- Imports. Whether a CSV import raises created events was not verified. Do not depend on it.
Next steps
- Webhook trigger: send events to other services.
- Webhooks: the built-in signed and retried delivery feature.
- Built-in objects: what a function can call.
- Recipes: more examples.