4f9fd6f674
## Summary Restores the application **custom settings tab** feature that was removed in #22156. This reverts that removal so applications can again expose a custom settings tab via a front component. ## Changes - Restore the `SettingsApplicationCustomTab` component and its tab entry/rendering in `SettingsApplicationDetails`. - `ApplicationManifestMigrationService` syncs `settingsCustomTabFrontComponent` from application manifests again (`syncDefaultRoleAndSettingsCustomTab`), resolving the front component from `settingsCustomTabFrontComponentUniversalIdentifier`. - Remove the deprecation annotations added by #22156: - `ApplicationDTO.settingsCustomTabFrontComponentId` (drop GraphQL `@deprecated`) - `ApplicationManifest.settingsCustomTabFrontComponentUniversalIdentifier` - the `settingsCustomTabFrontComponentId` column comment on `ApplicationEntity` - Regenerate the corresponding GraphQL schema/types to drop the `@deprecated` reason. The DB column was never dropped, so no schema migration is required. --- _Generated by [Claude Code](https://claude.ai/code/session_01A6aoLa5kZjba9C3uwo6nay)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23256?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
123 lines
7.1 KiB
Plaintext
123 lines
7.1 KiB
Plaintext
---
|
||
title: Application Config
|
||
description: Declare your app's identity, default role, variables, and marketplace metadata with defineApplication.
|
||
icon: "rocket"
|
||
---
|
||
|
||
Every app must have exactly one `defineApplication` call. It declares:
|
||
|
||
- **Identity** — universal identifier, display name, description.
|
||
- **Permissions** — which role its logic functions and front components run under.
|
||
- **Variables** *(optional)* — key–value pairs exposed to your code as environment variables.
|
||
- **Pre-install / post-install / uninstall hooks** *(optional)* — see [Logic Functions](/developers/extend/apps/logic/logic-functions).
|
||
|
||
```ts src/application-config.ts
|
||
import { defineApplication } from 'twenty-sdk/define';
|
||
|
||
export default defineApplication({
|
||
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
|
||
displayName: 'My Twenty App',
|
||
description: 'My first Twenty app',
|
||
applicationVariables: {
|
||
DEFAULT_RECIPIENT_NAME: {
|
||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||
description: 'Default recipient name for postcards',
|
||
value: 'Jane Doe',
|
||
isSecret: false,
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
Notes:
|
||
- `universalIdentifier` fields are deterministic IDs you own. Generate them once and keep them stable across syncs.
|
||
- `applicationVariables` become environment variables for your functions and front components. In logic functions (server-side), they are available as `process.env.VARIABLE_NAME`. In front components, use `getApplicationVariable('VARIABLE_NAME')` from `twenty-sdk/front-component`. Variables marked with `isSecret: true` are only injected into logic functions. Front components receive only non-secret variables.
|
||
- The default role is detected automatically from the role file marked with [`defineApplicationRole()`](/developers/extend/apps/config/roles) — you do not need to reference it from `defineApplication()`.
|
||
- Pre-install, post-install, and uninstall functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`.
|
||
- Passing `defaultRoleUniversalIdentifier` explicitly is still supported for backward compatibility, but is deprecated in favor of `defineApplicationRole()`.
|
||
- `serverVariables` are instance-scoped configuration and secrets (e.g. API keys). Unlike `applicationVariables`, they declare no value in the manifest — the workspace operator fills them in from the app's settings, and they are injected into logic functions only once set.
|
||
- To render a custom configuration UI inside the app's **Settings** tab (in place of the default variable configuration section), declare a front component with [`defineSettingsFrontComponent()`](/developers/extend/apps/layout/front-components#custom-settings-component) in its own file. Only one is allowed per app. System-managed sections (auto-upgrade, App URL, connections) always remain visible.
|
||
|
||
## Variable types
|
||
|
||
Both `applicationVariables` and `serverVariables` accept an optional `type` (and, for `SELECT` / `MULTI_SELECT`, an `options` list). Supported types: `TEXT` (default), `BOOLEAN`, `NUMBER`, `NUMERIC`, `DATE`, `DATE_TIME`, `SELECT`, `MULTI_SELECT`, `ARRAY`, `RAW_JSON`, `RICH_TEXT`.
|
||
|
||
```ts src/application-config.ts
|
||
import { defineApplication, FieldType } from 'twenty-sdk/define';
|
||
|
||
export default defineApplication({
|
||
// ...identity, role...
|
||
applicationVariables: {
|
||
MAX_POSTCARDS: {
|
||
universalIdentifier: '5f4497e4-9030-4085-85eb-2c48b8d53713',
|
||
description: 'Maximum postcards per batch',
|
||
type: FieldType.NUMBER,
|
||
value: 10,
|
||
},
|
||
DEFAULT_REGION: {
|
||
universalIdentifier: '76c5c321-b6b6-46eb-b4fc-f9f04bb04227',
|
||
description: 'Default shipping region',
|
||
type: FieldType.SELECT,
|
||
options: [
|
||
{ label: 'Europe', value: 'eu' },
|
||
{ label: 'United States', value: 'us' },
|
||
],
|
||
value: 'eu',
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
The `type` only affects **presentation and validation** — it selects the matching input in the workspace settings UI (a toggle, number field, dropdown, date picker, JSON editor, …) and lets the build validate your config (for example, `SELECT` / `MULTI_SELECT` must declare non-empty `options`). It does **not** change how the value reaches your code.
|
||
|
||
Values are **always injected as strings** — this is inherent to environment variables (`process.env.*` is string-only). When your logic function runs, the executor serializes each value by its declared `type` while building `process.env`, so the string format is consistent no matter how the value was set (manifest default, settings UI, or a previous version):
|
||
|
||
| Type | `process.env` string |
|
||
|------|----------------------|
|
||
| `TEXT`, `SELECT`, `DATE`, `DATE_TIME` | the raw value (`"eu"`, `"2026-01-01"`) |
|
||
| `BOOLEAN` | `"true"` / `"false"` |
|
||
| `NUMBER`, `NUMERIC` | decimal string (`"10"`, `"2.5"`) |
|
||
| `MULTI_SELECT`, `ARRAY` | JSON array (`'["email","postcard"]'`) |
|
||
| `RAW_JSON`, `RICH_TEXT` | JSON object (`'{"retries":3}'`) |
|
||
|
||
Parse the string back into the type you expect:
|
||
|
||
```ts
|
||
const maxCards = Number(process.env.MAX_POSTCARDS); // "10" -> 10
|
||
const enabled = process.env.ENABLE_TRACKING === 'true'; // "true" -> true
|
||
const channels = JSON.parse(process.env.ENABLED_CHANNELS ?? '[]'); // '["email"]' -> ["email"]
|
||
const config = JSON.parse(process.env.PROVIDER_CONFIG ?? '{}'); // '{"retries":3}' -> { retries: 3 }
|
||
```
|
||
|
||
The same applies to front components reading values via `getApplicationVariable('VARIABLE_NAME')` — the returned value is a string; parse it as needed.
|
||
|
||
## Default function role
|
||
|
||
The role declared with [`defineApplicationRole()`](/developers/extend/apps/config/roles) controls what the app's logic functions and front components can access:
|
||
|
||
- The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role.
|
||
- The typed API client is restricted to the permissions granted to that role.
|
||
- Follow least-privilege: declare only the permissions your functions need.
|
||
|
||
When you scaffold a new app, the CLI creates a starter role file at `src/roles/default-role.ts`. See [Roles & Permissions](/developers/extend/apps/config/roles) for the full reference.
|
||
|
||
## Marketplace metadata
|
||
|
||
If you plan to [publish your app](/developers/extend/apps/operations/publishing), these optional fields control how it appears in the marketplace:
|
||
|
||
| Field | Description |
|
||
|-------|-------------|
|
||
| `author` | Author or company name |
|
||
| `category` | App category for marketplace filtering |
|
||
| `logo` | Path to your app logo bundled in `public/` (e.g., `public/logo.png`) |
|
||
| `galleryImages` | Array of gallery image paths bundled in `public/` (e.g., `public/screenshot-1.png`) |
|
||
| `aboutDescription` | Longer markdown description for the "About" tab. If omitted, the marketplace uses the package's `README.md` from npm |
|
||
| `websiteUrl` | Link to your website |
|
||
| `termsUrl` | Link to terms of service |
|
||
| `emailSupport` | Support email address |
|
||
| `issueReportUrl` | Link to issue tracker |
|
||
|
||
<Note>
|
||
`logoUrl` and `screenshots` are deprecated aliases of `logo` and `galleryImages`. External absolute URLs (`http://` or `https://`) are not supported for these fields: they are dropped with a warning at build time. Bundle the images in your app's `public/` folder instead.
|
||
</Note>
|