Server-side logic
Run your own code on a schedule, a webhook, or a save
An automation is a block of PHP you write and a trigger that fires it. It lives in the CMS — there is no worker service to deploy and no queue infrastructure to run.
Pro
Automations are a Pro feature. Each one is an object in the reserved automations collection with two halves: one or more triggers that decide when it runs, and a handler — a PHP file that returns a closure — that does the work. The handler is stored in its own file beside the object, so it edits like any code field in the admin, never bloats the collection JSON, and travels with the object through Sync and JumpStart.
There are three trigger types and you can mix them on one automation. A schedule takes a standard five-field cron expression, evaluated in your site timezone, with a dropdown of common schedules to start from. A webhook exposes POST /automations/{id}, authenticated with an API key, restricted to same-origin browser posts, or left public and rate-limited per IP; turn sync on and the response is the handler's return value, turn it off and the run is queued with a 202 Accepted. An event trigger fires on a core content event such as object.created or user.login, optionally narrowed to one collection, and is queued so a slow handler never blocks the write that triggered it.
The handler receives one argument — an AutomationContext — already wired with the services it needs: fetch, save, update, remove and clone objects; increment counters; read and query indexes; work with deck items; import CSV, JSON or RSS; send a Mailer email; write to the log; and read the trigger row, the caller's arguments and the event payload. There is no container to reach into.
Schedules need one heartbeat: tcms automations:process, run every minute by cron. Each tick drains the queued asynchronous runs and fires whichever schedules are now due, and a single-flight lock stops overlapping ticks from double-firing.
Failures are handled by environment. In development an exception surfaces loudly so you can debug it. In production it is contained, you are emailed if the automation has an error mailer set, and after five consecutive failures the automation is auto-disabled so a broken handler cannot fail — and email — forever. The admin shows a banner with a one-click re-enable, and every run is recorded with its status, duration, and return value or exception.
What you get
Three triggers, mixed freely
Cron schedule, incoming webhook, or a content event like object.created. A nightly report can also expose a webhook so you can run it on demand.
A context, not a container
The handler gets one argument with objects, deck items, index queries, importers, the mailer, config and a logger already wired up.
One cron line
tcms automations:process, run every minute, drains queued runs and fires due schedules. The command decides what is actually due; a lock prevents double-firing.
Guard rails on production
Exceptions are contained, optionally emailed through a Mailer, and five consecutive failures auto-disable the automation. Re-enabling is one click and clears the count.
Run history and Run now
Every run records its status, duration and return value or exception. A Run now button executes the handler on demand while you are still writing it.
In practice
One snippet
<?php
return function ($ctx) {
$posts = $ctx->indexReader->fetchIndex('blog')->objects
->where('draft', false)
->take(5);
$ctx->mailer->sendEmail('weekly-digest', ['posts' => $posts->all()]);
$ctx->logger->info('weekly digest sent', ['count' => $posts->count()]);
return ['sent' => $posts->count()];
};
A digest handler on a weekly schedule. Whatever the closure returns is recorded on the run record — and becomes the response body for a synchronous webhook.
FAQ
Common questions
Do I need a cron job?
For schedules, yes — one line running tcms automations:process every minute. Webhook and event automations fire the moment their trigger happens, though event runs are queued and processed on the next tick so they never block the write that caused them.
What happens when a handler throws?
In development the error surfaces loudly and the automation keeps running. In production it is contained, emailed to you if an error mailer is set, and recorded on the run. Five consecutive failures auto-disable the automation until you re-enable it.
Can a webhook return data to the caller?
Yes. A webhook trigger with sync turned on blocks the request and returns the handler's return value as the response body. With sync off, the run is queued and the endpoint answers 202 Accepted immediately.
Is there any protection against risky handler code?
The editor scans a handler on save for high-risk calls — exec, shell_exec, eval, backticks, remote file reads and similar — and shows a non-blocking advisory listing what it found. It is a heads-up rather than a block, since plenty of legitimate handlers use them.
Related features
- REST API JSON endpoints for every collection: CRUD, filtered queries, property-level writes, uploads and reports. Pro adds API keys for external access.
- Extensions Add Twig functions, CLI commands, admin pages, routes, field types and widgets through a stable API. A broken extension cannot crash the site.
- Data Views Pre-compute and cache cross-collection queries with Twig. Views rebuild themselves when a dependent collection changes; templates read them instantly.
Want the details? Read the Automations documentation →
Or keep browsing: every Total CMS feature →
Start Your Free 45-Day Trial