23cae2040a
App manifests could point the logo and screenshots at either external
URLs or public folder paths, and that was handled inconsistently across
install, sync and the marketplace.
This makes assets always bundled files:
- Manifests now use `logo` and `galleryImages` (a `string[]` of public
folder paths) instead of `logoUrl` and `screenshots`. The old fields
still work but are deprecated. Gallery order comes from the array index.
Normalization (deprecated-field migration, and warning about + ignoring
external URLs) happens in `defineApplication`, so the warnings surface
at define time.
- Logo is stored as a File record (`logoFileId`).
- The registration gallery is configured via a `settings` jsonb column
on `applicationRegistration` (`{ galleryImages: string[] }`) — populated
from the manifest, read by the marketplace detail (falling back to the
legacy `screenshots` column, then the manifest). No dedicated gallery
table.
- The marketplace detail DTO and front now use `galleryImages`.
Verified against a local Postgres: the fast instance commands run with
no pending-migration diff, the schema is correct, and the server boots.
Typecheck, lint, codegen and the application unit tests pass.
Not included yet: rehosting assets into storage for npm catalog and
tarball registrations, versioned cache busting on the serving route, and
a backfill for existing installs.
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22564?utm_source=github"
rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review
in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a>
122 lines
6.7 KiB
Plaintext
122 lines
6.7 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 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 and post-install 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.
|
||
|
||
## 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>
|