Guide 09 · Developer docs

Automation, jobs and integration

A job is a trigger, conditions and an ordered list of actions, set up under Customization › Orchestration in the admin; jobs were called hooks before the module was renamed, and the API's routes keep that name. What starts a job, what it can do, branches and loops, email, scheduled work, integrations with other systems, and every event and action.

L2–L3
Unit 02 · What starts a job

Entity events

An entity event starts a job when a record is created, updated or deleted, or when one of a few entities reports something of its own. When each one fires, what the run receives, what an update can see of the previous values, and the records whose changes start nothing.

Who this is for. Anyone wiring a job to content, commerce or member records. Afterwards you can pick the right entity and method, narrow the trigger, and detect what actually changed.

The methods

An Entity Event Trigger names an entity (pages, posts, products, orders, form submissions, people, the records of a custom entity type, and many more) and a method. Every entity has the first three; the others belong to particular entities or need a setting.

Method (admin label)Fires when
CreateRecord (Created)A record is saved for the first time, from the admin, the API or a front-end form
UpdateRecord (Updated)A saved record changes, once per save. Also when a scheduled publish or unpublish date passes, with no save
DeleteRecord (Deleted)A record is deleted
StartEditingRecord (Editing)Someone opens a record for editing in the admin, only when the Globals setting Log record editing start is on, and at most once per record every 10 seconds (Log record editing start delay)
CompletedForm submissions: the submission is completed, once, including the last step of a multi-step form
AbandonedCarts: a background check finds the cart abandoned
SendReceipt (Send Receipt)Invoices: an administrator uses Send receipt on the invoice in the admin
LoginUsers: someone signs in with a password or passkey through the platform's login, only when the Globals setting Log login event is on
ExportPDF (Export PDF)Books: a PDF export of the book is requested
BackInStock (Back in stock)Products and variants: the available stock rises above the threshold that shoppers' stock interests wait for

A trigger can be narrowed. Record limits it to one record (not offered for Created, which has no record yet). The parent field limits it to the children of one record: the submissions of one form, the posts of one blog, the pages under one page. Every listed trigger is in the job event reference.

When the job runs

An entity event never runs a job inside the save. The save puts the event on a queue; a background worker matches it against every published job with a trigger for that entity and method (and, if the job has a site, only for records of that site), and queues a run for each. The run starts a moment later and always runs in the background, whatever the job's Synchronous execution setting.

That delay has a consequence. event.getRecord() loads the record when a template first asks for it, so it shows the record as it is then, which after a quick second edit is not how it was at the event.

What the run receives

On eventReturns
getEntityName(), getMethod(), getRecordId()Which entity, which method, which record
getRecord()The record, unpublished or not; empty after a delete
getPreEventData()The values captured before the change, if the trigger asked for any (below)

What an update can see of the previous values

Nothing, unless the trigger asks. List the fields you need under the trigger's Pre-Event properties, offered for Updated and Deleted, in the API's properties form with the API's field names: {published, price}, {financialStatus}. The platform reads them just before the change and passes them as getPreEventData(). Without that list, the run sees only the record as it now is, with no earlier value to compare.

A delete is the other case that needs it. By the time the run starts the record is gone, so getRecord() is empty. getPreEventData() always holds its id (and parentRecordId for a record with a parent); anything else the job needs, such as an externalId to tell another system, must be listed as a Pre-Event property on the Deleted trigger.

Detecting that a record went live

There is no published event. A record published at once arrives as UpdateRecord with an empty published before and a date after. A record whose scheduled date passes also arrives as UpdateRecord, but nothing was saved: the snapshot is taken at that moment, so published is the same date before and after. A condition that only compares the two misses every scheduled go-live, and one that only checks for a date fires when an editor schedules a page, before it is live. With {published} as the Pre-Event property, this template condition carries on in both real cases and in neither false one:

Template code
{$record = $event->getRecord()}
{$before = $event->getPreEventData()}
{if $record && $record->isPublished()
    && (empty($before.published) || $record->getModified() < $record->getPublished())}
  {$result->setMatching(true)}
{/if}
{% set record = event.getRecord() %}
{% set before = event.getPreEventData() %}
{% if record and record.isPublished()
    and (before.published is empty or record.getModified() < record.getPublished()) %}
  {% do result.setMatching(true) %}
{% endif %}

isPublished() is true once the publish date has passed. The second test separates a scheduled go-live, where the record was last saved before its publish date, from a later edit of a live record, saved after it.

Changes that start nothing

  • Records written by a background job. Saves made by an asynchronous job, a loop element, or any other platform worker raise no entity events. A record an asynchronous job creates does not start another job's Created trigger. This is what keeps most jobs out of recursion, and it surprises people who expected a chain.
  • Events the platform raises itself are the exception: Abandoned, Back in stock and scheduled publishing fire whoever changed the record.
  • An unpublished job runs on none of its triggers.

Changes to some child records also raise UpdateRecord on the record they belong to, such as a product when its description changes, so a job on products sees those edits too.

Web events

Web events and schedules have their own page: Web events and schedules.

Chaining and recursion

One job starting another is on Chaining one job to another; a job starting itself, and the guard against it, on Guarding against recursion.

Choosing a trigger

The decision table, from the trigger you have in mind to the event that serves it, is on Choosing a trigger.

Go deeper

Was this page helpful?

Your answer goes to the docs team.