# Settings model

Apps rarely ship with a single fixed configuration. A CRM might need different currency symbols per workspace, a ticketing app might hide certain [actions](/developer-guide/building-blocks/actions.md) for specific teams, and an integration app needs API credentials that must never reach the browser. Comind.work handles all of this through three settings levels - each targeting a different audience and taking effect at a different point in the [app lifecycle](/developer-guide/app-architecture/app-lifecycle.md).

## Three settings levels[​](#three-settings-levels "Direct link to Three settings levels")

### App system settings (compile-time)[​](#app-system-settings-compile-time "Direct link to App system settings (compile-time)")

System admins configure these settings for the entire platform. They apply to every workspace where the app is installed.

* Changing a system setting triggers redeployment (recompilation).
* Access in code via `import` - the value is preprocessed and baked in at compile time.
* System settings can update the app schema through the app-mutator (any aspect of the data model, actions, or layouts).

These are sometimes called deployment settings or system constants.

### App workspace settings (runtime)[​](#app-workspace-settings-runtime "Direct link to App workspace settings (runtime)")

Workspace admins configure these settings for their own workspace. They take effect immediately - no redeployment required.

* Access in code via `ComindView.getVars()` (client-side) or `ComindServer.getVars()` (server-side), which returns the value for the current workspace at runtime.
* Each workspace can have a different value for the same setting.

warning

Do not use `import` to read workspace settings. An `import` resolves at compile time, so it returns the default value rather than the workspace-specific one. Always use `ComindView.getVars()` (client-side) or `ComindServer.getVars()` (server-side) for runtime settings. See [Common mistakes](/developer-guide/how-to/common-mistakes.md) for more pitfalls.

#### Workspace variables in practice[​](#workspace-variables-in-practice "Direct link to Workspace variables in practice")

Define default values for workspace variables in `settings/index.ts`. Workspace admins can override these values per workspace without touching code:

```
// settings/index.ts
export default {
    vars: {
        default_assignee_group: 'Support team',
        sla_hours: '24',
        enable_auto_assignment: 'true',
    },
} satisfies AppSettings;
```

Read variables on the server side inside action logic:

```
// actions/logic/index.ts
function onSubmit() {
    const vars = ComindServer.getVars();
    const slaHours = parseInt(vars.sla_hours || '24');
    entity.c_due_date = new Date(Date.now() + slaHours * 3600000);
}
```

Read variables on the client side inside view logic:

```
// views/logic/index.ts
function onReady() {
    const vars = ComindView.getVars();
    if (vars.enable_auto_assignment === 'true') {
        // show auto-assignment UI
    }
}
```

All variable values are strings. Parse them to the type you need (`parseInt`, `=== 'true'`, `.split(',')`, etc.). If a workspace admin has not overridden a variable, the default from `settings/index.ts` is returned.

### App personalization settings (runtime)[​](#app-personalization-settings-runtime "Direct link to App personalization settings (runtime)")

Individual end-users configure these settings for themselves. The scope can be per-user within a single workspace or per-user across all workspaces. Personalization settings control app-defined UI preferences and behavior customizations.

## Settings types[​](#settings-types "Direct link to Settings types")

Within each level, settings fall into three categories:

| Type                   | Scope                                            | Examples                                                               |
| ---------------------- | ------------------------------------------------ | ---------------------------------------------------------------------- |
| Common settings        | system-wide, every app                           | hide conversation/history in form, disable email notifications         |
| App-specific variables | per-app, introduced by the developer             | currency for a field, default assigned user, default record visibility |
| App-specific secrets   | per-app, server-only (never sent to the browser) | auth tokens for external systems, passwords, API credentials           |

Customized settings are not part of the code. They cannot be pulled with `npx comind pull`. Changing a setting value does not change the app source - it only changes runtime behavior.

## File structure[​](#file-structure "Direct link to File structure")

Settings files live inside the `settings/` directory of your [app package](/developer-guide/app-architecture/package-structure.md):

* `settings/index.ts` - exports `AppSettings` containing backlinks, mutations, and config.
* `settings/app-mutator.ts` - schema post-processing logic that runs based on system settings. Use this to add, remove, or modify [fields](/developer-guide/building-blocks/fields-and-field-options.md), actions, or layouts at compile time.
* `settings/secrets.ts` - sensitive configuration using `COMIND_SECRET_*` environment variables. Secrets are resolved on the server only and are never included in client bundles.

All three files are optional. Add them as your app's configuration needs grow.

### The app-mutator[​](#the-app-mutator "Direct link to The app-mutator")

The app-mutator is a function that receives the full app schema object and modifies it before deployment. It runs at install time (compile-time) - not at runtime. Changes take effect after `npx comind install`. This is how workspace admins customize apps without code changes.

```
// settings/app-mutator.ts
export default function (app: AppSchemaObject) {
    // Dynamically set lookup options from workspace settings
    const types = AppSchema.getVars().available_types?.split(',') || [];
    app.fields.c_type.lookupOptions = types.map(t => ({
        uid: t.trim(),
        caption: t.trim(),
    }));

    // Conditionally remove a field based on settings
    const enableBilling = AppSchema.getVars().enable_billing;
    if (enableBilling !== 'true') {
        delete app.fields.c_billing_code;
    }
}
```

Because the mutator runs at compile time, any change you make here requires redeployment. Use it for structural changes - adding or removing fields, adjusting lookup options, toggling entire features. For behavior that should change without redeployment, use workspace variables instead.

## What you can customize[​](#what-you-can-customize "Direct link to What you can customize")

### Data model[​](#data-model "Direct link to Data model")

Settings can override [field](/developer-guide/building-blocks/fields-and-field-options.md) properties without modifying source code:

* Field caption, default value, prefix, mask, and help text
* Reference field target workspace and app
* User lookup restriction by group

### Actions[​](#actions "Direct link to Actions")

Settings can override [action](/developer-guide/building-blocks/actions.md) properties:

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

### Operations and calculated fields[​](#operations-and-calculated-fields "Direct link to Operations and calculated fields")

* Variables used in calculation expressions
* External service URLs
* Auth credentials for external service calls (via secrets)

## Secrets[​](#secrets "Direct link to Secrets")

Secrets protect sensitive values such as API keys and passwords. Define them in `settings/secrets.ts` using the `COMIND_SECRET_` prefix:

```
// settings/secrets.ts
export const secrets = {
    COMIND_SECRET_OPENAI_KEY: '',
    COMIND_SECRET_SMTP_PASSWORD: '',
};
```

Access secrets at runtime through `AppSchema.getSecrets()` inside server-side action logic:

```
// actions/logic/index.ts
import { secrets } from '../settings/secrets';

function sendToExternalApi() {
    const { COMIND_SECRET_OPENAI_KEY } = AppSchema.getSecrets();
    // use the key for external API calls
}
```

warning

Secrets are only available in `actions/logic/` (server-side). Importing secrets from `views/logic/` causes a build error because it would expose them to the browser.

Secrets are resolved on the server and are never sent to the browser. Do not import secret values with a regular `import` in client-side code - that would embed the value in the browser bundle. See [Common mistakes](/developer-guide/how-to/common-mistakes.md) for details.

note

Treat the server environment as a trusted boundary and restrict access to deployment configurations accordingly.

## Key takeaways[​](#key-takeaways "Direct link to Key takeaways")

* **System settings** change the compiled schema. Use `import` and the app-mutator.
* **Workspace settings** change runtime behavior per workspace. Use `ComindView.getVars()` or `ComindServer.getVars()`.
* **Personalization settings** let end-users tweak their own experience.
* **Secrets** stay on the server. Use `COMIND_SECRET_*` env vars and `settings/secrets.ts`.
* Settings live outside the code - they are not version-controlled and cannot be pulled.
