snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
---
title: Konfigurace aplikace
description: Deklarujte identitu své aplikace, výchozí roli, proměnné a metadata tržiště pomocí defineApplication.
icon: rocket
---
Každá aplikace musí mít právě jedno volání `defineApplication`. Deklaruje:
* **Identita** — univerzální identifikátor, zobrazovaný název, popis.
* **Oprávnění** — pod jakou rolí běží její logické funkce a frontendové komponenty.
* **Proměnné** *(volitelné)* — páry klíč–hodnota zpřístupněné vašemu kódu jako proměnné prostředí.
* **Předinstalační / postinstalační / odinstalační hooky** *(volitelné)* — viz [Logické funkce](/l/cs/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,
},
},
});
```
Poznámky:
* Pole `universalIdentifier` jsou deterministické identifikátory, které vlastníte. Vygenerujte je jednou a zachovejte je stabilní napříč synchronizacemi.
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce a frontendové komponenty. V logických funkcích (na straně serveru) jsou dostupné jako `process.env.VARIABLE_NAME`. Ve frontendových komponentách použijte `getApplicationVariable('VARIABLE_NAME')` z `twenty-sdk/front-component`. Proměnné označené jako `isSecret: true` jsou předávány pouze do logických funkcí. Frontendové komponenty přijímají pouze proměnné, které nejsou tajné.
* Výchozí role je automaticky detekována ze souboru role označeného pomocí [`defineApplicationRole()`](/l/cs/developers/extend/apps/config/roles) — není potřeba na ni odkazovat z `defineApplication()`.
* Předinstalační, postinstalační a odinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`.
* Předávání `defaultRoleUniversalIdentifier` explicitně je stále podporováno kvůli zpětné kompatibilitě, ale je zastaralé ve prospěch `defineApplicationRole()`.
* `serverVariables` představují konfiguraci a tajné údaje vázané na instanci (např. klíče API). Na rozdíl od `applicationVariables` neuvádějí v manifestu žádnou hodnotu — operátor pracovního prostoru je vyplní v nastavení aplikace a do logických funkcí jsou injektovány až poté, co jsou nastaveny.
* Oba druhy proměnných přijímají `isDeprecated: true`. Použijte ji k vyřazení proměnné místo jejího smazání: ponechání deklarovaného klíče zachová uloženou hodnotu (jejím smazáním se zničí hodnota, kterou zadal uživatel) a proměnná se stále injektuje, takže ji váš kód může použít jako záložní hodnotu — `process.env.NEW_API_KEY ?? process.env.API_KEY`. Zastaralá proměnná zmizí z nastavení aplikace, jakmile nemá žádnou hodnotu, a nikdy se nezapočítává do kontroly konfigurace aplikace, takže `isDeprecated` má přednost před `isRequired`.
* Chcete-li vykreslit vlastní konfigurační uživatelské rozhraní na kartě **Settings** aplikace (namísto výchozí sekce pro konfiguraci proměnných), deklarujte frontovou komponentu pomocí [`defineSettingsFrontComponent()`](/l/cs/developers/extend/apps/layout/front-components#custom-settings-component) v jejím vlastním souboru. Na jednu aplikaci je povolena pouze jedna instance. Sekce spravované systémem (automatická aktualizace, App URL, připojení) zůstávají vždy viditelné.
## Typy proměnných
Jak `applicationVariables`, tak `serverVariables` přijímají volitelný `type` (a pro `SELECT` / `MULTI_SELECT` i seznam `options`). Podporované typy: `TEXT` (výchozí), `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',
},
},
});
```
`type` ovlivňuje pouze **prezentaci a validaci** — v uživatelském rozhraní nastavení pracovního prostoru vybere odpovídající vstup (přepínač, číselné pole, rozbalovací seznam, výběr data, editor JSON, …) a umožní sestavení ověřit vaši konfiguraci (například `SELECT` / `MULTI_SELECT` musí deklarovat neprázdné `options`). Nijak **nemění** způsob, jakým se hodnota dostane do vašeho kódu.
Hodnoty jsou **vždy předávány jako řetězce** — je to dáno povahou proměnných prostředí (`process.env.*` obsahuje pouze řetězce). Když se spustí vaše logická funkce, executor serializuje každou hodnotu podle jejího deklarovaného `type` při sestavování `process.env`, takže formát řetězce je konzistentní bez ohledu na to, jak byla hodnota nastavena (výchozí hodnota v manifestu, v uživatelském rozhraní nastavení nebo v předchozí verzi):
| Typ | řetězec `process.env` |
| ------------------------------------- | --------------------------------------- |
| `TEXT`, `SELECT`, `DATE`, `DATE_TIME` | surová hodnota (`"eu"`, `"2026-01-01"`) |
| `BOOLEAN` | `"true"` / `"false"` |
| `NUMBER`, `NUMERIC` | desetinný řetězec (`"10"`, `"2.5"`) |
| `MULTI_SELECT`, `ARRAY` | JSON pole (`'["email","postcard"]'`) |
| `RAW_JSON`, `RICH_TEXT` | JSON objekt (`'{"retries":3}'`) |
Parsujte řetězec zpět do typu, který očekáváte:
```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 }
```
Totéž platí pro frontendové komponenty, které čtou hodnoty pomocí `getApplicationVariable('VARIABLE_NAME')` — vrácená hodnota je řetězec; podle potřeby ji parsujte.
## Výchozí role funkce
Role deklarovaná pomocí [`defineApplicationRole()`](/l/cs/developers/extend/apps/config/roles) určuje, k čemu mají přístup logické funkce a front-endové komponenty aplikace:
* Běhové tokeny vložené do vašich logických funkcí jsou odvozeny z této role. Volání jednající jako osoba je dále omezeno tím, co tato osoba může dělat, takže nikdy nemůže překročit ani jedno z těchto oprávnění. Vizte [Čí přístup volání používá](/l/cs/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses).
* Typovaný klient API je omezen na oprávnění udělená této roli.
* Dodržujte princip nejmenších oprávnění: deklarujte pouze ta oprávnění, která vaše funkce potřebují.
Když vygenerujete novou aplikaci, CLI vytvoří úvodní soubor role v `src/roles/default-role.ts`. Úplnou referenci najdete v části [Role a oprávnění](/l/cs/developers/extend/apps/config/roles).
## Metadata tržiště
Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/operations/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje v tržišti:
| Pole | Popis |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `author` | Jméno autora nebo název společnosti |
| `category` | Kategorie aplikace pro filtrování v tržišti |
| `logo` | Cesta k logu vaší aplikace uloženému ve složce `public/` (např. `public/logo.png`) |
| `galleryImages` | Pole cest ke snímkům galerie uloženým ve složce `public/` (např. `public/screenshot-1.png`) |
| `aboutDescription` | Delší popis v Markdownu pro kartu "O aplikaci". Pokud je vynecháno, tržiště použije `README.md` balíčku z npm |
| `websiteUrl` | Odkaz na váš web |
| `termsUrl` | Odkaz na podmínky služby |
| `emailSupport` | E-mailová adresa podpory |
| `issueReportUrl` | Odkaz na nástroj pro sledování problémů |
<Note>
`logoUrl` a `screenshots` jsou zastaralé aliasy `logo` a `galleryImages`. Externí absolutní adresy URL (`http://` nebo `https://`) nejsou pro tato pole podporovány: při sestavení jsou vynechány s varováním. Místo toho přiložte obrázky do složky `public/` vaší aplikace.
</Note>

View File

@@ -0,0 +1,188 @@
---
title: Instalační hooky
description: Spouštějte logiku během životního cyklu instalace, upgradu nebo odinstalace – vložte výchozí data, zálohujte záznamy, ověřte upgrade, vyčistěte externí prostředky.
icon: wrench
---
Instalační hooky jsou speciální logické funkce, které se spouštějí během životního cyklu instalace, upgradu nebo odinstalace. Sdílí stejný runtime handleru jako běžné [logické funkce](/l/cs/developers/extend/apps/logic/logic-functions), ale deklarují se pomocí vlastních definičních funkcí a fungují mimo běžný model triggerů (HTTP, cron, databázové události). Instalační hooky přijímají `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` je při čisté instalaci `undefined`); hook pro odinstalaci přijímá `UninstallPayload` (`{ version?: string }` — verze, která se odstraňuje).
Každá aplikace smí definovat **nanejvýš jeden** hook každého typu (předinstalační, poinstalační, odinstalační). Sestavení manifestu skončí chybou, pokud je zjištěn více než jeden hook daného typu.
```
┌─────────────────────────────────────────────────────────────┐
│ install flow │
│ │
│ upload package → [pre-install] → metadata migration → │
│ generate SDK → [post-install] │
│ │
│ old schema visible new schema visible │
└─────────────────────────────────────────────────────────────┘
```
## Na první pohled
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| --------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Spuštění | Před migrací metadat — **předchozí** schéma a data jsou stále neporušené | Po migraci a vygenerování SDK — **nové** schéma je připravené |
| Spuštění | Vždy synchronní; blokuje instalaci | Ve výchozím nastavení asynchronní (zařazeno do fronty, 3 pokusy); synchronní režim volitelný přes `shouldRunSynchronously: true` |
| Při selhání | Instalace je **zrušena** před jakoukoli změnou schématu | Asynchronně: opakováno až 3krát. Synchronně: volající obdrží `POST_INSTALL_ERROR` (změny schématu se **ne**vracejí zpět) |
| Typické použití | Zálohovat nebo opravit data, která by migrace ztratila; odmítnout rizikovou aktualizaci vyvoláním výjimky | Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky |
**Pravidlo:** výchozí volbou je post-install. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí.
| Chcete... | Použít |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Naplňte data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` |
| Dlouho běžící práce, která by neměla blokovat odpověď instalace | `post-install` (výchozí asynchronní režim, s opakovanými pokusy workeru) |
| Rychlé nastavení, na které volající spoléhá ihned po dokončení instalace | `post-install` s `shouldRunSynchronously: true` |
| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` |
| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) |
| Srovnání stavu při každé aktualizaci | Libovolný hook s `shouldRunOnVersionUpgrade: true` |
## Chování sdílené oběma hooky
* Konfigurace je konfigurace `defineLogicFunction` bez nastavení triggeru, doplněná o `shouldRunOnVersionUpgrade`.
* **Kdy se spouští**: ve výchozím nastavení pouze při čistých instalacích. Nastavte `shouldRunOnVersionUpgrade: true`, aby se spouštěl i při aktualizacích. Použijte `previousVersion` / `newVersion` k větvení podle cesty aktualizace.
* **Idempotence je důležitá**: asynchronní post-install se může spouštět opakovaně a kterýkoli hook se znovu spouští při aktualizacích, když je `shouldRunOnVersionUpgrade` zapnuté.
* Běžné prostředí logické funkce (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) je injektováno, takže můžete volat Twenty API s tokenem své aplikace.
* Hook je při sestavení automaticky připojen k manifestu aplikace (`preInstallLogicFunction` / `postInstallLogicFunction`) — v [`defineApplication()`](/l/cs/developers/extend/apps/config/application) není potřeba na nic odkazovat.
* Výchozí `timeoutSeconds` je 300, aby umožnil delší úlohy nastavení, jako je naplnění daty.
* **Nespouští se v dev režimu**: `yarn twenty dev` přeskočí instalační flow a soubory synchronizuje přímo, takže se hooky v tomto režimu nikdy nespustí. Místo toho je spouštějte ručně:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Spouští se po aplikování migrace metadat pracovního prostoru">
Spouští se, jakmile vaše aplikace dokončí instalaci: metadata jsou synchronizovaná, klient SDK vygenerovaný a nové schéma je možné dotazovat. Příklad — při čisté instalaci naplňte výchozí záznam:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
Příznak `shouldRunSynchronously` řídí model spuštění:
* `false` *(výchozí)* — zařazeno do fronty zpráv (`retryLimit: 3`) a spuštěno workerem. Odezva instalace se vrátí, jakmile je úloha zařazena do fronty. **Používejte pro dlouho běžící práci** — naplňování velkých datových sad, pomalá API třetích stran.
* `true` — spuštěno inline během instalačního flow. Instalační požadavek blokuje, dokud handler neskončí; vyvolaná chyba se projeví pro volajícího jako `POST_INSTALL_ERROR` (žádné opakované pokusy). **Používejte pro rychlou práci, která musí být dokončena před odpovědí.** Migrace už je v tomto bodě aplikovaná, takže selhání nevrací změny schématu zpět — pouze předá chybu dál.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Spouští se před aplikováním migrace metadat pracovního prostoru">
Spouští se před migrací metadat, nad **předchozím** schématem — vhodné místo pro zálohování dat, která by migrace ztratila, nebo pro odmítnutí rizikové aktualizace. Před spuštěním server provede čistě aditivní „zjednodušenou synchronizaci“, která zaregistruje pouze pre-install funkci nové verze; vše ostatní — objekty, pole a data předchozí verze — zůstává při běhu vašeho handleru nedotčeno.
Pre-install je vždy **synchronní** a blokuje instalaci. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před jakoukoli změnou schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci.
Příklad — zkopírujte hodnoty staršího pole dříve, než ho migrace odstraní:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
return;
}
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Backs up legacy notes into description before the v2 migration.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
</Accordion>
</AccordionGroup>
## Odinstalační hook
`defineUninstallLogicFunction` deklaruje hook, který se spustí, když uživatel odinstaluje vaši aplikaci. Spustí se **předtím**, než jsou odstraněna metadata, data a kód aplikace — jakmile se spustí migrační skript pro smazání, nezbude nic, co by se dalo vykonat — takže váš handler může stále dotazovat objekty a záznamy aplikace. Použijte ho k úklidu externích prostředků: zrušení přidělených API prostředků, smazání zbývajících botů, zrušení webhooků.
Poznámky:
* Hook pracuje v režimu best effort: běží synchronně, ale chyba se pouze zapíše do logu a nikdy neblokuje odinstalaci — úklid nesmí znemožnit odebrání aplikace.
* Přijímá `UninstallPayload` (`{ version?: string }` — verze, která se odstraňuje).
* Nespouští se, pokud je neúspěšná čistá instalace vrácena zpět — aplikace nikdy nedokončila instalaci.
* Hook nemůže běžet poté, co je aplikace pryč, takže externí úklid, který závisí na datech aplikace (např. ID botů uložená v záznamech), patří sem, ne do externí naplánované úlohy.
* Stejně jako instalační hooky se **nespouští v dev módu** — místo toho ho vyvolejte ručně:
```bash filename="Terminal"
yarn twenty dev:function:exec --uninstall
```
```ts src/logic-functions/uninstall.ts
import { defineUninstallLogicFunction, type UninstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (_payload: UninstallPayload): Promise<void> => {
const client = new CoreApiClient();
const { meetingBots } = await client.query({
meetingBots: { edges: { node: { id: true, externalBotId: true } } },
});
// Delete the provider-side bots so nothing keeps recording after uninstall.
for (const { node } of meetingBots.edges) {
await fetch(`https://api.recorder.example/bots/${node.externalBotId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.RECORDER_API_KEY}` },
});
}
};
export default defineUninstallLogicFunction({
universalIdentifier: 'b2c3d4e5-6789-01bc-def0-234567890abc',
name: 'uninstall',
description: 'Deletes remaining recorder bots when the app is uninstalled.',
timeoutSeconds: 300,
handler,
});
```

View File

@@ -0,0 +1,56 @@
---
title: Přehled
description: Nakonfigurujte samotnou aplikaci – její identitu, výchozí oprávnění a to, co se spouští při instalaci.
icon: screwdriver-wrench
---
**Konfigurační vrstva** aplikace Twenty popisuje aplikaci *platformě* – její identitu, oprávnění, která má, a kód, který se spouští během instalace nebo aktualizace. Tato deklarativní nastavení nepřidávají nové datové struktury ani chování za běhu; říkají Twenty, *co je aplikace zač* a *jak ji nastavit*.
```text
┌────────────────────────────────────────────────────────┐
│ Application — identity, default role, variables, │
│ marketplace metadata │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Role — what the app's logic functions can read │ │
│ │ and write (referenced by Application) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼ (at install / upgrade time)
┌──────────────────────────────────┐
│ Pre-install hook │ before metadata migration
└──────────────────────────────────┘
┌──────────────────────────────────┐
│ Post-install hook │ after metadata migration
└──────────────────────────────────┘
│
▼ (at uninstall time)
┌──────────────────────────────────┐
│ Uninstall hook │ before app removal
└──────────────────────────────────┘
```
## V této části
<CardGroup cols={2}>
<Card title="Konfigurace aplikace" icon="rocket" href="/l/cs/developers/extend/apps/config/application">
`defineApplication` – identita, výchozí role, proměnné, metadata pro marketplace.
</Card>
<Card title="Role a oprávnění" icon="shield-halved" href="/l/cs/developers/extend/apps/config/roles">
`defineRole` – deklaruje, co mohou logické funkce vaší aplikace číst a zapisovat.
</Card>
<Card title="Instalační hooky" icon="wrench" href="/l/cs/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction`, `definePostInstallLogicFunction` a `defineUninstallLogicFunction` — zálohují data, nastavují výchozí hodnoty, validují aktualizace, uklízejí při odebrání.
</Card>
</CardGroup>
## Jak spolu části souvisejí
* **Aplikace** je vstupní bod. Každá aplikace má právě jedno volání `defineApplication()`, které ukazuje na jednu **roli** jako výchozí.
* **Role** určuje, co mohou logické funkce aplikace a front-endové komponenty číst a zapisovat. Dodržujte zásadu nejmenších oprávnění: udělujte pouze ta oprávnění, která váš kód skutečně potřebuje.
* **Instalační hooky** se spouštějí během instalace nebo aktualizace – pre-install před migrací metadat (aby mohly odmítnout rizikovou aktualizaci), post-install po migraci (aby mohly proti novému schématu naplnit výchozí data). Hook pro odinstalaci se spouští těsně před odebráním aplikace, takže může uklidit externí prostředky, zatímco data aplikace jsou stále čitelná.
<Note>
Instalační hooky sdílejí běhové prostředí [logických funkcí](/l/cs/developers/extend/apps/logic/logic-functions) – stejný podpis handleru, stejné proměnné prostředí, stejný typovaný klient API – ale deklarují se pomocí vlastních funkcí define a fungují mimo běžný model spouštěčů (HTTP, cron, databázové události).
</Note>

View File

@@ -0,0 +1,67 @@
---
title: Veřejné soubory
description: Doručujte statické soubory — obrázky, ikony, písma — spolu se svou aplikací prostřednictvím složky public/.
icon: folder-open
---
Složka `public/` v kořenu vaší aplikace obsahuje statické soubory — obrázky, ikony, písma a další prostředky, které vaše aplikace potřebuje za běhu. Tyto soubory jsou automaticky zahrnuty do buildů, synchronizovány během vývojového režimu a nahrávány na server.
Soubory umístěné v `public/` jsou:
* **Veřejně přístupné** — po synchronizaci na server jsou prostředky dostupné na veřejné URL. K přístupu k nim není potřeba žádná autentizace.
* **Dostupné ve frontendových komponentách** — použijte URL prostředků k zobrazení obrázků, ikon či jiných médií uvnitř komponent Reactu.
* **Dostupné v logických funkcích** — odkazujte na URL prostředků v e-mailech, odpovědích API či jiné serverové logice.
* **Používány pro metadata Marketplace** — pole `logo` a `galleryImages` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Zobrazují se v Marketplace, když je vaše aplikace zveřejněna. Pro tato pole jsou externí absolutní adresy URL ignorovány — místo toho přibalte obrázky do `public/`. Každý obrázek nesmí přesáhnout velikost 10 MB.
* **Automaticky synchronizované ve vývojovém režimu** — když v `public/` přidáte, aktualizujete nebo smažete soubor, je automaticky synchronizován na server. Není potřeba restartovat.
* **Zahrnuté do buildů** — `yarn twenty dev:build` zabalí všechny veřejné prostředky do distribučního výstupu.
## Přístup k veřejným prostředkům pomocí `getPublicAssetUrl`
K získání plné URL souboru ve vaší složce `public/` použijte pomocnou funkci `getPublicAssetUrl` z `twenty-sdk`. Funguje jak v logických funkcích, tak ve frontendových komponentách.
**V logické funkci:**
```ts src/logic-functions/send-invoice.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const handler = async (): Promise<any> => {
const logoUrl = getPublicAssetUrl('logo.png');
const invoiceUrl = getPublicAssetUrl('templates/invoice.png');
// Fetch the file content (no auth required — public endpoint)
const response = await fetch(invoiceUrl);
const buffer = await response.arrayBuffer();
return { logoUrl, size: buffer.byteLength };
};
export default defineLogicFunction({
universalIdentifier: 'a1b2c3d4-...',
name: 'send-invoice',
description: 'Sends an invoice with the app logo',
timeoutSeconds: 10,
handler,
});
```
**Ve frontendové komponentě:**
```tsx src/front-components/company-card.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const CompanyCard = () => {
const logoUrl = getPublicAssetUrl('logo.png');
return <img src={logoUrl} alt="App logo" />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'company-card',
component: CompanyCard,
});
```
Argument `path` je relativní ke složce `public/` vaší aplikace. Jak `getPublicAssetUrl('logo.png')`, tak `getPublicAssetUrl('public/logo.png')` se vyhodnotí na stejnou URL — předpona `public/` je, je-li přítomna, automaticky odstraněna.

View File

@@ -0,0 +1,201 @@
---
title: Role a oprávnění
description: Určete, které objekty a pole mohou funkce aplikační logiky a front-endové komponenty vaší aplikace číst a zapisovat.
icon: shield-halved
---
**Role** je sada oprávnění: které objekty může aplikace číst nebo zapisovat, která pole může vidět a jaké schopnosti na úrovni platformy může používat. Všechny logické funkce aplikace a front-endové komponenty dědí oprávnění role označené pomocí `defineApplicationRole()` (viz [Výchozí role funkce](#the-default-function-role) níže).
```ts src/roles/restricted-company-role.ts
import {
defineRole,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
SystemPermissionFlag,
} from 'twenty-sdk/define';
export default defineRole({
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
label: 'My new role',
description: 'A role that can be used in your workspace',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
],
fieldPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.APPLICATIONS],
});
```
## Zabezpečení na úrovni řádků
Oprávnění objektů a polí určují, *kterých objektů a polí* se může role dotýkat. **Predikáty oprávnění na úrovni řádků** jdou dále a určují, *které záznamy* může role vidět a s nimi pracovat — například samoobslužná role, kde každý externí uživatel vidí pouze své vlastní záznamy.
Predikáty deklarujte na roli pomocí `rowLevelPermissionPredicates`. Stejně jako zbytek manifestu nese každý predikát svůj vlastní `universalIdentifier` a odkazuje na objekt a pole podle jejich `universalIdentifier`, dále na `operand` a (volitelně) na pole workspaceMember, jehož hodnota je vložena v době dotazu — takže můžete vyjádřit „vztah vlastníka záznamu **je** aktuálním členem pracovního prostoru“:
```ts src/roles/partner-role.ts
import {
defineRole,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/account-owner.field';
export default defineRole({
universalIdentifier: 'c3c1dc2e-1a08-4de5-abb7-2139b3d99343',
label: 'Partner',
description: 'External partner — sees only its own records',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd0f0c1a2-3b4c-4d5e-8f60-111111111111',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier: ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier,
},
],
});
```
Protože predikáty jsou součástí manifestu, vytvářejí se, aktualizují a odstraňují společně s rolí při každé instalaci a aktualizaci — není potřeba žádný samostatný post‑instalační krok, který by je udržoval v synchronizaci.
### Kombinování predikátů se skupinami
Ve výchozím nastavení se predikáty role kombinují pomocí `AND`. Aby bylo možné některé z nich kombinovat pomocí `OR` (nebo vnořovat logiku), deklarujte položku `rowLevelPermissionPredicateGroups` a každý predikát na ni odkažte prostřednictvím `predicateGroupUniversalIdentifier`. Tato role umožňuje partnerovi zobrazit obchodní příležitost, kterou buď vlastní, nebo u ní vystupuje jako kontaktní osoba:
```ts src/roles/partner-opportunities-role.ts
import {
defineRole,
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
const OPPORTUNITY = STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity;
const CURRENT_MEMBER =
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier;
export default defineRole({
universalIdentifier: 'b2a1c0d9-8e7f-4a6b-9c5d-222222222222',
label: 'Partner (opportunities)',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
canReadObjectRecords: true,
},
],
rowLevelPermissionPredicateGroups: [
{
universalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.OR,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd4c3b2a1-0e9f-4c8b-9e7d-444444444444',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier: OPPORTUNITY.fields.owner.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
{
universalIdentifier: 'e5d4c3b2-1f0e-4d9c-8f8e-555555555555',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier:
OPPORTUNITY.fields.pointOfContact.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
],
});
```
Poznámky:
* Každému predikátu a skupině dejte stabilní `universalIdentifier` (libovolný uuid) — podle něj se entita identifikuje při aktualizacích a predikáty podle něj odkazují na skupiny.
* Predikáty mohou odkazovat na objekty a pole vlastněné vaší aplikací nebo na standardní objekty Twenty.
* Zabezpečení na úrovni řádků je vynucováno pro pracovní prostory v tarifech, které ho zahrnují; predikáty se i na ostatních tarifech stále synchronizují, pouze nejsou vynucovány.
## Výchozí role funkce
Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role deklarovaný pomocí `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineApplicationRole } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagUniversalIdentifiers: [],
});
```
`defineApplicationRole()` je tenký wrapper kolem `defineRole()`, který označuje **tu** roli, jež je při instalaci použita jako výchozí role vaší aplikace. Validace je shodná s `defineRole`, ale build pipeline automaticky propojí její `universalIdentifier` s `defaultRoleUniversalIdentifier` v manifestu aplikace — takže na něj nemusíte v [`defineApplication`](/l/cs/developers/extend/apps/config/application) sami odkazovat.
Poznámky:
* Na jednu aplikaci je povolena přesně **jedna** definice `defineApplicationRole(...)` — pokud build manifestu najde více než jednu, sestavení selže.
* Pro všechny **další** role, které vaše aplikace poskytuje, použijte `defineRole()` (nikoli `defineApplicationRole()`).
* Explicitní nastavení `defaultRoleUniversalIdentifier` v `defineApplication()` je stále podporováno kvůli zpětné kompatibilitě, ale je označeno jako zastaralé ve prospěch `defineApplicationRole()`.
## Osvědčené postupy
* Začněte od vygenerované role a postupně ji omezujte — výchozí nastavení poskytuje široká oprávnění pro čtení, což je v produkci zřídka žádoucí.
* Nahraďte `objectPermissions` a `fieldPermissions` přesně těmi objekty a poli, které vaše funkce skutečně potřebují.
* `permissionFlagUniversalIdentifiers` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší.
* Podívejte se na funkční příklad: [`examples/hello-world/src/roles/default-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/hello-world/src/roles/default-role.ts).