Developing and packaging EspoCRM extensions

Shipping EspoCRM customizations as an installable extension keeps custom code separate from core files, so it can be moved, versioned and upgraded. This page covers how to plan, build and package one.

On this page

When EspoCRM extension development is the right way to customize

Build an extension when customizations have outgrown the instance they were made in. A team that has added fields, layouts, formula scripts, hooks and buttons directly through the admin interface usually ends up with logic spread across Entity Manager screens, server files and configuration that nobody has written down. An extension packages those changes into one versioned unit with a manifest, files and scripts directories, so the same set of changes can be installed, reviewed, tested on a copy of the instance and removed. Choose this route when the changes must move between environments, when more than one person or team edits the customization, or when you need to show an auditor or a future administrator exactly what was changed and when. The open-source edition of EspoCRM is licensed under AGPL-3.0 and can be self-hosted at no software cost, so the case for an extension rests on structure and maintenance, not on licensing.

Keep ad hoc changes for settings that are simple, local and easy to reproduce. A new picklist value, a renamed label or a single field that appears for one team can usually stay in the Entity Manager and layout settings, provided someone records the change in your change log. Before-save formula scripts and workflows often cover calculated fields, approvals and notifications without any PHP code, and the Advanced Pack, which is included in all EspoCRM Cloud plans, adds the Workflows and Business Process Management features those automations depend on. Move to an extension when a change needs custom PHP hooks, new service code, bundled frontend files or several of these pieces that must be installed together. Before you decide, confirm with your team which features are available in your edition and which official extensions you already run, because that determines what counts as a built-in option.

Extension, Entity Manager setting or workflow: which fits

Configuration through the Entity Manager and layout editors is the lowest-risk option. It adds custom entities, fields, relationship types and layouts from the admin interface, without writing code. Changes are stored in EspoCRM's own metadata, so the platform's upgrade process handles them in the normal way, and administrators who know the product can maintain them without a developer.

  • What it changes: entity types, custom fields, enum options, detail and list layouts, and relationship types for a single instance.
  • Who maintains it: trained administrators, usually inside the operations or sales operations team.
  • How it moves: through the instance's own export and import process or by recreating the settings by hand on the target. Confirm the current method in the migration documentation for your version.
  • Limits: it cannot add new server-side behavior or new screens with custom logic.
  • Upgrade effect: low, but review custom fields and layouts after each major version.

Before you choose this path, check whether the requirement is a configuration need or a logic need, because configuration rarely covers calculations or approvals.

How the manifest, files and scripts directories divide the work

The package is split into three parts that each do a different job. The manifest is the metadata file that declares the extension's name, its version, and the range of EspoCRM versions it is built to run on. The files directory holds an overlay of PHP and frontend files that are copied into the EspoCRM installation when the extension is installed, so a file placed at a given path in files lands at the matching path in the core tree. The scripts directory holds the install and uninstall hooks, which run custom code at those lifecycle points, for example to create metadata, migrate data or clean up. Before you write any code, confirm the current manifest keys, the exact names of the scripts and how the overlay is resolved against the EspoCRM extension documentation, because this page does not restate them and those names change between releases.

Read the split as a set of risks. The version range in the manifest is the single most important field to get right, since it determines which EspoCRM releases your package claims to support and therefore which upgrades will be blocked or allowed. Overlay files are the riskiest part of the package: a file in files that replaces a core file will silently override that file on every upgrade, so each overlay should be reviewed against the core files it touches. Ask your team to list every overlay file and the core file it shadows, and to record which of them are needed at all once a Formula script, a workflow or a hook could do the same job. Keeping that list short is the cheapest way to keep the package upgrade-safe.

Install and uninstall scripts are where data and schema changes happen, so they need the most testing. Plan them to be idempotent, so that a failed or repeated install does not corrupt existing records, and decide in advance what uninstall should remove, since deleting custom fields or entity data can be destructive. Test both paths on a copy of production data, and keep a backup taken before each install or upgrade. The same overlay and script structure will be exercised again during each major EspoCRM upgrade, so treat the scripts as part of your upgrade test plan, not only as installation code.

Keeping custom code upgrade-safe across EspoCRM versions

An extension is only useful if it still installs and works after the next EspoCRM release. The practices below share one goal: keep your custom code separate from core files, keep its metadata in the package, and prove compatibility before each upgrade reaches production.

Leave core files untouched

Core files in the EspoCRM installation are replaced during every upgrade, so any edit made there disappears or conflicts with the new release. Put every change into your extension's files and custom directories instead, and override behavior through the supported extension mechanisms. During review, search the codebase for changes outside your package, because a manual edit made on the server is the most common reason an upgrade breaks silently.

Namespace custom code

Give PHP classes, services and frontend modules a namespace that belongs to your organization or extension, so they cannot collide with core or with other extensions. Name custom entities and fields with the c prefix convention used in the Entity Manager. Clear naming makes it obvious during an upgrade which code is yours and which belongs to the platform, and it prevents two packages from overriding the same class.

Pin and test version ranges

Declare the EspoCRM versions your extension supports in its manifest, and set the range deliberately rather than leaving it open. Before each new release, install the extension on a staging copy of production, run your test scripts and record the result. Treat a new minor or major release as a change to be verified, not as an automatic compatibility claim. Ask your partner or internal owner who signs off on the range.

Keep metadata in the package

Layouts, field definitions, entity settings and clientDefs changed through the administration UI live in the database, where they are invisible to version control and easy to lose on a rebuild. Move those definitions into the extension's files directory so a new instance installs identically. Review any database-only change before the next release, and export it to the package so the upgrade and the rebuild reproduce the same configuration.

Rehearse the CLI upgrade

Upgrading through the user interface is disabled by default as of version 10.0, so upgrades run through the command-line process. Rehearse that exact sequence on a copy of production with a restored backup, including the extension install or upgrade step and the cache and rebuild commands. Record the duration and every warning, and confirm that custom fields, workflows and hooks still behave as expected before scheduling the production change.

Track platform changes before upgrading

Read the release notes for each version between your current release and the target before changing anything. Focus on changes to hook signatures, deprecated services, frontend framework code, and default security settings, since those are the areas most likely to affect extension code. Note which items need a code change, which need only a test, and which can be ignored, and assign an owner to each open item before the upgrade window opens.

How to build and package an EspoCRM extension

This sequence turns a set of CRM customizations into an extension package that installs, uninstalls and upgrades cleanly on instances other than the one where it was built. Each step ends with a check so that packaging problems surface on a staging instance rather than in production.

  1. Define requirements and the entities involved

    Before writing any code, the team lists the business behavior the extension must add, the entities and fields it touches, and which parts are configuration that already exists in the Entity Manager and which are new code. Someone who knows the sales or service process signs off on the list, and an engineer confirms which core features (layouts, dynamic logic, formulas, workflows or hooks) can deliver each item without custom PHP. The output is a short requirements note with an explicit boundary: what the extension owns and what it expects from the base instance. Done when every requirement maps to an entity, a field or a behavior and nothing is left as a vague goal.

  2. Scaffold the project from the ext-template

    A developer starts a new project from the official ext-template repository rather than from an empty folder, so the expected layout is in place from the beginning. They set the extension name, version and author fields in the manifest, and they keep the template's directory structure for files, scripts and source code. Version numbers should follow a scheme the team writes down now, since version ranges in the manifest depend on it later. Done when the project builds an empty archive that installs on a test instance without errors.

  3. Write metadata, PHP and frontend code

    The developer writes the metadata definitions for entities, fields, layouts and client behavior, then adds PHP for server-side logic such as hooks or services and JavaScript for custom views or buttons, placing each file in the directory the template expects. Keeping metadata as files in the package, rather than changes made by hand in the admin interface, is what makes the extension reproducible. Custom code should be kept small and scoped to what configuration cannot do, because every custom class is something the next EspoCRM upgrade must still support. Done when the code loads on a test instance, the new fields and layouts appear, and the server logs show no errors on save.

  4. Add install and uninstall scripts

    The developer adds the install script to create or adjust database structures, default settings and rebuild steps, and the uninstall script to reverse what the extension added without deleting data the business has already entered. The team decides in advance which data is kept on uninstall, since removing a custom field can remove its values. Both scripts must be safe to run more than once. Done when the install and uninstall scripts run on a copy of a populated instance and the team has written down what each one changes.

  5. Build the archive and verify its contents

    The developer builds the extension archive with the packaging steps from the template and opens it to confirm that the manifest, files, scripts and source directories are all present and that no test data, local settings or credentials were included. The manifest version and the EspoCRM version range it declares are checked against the release being targeted. Done when the archive contents match a written checklist and the version metadata is consistent across the manifest and the build output.

  6. Install on staging and run the three install checks

    The archive is installed on a staging instance that holds a copy of production-like data, and three checks are recorded: a clean install on a fresh instance, a clean uninstall that leaves the base system working, and a second install over an existing instance that proves the install script is idempotent. Each check is logged with the EspoCRM version, PHP version and any errors. Done when all three checks pass and no unexpected data was removed.

  7. Run regression checks on layouts, formulas and permissions

    The team tests the behavior the extension touches and the behavior around it: detail and list layouts for each affected role, formula and before-save scripts that run on record saves, workflows or BPM processes that read the new fields, and access rules for each user type. Testers also confirm that existing entities and integrations behave as before. Results are recorded against the requirements list from the first step, and any defect is fixed and retested. Done when every requirement has a passing test and the regression log shows no changed behavior outside the extension's scope.

  8. Promote the package to production

    The approved archive is installed on production through the same procedure used on staging, with a backup taken immediately beforehand and a rollback plan written down. The team confirms who can run the upgrade and when the change window is, and checks the post-install state against the staging results. Done when production shows the same version, the same install and uninstall results and clean logs, and the backup has been verified as restorable.

Packaging options and when each fits

This table compares the four ways a team can deliver an EspoCRM change: a packaged extension, Entity Manager configuration, a Formula script and a workflow. Read each row to see who maintains the change, whether it moves cleanly between instances and how exposed it is when EspoCRM is upgraded. The main takeaway is that anything meant for several instances or a long life should be packaged, while small, instance-specific changes rarely justify that overhead.

OptionPurpose and typical useMaintenance ownerPortability between instancesUpgrade exposure
Packaged extensionShips PHP, JavaScript, templates and scripts as one installable unit with a manifest and version range, for changes reused across instances or clientsYour development team or partner, who keeps the code and version ranges currentHigh: installed through the Extension Manager on each instance with the same package fileModerate: custom code must be tested against each new EspoCRM release and version range
Entity Manager configurationAdds custom entities, fields, relationships and layouts through the admin interface without writing codeAdministrators who own the instance configurationLow to moderate: must be recreated or exported by hand on each instanceLow: stored as metadata, but field and layout changes still need regression checks after upgrades
Formula scriptCalculates field values or validates data in before-save scripts configured in the Entity Manager or through the APIAdministrators with some scripting skill, documented alongside the instanceLow: scripts are copied manually and tied to the entity definitions on that instanceLow to moderate: depends on the functions used, so confirm each function still behaves as expected after upgrades
WorkflowRuns automated actions such as field updates, notifications or assignments when conditions are met, typically in a process owned by a business teamBusiness process owners with an administrator for changesLow: workflows are rebuilt by hand unless the team keeps a written specificationLow to moderate: requires the Advanced Pack, so license and extension compatibility must be confirmed at each upgrade

Security risks in custom EspoCRM code

An extension runs inside the EspoCRM application, so a weak file or endpoint carries the same reach as the core product. The risks below cluster around who can change code, what the API exposes and what gets shipped in the package. Each one has a check you can run before a package reaches production.

Custom PHP runs with full application rights

Server-side hooks, services and scripts execute with the same privileges as EspoCRM itself, so they can read any record, alter permissions or touch the database directly. A hook that skips the normal access layer can expose data that the user interface would never show. Review every PHP file in the package as privileged code, and confirm that the web server account cannot write to the application directories beyond what the upgrade process needs. Ask who is allowed to install packages on each instance.

Unreviewed third-party extensions

A community extension can carry the same code paths as your own, plus code you did not write and cannot easily audit. Before installing one, check its source, its maintenance history and the versions it declares as compatible. Keep an inventory of every installed extension with its version, owner and reason for being there. Treat an extension update as a code change that goes through the same review and test cycle as your own packages.

API endpoints added without ACL checks

A custom controller or action that accepts requests without verifying the caller's permissions can return or modify records the API user should never reach. This is easy to miss because the endpoint works in testing with an administrator account. Test each new endpoint with a restricted API user and with an unauthenticated request. Confirm that the role assigned to the API user grants only the entity access the integration needs.

API authentication method left weak

EspoCRM supports API key, HMAC and basic authentication through dedicated API users. The security guidance treats HMAC as the most secure option and does not recommend basic authentication. A package that documents or configures basic authentication for an integration moves a long-lived credential into a place where it can be copied. Specify HMAC in the package documentation, and confirm each integration uses its own API user with a narrow role.

Secrets stored in package files

API keys, mail passwords, OAuth client secrets and tokens sometimes end up in a package's config or scripts directory during development. Once the package is archived, shared or committed to a repository, those values leave your control. Keep credentials in the instance configuration or the application's secret settings, not in files that ship with the extension. Scan the package before every release for key-like strings, and rotate any value that has already left the controlled environment.

Debug code and admin access left in production

Verbose logging, test endpoints, disabled permission checks and hard-coded admin accounts often survive from development into a release. Add a release checklist item that removes debug output and confirms that administrator rights are not granted to the extension's own users. Enforce a password policy, require two-factor authentication for admin accounts, avoid daily admin work with those accounts, and restrict admin access by IP address as the security guidance recommends. Verify these settings on the production instance after each installation.

Questions buyers ask about EspoCRM extension development

How much does it cost to build an EspoCRM extension?

The cost follows from the written scope, and DeployOpen does not publish a price list. The main cost components are discovery and design, Entity Manager and layout work, PHP backend code and hooks, frontend JavaScript, automated or manual testing, packaging and documentation. Cost rises with the number of entities and fields, the depth of PHP and frontend logic, the integrations involved, the test coverage you require and the age of any code you inherit. Some work is one-time, such as the build. Upgrade testing and fixes recur for as long as the extension runs. Ask for a quote itemized by these components, with the target EspoCRM version and the test deliverables stated.

How long does an EspoCRM extension take to reach production?

Duration depends on the same drivers as cost: the number of entities, the amount of backend and frontend code, the integrations, and how many review rounds your business owners need. Requirements that are still undecided at the start usually cause the most delay. Before work starts, the agreed scope should include milestones for design sign-off, a build on a non-production instance, user acceptance testing, and a release into production. Ask for a timeline tied to those milestones, and confirm which dependencies sit with your team, such as access approvals and test users.

What does our team need to provide for an extension project?

The work needs a named business owner who can decide requirements and approve layouts and logic. It also needs sample data that reflects real records, a non-production EspoCRM instance on the same version and PHP runtime as production, and access to your source repository and deployment pipeline. Your team should confirm the target EspoCRM version early, because it sets which API and frontend features are available. Finally, plan testers from each affected team for user acceptance, and someone who can approve the production release window.

Can one internal developer build and maintain an EspoCRM extension alone?

A capable developer can build a small extension alone, but the risk sits in continuity and review. An extension combines PHP hooks, JavaScript views, metadata files and permissions, and each layer changes when EspoCRM is upgraded. With one person, there is no second reviewer for security-sensitive code, and knowledge leaves with that person. Before committing internally, confirm the developer has tested against the EspoCRM release process, can set up a clean test instance, and has time reserved for upgrade work. Many organizations pair an internal owner with an external review or retainer for the first release.

How are EspoCRM extensions kept working after an upgrade?

Upgrade safety comes from keeping custom code in the extension structure rather than editing core files, then testing each new release before production. Declare compatible EspoCRM version ranges in the manifest so incompatible installs are visible. Keep the extension's files, scripts and hooks in version control, and run them against a staging copy of production data. Since EspoCRM 10.0, the UI upgrade is disabled by default, so plan the command-line upgrade and your PHP version together. Check the EspoCRM server configuration docs for supported PHP versions before you choose a target.

What should testing cover before an EspoCRM extension ships?

Testing should cover the install, upgrade and uninstall paths on a clean instance, not only the happy path. Verify that each role sees only the fields, buttons and records its permissions allow, and that formula scripts and hooks behave correctly on create, update and delete. Test imports and bulk actions, since they can bypass some UI logic. Run the suite against the EspoCRM version you plan to upgrade to, and document a rollback step that restores the database and the previous extension version. Ask for these test deliverables explicitly in the quote.

Who owns the extension code and what license applies?

Ownership should be written into the agreement. Ask for the source code in a repository your organization controls, along with installation notes, the manifest, test scripts and a handover session. EspoCRM is licensed under AGPL-3.0, according to the license metadata of the espocrm/espocrm GitHub repository, so ask whether your extension will be distributed to others or used only internally. Distribution and network-use obligations depend on how the code is deployed and shared, so confirm the license choice with your legal counsel before release.

Where to confirm the extension format before building

Before you write a single line of extension code, confirm the current extension format against the official EspoCRM sources. The EspoCRM documentation and the espocrm/espocrm GitHub repository are the authoritative references for the structure of an extension, including the expected layout of the package and the files that the installer reads. Treat anything you find in older blog posts, forum answers or third-party examples as a starting point to verify, not as the specification.

Details change between major versions, so the release notes for your target version matter more than the version you first learned from. EspoCRM 10.0.0 was released on July 2, 2026, and the previous major version, 9.0.0, was released on January 20, 2025. Check the espocrm/espocrm releases page for the exact target release, read its notes for changes to extension handling, and record the version you tested against in your build documentation. Confirm with the vendor how the manifest version range and compatibility rules apply to any release you plan to target.

If your package depends on official functionality, such as reports, workflows or Business Process Management, review the EspoCRM Advanced Pack page to understand what that official extension provides and which deployments include it. Also confirm whether your target instance is self-hosted or cloud, because the set of extensions and install options can differ between them. Ask your DeployOpen contact to validate the format against the release you plan to deploy before any custom package reaches a shared environment.

How DeployOpen scopes and delivers EspoCRM extensions

DeployOpen plans, builds, tests, documents and supports extensions for self-hosted platforms inside your own infrastructure. For EspoCRM, we treat the extension as a deliverable with a written scope, a version range you can test against, and a handover your own team can install and maintain.

How we scope

  • Written scope lists each entity, layout, formula, hook or workflow the extension must ship.
  • We record which EspoCRM versions and PHP versions the package must support before any code is written.
  • Scope names the extensions, Advanced Pack features or custom code the package depends on and must not replace.

How we build

  • Custom code lives in the extension's files and scripts directories, not in edited core files.
  • We check each manifest version range against the EspoCRM releases your environment will run.
  • Packages are tested on a staging copy of your instance with production-like data before installation is approved.

How we hand over

  • Handover includes the packaged extension, install and uninstall steps, and a written upgrade-safety note.
  • Documentation explains which customizations are custom code and which are standard EspoCRM configuration.
  • Your team receives a walkthrough of installing, testing and rolling back the package on its own instance.

How we support

  • Fixed-scope engagements end with agreed deliverables, and any later change starts from a new written scope.
  • Ongoing support, upgrade testing for new EspoCRM releases, and continuous testing are available as a retainer.
  • Ask any partner to confirm who owns the source code, the license terms and the version range in writing.

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.