Extensions
Out-of-process custom code with resource limits, an outbound allowlist and a circuit breaker — the supported way to add server-side logic.
Extensions
An extension is custom server-side code registered with the platform, bound to a point in its behaviour, and run under controlled conditions.
Where to find it
Architect Panel → Automation:
- Extensions — the extension registry, its versions and runs
Architect Panel → Layout & Pages:
- App Code — the in-browser editor
Architect Panel → Data:
- Datastores — what an extension typically acts on
Why this rather than a code field
A Custom PHP Code field is convenient and unmanaged: it is edited in a form, invisible to version control, reviewed by nobody, and runs inside the request with the platform's own access.
An extension is the supported alternative. It is registered, versioned, resource-limited, monitored, and can be disabled without touching a datastore. For anything that will live, grow or matter, this is the right home.
How it binds
An extension declares a hook — the point in the platform's behaviour it attaches to — and a binding, which narrows where it applies: a datastore, a field, or a named key.
That binding is what stops an extension being global. Something intended to run on one datastore should be bound to it, not left to check whether it should act.
It runs out of process
The important architectural point. An extension does not run inside the request the way a code field does, which is what makes the limits and the circuit breaker below possible.
It also means an extension that hangs or exhausts memory does not take the request with it — which is the difference between a bad deployment being an inconvenience and being an outage.
Every extension is code you own
Registered, sandboxed and monitored, and still yours to maintain. It gets the same review and testing as anything else you deploy — the platform bounds the damage, it does not make the code correct.
Keep the number small
Each is a thing to maintain, test at upgrade and reason about when something behaves unexpectedly. A handful of well-chosen extensions is manageable; a long list accumulated over years is a liability nobody fully understands.
Prefer configuration where it exists
The standing principle. Before writing an extension, check whether a workflow, a journey, a rule or a calculated field would do — configuration is visible, reviewable and does not need testing at every upgrade.
Record what each one does
Name, purpose, what it binds to, who owns it. Extensions are written once and consulted years later, usually urgently, by somebody who was not involved.
Worked example
An organisation runs three extensions: one scoring risk on referral, one pushing approved orders to a supplier, one generating a nightly reconciliation. Each is bound to one datastore, each is documented with an owner, and everything expressible as a workflow was left as one.
Recommendations
- Use extensions rather than code fields for anything lasting.
- Bind narrowly rather than checking inside the code.
- Prefer configuration where it can do the job.
- Document each one with an owner.
Limits and the Circuit Breaker
Extensions run under limits, and the platform will disable one that misbehaves. Understanding both is what makes running custom code safe.
Where to find it
Architect Panel → Automation:
- Extensions — the extension registry, its versions and runs
Architect Panel → Activity:
- Error Log — extension failures
Architect Panel → Security:
- Permissions — who may register or change an extension
The limits
- Timeout — how long a run may take.
- Memory — how much it may use.
- HTTP hosts — an allowlist of what it may call out to.
- Maximum mails per run — a cap on how much it may send.
- Run mode and fail mode.
The HTTP allowlist is the one to think about
An extension can only reach hosts you have listed. That is a genuine security boundary rather than a convenience: without it, custom code can send your data anywhere, and nothing about the rest of your configuration would prevent it.
List the specific hosts an extension needs. A permissive entry defeats the control entirely, and it is the kind of thing added during debugging and never removed.
The mail cap prevents a specific disaster
An extension in a loop that sends e-mail will send thousands before anybody notices, to real people, from your domain. The cap bounds that to something survivable.
Set it to a little above what the extension legitimately needs, not to a comfortable round number.
Fail mode decides what happens when it breaks
Whether a failing extension stops the operation it was attached to, or is skipped so the operation continues.
Neither is universally right. An extension performing an essential validation should fail closed — better to refuse than to proceed unchecked. One enriching a record with something optional should fail open, because a broken extension should not stop people working.
Decide per extension, deliberately. The default will be wrong for half of them.
The circuit breaker
The platform tracks invocations, failures and consecutive failures over a window. An extension failing repeatedly is tripped — automatically disabled — and periodically probed to see whether it has recovered.
This is why a broken extension is a contained problem rather than a rolling one. Without it, something failing on every record would keep being called, keep failing, and keep consuming resources indefinitely.
A tripped extension is telling you something
Do not simply re-enable it. It tripped because it failed repeatedly, and re-enabling without fixing the cause produces the same trip and a longer outage of whatever it does.
Read the error log, fix the cause, then let it recover.
Disabling is recorded
An extension carries who disabled it, when and why — so a disabled extension is explicable rather than a mystery for whoever finds it next.
Worked example
A supplier integration extension is allowlisted to one host, capped at five e-mails per run, and set to fail open so a supplier outage does not block order entry. When the supplier had a bad afternoon the breaker tripped after repeated timeouts, order entry continued unaffected, and the error log showed exactly why.
Recommendations
- Allowlist specific hosts, never something permissive.
- Choose fail mode per extension — the default is wrong half the time.
- Never re-enable a tripped extension without fixing the cause.
- Set the mail cap just above genuine need.
Versions and Rollout
An extension carries a current version and a draft, so changes can be prepared without affecting what is running.
Where to find it
Architect Panel → Automation:
- Extensions — the extension registry, its versions and runs
Architect Panel → Layout & Pages:
- App Code — the in-browser editor
Architect Panel → Activity:
- Error Log — failures after a change
Draft and current
The running code is the current version. A draft is what you are working on, and it does not affect anything until it is promoted.
That separation is what makes editing a live extension safe. Without it, every save is a deployment.
Work in the draft
Always, even for a small change. "It is a one-line fix" is how a production extension gets broken, and the cost of using the draft is nothing.
Test before promoting
An extension runs against real data on real records. Exercise the change against realistic input — including the awkward cases — before it becomes current.
Particularly test what happens with missing or malformed data, because that is what production will supply eventually and it is what the previous version had learned to survive.
Promote deliberately, and watch
Promotion is a deployment. Do it when somebody is available to notice a problem, not at the end of a Friday, and check the error log afterwards rather than assuming.
The circuit breaker will contain a badly broken version; it will not tell you about one that is subtly wrong.
Rolling back
Versions are retained, so reverting is possible. Know how to do it before you need to — the moment an extension is misbehaving in production is not the moment to be working out the mechanism.
Watch the first runs
Extension runs are recorded, so after promoting you can see whether it is being invoked and whether it is succeeding. That is a more direct answer than waiting for somebody to report a problem.
Changing what it binds to
More consequential than changing the code. Rebinding an extension changes where it runs, so it may start acting on records it never has — with logic written for something else.
Treat a binding change as a bigger change than a code change, which is the opposite of how it feels.
Keep versions meaningful
Note what changed and why. An extension with fifteen versions and no record of what each did is one nobody will dare roll back, because they cannot predict what they would be reverting to.
Worked example
A team edits an extension in its draft, tests against a copy of real referrals including three malformed ones, promotes on a Tuesday morning and watches the runs. A subtle error found an hour later is reverted to the previous version in under a minute, fixed in the draft, and promoted again the next day.
Recommendations
- Always work in the draft, however small the change.
- Test with malformed data, not just good data.
- Know the rollback before you promote.
- Treat a binding change as bigger than a code change.