# Actions

Actions represent the operations a user can perform on a record - approving a task, closing a ticket, assigning a deal. Each action triggers a state change, runs server-side logic, or both. Actions are defined in `actions/index.ts` and exported as an `AppActionArray`.

## Base actions[​](#base-actions "Direct link to Base actions")

Every app inherits a set of core actions from the `@comind/base` plugin. You do not need to redeclare them:

* `add` - create a new record
* `edit` - modify an existing record
* `delete` - remove a record
* `copy` - duplicate a record
* `move` - move a record to another workspace
* `archive` - archive a record

Apps add their own actions on top of these. A CRM deal might add `qualify`, `negotiate`, and `close-won`. A support ticket might add `escalate`, `resolve`, and `reopen`. See [Plugins](/developer-guide/building-blocks/plugins-and-inheritfrom.md) for how base actions merge with app-specific ones.

## Defining actions[​](#defining-actions "Direct link to Defining actions")

Actions are defined in `actions/index.ts` and typed as `AppActionArray`. Each entry describes one operation the user can invoke:

```
import type { AppActionArray } from '@comind/api';

const actions = [
  {
    uid: 'approve',
    caption: 'Approve',
  },
  {
    uid: 'reject',
    caption: 'Reject',
  },
] as const satisfies AppActionArray;

export default actions;
```

The `uid` property is the action's unique identifier. It links the action definition to its [logic handler](#action-logic) and [precondition](#preconditions). State transitions are not declared on the action definition itself - instead, you set `entity.state` inside the action logic function. This structure is what makes actions appear as buttons in the UI.

You can split definitions across multiple files and re-export them from `actions/index.ts`, the same way [Fields](/developer-guide/building-blocks/fields-and-field-options.md) are organized into topical modules. The engine only cares about the final exported `AppActionArray`.

## Action logic[​](#action-logic "Direct link to Action logic")

Server-side code for actions lives in `actions/logic/index.ts`, exported as a default object typed with `satisfies ActionLogic<typeof entity>`. This code runs on the server - not in the browser.

```
import { entity } from '#typings';
import type { ActionLogic } from '@comind/api';

export default {
    approve,
    reject,
} satisfies ActionLogic<typeof entity>;

function approve() {
    entity.state = 'approved';
    entity.c_approved_date = new Date().toISOString();
    entity.c_approved_by = currentUser.id;
}

function reject() {
    entity.state = 'rejected';
    entity.c_rejection_reason = entity.c_rejection_reason || 'No reason provided';
}
```

Each method name matches an action's `uid`. When that action fires, the engine calls the matching method. Inside the method you can read and write fields on `entity` (including `entity.state` for state transitions), compare against `entityOld`, query other records, call external APIs, or generate files.

Do not use `as const satisfies ActionLogic` - omit `as const`. Do not import `makeActionLogic` - the builder auto-injects it during compilation.

### Available globals[​](#available-globals "Direct link to Available globals")

Action logic methods have access to several global objects. The most commonly used are:

* `entity` - current record being processed (mutable)
* `entityOld` - previous state of the record (read-only)
* `records` - array of records when running bulk operations
* `currentUser` - the user executing the action, with methods for role checks (`isGlobalAdmin`, `isWorkspaceAdmin`) and group membership (`isInGroup()`, `isInAnyOfGroups()`)
* `ComindServer` - the main server API for querying, creating, updating, and deleting records, plus ACL helpers and utility methods
* `RestApi` - HTTP client for calling external APIs
* `Files` - file operations for Excel, CSV, PDF, and ZIP
* `AppSchema` - install or uninstall apps, access variables and secrets
* `Utils` - formatting, date math, GUID generation, hashing
* `console` - logging with `log`, `debug`, `warn`, `info`, and `error`

For the full method reference on each global, see [Server-side API](/developer-guide/reference/server-side-globals.md).

## Preconditions[​](#preconditions "Direct link to Preconditions")

Precondition checks live in `actions/logic/preconditions.ts`, exported as a default object typed with `satisfies PreconditionLogic<typeof entity>`. They control when an action is available to a user. If a precondition returns `false`, the action button is hidden or disabled in the UI.

```
import { entity } from '#typings';
import type { PreconditionLogic } from '@comind/api';

export default {
    approve: () => currentUser.isInGroup('Reviewers') && entity.state === 'review',
    delete: () => currentUser.isWorkspaceAdmin || entity.created_by === currentUser.id,
} satisfies PreconditionLogic<typeof entity>;
```

Use preconditions to enforce business rules based on state, user role, field values, or group membership. The method name matches the action it guards. You can override preconditions for base actions like `delete` to add stricter access control.

Do not import `makePreconditionLogic` - the builder auto-injects it during compilation.

## Bulk operations[​](#bulk-operations "Direct link to Bulk operations")

When an action runs on multiple records at once (mass edit), the `records` array is populated instead of `entity`. Your action logic can iterate over all affected records:

```
function bulk_approve() {
    for (const record of records) {
        record.c_approved_date = new Date().toISOString();
    }
}
```

## Impersonation[​](#impersonation "Direct link to Impersonation")

The `ComindServer` API supports a `runAsUser` parameter on `createAppRecord()`, `updateAppRecord()`, and `deleteAppRecord()`. This lets action logic create or modify records as a different user - useful for automated workflows where the system needs to act on someone's behalf.

```
ComindServer.createAppRecord('TASK', { title: 'Follow-up' }, targetUserId);
```

## Customizing actions through settings[​](#customizing-actions-through-settings "Direct link to Customizing actions through settings")

Actions can be adjusted through app settings without changing code. The [Settings model](/developer-guide/building-blocks/settings-model.md) supports overriding:

* Action caption (the label shown on the button)
* Action visibility (show or hide specific actions)
* Help text displayed alongside the action
* Group names used in precondition checks

This lets workspace administrators fine-tune the UI for their team without modifying the app package.

## Plugin logic combination[​](#plugin-logic-combination "Direct link to Plugin logic combination")

When multiple [plugins](/developer-guide/building-blocks/plugins-and-inheritfrom.md) define `ActionLogic` or `PreconditionLogic`, their methods are combined during composition. Each plugin's logic runs in sequence - the base plugin's logic executes first, then each additional plugin's logic in dependency order, and finally the app's own logic. This means a plugin can set up default behavior that the app layer refines or overrides.

Tutorial

For a step-by-step walkthrough of creating an action with preconditions and server-side logic, see [Create new action](/developer-guide/tutorials/tutorial-customize-app/create-new-action.md).

For details on where action files sit within the app directory, see [Package structure](/developer-guide/app-architecture/package-structure.md).

## Related[​](#related "Direct link to Related")

* [How to create an AI-based action](/developer-guide/how-to/advanced/how-to-create-ai-based-action.md) - use OpenAI to generate content from action logic
* [External API calls](/developer-guide/how-to/advanced/external-api-calls.md) - the `RestApi` builder pattern for calling REST APIs from actions
