EspoCRM hooks for server-side business logic

EspoCRM hooks run PHP when records are saved, removed or related, so server-side rules hold for every API call and import. This page explains hook types, file locations, ordering, upgrade risk and when a workflow or formula script is the better choice.

On this page

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.

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.

MechanismWhere it runsWho can maintain itUpgrade exposureWhen it fits
Server-side PHP hooksOn the EspoCRM server, inside the save, remove or relate lifecycle of a recordDevelopers comfortable with the EspoCRM PHP codebase and deployment processHigh: code depends on internal classes and must be retested on each major versionLogic that needs full control, external calls inside the transaction or behavior no declarative tool can express
Advanced Pack workflows and BPMOn the server, triggered by record events or schedules, with the Advanced Pack installedAdministrators who can model processes, with some training on the workflow editorMedium: depends on the Advanced Pack version staying compatible with the core releaseMulti-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 entityAdministrators who can read the Formula syntax and test changes on a copyLow to medium: uses the built-in scripting language, but scripts should be reviewed after upgradesCalculated fields and validation that depend only on the record being saved
API before-save scriptsOn the server, when a record is saved through the REST APIAdministrators or developers who manage integrations and understand API user behaviorLow to medium: scripts apply to API traffic and need testing with each integrationKeeping 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 fieldsAdministrators who manage layouts, with no code requiredLow: configured in metadata, but test layouts after upgradesGuiding 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.