Files
twenty/packages/twenty-apps/fixtures/rich-app/application.config.ts
T
martmull 25fe66565c feat(applications): add type and options to application variables (#22157)
## Before
<img width="1452" height="709" alt="image"
src="https://github.com/user-attachments/assets/cd384ffa-cbe6-49d5-a807-ca8d580f55a9"
/>

<img width="1074" height="452" alt="image"
src="https://github.com/user-attachments/assets/720d38db-3495-4032-8831-17d24ec6a7e7"
/>

## After

<img width="1421" height="865" alt="image"
src="https://github.com/user-attachments/assets/2275c996-c895-4800-8324-2aa2ddfddd43"
/>

<img width="1348" height="870" alt="image"
src="https://github.com/user-attachments/assets/3e1a891d-6db0-4cbd-870a-2a5bbde4929d"
/>


## Summary

Adds typed application variables with optional select **options**. This
is the other half of #22059, split out from the custom-settings-tab
removal.

## Changes

- **Shared types**: `ApplicationVariable` / `ServerVariables` gain an
optional `type` (a `FieldMetadataType` subset — `TEXT`, `BOOLEAN`,
`NUMBER`, `DATE`, `SELECT`, `MULTI_SELECT`, `RAW_JSON`, `RICH_TEXT`,
`ARRAY`, …) and select `options`. New
`serializeApplicationVariableValue` /
`deserializeApplicationVariableValue` helpers convert typed values
to/from the encrypted string storage.
- **Server**: `type`/`options` columns on `applicationVariable` and
`applicationRegistrationVariable` (entities + DTOs), a fast `2-17`
instance command, manifest processing via the serialization helpers, and
a `QueryDeepPartialEntity` cast where the manifest JSON column is
persisted.
- **Frontend**: a polymorphic `SettingsApplicationVariableInput` that
renders the native `Form*` field component for each type (boolean,
number, date/date-time, select, multi-select, array, raw JSON, rich
text, text); fragment/query updates to fetch `type`/`options`.
- **SDK**: `defineApplication` validates that `SELECT`/`MULTI_SELECT`
variables declare non-empty `options` at build time (since `options` is
kept structurally optional for TypeORM/SDK compatibility).

Variables default to `TEXT` when no type is given, so existing manifests
are unaffected.

## Notes

The generated GraphQL artifacts (`type`/`options` on the variable types)
are regenerated by codegen; that change accompanies this PR.

https://claude.ai/code/session_013Z7UB35V2mvUozh55QHG23

---
_Generated by [Claude
Code](https://claude.ai/code/session_013Z7UB35V2mvUozh55QHG23)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22157?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. -->
2026-07-03 10:52:22 +02:00

176 lines
5.6 KiB
TypeScript

import { defineApplication, FieldType } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-function.role';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'Rich App',
description: 'A simple rich app',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Alex Karp',
isSecret: false,
},
GREETING_TEXT: {
universalIdentifier: 'ad19edc5-4cc5-4003-a996-aef53a5c8de0',
description: 'Free text shown on the postcard',
type: FieldType.TEXT,
value: 'Hello from Rich App',
},
ENABLE_TRACKING: {
universalIdentifier: 'b9c58bb2-58c3-4c6c-9877-498ea6c03fde',
description: 'Toggle delivery tracking',
type: FieldType.BOOLEAN,
value: true,
},
MAX_POSTCARDS: {
universalIdentifier: '5f4497e4-9030-4085-85eb-2c48b8d53713',
description: 'Maximum postcards per batch',
type: FieldType.NUMBER,
value: 10,
},
DISCOUNT_RATE: {
universalIdentifier: 'd32f810f-06bb-4d29-b0ee-1dc4228f7cb8',
description: 'Bulk discount rate applied at checkout',
type: FieldType.NUMERIC,
value: 2.5,
},
CAMPAIGN_START_DATE: {
universalIdentifier: '5aa4fcec-e8a3-4bc1-9c7f-762e1f9dfb40',
description: 'Date the campaign starts',
type: FieldType.DATE,
value: '2026-01-01',
},
CAMPAIGN_START_AT: {
universalIdentifier: '273d0cfd-d6ab-4148-8816-c4a5d4b1d600',
description: 'Exact moment the campaign starts',
type: FieldType.DATE_TIME,
value: '2026-01-01T09:00:00.000Z',
},
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' },
{ label: 'Asia-Pacific', value: 'apac' },
],
value: 'eu',
},
ENABLED_CHANNELS: {
universalIdentifier: '706a2b08-8284-4715-8bc0-922a99cb26af',
description: 'Channels the app is allowed to use',
type: FieldType.MULTI_SELECT,
options: [
{ label: 'Email', value: 'email' },
{ label: 'SMS', value: 'sms' },
{ label: 'Postcard', value: 'postcard' },
],
value: ['email', 'postcard'],
},
ALLOWED_TAGS: {
universalIdentifier: 'c1c8a4c9-9130-4ab8-8dce-18d2b50879ed',
description: 'Free-form tags applied to recipients',
type: FieldType.ARRAY,
value: ['vip', 'returning'],
},
PROVIDER_CONFIG: {
universalIdentifier: '183d5285-c70c-4f29-96e0-c68659fbe5ae',
description: 'Raw JSON configuration for the printing provider',
type: FieldType.RAW_JSON,
value: { retries: 3, timeoutMs: 5000 },
},
WELCOME_MESSAGE: {
universalIdentifier: '25a66ff5-8458-498e-9e7e-33ea458a6f3c',
description: 'Rich text welcome message',
type: FieldType.RICH_TEXT,
value: { blocknote: null, markdown: 'Welcome to **Rich App**!' },
},
},
serverVariables: {
POSTCARD_API_KEY: {
description: 'API key for the postcard printing service',
isSecret: true,
isRequired: true,
},
POSTCARD_SENDER_NAME: {
description: 'Default sender name on postcards',
isSecret: false,
isRequired: false,
},
POSTCARD_DELIVERY_SPEED: {
description: 'Delivery speed requested from the provider',
type: FieldType.SELECT,
options: [
{ label: 'Standard', value: 'standard' },
{ label: 'Express', value: 'express' },
],
isSecret: false,
isRequired: true,
},
POSTCARD_DAILY_LIMIT: {
description: 'Maximum postcards the provider will accept per day',
type: FieldType.NUMBER,
isSecret: false,
isRequired: false,
},
POSTCARD_UNIT_PRICE: {
description: 'Price charged by the provider per postcard',
type: FieldType.NUMERIC,
isSecret: false,
isRequired: false,
},
POSTCARD_SANDBOX_MODE: {
description: 'Send postcards through the provider sandbox instead of production',
type: FieldType.BOOLEAN,
isSecret: false,
isRequired: false,
},
POSTCARD_CONTRACT_START_DATE: {
description: 'Date the provider contract starts',
type: FieldType.DATE,
isSecret: false,
isRequired: false,
},
POSTCARD_CONTRACT_RENEWAL_AT: {
description: 'Exact moment the provider contract renews',
type: FieldType.DATE_TIME,
isSecret: false,
isRequired: false,
},
POSTCARD_ENABLED_REGIONS: {
description: 'Regions the provider is allowed to ship to',
type: FieldType.MULTI_SELECT,
options: [
{ label: 'Europe', value: 'eu' },
{ label: 'United States', value: 'us' },
{ label: 'Asia-Pacific', value: 'apac' },
],
isSecret: false,
isRequired: false,
},
POSTCARD_WEBHOOK_EVENTS: {
description: 'Provider webhook events the app subscribes to',
type: FieldType.ARRAY,
isSecret: false,
isRequired: false,
},
POSTCARD_PROVIDER_CONFIG: {
description: 'Raw JSON configuration for the printing provider',
type: FieldType.RAW_JSON,
isSecret: false,
isRequired: false,
},
POSTCARD_INVOICE_NOTE: {
description: 'Rich text note appended to provider invoices',
type: FieldType.RICH_TEXT,
isSecret: false,
isRequired: false,
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});