Plugins and inheritFrom
Comind apps are assembled from layers. A base plugin provides the fields and actions every app needs. Optional plugins add capabilities like notifications, followers, or color coding. Apps add their own domain-specific schema on top. The inheritFrom pattern takes this further - a customer-specific app can inherit from a standard platform app and override only what it needs.
This page covers how composition works, what the base plugin provides, the full plugin catalog, and how inheritFrom enables vertical customization.
Composition layers
Every app is built from at least two layers - the base plugin and the app itself. Most apps include several plugins in between:
@comind/base <-- foundation: core fields, actions, permissions
+ @comind/plugin-follower <-- followers field + UI
+ @comind/plugin-notification <-- notification logic (email, Slack, Teams)
+ @comind/plugin-color <-- color coding
+ ...other plugins
+ @comind/app-task <-- task-specific fields, actions, layouts
Adding a plugin is as simple as listing it in your app's package.json dependencies. The compilation pipeline handles the rest.
How composition works
The builder resolves and merges all layers in a four-step process:
- Dependency resolution - the builder reads
package.jsondependencies, filters packages matching@comind/(app-|block-|plugin-), and always includes@comind/base. - Recursive loading - each plugin's
fields/,actions/,views/, andsettings/directories are loaded from disk. - Deep merge - components merge in order: base, then plugins, then parent app (if using
inheritFrom), then the app itself. Arrays replace rather than concatenate. Individual fields can override earlier definitions. The engine tracks the origin of each component so you can trace where a field or action came from. - Logic combination -
ActionLogic,PreconditionLogic, andViewLogicfrom all plugins are combined. Each plugin's methods run in sequence. See Actions and View logic for details on how logic classes work.
Because arrays replace rather than merge, a plugin or app can completely redefine a list of options or layout sections introduced by an earlier layer.
The base plugin
@comind/base lives in plugins-common/base/ and provides the fields and actions that every app shares. You never need to declare it as a dependency - the builder adds it automatically.
Fields:
fields/common-fields.ts- id, title, description, state, datesfields/system-fields.ts- creation_date, update_date, created_byfields/permissions-fields.ts- ACL fieldsfields/link-fields.ts- parent, folder, backlinksfields/imported-fields.ts- import-related fields
Actions:
actions/core-actions.ts- add, edit, delete, copyactions/misc-actions.ts- move, archive, and other utility actions
Layouts:
views/layouts/- default form layout used when an app does not define its own
For the full directory structure of a plugin or app, see Package structure.
Plugin catalog
The platform ships 18 plugins in plugins-common/. Each one adds a self-contained capability that any app can opt into:
| Plugin | Purpose |
|---|---|
base | Core fields and actions for all apps |
plugin-notification | Email, Slack, and Teams notifications |
plugin-follower | Watch and follow records |
plugin-permission | Field-level and record-level ACL |
plugin-reminder | Scheduled reminders |
plugin-recurrence | Recurring record creation |
plugin-color | Color coding |
plugin-icon | Custom icons |
plugin-cover-image | Cover images |
plugin-contact | Contact info fields |
plugin-country | Country lookup |
plugin-custom-fields-section | Custom fields UI section |
plugin-rice-score | RICE prioritization scoring |
plugin-task-rate | Task rating |
plugin-google-drive | Google Drive integration |
plugin-cloudflare | Cloudflare Workers integration |
plugin-demo-data | Demo data generation |
plugin-admin-edit | Admin editing capabilities |
plugin-pack-basic | Basic plugin bundle |
Organizations can also create their own custom plugins following the same package structure conventions.
The inheritFrom pattern
inheritFrom enables a DVL (Developer Vertical Layer) override pattern. A standard platform app serves as the base, and a customer-specific app inherits from it, overriding only what it needs:
@comind/base <-- foundation
+ plugins <-- shared capabilities
+ @comind/app-task <-- standard platform app
+ @customer/app-custom-task <-- customer override (inheritFrom: "@comind/app-task")
The customer app declares inheritFrom in its package.json:
{
"comindApp": {
"inheritFrom": "@comind/app-task",
"publishingAlias": "TASK"
}
}
When the builder encounters inheritFrom, it:
- Builds the parent app's full schema, including all of its own plugins.
- Overlays the child app's fields, actions, layouts, and settings on top.
- Keeps the same
publishingAlias- the child app replaces the standard app in that workspace.
Multi-level chains
Inheritance can go multiple levels deep. App C inherits from B, which inherits from A. The builder resolves the full chain before applying the final overlay.
Different packages can share the same alias. For example, TASK-for-developers, TASK-for-marketing, and TASK-for-basic are all separate packages that use publishingAlias: "TASK". Each workspace gets whichever variant is deployed to it.
This pattern keeps the standard app clean and upgradeable while giving customers full control over their vertical-specific behavior.