## Summary - Converts applicationVariable from a bespoke sync path to a proper SyncableEntity, unifying it with the workspace migration pipeline used by all other manifest-managed entities (agent, skill, frontComponent, webhook, etc.) - Removes the upsertManyApplicationVariableEntities method and its direct-DB-mutation approach in favor of the standard validate → build → run action handler pipeline - Adds universalIdentifier, deletedAt columns and makes applicationId NOT NULL via an instance command migration ## Motivation Before this change, applicationVariable was the only manifest-managed entity that bypassed ApplicationManifestMigrationService.syncMetadataFromManifest(). It used a bespoke service method called directly from syncApplication(), creating two mental models, two validation styles, and two cache invalidation patterns. Now there's one unified pipeline for all manifest entities. ## What changed ### Entity refactor: - ApplicationVariableEntity now extends SyncableEntity (gains universalIdentifier, non-nullable applicationId with CASCADE, soft-delete via deletedAt) ### New flat entity layer (flat-application-variable/): - Type, maps type, editable properties constant, entity-to-flat converter, cache service, module ### New migration pipeline wiring: - Manifest converter (fromApplicationVariableManifestToUniversalFlatApplicationVariable) - Validator service (FlatApplicationVariableValidatorService) - Builder service (WorkspaceMigrationApplicationVariableActionsBuilderService) - Create/Update/Delete action handlers with secret encryption hooks - Registered in orchestrator, builder module, runner module, and all type registries ### Removed bespoke path: - Deleted upsertManyApplicationVariableEntities from ApplicationVariableEntityService - Removed its call from ApplicationSyncService.syncApplication() - Kept update() (operator-set value at runtime) and getDisplayValue() (runtime display) ### Database migration: - Instance command to add columns, backfill universalIdentifier, enforce NOT NULL constraints, and update indexes ## Test plan - npx nx typecheck twenty-server passes (0 errors) - Unit tests pass (application-variable.service.spec.ts, build-env-var.spec.ts) - Install an app with applicationVariables in its manifest → variables appear with correct universalIdentifier - Update app manifest (add/remove/modify a variable) → migration pipeline handles diff correctly - Operator-set value via update endpoint persists correctly with encryption - Uninstall app → variables cascade-deleted - app dev --once on example app syncs without errors
Postcard App — Twenty App Example
A rich example app showcasing all Twenty app entity types. Use this as a reference when building your own apps.
What's included
This app demonstrates every entity type available in the Twenty SDK:
| Entity | Files | What it shows |
|---|---|---|
| Application | src/application.config.ts |
App metadata, application variables, server variables |
| Objects | src/objects/ |
Custom objects with inline fields, junction tables |
| Fields | src/fields/ |
Standalone fields, relations (ONE_TO_MANY, MANY_TO_ONE), extending standard objects |
| Logic Functions | src/logic-functions/ |
HTTP routes, database event triggers, cron schedules, tool functions, install hooks |
| Front Components | src/components/ |
React components rendered inside Twenty's UI |
| Roles | src/roles/ |
Permission roles with object and field-level access control |
| Views | src/views/ |
Saved table views with column configuration |
| Navigation | src/navigation-menu-items/ |
Sidebar links targeting views |
| Skills | src/skills/ |
AI skill providing context to agents |
| Agents | src/agents/ |
AI agent with a system prompt |
| Page Layouts | src/page-layouts/ |
Custom record page with a front component widget |
Getting started
# From this directory
yarn install
yarn twenty dev
Or use it as a template via create-twenty-app:
npx create-twenty-app@latest my-app --example postcard