When EspoCRM hooks for server-side business logic are the right tool
A hook is the right tool when a business rule must hold on every save, whether the record changes through the web interface, the REST API, a CSV import or an integration. In EspoCRM, a hook is a PHP class that runs at a defined point in a record's lifecycle, such as before a record is saved, after it is saved, before or after it is removed, or when a related record is linked or unlinked. Because the logic lives on the server, it runs regardless of which client wrote the data, which is the property that separates it from front-end behavior. This page explains when that property justifies writing PHP, and when a simpler mechanism already covers the requirement.
The decision starts with where the rule must hold and what it needs to do. If a rule only shapes what a user sees, such as showing or requiring a field, dynamic logic in the layout is enough. If it calculates or validates a single field from other values on the same record, a Formula before-save script configured in the Entity Manager is usually easier to review and maintain. A workflow or BPM process in the Advanced Pack suits rules that wait for time to pass, notify people, or call external systems, and it can be audited by business owners. A hook is justified when the logic needs full PHP access, such as querying several related entities, enforcing rules across API and import paths, or stopping a save with a precise error. Confirm the Advanced Pack entitlement and the EspoCRM version in your environment before deciding, since the workflow option depends on the extension and the hook API depends on the release you run.
Which hook type runs at which point in the record lifecycle
Before-save hooks run while the record is still being prepared for storage, so they are the last point where code can change the values that will be written. They suit normalizing input, deriving field values from the record, and stopping a save with a validation error. The changes made to the entity object are persisted with that same write.
- Changing the record: Set or normalize fields on the entity being saved so the change is stored in the same write.
- Validating input: Raise a validation error to stop the save and return a clear message to the user or API client.
- Reading related data: Look up linked records to compute values, keeping queries narrow because this code runs on every save.
- Avoiding external side effects: Do not send email, call external APIs or create other records here, since the save can still fail afterward.
- Comparing changed fields: Check which fields differ from the stored values so the logic acts only on changes that matter.
Check whether the same logic fits a formula script or dynamic logic, and confirm the exact hook method names and signatures in the EspoCRM developer documentation for your target version.
After-save hooks run once the record has been written, so the stored values are available and the record can be treated as persisted. They suit creating or updating dependent records, recalculating summary fields, and writing audit entries. Because the primary write has already happened, an error raised here may leave the parent saved, so confirm how failures behave in your version.
- Creating dependent records: Build follow-up records such as tasks or line items after the parent is stored.
- Recalculating totals: Update parent or summary fields when child values change.
- Queuing slow work: Hand longer tasks to the background job queue so the user request returns promptly.
- Logging changes: Write audit-style entries that reflect the values that were actually saved.
- Preventing loops: Guard against saving the same record again from inside its own hook, since that can trigger the hook repeatedly.
Confirm in the developer documentation for your target version whether after-save code runs inside the same database transaction, and what happens to the saved record when that code throws an error.
Remove hooks surround deletion of a record, and relation hooks fire when records are linked or unlinked, whether through the interface, the API or a related panel. These points are where cleanup and integrity checks belong. They are easy to underestimate because bulk actions and imports can fire them once per record.
- Before remove: Stop deletion when dependent data must be kept, such as an open case or an active contract record.
- After remove: Clean up derived records or references in other systems once the row is gone.
- After relate and unrelate: Update counters, ownership or summary fields when a link is added or removed.
- Bulk operations: Mass delete, mass update and imports can run these hooks for each record, which affects run time and load.
- Call chains: A relation hook that saves the linked record starts further hooks, so map the full chain before deploying.
Test deletion and linking through the interface, the API and the import path, because each entry point can produce different hook activity. Confirm the relation hook names for your target version in the developer documentation.
From version 10, EspoCRM provides late hooks that run after the database transaction commits. They exist for work that should happen only when data is durably stored, such as notifications or calls to external systems, so a save that is rolled back does not trigger them. The exact hook names, registration and execution order must be confirmed in the developer documentation for the version you deploy.
- Committed data only: Code here sees the saved state, so an outbound notification will not describe a change that was rolled back.
- External calls: Place webhook or API calls here only when you have a retry plan, because the database cannot undo a call that has already been sent.
- Idempotency: Design calls so a retry or a duplicate delivery does not create duplicate records in the receiving system.
- Failure handling: Decide whether a failed call is logged, queued or shown to the user, since the save has already succeeded.
- Version dependency: Deployments earlier than version 10 do not have this hook family, so code written for it must target a release that supports it.
Confirm which events late hooks cover in your target version, and whether a failure inside one can be retried by the platform or must be handled in your own code.
Comparing hooks, workflows, formula scripts and dynamic logic
This table compares the four mechanisms that can enforce business rules in EspoCRM: PHP hooks, Advanced Pack workflows and BPM, Formula before-save scripts and API before-save scripts, and dynamic logic in the user interface. Read each row as a question you need to answer for your rule: where it executes, who on your team can safely change it, and how exposed it is when you upgrade. The main takeaway is that PHP hooks give the most control but carry the highest upgrade and maintenance cost, so declarative options should be checked first.
| Mechanism | Where it runs | Who can maintain it | Upgrade exposure | When it fits |
|---|---|---|---|---|
| Server-side PHP hooks | On the EspoCRM server, inside the save, remove or relate lifecycle of a record | Developers comfortable with the EspoCRM PHP codebase and deployment process | High: code depends on internal classes and must be retested on each major version | Logic that needs full control, external calls inside the transaction or behavior no declarative tool can express |
| Advanced Pack workflows and BPM | On the server, triggered by record events or schedules, with the Advanced Pack installed | Administrators who can model processes, with some training on the workflow editor | Medium: depends on the Advanced Pack version staying compatible with the core release | Multi-step processes, approvals, notifications, assignment and scheduled actions that business owners need to see and adjust |
| Formula before-save scripts (Entity Manager) | On the server, when a record is saved through the application, configured per entity | Administrators who can read the Formula syntax and test changes on a copy | Low to medium: uses the built-in scripting language, but scripts should be reviewed after upgrades | Calculated fields and validation that depend only on the record being saved |
| API before-save scripts | On the server, when a record is saved through the REST API | Administrators or developers who manage integrations and understand API user behavior | Low to medium: scripts apply to API traffic and need testing with each integration | Keeping data consistent when records arrive from integrations, imports or other external systems |
| Dynamic logic (layouts and fields) | In the browser, controlling how forms show, require or lock fields | Administrators who manage layouts, with no code required | Low: configured in metadata, but test layouts after upgrades | Guiding users through forms. It does not enforce rules for API or import traffic, so pair it with server-side checks where data integrity matters |
How to build and verify a hook safely
This sequence takes a server-side rule from a business requirement to a tested, upgrade-ready hook in an extension package. Each step ends with a check so the team knows whether to continue, revise the design, or stop.
Confirm the rule and its entry points
Write the business rule as a single statement, then list every way a record can change: web UI, REST API, CSV import, workflows, and mass actions. Hooks run on record operations, so a rule enforced only in the UI can be bypassed by an import or API call. Confirm with the process owner which entry points must enforce the rule. The step is complete when the team has a written list of entry points and an agreed decision on whether a workflow or formula can handle the case instead.
Pick the hook type and trigger point
Choose the hook type that matches the moment the rule must fire, such as before or after save, remove, or relate, and confirm in the EspoCRM documentation which hook types apply to your version. Version 10 introduced late hooks that run after commit, which matters when a handler must read committed data or call an external system. Check that the chosen hook is the narrowest one that enforces the rule. The step is complete when the hook type and target entity are recorded in the design note.
Place the code in an extension package
Build the handler inside an extension package that follows the extension template structure, not in core files under the EspoCRM installation. Core edits are overwritten during upgrades and make it hard to tell which code is custom. Declare the package version range in its manifest so the package is tied to the EspoCRM releases it was tested against. The step is complete when the package installs cleanly on a clean test instance and the hook file is loaded from the package directory.
Write an ACL-aware and idempotent handler
Write the handler so it checks the acting user's permissions before changing related records, and so running it twice produces the same result as running it once. Hooks can fire again on updates, retries, or repeated imports, so guard against duplicate child records or repeated notifications by checking existing state first. Avoid loops where a hook saves a record that triggers the same hook. The step is complete when code review confirms each write path checks access and each action can be repeated safely.
Test through UI, API and import
Run the same scenarios through the web interface, an API user with restricted permissions, and a CSV import in each mode the rule could meet, including updates and failed rows. Compare the results with the rule statement from the first step. Confirm that a user without access gets the expected error and that the import report reflects the outcome. The step is complete when every entry point produces the same outcome for the same input and the results are recorded.
Check ordering with other hooks and extensions
List every other hook, formula script, workflow, and installed extension that touches the same entity and event. Run the combined setup and confirm that the order of execution gives the intended result, especially when one handler modifies a field another reads. Document the expected order so future changes do not break it silently. The step is complete when the team can state the execution order for the entity and the combined test passes.
Verify behavior on a staging copy
Deploy the package to a staging copy of production data, with the same PHP and database versions as production, and repeat the test scenarios using realistic record volumes. Check logs for errors and confirm that background jobs and cron-driven tasks behave as expected. Involve the business owner in signing off the results. The step is complete when the staging results match the agreed rule and the sign-off is recorded with the package version.
Plan the upgrade path for custom code
Before release, record which EspoCRM version the hook was tested on, which package version range it supports, and who reruns these tests when EspoCRM or the extension changes. Schedule the test pass as part of each upgrade rather than treating it as a one-time check, because a hook that works today can break after a major release changes an internal API. Confirm the upgrade procedure with the team that runs your instance. The step is complete when the upgrade test steps and owners are written down and linked to the package.
Where hook files live and how ordering is decided
Before you write a hook, decide where its file lives, because the location determines who owns the code and whether it survives an upgrade. EspoCRM hooks sit in a directory structure that separates core code from custom code and from extensions. Core hooks ship with the product and should never be edited. Custom hooks for your own entities belong in the custom code area of the installation, and hooks delivered as part of a packaged extension belong in that extension's files. Confirm the exact paths, the file naming pattern and whether a hook class must match its entity name in the EspoCRM developer documentation and in a test instance before your team commits to a layout. Keeping one clear owner for each hook file makes later audits and upgrades far easier.
The harder question is order. When several hooks respond to the same save, remove or relate event on one entity, the sequence in which they run can change the result. A hook that recalculates a field, a hook that sends a notification, and a hook that writes an audit value may each assume a different state of the record. If the notification runs before the recalculation, the message may carry stale numbers. The supplied material does not confirm how EspoCRM orders hooks across core, custom and extension locations, or whether ordering depends on file names, class names or registration. Do not design around an assumed rule. Instead, write a short inventory of every hook that touches each entity, then verify the real sequence by saving a test record and checking the values and side effects it produces. Treat ordering as a standing control, not a one-time check. Each time you add an extension, upgrade EspoCRM or introduce a second hook on a busy entity, repeat the test with records that trigger all relevant paths, including related-record and removal events. Record the expected order and outcome in your change log so an engineer reviewing a later bug can compare the current behavior with the documented baseline. Where two hooks must cooperate, consider whether a workflow or a formula script can carry part of the logic, since those tools make the sequence easier to inspect.
What goes wrong with server-side hooks in production
Hooks run inside the save path, so a fault in one shows up as a slow, looping or silently incomplete record change. The cards below cover the failure modes we see most often and the control that stops each one. Check each control in your own instance before the hook reaches production.
Save loops between related records
A hook that saves a related record can trigger that record's own hooks, which may save the original again. The symptom is a request that never completes, duplicate timeline entries or repeated updates to modified dates. Guard against it with a check on the changed attributes before any save, and test each hook with a record that has relationships in both directions. Confirm the loop guard in a staging copy before release.
Bypassing access control checks
Hooks often call the entity manager with system privileges, which skips the role and team checks that a user's own save would hit. The symptom is a user changing or reading records they could not reach through the interface. Decide explicitly which operations should run as the acting user, and review every hook that writes to records outside that user's scope. Ask for a role-based test case for each path.
Slow external calls in the save path
A hook that calls a payment, email or enrichment API blocks the user's save until that call returns or times out. The symptom is slow saves that correlate with a third-party outage. Move external calls to background jobs or a queued workflow where possible, set explicit timeouts, and make the hook tolerant of failure. Measure save time before and after the hook in a test environment.
Hooks that break after upgrade
Hook files depend on internal class names, method signatures and container services that a new release can change. The symptom is a fatal error or a silently skipped hook after an upgrade. Keep custom code in an extension package, read the release notes for every version in the upgrade path, and run a full set of save and delete tests in staging before upgrading production. Confirm the target version's compatibility with your PHP version first.
Logic duplicated between hooks and workflows
Teams often implement the same rule as a hook and as a workflow, so each change needs two edits and the two versions drift apart. The symptom is records that get different values depending on whether they were saved in the UI, imported or created by an API call. Assign each rule to one mechanism, document the choice in a shared register, and review the register whenever a rule changes.
Silent failures during imports
Hooks behave differently during bulk imports, API batches and idle jobs, and an exception may be logged without stopping the import. The symptom is records that import successfully but lack the values the hook should have set. Test each hook with a representative import file, check the log after each run, and confirm that the import field mapping does not bypass a required calculation. Reconcile a sample of imported records against the expected output.
Security and operational considerations for custom PHP
Server-side hooks run with the privileges of the process that saves or removes a record, so every hook is part of your security surface. The cards below cover how to review, version, scope, configure and monitor that code so a small change does not become an access or data problem.
Review and version every hook
Keep each hook file in a version control repository and require a second reviewer before a change reaches production. Review for what the hook reads and writes, which entity types it touches, and whether it calls external services. Check that the same hook code is what runs on staging and production, since a hand-edited file on one server is the most common way custom PHP drifts out of review.
Scope API users to least privilege
Records created through the REST API still fire hooks, so an API user with broad write access can trigger business logic you did not intend. Give each integration its own API user with only the entity permissions it needs, and confirm in testing which hooks run for that user's requests. The EspoCRM security guidance on API users and on keeping admin accounts separate from daily work is the baseline to check against.
Keep secrets out of hook code
Hooks often need credentials for a payment provider, a mail relay or an internal API. Do not place those values in PHP files, where they end up in version control and backups. Load them from the server environment or a secrets store your platform team controls, and confirm that no hook writes them to a log or to a record field visible to users.
Log hook failures deliberately
A hook that throws an exception during save can block a record write or leave related records half-updated, and users often report only the symptom. Decide where hook errors are written, make sure the entries include the entity, record ID and hook name, and set an alert for repeated failures. Check the EspoCRM logs location and rotation in your server configuration before go-live.
Restrict who can deploy extensions
Deploying a custom PHP file changes behavior for every user on the instance, so deployment rights should be narrower than administrator rights in the application. Limit server access to a small group, route changes through a pipeline with approval, and keep a record of who deployed which package. Confirm with your team how extension installs and rebuilds are triggered in your hosting model before you set those rules.
Test hooks with production-like data
Run hook changes against a copy of production data that includes the permissions, teams and API users used in real traffic. Check the behavior on bulk operations, imports and related-record updates, not only a single manual save. Keep a rollback plan that removes the hook file and rebuilds the application cache, and record that step in your change runbook.
Questions buyers ask about EspoCRM hook development
What drives the cost of an EspoCRM hook project?
The main cost drivers are the number of business rules, how many entities and events each hook touches, and how much existing data and customization it must respect. Each hook needs design, PHP development, a test plan run against a copy of your instance, and review. Costs rise when rules depend on other systems, when hooks must behave correctly during bulk imports or API calls, or when the logic must be documented for auditors. Costs fall when rules are written down precisely before the build starts and when a staging environment with representative data already exists. The build is a one-time cost, and ongoing fixes or upgrades recur only if you add a retainer. Ask for a quote that lists each hook, its trigger, its test cases and the environments it will be deployed to. Quotes follow from a written scope, and DeployOpen does not publish a price list.
How long does it take to build and test a server-side hook?
No researched duration applies to hook work in general, so ask for the timeline in the written scope rather than relying on a generic figure. The schedule depends on how many hooks are involved, how complex the rules are, and how many test scenarios are needed to cover creates, updates, deletes and related-record changes. For logic that touches financial or customer data, testing often takes as long as building. Ask the provider to show the sequence: specification, build, testing on a copy of your instance, fixes, then deployment. Also ask which dependencies could delay the start, such as access to a staging environment, sign-off from business owners, or a decision on who owns each rule. A fixed-scope agreement sets deliverables and timing in writing before work begins.
What does our team need to provide for a hook engagement?
Most of the work depends on decisions and access that only your team can provide. A named business owner should sign off each rule with worked examples, including edge cases. The engagement also needs:
- A staging copy of your instance with representative records, loaded under the policies your organization sets for personal data.
- Administrator access to configure test users, roles and teams that match production.
- A delivery route for code, such as your source control system, and an agreed release window.
- Named contacts and test credentials for each external system a hook calls.
- An administrator who can read logs and run scheduled jobs during testing.
Write these requirements into the scope so that delays from missing access are visible early.
Can an internal developer maintain EspoCRM hooks after handover?
Yes, if the developer has solid PHP skills and has read the EspoCRM code paths the hook touches, but ongoing maintenance is where teams underestimate effort. Each hook runs inside the save path, so a fault can block record updates for users, which makes review and regression testing part of routine work. Keep every hook in version control with a short description of the business rule, the trigger it listens to and its test cases. Ask any provider for a handover that includes source code, tests, a deployment runbook and a list of dependent integrations. Internal teams should also agree who approves changes to rules owned by sales or operations. If capacity is limited, an ongoing retainer can cover upgrades and fixes. Confirm your team's skills in writing before choosing between building internally and buying a fixed-scope build.
Do server-side hooks keep working after an EspoCRM major upgrade?
Hooks depend on EspoCRM internals, so each major upgrade needs regression testing of every hook before production. The GitHub releases page lists EspoCRM 10.0.0 as released on July 2, 2026, following 9.0.0 on January 20, 2025. Check the 10.0 release notes for changes to hook behavior, including timing for hooks that run after a database commit, before you plan the upgrade. Test each hook on a staging copy with representative data, covering creates, updates, deletes and related-record changes. Verify API and import paths as well as the user interface. Upgrading through the UI is disabled by default as of version 10.0, so the upgrade runs through the command-line process described in the EspoCRM documentation. Ask for an upgrade test plan and a rollback step in the written scope.
When is an EspoCRM workflow enough instead of a hook?
Use a workflow when a record change should trigger simple actions such as field updates, notifications, assignment or approvals, and the logic fits the builder. Workflows and BPM are part of the Advanced Pack, an official add-on that is included in all EspoCRM Cloud plans, so confirm its licensing for a self-hosted edition before you design around it. Use a formula script for calculated fields and validation in before-save logic. Choose a hook when the logic needs PHP, such as calls to external services with error handling, custom services, complex data checks, or behavior that must run identically for the interface, the API and imports. The trade-off is maintenance: workflows can be adjusted in the admin interface without a code release, while hooks require review, testing and deployment. Write down the rule first, then choose the lowest-code option that meets it.
How does a hook differ from a formula script in EspoCRM?
Formula is EspoCRM's built-in scripting language, used in before-save scripts configured in the Entity Manager, API before-save scripts, workflows and BPM. A hook is PHP code that runs on server-side events, so it can call external services, use application classes and handle errors in ways a formula cannot. Formula is faster to change, can be maintained by trained administrators and keeps the logic in configuration. Hooks give developers more control but need source control, testing, deployment and upgrade checks. When a rule can be expressed as a formula, it usually belongs there. When it needs integration logic, custom data access or a clear failure path, a hook is the better fit. Review the formula reference for your version to confirm which functions are available before you design either approach.
Limits to confirm in your EspoCRM environment
Hooks run PHP inside the EspoCRM application, so their behavior depends on the exact version, the PHP runtime, the job scheduler and the plan or hosting model you run. The cards below list the points to verify with the vendor documentation and your own environment before you commit to a hook-based design.
Exact hook API for your version
Hook classes, method signatures, event names and the order in which they fire can change between major releases, so a hook written against one version may not run unchanged on another. Confirm the hook API against the documentation and release notes for the version you will deploy, and test the hook on a staging copy of your instance. Record the version in your change log so a later upgrade starts from a known baseline.
PHP runtime support
The server configuration docs list PHP 8.3 to 8.5 as supported, so server-side hooks should be tested on the PHP minor version your hosts actually run. Check the PHP version on every application node, including any container images, and confirm that your OS packages do not pin an older build. Any hook that calls a PHP extension must be verified on the same version.
Late after-commit hook behavior
Late hooks that run after the database transaction commits are central to safe side effects such as sending notifications or calling external systems. Confirm that your version supports the late hook type you plan to use, and test what happens when the request fails after the record is saved. Decide how a failed external call is retried, since a hook that runs after commit will not roll back the original record.
Cron and scheduled job dependencies
Background work triggered from hooks, such as queued jobs, mass processing and some notifications, depends on cron or another scheduled task running on the server. Confirm that the scheduler is configured on every instance, and that failed or stalled jobs are visible to administrators. If the scheduler is not running, work that a hook queues will sit unprocessed, which can look like a hook failure.
Advanced Pack licensing for workflows
Workflows and Business Process Management belong to the Advanced Pack, which is included in all EspoCRM Cloud plans. For a self-hosted open-source instance, confirm whether you will license the Advanced Pack before you choose a workflow-based design over a hook. Put the licensing decision in the same scope document as the hook design so that the cost of each option is compared on equal terms.
File-level changes on cloud and self-hosted plans
Hooks are PHP files placed in the application tree, so they require file-system access that a hosted plan may not provide. Confirm with the vendor whether your plan allows custom PHP files, how custom code survives upgrades, and who is responsible for reviewing it. On self-hosted systems, decide where hook code lives in version control and how deployments copy it into the application directory.
Where to read the official EspoCRM hook and formula references
Start with the official EspoCRM documentation before you write any PHP hook. The Formula reference is the first page to read because it describes the built-in scripting language used in before-save scripts, API before-save scripts and workflows. Many logic requirements that look like they need a hook can be met there, and the Formula page shows which record and date functions are available inside a save cycle. Read it alongside the API documentation, which explains how API calls reach the same record logic, so you know whether a rule will also apply when a record is created or updated through an integration rather than the user interface.
The API documentation matters most when server-side logic must hold for every write path. Check which authentication method each integration user uses, because the security documentation recommends HMAC over basic authentication and advises restricting API users by role. Then read the security documentation for the controls that interact with custom code, including token lifetime, IP restrictions and the advice to keep admin accounts out of daily work. A hook that runs with elevated rights can bypass the access model you set up, so confirm which user context your logic executes under before you rely on it.
Developer hook references are version specific, so check any hook name, signature or execution point against the release you actually run. The EspoCRM releases page lists the current major line, and the server configuration page sets the PHP and database versions your environment must meet. Confirm in the release notes and your own test instance whether the behavior you need, including late hooks that run after commit in v10, is present and unchanged in your version. Record the version you verified against, so that a later upgrade triggers a focused review of every custom hook.

