From ae202a1b59192bf79a887012f6490534f49c34c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:27:21 +0200 Subject: [PATCH] i18n - docs translations (#19226) Created by Github action Co-authored-by: github-actions --- .../l/ar/developers/extend/apps/building.mdx | 1559 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../ar/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/cs/developers/extend/apps/building.mdx | 1558 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../cs/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/de/developers/extend/apps/building.mdx | 1560 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../de/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/it/developers/extend/apps/building.mdx | 1560 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../it/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/pt/developers/extend/apps/building.mdx | 1750 +++++++++++++---- .../extend/apps/getting-started.mdx | 468 +++-- .../pt/developers/extend/apps/publishing.mdx | 182 +- .../developers/extend/capabilities/apps.mdx | 1441 +------------- .../self-host/capabilities/setup.mdx | 10 + .../generate-pdf-from-twenty.mdx | 83 +- .../l/ro/developers/extend/apps/building.mdx | 1560 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../ro/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/ru/developers/extend/apps/building.mdx | 1560 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../ru/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/tr/developers/extend/apps/building.mdx | 1560 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../tr/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- .../l/zh/developers/extend/apps/building.mdx | 1560 +++++++++++---- .../extend/apps/getting-started.mdx | 475 +++-- .../zh/developers/extend/apps/publishing.mdx | 226 +-- .../developers/extend/capabilities/apps.mdx | 1440 +------------- 38 files changed, 14842 insertions(+), 18697 deletions(-) diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx index cd2b078ea9..e3b02a54b2 100644 --- a/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: عرّف الكائنات، والدوال المنطقية، وم --- -التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور. + التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور. -## استخدم موارد SDK (الأنواع والتكوين) +توفر حزمة `twenty-sdk` لبنات بناء مضبوطة الأنواع لإنشاء تطبيقك. تغطي هذه الصفحة كل نوع كيان وكل عميل واجهة برمجة تطبيقات متاح في SDK. -يوفّر twenty-sdk كتلَ بناءٍ مضبوطة الأنواع ودوال مساعدة تستخدمها داخل تطبيقك. فيما يلي الأجزاء الأساسية التي ستتعامل معها غالبًا. +## دوال DefineEntity -### دوال مساعدة +يوفّر SDK دوالًا لتعريف كيانات تطبيقك. يجب عليك استخدام `export default defineEntity({...})` لكي يكتشف SDK الكيانات الخاصة بك. تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع. -يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](/l/ar/developers/extend/apps/getting-started#entity-detection)، يجب استخدام `export default define({...})` كي يتم اكتشاف كياناتك: + + **تنظيم الملفات يعود إليك.** + يعتمد اكتشاف الكيانات على AST — حيث يعثر SDK على استدعاءات `export default defineEntity(...)` بغض النظر عن مكان وجود الملف. تجميع الملفات حسب النوع (مثلًا، `logic-functions/` و`roles/`) هو مجرّد عرف، وليس متطلبًا. + -| دالة | الغرض | -| -------------------------------- | -------------------------------------------------------------- | -| `defineApplication` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) | -| `defineObject` | تعريف كائنات مخصصة مع حقول | -| `defineField` | وسّع الكائنات الموجودة بحقول إضافية أو عرّف حقول علاقات مستقلة | -| `defineLogicFunction` | تعريف وظائف منطقية مع معالجات | -| `definePreInstallLogicFunction` | تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق) | -| `definePostInstallLogicFunction` | تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق) | -| `defineFrontComponent` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة | -| `defineRole` | تهيئة صلاحيات الدور والوصول إلى الكائنات | -| `defineView` | تعريف العروض المحفوظة للكائنات | -| `defineNavigationMenuItem` | تعريف روابط التنقل في الشريط الجانبي | -| `defineSkill` | عرّف مهارات وكيل الذكاء الاصطناعي | -| `defineAgent` | عرّف وكلاء الذكاء الاصطناعي | -| `definePageLayout` | عرّف تخطيطات صفحات مخصّصة | + + -تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع. +تُغلّف الأدوار الصلاحيات على كائنات وإجراءات مساحة العمل لديك. -### تعريف الكائنات +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +يجب أن يحتوي كل تطبيق على استدعاء واحد فقط لـ `defineApplication` يصف: + +* **الهوية**: المعرّفات، اسم العرض، والوصف. +* **الأذونات**: أيُّ دورٍ تستخدمه وظائفه ومكوّناته الأمامية. +* **(اختياري) المتغيرات**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة. +* **(اختياري) دوال ما قبل التثبيت/ما بعد التثبيت**: دوال منطقية تعمل قبل التثبيت أو بعده. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +الملاحظات: +* حقول `universalIdentifier` هي معرّفات حتمية تملكها أنت. أنشِئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة. +* `applicationVariables` تصبح متغيرات بيئة لوظائفك ومكوّناتك الأمامية (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` يجب أن يُشير إلى دور مُعرَّف باستخدام `defineRole()` (انظر أعلاه). +* يتم اكتشاف دوال ما قبل التثبيت وما بعده تلقائيًا أثناء بناء البيان — لا حاجة للإشارة إليها في `defineApplication()`. + +#### بيانات التعريف لسوق التطبيقات + +إذا كنت تخطط لـ [نشر تطبيقك](/l/ar/developers/extend/apps/publishing)، فإن هذه الحقول الاختيارية تتحكّم في كيفية ظهوره في السوق: + +| الحقل | الوصف | +| ------------------ | ------------------------------------------------------------------------------------------------------------ | +| `author` | اسم المؤلف أو الشركة | +| `category` | فئة التطبيق لتصفية سوق التطبيقات | +| `logoUrl` | مسار شعار تطبيقك (مثلًا، `public/logo.png`) | +| `screenshots` | مصفوفة لمسارات لقطات الشاشة (مثلًا، `public/screenshot-1.png`) | +| `aboutDescription` | وصف ماركداون أطول لعلامة التبويب "حول". إذا لم يتم تضمينه، يستخدم السوق ملف `README.md` الخاص بالحزمة من npm | +| `websiteUrl` | رابط إلى موقعك الإلكتروني | +| `termsUrl` | رابط إلى شروط الخدمة | +| `emailSupport` | عنوان البريد الإلكتروني للدعم | +| `issueReportUrl` | رابط إلى متتبّع المشاكل | + +#### الأدوار والصلاحيات + +يُحدّد الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق والمكوّنات الأمامية في تطبيقك. راجع `defineRole` أعلاه للحصول على التفاصيل. + +* رمز وقت التشغيل المحقون باسم `TWENTY_APP_ACCESS_TOKEN` مستمد من هذا الدور. +* العميل مضبوط الأنواع مقيَّد بالأذونات الممنوحة لذلك الدور. +* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا يضم فقط الأذونات التي تحتاجها وظائفك. + +##### الدور الافتراضي للوظيفة + +عند توليد تطبيق جديد بالقالب، ينشئ CLI ملفّ دور افتراضي: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +يُشار إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** يحدد ما يمكن أن يفعله الدور. +* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته. + +الملاحظات: +* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز. +* استبدل `objectPermissions` و`fieldPermissions` بالكائنات والحقول التي تحتاجها وظائفك فعليًا. +* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في حدّها الأدنى. +* اطّلع على مثال عملي: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ export default defineObject({ لكن هذا غير مستحسن. -### تعريف الحقول على الكائنات الموجودة + + استخدم `defineField()` لإضافة حقول إلى كائنات لا تملكها — مثل كائنات Twenty القياسية (Person, Company, etc.) أو كائنات من تطبيقات أخرى. على خلاف الحقول المضمّنة في `defineObject()`، تتطلّب الحقول المستقلة `objectUniversalIdentifier` لتحديد الكائن الذي تقوم بتوسيعه: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` النقاط الرئيسية: - * `objectUniversalIdentifier` يحدّد الكائن الهدف. بالنسبة للكائنات القياسية، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` المُصدَّر من `twenty-sdk`. * عند تعريف الحقول بشكل مضمّن في `defineObject()`، **لا** تحتاج إلى `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب. * `defineField()` هي الطريقة الوحيدة لإضافة حقول إلى كائنات لم تُنشئها باستخدام `defineObject()`. -### العلاقات + + تربط العلاقات الكائنات معًا. في Twenty، تكون العلاقات دائمًا **ثنائية الاتجاه** — حيث تعرّف الجانبين، ويشير كل جانب إلى الآخر. @@ -177,8 +310,7 @@ export default defineField({ **الخطوة 1: عرّف جانب ONE_TO_MANY على PostCard** (جانب "الواحد"): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **الخطوة 2: عرّف جانب MANY_TO_ONE على PostCardRecipient** (جانب "العديد" — يحمل المفتاح الخارجي): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ لإنشاء علاقة مع كائن Twenty مضمّن (Person, Company, etc.)، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ يمكنك أيضًا تعريف حقول العلاقات مباشرةً داخل `defineObject()`. في هذه الحالة، احذف `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### تكوين التطبيق (application-config.ts) - -كل تطبيق لديه ملف واحد `application-config.ts` يصف: - -* **هوية التطبيق**: المعرفات، اسم العرض، والوصف. -* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات. -* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة. -* **(اختياري) دالة ما قبل التثبيت**: دالة منطقية تعمل قبل تثبيت التطبيق. -* **(اختياري) دالة ما بعد التثبيت**: دالة منطقية تعمل بعد تثبيت التطبيق. - -استخدم `defineApplication()` لتعريف تهيئة تطبيقك: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -الملاحظات: - -* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة. -* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه). -* يتم اكتشاف دوال ما قبل التثبيت وما بعد التثبيت تلقائيًا أثناء إنشاء ملف البيان. راجع [دوال ما قبل التثبيت](#pre-install-functions) و[دوال ما بعد التثبيت](#post-install-functions). - -#### بيانات التعريف لسوق التطبيقات - -إذا كنت تخطط لـ [نشر تطبيقك](/l/ar/developers/extend/apps/publishing)، فإن هذه الحقول الاختيارية تتحكّم في كيفية ظهور تطبيقك في السوق: - -| الحقل | الوصف | -| ------------------ | ------------------------------------------------------------------------------------------------------------ | -| `author` | اسم المؤلف أو الشركة | -| `category` | فئة التطبيق لتصفية سوق التطبيقات | -| `logoUrl` | المسار إلى شعار تطبيقك (نسبيًا إلى `./assets/`) | -| `screenshots` | مصفوفة لمسارات لقطات الشاشة (نسبيًا إلى `./assets/`) | -| `aboutDescription` | وصف ماركداون أطول لعلامة التبويب "حول". إذا لم يتم تضمينه، يستخدم السوق ملف `README.md` الخاص بالحزمة من npm | -| `websiteUrl` | رابط إلى موقعك الإلكتروني | -| `termsUrl` | رابط إلى شروط الخدمة | -| `emailSupport` | عنوان البريد الإلكتروني للدعم | -| `issueReportUrl` | رابط إلى متتبّع المشاكل | - -#### الأدوار والصلاحيات - -يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق في تطبيقك. - -* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا. -* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور. -* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا بالأذونات التي تحتاجها وظائفك فقط، ثم أشِر إلى معرّفه الشامل. - -##### الدور الافتراضي للوظيفة (*.role.ts) - -عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`. بعبارة أخرى: - -* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة. -* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته. - -الملاحظات: - -* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز. -* استبدل `objectPermissions` و`fieldPermissions` بالكائنات/الحقول التي تحتاجها وظائفك. -* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه. -* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### تكوين الوظيفة المنطقية ونقطة الدخول + + كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,55 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -أنواع المشغلات الشائعة: - -* **route**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**: - -> مثال: `path: '/post-card/create',` -> الاستدعاء على `/s/post-card/create` - +أنواع المشغّلات المتاحة: +* **httpRoute**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**: +> مثال: `path: '/post-card/create'` يمكن استدعاؤه عبر `https://your-twenty-server.com/s/post-card/create` * **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON. * **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة. +> مثال: `person.updated`، `*.created`، `company.*` -> مثال: `person.updated` - -الملاحظات: - -* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى. -* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة. - -### دوال ما قبل التثبيت - -دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا قبل تثبيت تطبيقك على مساحة عمل. يفيد ذلك في مهام التحقق، وفحص المتطلبات المسبقة، أو تجهيز حالة مساحة العمل قبل متابعة التثبيت الرئيسي. - -عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما قبل التثبيت لك في `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI: + +يمكنك أيضًا تنفيذ دالة يدويًا باستخدام CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -النقاط الرئيسية: - -* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). -* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة). -* يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. -* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `preInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`. -* تم ضبط المهلة الافتراضية على 300 ثانية (5 دقائق) للسماح بمهام التحضير الأطول. -* لا تحتاج دوال ما قبل التثبيت إلى مشغّلات — إذ يستدعيها النظام الأساسي قبل التثبيت أو يدويًا عبر `exec --preInstall`. - -### دوال ما بعد التثبيت - -دالة ما بعد التثبيت هي دالة منطقية تعمل تلقائيًا بعد تثبيت تطبيقك على مساحة عمل. هذا مفيد لمهام الإعداد لمرة واحدة مثل تهيئة البيانات الافتراضية، وإنشاء السجلات الأولية، أو تكوين إعدادات مساحة العمل. - -عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما بعد التثبيت لك في `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -النقاط الرئيسية: +يمكنك متابعة السجلات باستخدام: -* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). -* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة). -* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. -* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `postInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`. -* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات. -* لا تحتاج دوال ما بعد التثبيت إلى مشغّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `exec --postInstall`. - -### حمولة مشغل المسار - - -**تغيير غير متوافق (v1.16، يناير 2026):** لقد تغير تنسيق حمولة مشغل المسار. قبل v1.16، كانت معلمات الاستعلام، ومعلمات المسار، وجسم الطلب تُرسل مباشرةً كحمولة. بدءًا من v1.16، أصبحت متداخلة داخل كائن منظَّم `RoutePayload`. - -**قبل v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**بعد v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### حمولة مشغل المسار -**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params. - +عندما يستدعي مُشغِّل المسار وظيفتك المنطقية، فإنها تتلقّى كائن `RoutePayload` الذي يتبع [صيغة AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +استورد نوع `RoutePayload` من `twenty-sdk`: -عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +530,39 @@ const handler = async (event: RoutePayload) => { يحتوي نوع `RoutePayload` على البنية التالية: -| الخاصية | النوع | الوصف | -| ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- | -| `headers` | `Record` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) | -| `pathParameters` | `Record` | معلمات المسار المستخرجة من نمط المسار (على سبيل المثال، `/users/:id` -> `{ id: '123' }`) | -| `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) | -| `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | -| `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | المسار الخام للطلب | + | الخاصية | النوع | الوصف | مثال | + | ---------------------------- | ------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- | + | `headers` | `Record` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) | انظر القسم أدناه | + | `queryStringParameters` | `Record` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | معلمات المسار المستخرجة من نمط المسار | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | | + | `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | المسار الخام للطلب | | -### تمرير رؤوس HTTP -افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`: +#### forwardedRequestHeaders -```typescript +افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية. +للوصول إلى رؤوس محددة، أدرِجها في مصفوفة `forwardedRequestHeaders`: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -في المعالج الخاص بك، يمكنك حينها الوصول إلى هذه الرؤوس: +في معالجك، يمكنك الوصول إلى الرؤوس المُمرَّرة بهذه الطريقة: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +573,16 @@ const handler = async (event: RoutePayload) => { ``` - تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`). +تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`). -يمكنك إنشاء وظائف جديدة بطريقتين: +#### إتاحة دالة كأداة -* **مُنشأ بالقالب**: شغّل `yarn twenty add` واختر خيار إضافة وظيفة منطقية جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين. -* **يدوي**: أنشئ ملفًا جديدًا `*.logic-function.ts` واستخدم `defineLogicFunction()` مع اتباع النمط نفسه. +يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عند تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي في Twenty ويمكن استخدامها في أتمتة سير العمل. -### تمييز دالة منطقية كأداة +لتمييز دالة منطقية كأداة، عيِّن `isTool: true`: -يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عندما يتم تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي الخاصة بـ Twenty ويمكن اختيارها كخطوة في أتمتة سير العمل. - -لتمييز دالة منطقية كأداة، عيّن `isTool: true` وقدّم `toolInputSchema` يصف معاملات الإدخال المتوقعة باستخدام [مخطط JSON](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +611,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +النقاط الرئيسية: + +* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة الأحداث في الوقت نفسه. +* **`toolInputSchema`** (اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يُحسَب المخطط تلقائيًا من خلال تحليل ساكن للشيفرة المصدرية، ولكن يمكنك تعيينه صراحةً: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +639,364 @@ export default defineLogicFunction({ }); ``` -النقاط الرئيسية: - -* **`isTool`** (`boolean`, الافتراضي: `false`): عند ضبطه على `true`، يتم تسجيل الدالة كأداة وتصبح متاحة لوكلاء الذكاء الاصطناعي ولأتمتة سير العمل. -* **`toolInputSchema`** (`object`, اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يستخدم وكلاء الذكاء الاصطناعي هذا المخطط لفهم المدخلات التي تتوقعها الأداة وللتحقق من صحة الاستدعاءات. إذا تم إغفاله، فالقيمة الافتراضية للمخطط هي `{ type: 'object', properties: {} }` (من دون معلمات). -* الدوال التي لديها `isTool: false` (أو غير معيَّنة) **غير** معروضة كأدوات. لا يزال بالإمكان تنفيذها مباشرةً أو استدعاؤها بواسطة دوال أخرى، لكنها لن تظهر في اكتشاف الأدوات. -* **تسمية الأداة**: عند كشفها كأداة، يتم تطبيع اسم الدالة تلقائيًا إلى `logic_function_` (تحويله إلى أحرف صغيرة، واستبدال المحارف غير الأبجدية الرقمية بشرطات سفلية). على سبيل المثال، `enrich-company` تصبح `logic_function_enrich_company`. -* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة أحداث (cron، وأحداث قاعدة البيانات، والمسارات) في الوقت نفسه. - **اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها. -### المكوّنات الأمامية + + -تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج: +دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا قبل تثبيت تطبيقك على مساحة عمل. يفيد ذلك في مهام التحقق، وفحص المتطلبات المسبقة، أو تجهيز حالة مساحة العمل قبل متابعة التثبيت الرئيسي. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +النقاط الرئيسية: +* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). +* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة). +* يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. +* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `preInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`. +* تم ضبط المهلة الافتراضية على 300 ثانية (5 دقائق) للسماح بمهام التحضير الأطول. + + + + +دالة ما بعد التثبيت هي دالة منطقية تعمل تلقائيًا بعد تثبيت تطبيقك على مساحة عمل. هذا مفيد لمهام الإعداد لمرة واحدة مثل تهيئة البيانات الافتراضية، وإنشاء السجلات الأولية، أو تكوين إعدادات مساحة العمل. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +النقاط الرئيسية: +* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). +* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة). +* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. +* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `postInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`. +* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات. + + + + +المكوّنات الأمامية هي مكوّنات React تُعرَض مباشرة داخل واجهة مستخدم Twenty. تعمل ضمن **Web Worker** معزول باستخدام Remote DOM — تكون شيفرتك في صندوق عزل لكنها تُعرَض أصيلًا داخل الصفحة، وليس ضمن iframe. + +#### مثال أساسي + +أسرع طريقة لرؤية مكوّن أمامي قيد العمل هي تسجيله كأمر. إضافة حقل `command` مع `isPinned: true` يجعلُه يظهر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة — دون الحاجة إلى تخطيط صفحة: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -النقاط الرئيسية: +بعد المزامنة باستخدام `yarn twenty dev`، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة: -* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty. -* يشير الحقل `component` إلى مكوّن React الخاص بك. -* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn twenty dev`. +
+ زر إجراء سريع في الزاوية العلوية اليمنى +
-يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين: +انقره لعرض المكوّن مضمنًا داخل الصفحة. -* **مُنشأ بالقالب**: شغّل `yarn twenty add` واختر خيار إضافة مكوّن أمامي جديد. -* **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه. +{/* TODO: add screenshot of the rendered front component */} -### المهارات +#### حقول التكوين + +| الحقل | مطلوب | الوصف | +| --------------------- | ----- | ----------------------------------------------------------------- | +| `universalIdentifier` | نعم | معرّف فريد ثابت لهذا المكوّن | +| `component` | نعم | دالة مكوّن React | +| `name` | لا | اسم العرض | +| `الوصف` | لا | وصف لما يفعله المكوّن | +| `isHeadless` | لا | عيِّنه إلى `true` إذا كان المكوّن بلا واجهة مرئية (انظر أدناه) | +| `أمر` | لا | سجّل المكوّن كأمر (انظر [خيارات الأوامر](#command-options) أدناه) | + +#### وضع مكوّن أمامي على صفحة + +إضافةً إلى الأوامر، يمكنك تضمين مكوّن أمامي مباشرةً في صفحة سجل عبر إضافته كودجت في **تخطيط صفحة**. راجع قسم [definePageLayout](#definepagelayout) للتفاصيل. + +#### مكوّنات عديمة الواجهة (`isHeadless: true`) + +المكوّنات عديمة الواجهة لا تُظهر واجهة مرئية لكنها ما تزال تُشغِّل منطق React. يكون هذا مفيدًا لـ **مكوّنات التأثير** — وهي مكوّنات تُجري تأثيرات جانبية عند التثبيت، مثل مزامنة البيانات، بدء مؤقّت، الاستماع إلى الأحداث، أو إطلاق إشعار. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +نظرًا لأن المكوّن يُرجع `null`، فإن Twenty يتخطّى عرض حاوية له — ولن تظهر مساحة فارغة في التخطيط. لا يزال لدى المكوّن إمكانية الوصول إلى جميع الخطافات وواجهة برمجة الاتصال مع المضيف. + +#### الوصول إلى سياق وقت التشغيل + +داخل مكوّنك، استخدم خطافات SDK للوصول إلى المستخدم الحالي، والسجل، ومثيل المكوّن: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +الخطافات المتاحة: + +| الخطّاف | القيم المعادة | الوصف | +| --------------------------------------------- | ------------------ | ---------------------------------------------- | +| `useUserId()` | `string` أو `null` | معرّف المستخدم الحالي | +| `useRecordId()` | `string` أو `null` | معرّف السجل الحالي (عند وضعه على صفحة سجل) | +| `useFrontComponentId()` | `string` | معرّف مثيل هذا المكوّن | +| `useFrontComponentExecutionContext(selector)` | يختلف | الوصول إلى سياق التنفيذ الكامل عبر دالة محدِّد | + +#### واجهة الاتصال مع المضيف + +يمكن للمكوّنات الأمامية تشغيل التنقّل والنوافذ المنبثقة والإشعارات باستخدام دوال من `twenty-sdk`: + +| دالة | الوصف | +| ----------------------------------------------- | ------------------------------ | +| `navigate(to, params?, queryParams?, options?)` | الانتقال إلى صفحة داخل التطبيق | +| `openSidePanelPage(params)` | فتح لوحة جانبية | +| `closeSidePanel()` | إغلاق اللوحة الجانبية | +| `openCommandConfirmationModal(params)` | عرض مربع حوار تأكيد | +| `enqueueSnackbar(params)` | عرض إشعار توست | +| `unmountFrontComponent()` | إلغاء تركيب المكوّن | +| `updateProgress(progress)` | تحديث مؤشّر التقدّم | + +#### خيارات الأوامر + +إضافة حقل `command` إلى `defineFrontComponent` تُسجِّل المكوّن في قائمة الأوامر (Cmd+K). إذا كانت قيمة `isPinned` هي `true`، فسيظهر أيضًا كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة. + +| الحقل | مطلوب | الوصف | +| --------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | نعم | معرّف فريد ثابت للأمر | +| `التسمية` | نعم | التسمية الكاملة المعروضة في قائمة الأوامر (Cmd+K) | +| `shortLabel` | لا | تسمية أقصر تُعرَض على زر الإجراء السريع المثبّت | +| `أيقونة` | لا | اسم الأيقونة المعروض بجانب التسمية (مثل `'IconBolt'` و`'IconSend'`) | +| `isPinned` | لا | عند كونها `true`، يعرض الأمر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة | +| `availabilityType` | لا | تتحكّم في مكان ظهور الأمر: `'GLOBAL'` (متاح دائمًا)، و`'RECORD_SELECTION'` (فقط عند تحديد سجلات)، أو `'FALLBACK'` (يُعرَض عند عدم تطابق أي أوامر أخرى) | +| `availabilityObjectUniversalIdentifier` | لا | تقييد الأمر بصفحات نوع كائن معيّن (مثل سجلات Company فقط) | +| `conditionalAvailabilityExpression` | لا | تعبير منطقي للتحكم ديناميكيًا في ما إذا كان الأمر مرئيًا (انظر أدناه) | + +#### تعابير الإتاحة الشرطية + +يتيح لك الحقل `conditionalAvailabilityExpression` التحكّم في وقت ظهور الأمر بناءً على سياق الصفحة الحالي. استورد متغيّرات ومشغّلات مضبوطة الأنواع من `twenty-sdk` لبناء التعابير: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**متغيّرات السياق** — تُمثّل الحالة الحالية للصفحة: + +| المتغيّر | النوع | الوصف | +| ------------------------------ | ------------- | --------------------------------------------------------------- | +| `pageType` | `string` | نوع الصفحة الحالي (مثل `'RecordIndexPage'` و`'RecordShowPage'`) | +| `isInSidePanel` | `قيمة منطقية` | ما إذا كان المكوّن معروضًا في لوحة جانبية | +| `numberOfSelectedRecords` | `رقم` | عدد السجلات المحدّدة حاليًا | +| `isSelectAll` | `قيمة منطقية` | ما إذا كان "تحديد الكل" مفعّلًا | +| `selectedRecords` | `array` | كائنات السجلات المحدّدة | +| `favoriteRecordIds` | `array` | معرّفات السجلات المفضّلة | +| `objectPermissions` | `الكائن` | الأذونات الخاصة بنوع الكائن الحالي | +| `targetObjectReadPermissions` | `الكائن` | أذونات القراءة للكائن الهدف | +| `targetObjectWritePermissions` | `الكائن` | أذونات الكتابة للكائن الهدف | +| `featureFlags` | `الكائن` | أعلام الميزات المفعَّلة | +| `objectMetadataItem` | `الكائن` | بيانات التعريف لنوع الكائن الحالي | +| `hasAnySoftDeleteFilterOnView` | `قيمة منطقية` | ما إذا كان العرض الحالي يحتوي على مرشّح حذف منطقي | + +**المُشغِّلات** — جمّع المتغيّرات في تعابير منطقية: + +| المُشغِّل | الوصف | +| ----------------------------------- | -------------------------------------------------------------- | +| `isDefined(value)` | `true` إذا لم تكن القيمة null/undefined | +| `isNonEmptyString(value)` | `true` إذا كانت القيمة سلسلة غير فارغة | +| `includes(array, value)` | `true` إذا كانت المصفوفة تحتوي على القيمة | +| `includesEvery(array, prop, value)` | `true` إذا كانت خاصية كل عنصر تتضمن القيمة | +| `every(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في كل عنصر | +| `everyDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في كل عنصر | +| `everyEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في كل عنصر | +| `some(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في عنصر واحد على الأقل | +| `someDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في عنصر واحد على الأقل | +| `someEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في عنصر واحد على الأقل | +| `someNonEmptyString(array, prop)` | `true` إذا كانت الخاصية سلسلة غير فارغة في عنصر واحد على الأقل | +| `none(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بخطأ في كل عنصر | +| `noneDefined(array, prop)` | `true` إذا كانت الخاصية غير معرّفة في كل عنصر | +| `noneEquals(array, prop, value)` | `true` إذا لم تكن الخاصية تساوي القيمة في أي عنصر | + +#### الأصول العامة + +يمكن للمكوّنات الأمامية الوصول إلى ملفات من دليل `public/` للتطبيق باستخدام `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +راجع [قسم الأصول العامة](#accessing-public-assets-with-getpublicasseturl) للتفاصيل. + +#### التنسيق + +تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام: + +* **أنماط مضمنة** — `style={{ color: 'red' }}` +* **مكوّنات Twenty لواجهة المستخدم** — استورد من `twenty-sdk/ui` (Button وTag وStatus وChip وAvatar وغيرها) +* **Emotion** — CSS-in-JS مع `@emotion/react` +* **Styled-components** — أنماط `styled.div` +* **Tailwind CSS** — أصناف مساعدة +* **أي مكتبة CSS-in-JS** متوافقة مع React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1014,333 @@ export default defineSkill({ ``` النقاط الرئيسية: - * `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case). * `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم. * `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي. * `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. * `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة. -يمكنك إنشاء مهارات جديدة بطريقتين: + + -* **مُنشأ بالقالب**: شغّل `yarn twenty add` واختر خيار إضافة مهارة جديدة. -* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineSkill()` مع اتباع النمط نفسه. +الوكلاء هم مساعدون ذكاء اصطناعي يعيشون داخل مساحة العمل لديك. استخدم `defineAgent()` لإنشاء وكلاء بموجه نظام مخصّص: -### عملاء واجهة برمجة التطبيقات ذات أنواع ثابتة (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -توفر حزمة `twenty-client-sdk` عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +النقاط الرئيسية: +* `name` هي سلسلة معرّف فريدة للوكيل (يُنصح باستخدام kebab-case). +* `label` هو اسم العرض الظاهر في واجهة المستخدم. +* `prompt` هو موجه النظام الذي يحدّد سلوك الوكيل. +* `description` (اختياري) يوفّر سياقًا حول ما يفعله الوكيل. +* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. +* `modelId` (اختياري) يتجاوز نموذج الذكاء الاصطناعي الافتراضي الذي يستخدمه الوكيل. + + + + +العروض هي تكوينات محفوظة لكيفية عرض سجلات كائن ما — بما في ذلك الحقول المرئية وترتيبها وأي مرشّحات أو مجموعات مُطبَّقة. استخدم `defineView()` لتضمين عروض مُهيّأة مسبقًا مع تطبيقك: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +النقاط الرئيسية: +* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا العرض. +* `key` يحدّد نوع العرض (مثل `ViewKey.INDEX` لعرض القائمة الرئيسي). +* `fields` يتحكّم في الأعمدة الظاهرة وترتيبها. يشير كل حقل إلى `fieldMetadataUniversalIdentifier`. +* يمكنك أيضًا تعريف `filters` و`filterGroups` و`groups` و`fieldGroups` لمزيد من التكوينات المتقدمة. +* `position` يتحكّم في الترتيب عند وجود عدة عروض لنفس الكائن. + + + + +تضيف عناصر قائمة التنقل إدخالات مخصّصة إلى الشريط الجانبي لمساحة العمل. استخدم `defineNavigationMenuItem()` للارتباط بالعروض أو عناوين URL خارجية أو الكائنات: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +النقاط الرئيسية: +* `type` يحدّد إلى ماذا يرتبط عنصر القائمة: `NavigationMenuItemType.VIEW` لعرض محفوظ، أو `NavigationMenuItemType.LINK` لعنوان URL خارجي. +* لروابط العروض، عيِّن `viewUniversalIdentifier`. لروابط خارجية، عيِّن `link`. +* `position` يتحكّم في الترتيب ضمن الشريط الجانبي. +* `icon` و`color` (اختياريان) يخصّصان المظهر. + + + + +تتيح لك تخطيطات الصفحات تخصيص مظهر صفحة تفاصيل السجل — ما الألسنة التي تظهر، وما الويدجتات داخل كل لسان، وكيف يتم ترتيبها. استخدم `definePageLayout()` لتضمين تخطيطات مخصّصة مع تطبيقك: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +النقاط الرئيسية: +* `type` يكون عادة `'RECORD_PAGE'` لتخصيص عرض التفاصيل لكائن محدّد. +* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا التخطيط. +* يُعرّف كل `tab` قسمًا من الصفحة مع `title` و`position` و`layoutMode` (`CANVAS` لتخطيط حرّ). +* يمكن لكل `widget` داخل لسان أن يعرض مكوّنًا أماميًا أو قائمة علاقات أو أنواع ويدجت مدمجة أخرى. +* `position` على الألسنة يتحكّم في ترتيبها. استخدم قيمًا أعلى (مثل 50) لوضع الألسنة المخصّصة بعد الألسنة المدمجة. + + +
+ +## الأصول العامة (مجلد `public/`) + +يحتوي مجلد `public/` في جذر تطبيقك على ملفات ثابتة — صور وأيقونات وخطوط وأي أصول أخرى يحتاجها تطبيقك وقت التشغيل. تُدرج هذه الملفات تلقائيًا في عمليات البناء، وتُزامَن أثناء وضع التطوير، وتُرفَع إلى الخادم. + +الملفات الموضوعة في `public/` هي: + +* **متاحة للعامة** — بمجرد مزامنتها إلى الخادم، تُقدَّم الأصول عبر عنوان URL عام. لا يلزم توثيق للوصول إليها. +* **متاحة في المكوّنات الأمامية** — استخدم عناوين الأصول لعرض الصور أو الأيقونات أو أي وسائط داخل مكوّنات React لديك. +* **متاحة في الدوال المنطقية** — أشِر إلى عناوين الأصول في رسائل البريد الإلكتروني أو استجابات واجهات البرمجة أو أي منطق على جهة الخادم. +* **مستخدمة لبيانات تعريف السوق** — يشير حقلا `logoUrl` و`screenshots` في `defineApplication()` إلى ملفات من هذا المجلد (مثل `public/logo.png`). تُعرَض هذه عند نشر تطبيقك في السوق. +* **تُزامَن تلقائيًا في وضع التطوير** — عند إضافة ملف في `public/` أو تحديثه أو حذفه، تتم مزامنته إلى الخادم تلقائيًا. لا حاجة لإعادة التشغيل. +* **مضمَّنة في عمليات البناء** — يقوم `yarn twenty build` بتجميع جميع الأصول العامة ضمن مخرجات التوزيع. + +### الوصول إلى الأصول العامة باستخدام `getPublicAssetUrl` + +استخدم المساعد `getPublicAssetUrl` من `twenty-sdk` للحصول على العنوان الكامل لملف في دليل `public/` لديك. يعمل ذلك في كلٍ من الدوال المنطقية والمكوّنات الأمامية. + +**في دالة منطقية:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**في مكوّن أمامي:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +وسيطة `path` نسبية إلى مجلد `public/` الخاص بتطبيقك. كلٌّ من `getPublicAssetUrl('logo.png')` و`getPublicAssetUrl('public/logo.png')` يُحلاّن إلى العنوان نفسه — تتم إزالة بادئة `public/` تلقائيًا إن وُجدت. + +## استخدام حِزَم npm + +يمكنك تثبيت واستخدام أي حزمة npm في تطبيقك. يتم تجميع كلٍ من الدوال المنطقية والمكوّنات الأمامية باستخدام [esbuild](https://esbuild.github.io/)، والذي يُضمّن جميع التبعيات ضمن المخرجات — لا حاجة إلى `node_modules` وقت التشغيل. + +### تثبيت حزمة + +```bash filename="Terminal" +yarn add axios +``` + +ثم استوردها في شيفرتك: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +وينطبق الأمر نفسه على المكوّنات الأمامية: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### كيف يعمل التجميع + +تستخدم خطوة البناء (`yarn twenty dev` أو `yarn twenty build`) أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية وكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة. + +**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت. + +**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح. + +كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم. + +## توليد قوالب الكيانات باستخدام `yarn twenty add` + +بدلًا من إنشاء ملفات الكيانات يدويًا، يمكنك استخدام أداة القوالب التفاعلية: + +```bash filename="Terminal" +yarn twenty add +``` + +ستطالبك باختيار نوع الكيان وتُرشدك خلال الحقول المطلوبة. تُولّد ملفًا جاهزًا للاستخدام مع `universalIdentifier` ثابت واستدعاء `defineEntity()` الصحيح. + +يمكنك أيضًا تمرير نوع الكيان مباشرة لتخطي المطالبة الأولى: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### أنواع الكيانات المتاحة + +| نوع الكيان | أمر | الملف المُولَّد | +| ------------------ | ------------------------------------ | ------------------------------------- | +| كائن | `yarn twenty add object` | `src/objects/.ts` | +| الحقل | `yarn twenty add field` | `src/fields/.ts` | +| دالة منطقية | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| مكوّن أمامي | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| دور | `yarn twenty add role` | `src/roles/.ts` | +| مهارة | `yarn twenty add skill` | `src/skills/.ts` | +| Agent | `yarn twenty add agent` | `src/agents/.ts` | +| عرض | `yarn twenty add view` | `src/views/.ts` | +| عنصر قائمة التنقّل | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| تخطيط الصفحة | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### ما الذي تُنشئه أداة القوالب + +لكل نوع كيان قالب خاص به. على سبيل المثال، يسأل `yarn twenty add object` عن: + +1. **الاسم (مفرد)** — مثل `invoice` +2. **الاسم (جمع)** — مثل `invoices` +3. **التسمية (مفرد)** — تُستمد تلقائيًا من الاسم (مثل `Invoice`) +4. **التسمية (جمع)** — تُملأ تلقائيًا (مثل `Invoices`) +5. **إنشاء عرض وعنصر تنقّل؟** — إذا أجبت بنعم، فستُنشئ أداة القوالب أيضًا عرضًا مطابقًا ورابط شريط جانبي للكائن الجديد. + +أنواع الكيانات الأخرى لها مطالبات أبسط — فمعظمها يطلب اسمًا فقط. + +نوع الكيان `field` أكثر تفصيلاً: يطلب اسم الحقل وتسمية الحقل ونوعه (من قائمة بكل أنواع الحقول المتاحة مثل `TEXT` و`NUMBER` و`SELECT` و`RELATION` وغيرها)، ومعرّف `universalIdentifier` للكائن الهدف. + +### مسار خرج مخصّص + +استخدم العلم `--path` لوضع الملف المُولَّد في موقع مخصّص: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## عملاء واجهة برمجة تطبيقات مضبوطة الأنواع (`twenty-client-sdk`) + +توفر حزمة `twenty-client-sdk` عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية. | العميل | استيراد | نقطة النهاية | مُولَّد؟ | | ------------------- | ---------------------------- | --------------------------------------------------- | -------------------------- | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — بيانات مساحة العمل (السجلات، الكائنات) | نعم، في وقت التطوير/البناء | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — تكوين مساحة العمل، رفع الملفات | لا، يأتي مُجهزًا مسبقًا | -#### CoreApiClient + + -`CoreApiClient` هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يتم توليده من مخطط مساحة العمل الخاصة بك أثناء `yarn twenty dev` أو `yarn twenty build`، لذا فهو مكتوب الأنواع بالكامل ليتوافق مع كائناتك وحقولك. +`CoreApiClient` هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يُولَّد من مخطط مساحة العمل لديك أثناء `yarn twenty dev` أو `yarn twenty build`، لذا فهو مضبوط الأنواع بالكامل ليتوافق مع كائناتك وحقولك. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1352,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1378,14 @@ const { createCompany } = await client.mutation({ يستخدم العميل صياغة مجموعة اختيار: مرِّر `true` لتضمين حقل، واستخدم `__args` للوسيطات، وعشّش الكائنات للعلاقات. ستحصل على إكمال تلقائي كامل وفحص للأنواع يعتمد على مخطط مساحة العمل لديك. -**يتم توليد CoreApiClient في وقت التطوير/البناء.** إذا حاولت استخدامه دون تشغيل `yarn twenty dev` أو `yarn twenty build` أولًا، فسوف ينتج خطأ. تتم عملية التوليد تلقائيًا — حيث يقوم CLI بفحص مخطط GraphQL الخاص بمساحة العمل لديك، ويولّد عميلًا مكتوب الأنواع باستخدام `@genql/cli`، ويكتب المصادر المُولَّدة إلى `node_modules/twenty-client-sdk/dist/core/generated/`، ويستبدل الأجزاء الوهمية في `node_modules/twenty-client-sdk/dist/core.mjs` و`node_modules/twenty-client-sdk/dist/core.cjs`. +**يتم توليد CoreApiClient في وقت التطوير/البناء.** إذا استخدمته دون تشغيل `yarn twenty dev` أو `yarn twenty build` أولًا، فسيؤدي ذلك إلى خطأ. تحدث عملية التوليد تلقائيًا — إذ يستطلع CLI مخطط GraphQL لمساحة عملك وينشئ عميلًا مضبوط الأنواع باستخدام `@genql/cli`. #### استخدام CoreSchema للتعليقات التوضيحية للأنواع -يوفّر `CoreSchema` أنواع TypeScript المطابقة لكائنات مساحة العمل لديك، وهو مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال: +`CoreSchema` يوفّر أنواع TypeScript المطابقة لكائنات مساحة العمل لديك — مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1404,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -يأتي `MetadataApiClient` مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية `/metadata` للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات: +يأتي `MetadataApiClient` مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية `/metadata` للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### بيانات الاعتماد أثناء وقت التشغيل - -عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية: - -* `TWENTY_API_URL` — عنوان URL الأساسي لواجهة Twenty البرمجية -* `TWENTY_API_KEY` — مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك - -لست **بحاجة** إلى تمرير هذه القيم إلى العملاء — فهي تُقرأ تلقائيًا من `process.env`. تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `defaultRoleUniversalIdentifier` ضمن `application-config.ts`. - #### رفع الملفات -يتضمن `MetadataApiClient` طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع الملف. يطبّق [مواصفة طلب GraphQL متعددة الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1457,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| المعلمة | النوع | الوصف | -| ---------------------------------- | -------- | ---------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | المحتوى الخام للملف | -| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) | -| `contentType` | `string` | نوع MIME للملف (القيمة الافتراضية هي `application/octet-stream` إذا لم يتم تحديده) | -| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك | +| المعلمة | النوع | الوصف | +| ---------------------------------- | -------- | ---------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | المحتوى الخام للملف | +| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) | +| `contentType` | `string` | نوع MIME (القيمة الافتراضية `application/octet-stream` إذا لم يُحدَّد) | +| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك | النقاط الرئيسية: - * يستخدم `universalIdentifier` الخاص بالحقل (وليس معرّفه الخاص بمساحة العمل)، بحيث يعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك. * العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع. -### مثال Hello World + + -استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية: + + * `TWENTY_API_URL` — عنوان URL الأساسي لواجهة Twenty البرمجية + * `TWENTY_APP_ACCESS_TOKEN` — مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك + + لست **بحاجة** إلى تمرير هذه القيم إلى العملاء — فهي تُقرأ تلقائيًا من `process.env`. تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `defaultRoleUniversalIdentifier` ضمن `application-config.ts`. + + +## اختبار تطبيقك + +يوفّر SDK واجهات برمجة قابلة للتنفيذ برمجيًا تمكّنك من بناء تطبيقك ونشره وتثبيته وإلغاء تثبيته من شيفرة الاختبار. بالاقتران مع [Vitest](https://vitest.dev/) وعملاء واجهة البرمجة مضبوطي الأنواع، يمكنك كتابة اختبارات تكامل تتحقّق من أن تطبيقك يعمل من البداية إلى النهاية مقابل خادم Twenty حقيقي. + +### إعداد + +يتضمّن التطبيق المُولَّد بالقالب بالفعل Vitest. إذا أعددته يدويًا، فثبّت التبعيات: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +أنشئ `vitest.config.ts` في جذر تطبيقك: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +أنشئ ملف إعداد يتحقّق من إمكانية الوصول إلى الخادم قبل تشغيل الاختبارات: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### واجهات SDK البرمجية + +يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار: + +| دالة | الوصف | +| -------------- | ----------------------------------------- | +| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball | +| `appDeploy` | رفع ملف tarball إلى الخادم | +| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة | +| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة | + +تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`. + +### كتابة اختبار تكامل + +إليك مثالًا كاملًا يبني التطبيق وينشره ويثبّته، ثم يتحقّق من ظهوره في مساحة العمل: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### تشغيل الاختبارات + +تأكّد من تشغيل خادم Twenty المحلي لديك، ثم: + +```bash filename="Terminal" +yarn test +``` + +أو في وضع المراقبة أثناء التطوير: + +```bash filename="Terminal" +yarn test:watch +``` + +### التحقق من الأنواع + +يمكنك أيضًا تشغيل التحقق من الأنواع على تطبيقك دون تشغيل الاختبارات: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع. + +## مرجع CLI + +بالإضافة إلى `dev` و`build` و`add` و`typecheck`، يوفّر CLI أوامر لتنفيذ الدوال وعرض السجلات وإدارة تثبيتات التطبيقات. + +### تنفيذ الدوال (`yarn twenty exec`) + +تشغيل دالة منطقية يدويًا دون تشغيلها عبر HTTP أو cron أو حدث قاعدة بيانات: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### عرض سجلات الدوال (`yarn twenty logs`) + +بثّ سجلات التنفيذ لدوال تطبيقك المنطقية: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +يختلف هذا عن `yarn twenty server logs`، الذي يعرض سجلات حاوية Docker. يعرض `yarn twenty logs` سجلات تنفيذ دوال تطبيقك من خادم Twenty. + + +### إلغاء تثبيت تطبيق (`yarn twenty uninstall`) + +أزل تطبيقك من مساحة العمل النشطة: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx index 58aa7411ba..d4de0f4988 100644 --- a/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: أنشئ أول تطبيق Twenty خلال دقائق. --- -التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور. +Apps are currently in alpha. The feature works but is still evolving. تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول ووظائف منطقية ومهارات ذكاء اصطناعي ومكونات واجهة مستخدم مخصصة — جميعها تُدار ككود. -**ما الذي يمكنك بناؤه:** - -* كائنات مخصّصة، وحقول، وطرق عرض، وعناصر تنقّل لتشكيل نموذج بياناتك -* دوال منطقية يتم تشغيلها عبر مسارات HTTP، وجداول cron، أو أحداث قاعدة البيانات -* مكوّنات واجهة أمامية تُعرَض مباشرة داخل واجهة مستخدم Twenty -* مهارات توسّع قدرات وكلاء الذكاء الاصطناعي في Twenty -* انشر تطبيقاً عبر مساحات عمل متعددة - ## المتطلبات الأساسية -* Node.js 24+ -* Yarn 4 -* Docker (أو مثيل Twenty محلي قيد التشغيل) +Before you begin, make sure the following is installed on your machine: -## البدء +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> استخدم الخيار `--minimal` لتهيئة تثبيت مصغّر +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -من هنا يمكنك: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## هيكل المشروع (مُنشأ بالقالب) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/` -* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4 -* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI -* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء. + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/ar/developers/extend/apps/publishing) for details. + -يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/ar/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -بشكل عام: +### Key files -* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة. -* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`.twenty/` و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`. -* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع. -* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع. -* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك. -* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية. -* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل. -* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود +| File / Folder | الغرض | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | مهارات توسّع قدرات وكلاء الذكاء الاصطناعي في Twenty. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### اكتشاف الكيانات +## Managing remotes -يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`: - -| دالة مساعدة | نوع الكيان | -| -------------------------------- | ---------------------------------------------- | -| `defineObject` | تعريفات كائنات مخصصة | -| `defineLogicFunction` | تعريفات الوظائف المنطقية | -| `definePreInstallLogicFunction` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) | -| `definePostInstallLogicFunction` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) | -| `defineFrontComponent` | تعريفات المكونات الواجهية | -| `defineRole` | تعريفات الأدوار | -| `defineField` | امتدادات الحقول للكائنات الموجودة | -| `defineView` | تعريفات العروض المحفوظة | -| `defineNavigationMenuItem` | تعريفات عناصر قائمة التنقل | -| `defineSkill` | تعريفات مهارات وكلاء الذكاء الاصطناعي | - - -**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا. - - -مثال على كيان تم اكتشافه: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات: - -* `yarn twenty dev` سيولّد تلقائياً `CoreApiClient` مضبوط الأنواع (لبيانات مساحة العمل عبر `/graphql`) داخل `node_modules/twenty-client-sdk/`. `MetadataApiClient` (لتهيئة مساحة العمل ورفع الملفات عبر `/metadata`) يأتي مُبنًى مسبقاً ومتاحاً على الفور. استوردْهما من `twenty-client-sdk/core` و`twenty-client-sdk/metadata` على الترتيب. -* `yarn twenty add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك. - -## المصادقة - -في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال: - -* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك) -* مفتاح واجهة برمجة التطبيقات - -تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. يمكنك الاحتفاظ بملفات تعريف متعددة والتبديل بينها. - -### إدارة مساحات العمل +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. لا يزال بإمكانك تجاوزه مؤقتًا باستخدام `--workspace `. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### بدء الخادم + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| أمر | الوصف | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## إعداد يدوي (بدون المهيئ) -بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -ثم أضف سكربتًا باسم `twenty`: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ yarn add -D twenty-sdk } ``` -الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty `، مثلًا: `yarn twenty dev`، `yarn twenty help`، إلخ. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## كيفية استخدام مثيل محلي من Twenty - -إذا كنت تقوم بتشغيل مثيل محلي من Twenty بالفعل (على سبيل المثال عبر `npx nx start twenty-server`)، فيمكنك الاتصال به بدلًا من استخدام Docker: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## استكشاف الأخطاء وإصلاحها -* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة. -* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة برمجة التطبيقات وأن خادم Twenty قابل للوصول. -* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty dev` — فهو يولِّد العميل مضبوط الأنواع تلقائيًا. -* وضع التطوير لا يزامن: تأكد من أن `yarn twenty dev` قيد التشغيل وأن التغييرات غير متجاهلة في بيئتك. +If you run into issues: -قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx index e57357210a..4e014a4ebc 100644 --- a/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ --- -التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور. + التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور. ## نظرة عامة بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه: -* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته. * **نشر أرشيف tar** — ارفع تطبيقك مباشرةً إلى خادم Twenty محدد للاستخدام الداخلي أو الخاص. +* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته. كلا المسارين يبدآن من نفس خطوة **build**. ## بناء تطبيقك -يقوم الأمر `build` بتجميع مصادر TypeScript الخاصة بك، وتحويل دوال المنطق ومكوّنات الواجهة الأمامية، وإنشاء ملف `manifest.json` يصف محتويات تطبيقك: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -يتم حفظ المخرجات في `.twenty/output/`. يحتوي هذا الدليل على كل ما يلزم للتوزيع: الكود المُجمَّع، والأصول، وملف manifest، ونسخة من `package.json` الخاص بك. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -لإنشاء حزمة tarball بصيغة `.tgz` أيضًا (تُستخدم داخليًا بواسطة أمر النشر، أو للتوزيع اليدوي): +## النشر إلى خادم (tarball) + +بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty. + +### المتطلبات الأساسية + +قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`. + +أضِف remote: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### النشر + +بناء تطبيقك ورفعه إلى الخادم في خطوة واحدة: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### مشاركة تطبيق منشور + +تطبيقات tarball لا تُدرَج في السوق العامة، لذا لن تكتشفها مساحات العمل الأخرى على الخادم نفسه عبر الاستعراض. لمشاركة تطبيق منشور: + +1. اذهب إلى **الإعدادات > التطبيقات > التسجيلات** وافتح تطبيقك +2. في علامة التبويب **التوزيع**، انقر **نسخ رابط المشاركة** +3. شارك هذا الرابط مع المستخدمين في مساحات عمل أخرى — سيأخذهم مباشرةً إلى صفحة تثبيت التطبيق + +يستخدم رابط المشاركة عنوان URL الأساسي للخادم (من دون أي نطاق فرعي لمساحة عمل)، لذا يعمل مع أي مساحة عمل على الخادم. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### إدارة الإصدارات + +لطرح تحديث: + +1. ارفع قيمة الحقل `version` في ملف `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. سترى مساحات العمل التي ثبّتت التطبيق الترقية متاحة في إعداداتها + +{/* TODO: add screenshot of the Upgrade button */} + ## النشر على npm يُتيح النشر على npm إمكانية العثور على تطبيقك في سوق Twenty. يمكن لأي مساحة عمل في Twenty استعراض تطبيقات السوق وتثبيتها وترقيتها مباشرةً من واجهة المستخدم. @@ -39,41 +81,42 @@ yarn twenty build --tarball ### المتطلبات * حساب على [npm](https://www.npmjs.com) -* الكلمة المفتاحية `twenty-app` **يجب** أن تُدرج في مصفوفة `keywords` في `package.json` الخاص بك - -### إضافة الكلمة المفتاحية المطلوبة - -يعثر سوق Twenty على التطبيقات من خلال البحث في سجل npm عن الحزم التي تحتوي على الكلمة المفتاحية `twenty-app`. أضِفها إلى `package.json` الخاص بك: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -يبحث السوق عن `keywords:twenty-app` في سجل npm. من دون هذه الكلمة المفتاحية، لن تظهر حزمتك في السوق حتى وإن كانت تحمل بادئة الاسم `twenty-app-`. - +### بيانات التعريف لسوق التطبيقات -### الخطوات +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **بناء تطبيقك:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **النشر على npm:** +See the [defineApplication accordion](/l/ar/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -هذا يُشغِّل `npm publish` من دليل `.twenty/output/`. - للنشر تحت dist-tag معيّن (مثلًا: `beta` أو `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### كيف تعمل آلية الاكتشاف في السوق -يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**: +يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**. -1. يبحث عن جميع حزم npm التي تحتوي على الكلمة المفتاحية `keywords:twenty-app` -2. ولكل حزمة، يجلب ملف `manifest.json` من شبكة CDN الخاصة بـ npm -3. يتم استخراج بيانات التعريف الخاصة بالتطبيق (الاسم، الوصف، المؤلف، الشعار، لقطات الشاشة، الفئة) من ملف manifest وعرضها في السوق - -بعد النشر، قد يستغرق ظهور تطبيقك في السوق ما يصل إلى ساعة واحدة. لتشغيل المزامنة فورًا بدلًا من انتظار التشغيل التالي كل ساعة: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -لاستهداف remote معيّن: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -تأتي بيانات التعريف المعروضة في السوق من استدعائك لـ `defineApplication()` في الشيفرة المصدرية لتطبيقك — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. إذا لم يحدد تطبيقك `aboutDescription` في `defineApplication()`، فسيستخدم السوق تلقائيًا ملف `README.md` الخاص بحزمتك من npm كمحتوى لصفحة حول. هذا يعني أنه يمكنك الاحتفاظ بملف README واحد لكل من npm وسوق Twenty. إذا كنت تريد وصفًا مختلفًا في السوق، فقم بتعيين `aboutDescription` بشكل صريح. @@ -108,7 +143,7 @@ yarn twenty catalog-sync -r production ### النشر عبر CI -يتضمن المشروع المُولَّد سير عمل GitHub Actions يقوم بالنشر عند كل إصدار: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` بالنسبة لأنظمة CI الأخرى (GitLab CI، وCircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `yarn twenty build`، ثم `npm publish` من `.twenty/output`. - + **npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد. - - -## النشر إلى خادم (tarball) - -بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty. - -### المتطلبات الأساسية - -قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`. - -أضِف remote: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -لخادم تطوير محلي: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -يمكنك أيضًا إجراء المصادقة باستخدام مفتاح API للبيئات غير التفاعلية: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -إدارة remotes الخاصة بك: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### النشر - -بناء تطبيقك ورفعه إلى الخادم في خطوة واحدة: - -```bash filename="Terminal" -yarn twenty deploy -``` - -يُنشئ هذا التطبيق باستخدام `--tarball`، ثم يرفع ملف tarball إلى الـ remote الافتراضي عبر رفع متعدد الأجزاء لـ GraphQL. - -لنشره إلى remote معيّن: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### مشاركة تطبيق منشور - -تطبيقات tarball لا تُدرَج في السوق العامة، لذا لن تكتشفها مساحات العمل الأخرى على الخادم نفسه عبر الاستعراض. لمشاركة تطبيق منشور: - -1. اذهب إلى **الإعدادات > التطبيقات > التسجيلات** وافتح تطبيقك -2. في علامة التبويب **التوزيع**، انقر **نسخ رابط المشاركة** -3. شارك هذا الرابط مع المستخدمين في مساحات عمل أخرى — سيأخذهم مباشرةً إلى صفحة تثبيت التطبيق - -يستخدم رابط المشاركة عنوان URL الأساسي للخادم (من دون أي نطاق فرعي لمساحة عمل)، لذا يعمل مع أي مساحة عمل على الخادم. - -### إدارة الإصدارات - -لطرح تحديث: - -1. ارفع قيمة الحقل `version` في ملف `package.json` -2. شغّل `yarn twenty deploy` (أو `yarn twenty deploy -r production`) -3. سترى مساحات العمل التي ثبّتت التطبيق الترقية متاحة في إعداداتها + ## تثبيت التطبيقات -بعد نشر التطبيق (npm) أو نشره إلى الخادم (tarball)، تقوم مساحات العمل بتثبيته عبر واجهة المستخدم: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -أو من صفحة **الإعدادات > التطبيقات** في واجهة Twenty، حيث يمكن استعراض التطبيقات من السوق ومن النشر عبر tarball وتثبيتها. - -## فئات توزيع التطبيقات - -تُنظِّم Twenty التطبيقات في ثلاث فئات استنادًا إلى طريقة توزيعها: - -| الفئة | كيف يعمل | مرئي في سوق Twenty؟ | -| ------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------- | -| **التطوير** | تطبيقات وضع التطوير المحلي التي تعمل عبر `yarn twenty dev`. تُستخدم للبناء والاختبار. | لا | -| **منشور (npm)** | تطبيقات منشورة على npm تحتوي على الكلمة المفتاحية `twenty-app`. مدرجة في سوق Twenty لتتمكن أي مساحة عمل من تثبيتها. | نعم | -| **داخلي (tarball)** | تطبيقات منشورة عبر tarball إلى خادم محدد. متاحة فقط لمساحات العمل على ذلك الخادم عبر رابط مشاركة. | لا | - - -ابدأ في وضع **التطوير** أثناء بناء تطبيقك. عندما يصبح جاهزًا، اختر **منشور** (npm) للتوزيع الواسع أو **داخلي** (tarball) للنشر الخاص. - - -## مرجع CLI - -| أمر | الوصف | الأعلام الرئيسية | -| --------------------------- | ------------------------------------ | ----------------------------------------------------- | -| `yarn twenty build` | تجميع التطبيق وإنشاء manifest | `--tarball` — يقوم أيضًا بإنشاء حزمة `.tgz` | -| `yarn twenty publish` | بناء التطبيق ونشره إلى npm | `--tag ` — وسم توزيع npm (مثلًا: `beta`، `next`) | -| `yarn twenty deploy` | بناء ورفع tarball إلى خادم | `-r, --remote ` — الـ remote المستهدف | -| `yarn twenty catalog-sync` | تشغيل مزامنة كتالوج السوق على الخادم | `-r, --remote ` — الـ remote المستهدف | -| `yarn twenty install` | تثبيت تطبيق منشور على مساحة عمل | `-r, --remote ` — الـ remote المستهدف | -| `yarn twenty dev` | مراقبة ومزامنة التغييرات المحلية | يستخدم الـ remote الافتراضي | -| `yarn twenty remote add` | إضافة اتصال بخادم | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | عرض الـ remotes المُكوَّنة | — | -| `yarn twenty remote switch` | تعيين الـ remote الافتراضي | — | -| `yarn twenty remote status` | عرض حالة الاتصال | — | -| `yarn twenty remote remove` | إزالة remote | — | diff --git a/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx index 6cef5662da..1f12d5443a 100644 --- a/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود. --- -التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور. +Apps are currently in alpha. The feature works but is still evolving. -## ما هي التطبيقات؟ +## What are apps? -تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف المنطق في الكود — مما يجعل البناء والصيانة والنشر إلى مساحات عمل متعددة أسرع. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**ما الذي يمكنك فعله اليوم:** +**ما الذي يمكنك بناؤه:** -* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار) -* أنشئ وظائف منطقية مع مشغلات مخصصة -* تعريف المهارات والوكلاء للذكاء الاصطناعي -* انشر التطبيق نفسه عبر مساحات عمل متعددة +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## المتطلبات الأساسية - -* Node.js 24+ وYarn 4 -* Docker (لخادم تطوير Twenty المحلي) - -## البدء - -أنشئ تطبيقًا جديدًا باستخدام المولّد الرسمي. يمكنه بدء مثيل محلي من Twenty تلقائيًا لك: +## Quick start ```bash filename="Terminal" -# إنشاء تطبيق جديد — ستعرض واجهة سطر الأوامر خيار بدء خادم Twenty محلي npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# ابدأ وضع التطوير: يُزامن التغييرات المحلية تلقائيًا مع مساحة العمل الخاصة بك yarn twenty dev ``` -### إدارة الخادم المحلي +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/ar/developers/extend/apps/getting-started) guide for the full walkthrough. -يتضمن SDK أوامر لإدارة خادم تطوير Twenty محلي (صورة Docker متكاملة تتضمن PostgreSQL وRedis والخادم والعامل على المنفذ 2020). تنطبق هذه الأوامر فقط على خادم التطوير المستند إلى Docker — ولا تُدير مثيل Twenty المُشغَّل من المصدر (مثل `npx nx start twenty-server` على المنفذ 3000): +## Detailed guides -```bash filename="Terminal" -# ابدأ الخادم المحلي (يسحب الصورة إذا لزم الأمر) -yarn twenty server start +| Guide | الوصف | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/ar/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/ar/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/ar/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# تحقّق من حالة الخادم -yarn twenty server status - -# بثّ سجلات الخادم -yarn twenty server logs - -# أوقف الخادم -yarn twenty server stop - -# أعد ضبط جميع البيانات وابدأ من جديد -yarn twenty server reset -``` - -يأتي الخادم المحلي مهيأً مسبقًا بمساحة عمل ومستخدم (`tim@apple.dev` / `tim@apple.dev`)، بحيث يمكنك البدء في التطوير فورًا دون أي إعداد يدوي. - -### المصادقة - -وصّل تطبيقك بالخادم المحلي باستخدام OAuth: - -```bash filename="Terminal" -# المصادقة عبر OAuth (يفتح المتصفح) -yarn twenty remote add --local -``` - -يدعم المُنشئ وضعين للتحكم في ملفات الأمثلة التي سيتم تضمينها: - -```bash filename="Terminal" -# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل، مهارة، وكيل) -npx create-twenty-app@latest my-app - -# الأدنى: الملفات الأساسية فقط (application-config.ts و default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### كيفية استخدام مثيل محلي من Twenty - -إذا كنت تقوم بتشغيل مثيل محلي من Twenty بالفعل، فيمكنك الاتصال به بدلًا من استخدام Docker. مرِّر المنفذ الذي يستمع عليه الخادم المحلي لديك (القيمة الافتراضية: `3000`): - -```bash filename="Terminal" -# During scaffolding -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding -yarn twenty remote add --local --port 3000 -``` - -من هنا يمكنك: - -```bash filename="Terminal" -# أضف كيانًا جديدًا إلى تطبيقك (موجّه) -yarn twenty entity:add - -# راقب سجلات وظائف تطبيقك -yarn twenty function:logs - -# نفّذ وظيفة بالاسم -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# نفّذ دالة ما قبل التثبيت -yarn twenty function:execute --preInstall - -# نفّذ دالة ما بعد التثبيت -yarn twenty function:execute --postInstall - -# ابنِ التطبيق للتوزيع -yarn twenty build - -# انشر التطبيق إلى npm أو إلى خادم Twenty -yarn twenty publish - -# أزل تثبيت التطبيق من مساحة العمل الحالية -yarn twenty uninstall - -# اعرض مساعدة الأوامر -yarn twenty help -``` - -راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## هيكل المشروع (مُنشأ بالقالب) - -عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي: - -* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/` -* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4 -* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI -* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء. - -يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # مجلد الأصول العامة (صور، خطوط، إلخ) - src/ - ├── application-config.ts # مطلوب - إعدادات التطبيق الرئيسية - ├── roles/ - │ └── default-role.ts # الدور الافتراضي للدوال المنطقية - ├── objects/ - │ └── example-object.ts # تعريف كائن مخصص — مثال - ├── fields/ - │ └── example-field.ts # تعريف حقل مستقل — مثال - ├── logic-functions/ - │ ├── hello-world.ts # دالة منطقية — مثال - │ ├── pre-install.ts # دالة منطقية لما قبل التثبيت - │ └── post-install.ts # دالة منطقية لما بعد التثبيت - ├── front-components/ - │ └── hello-world.tsx # مكوّن واجهة أمامية — مثال - ├── views/ - │ └── example-view.ts # تعريف عرض محفوظ — مثال - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال - ├── skills/ - │ └── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال - └── agents/ - └── example-agent.ts # تعريف وكيل الذكاء الاصطناعي — مثال -``` - -مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`). - -بشكل عام: - -* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة. -* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`. -* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع. -* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع. -* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك. -* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية. -* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل. -* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود +## Key concepts ### اكتشاف الكيانات -يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| دالة مساعدة | نوع الكيان | -| ---------------------------------- | ---------------------------------------------- | -| `defineObject()` | تعريفات كائنات مخصصة | -| `defineLogicFunction()` | تعريفات الوظائف المنطقية | -| `definePreInstallLogicFunction()` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) | -| `definePostInstallLogicFunction()` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) | -| `defineFrontComponent()` | Front component definitions | -| `defineRole()` | تعريفات الأدوار | -| `defineField()` | امتدادات الحقول للكائنات الموجودة | -| `defineView()` | تعريفات العروض المحفوظة | -| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل | -| `defineSkill()` | تعريفات مهارات وكيل الذكاء الاصطناعي | -| `defineAgent()` | تعريفات وكلاء الذكاء الاصطناعي | +### Available entity types - -**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا. - +| دالة | الغرض | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -مثال على كيان تم اكتشافه: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات: - -* سيقوم `yarn twenty dev` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/clients`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`). -* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك. - -## المصادقة - -في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال: - -* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك) -* مفتاح واجهة برمجة التطبيقات - -تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. You can maintain multiple profiles and switch between them. - -### Managing workspaces +### مرجع CLI ```bash filename="Terminal" -# تسجيل الدخول تفاعليًا (مُوصى به) -yarn twenty auth:login - -# تسجيل الدخول إلى ملف تعريف لمساحة عمل محددة -yarn twenty auth:login --workspace my-custom-workspace - -# عرض جميع مساحات العمل المُكوَّنة -yarn twenty auth:list - -# تبديل مساحة العمل الافتراضية (تفاعليًا) -yarn twenty auth:switch - -# التبديل إلى مساحة عمل محددة -yarn twenty auth:switch production - -# التحقق من حالة المصادقة الحالية -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. You can still override it temporarily with `--workspace `. - -## استخدم موارد SDK (الأنواع والتكوين) - -يوفّر twenty-sdk كتلَ بناءٍ مضبوطة الأنواع ودوال مساعدة تستخدمها داخل تطبيقك. فيما يلي الأجزاء الأساسية التي ستتعامل معها غالبًا. - -### دوال مساعدة - -يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define({...})` كي يتم اكتشاف كياناتك: - -| دالة | الغرض | -| ---------------------------------- | ---------------------------------------------------- | -| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) | -| `defineObject()` | تعريف كائنات مخصصة مع حقول | -| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات | -| `definePreInstallLogicFunction()` | تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق) | -| `definePostInstallLogicFunction()` | تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق) | -| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة | -| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات | -| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية | -| `defineView()` | تعريف العروض المحفوظة للكائنات | -| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي | -| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي | -| `defineAgent()` | عرِّف وكلاء الذكاء الاصطناعي باستخدام موجهات النظام | - -تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع. - -### تعريف الكائنات - -تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -النقاط الرئيسية: - -* استخدم `defineObject()` للحصول على تحقق مدمج ودعم أفضل من IDE. -* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر. -* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به. -* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة. -* يمكنك إنشاء كائنات جديدة باستخدام `yarn twenty entity:add`، والذي يرشدك خلال التسمية والحقول والعلاقات. - - -**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية -مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt`. -لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة. -يمكنك تجاوز الحقول الافتراضية من خلال تعريف حقل بالاسم نفسه في مصفوفة `fields` الخاصة بك، -لكن هذا غير مستحسن. - - -### تعريف الحقول على الكائنات الموجودة - -استخدم `defineField()` لإضافة حقول مخصصة إلى الكائنات الموجودة — سواء الكائنات القياسية (مثل `company` و`person` و`opportunity`) أو الكائنات المخصصة التي تُعرِّفها تطبيقات أخرى. يوجد كل حقل في ملفه الخاص ويشير إلى الكائن الهدف بواسطة `universalIdentifier` الخاص به. - -للإشارة إلى الكائنات القياسية، استورد `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` من `twenty-sdk`. يوفر هذا الثابت معرِّفات مستقرة لجميع الكائنات المضمنة وحقولها: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -النقاط الرئيسية: - -* `objectUniversalIdentifier` يُحدِّد لـ Twenty الكائن الذي سيُرفَق به الحقل. استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` للكائنات القياسية. -* يتطلب كل حقل `universalIdentifier` ثابتًا خاصًا به، و`name`، و`type`، و`label`، و`objectUniversalIdentifier` الخاص بالكائن الهدف. -* يمكنك إنشاء حقول جديدة باستخدام `yarn twenty entity:add` واختيار خيار الحقل. -* يُصدَّر `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` أيضًا باسم `STANDARD_OBJECT` لسهولة الاستخدام — كلاهما يشير إلى الثابت نفسه. - -تشمل الكائنات القياسية المتاحة: `attachment`، `blocklist`، `calendarChannel`، `calendarEvent`، `calendarEventParticipant`، `company`، `connectedAccount`، `dashboard`، `favorite`، `favoriteFolder`، `message`، `messageChannel`، `messageParticipant`، `messageThread`، `note`، `noteTarget`، `opportunity`، `person`، `task`، `taskTarget`، `timelineActivity`، `workflow`، `workflowAutomatedTrigger`، `workflowRun`، `workflowVersion`، و`workspaceMember`. - -يُوفِّر كل كائن قياسي أيضًا معرّفات حقوله. على سبيل المثال، للإشارة إلى حقل محدد على كائن قياسي ضمن أذونات الأدوار: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### حقول العلاقات على الكائنات الموجودة - -يمكنك أيضًا تعريف حقول علاقات تربط الكائنات الموجودة بكائناتك المخصصة: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### تكوين التطبيق (application-config.ts) - -كل تطبيق لديه ملف واحد `application-config.ts` يصف: - -* **هوية التطبيق**: المعرفات، اسم العرض، والوصف. -* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات. -* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة. -* **(اختياري) دالة ما قبل التثبيت**: دالة منطقية تعمل قبل تثبيت التطبيق. -* **(Optional) post-install function**: a logic function that runs after the app is installed. - -Use `defineApplication()` to define your application configuration: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'تطبيق Twenty الخاص بي', - description: 'أول تطبيق لي لـ Twenty', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'الاسم الافتراضي للمستلم للبطاقات البريدية', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -الملاحظات: - -* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة. -* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه). -* يتم اكتشاف دوال ما قبل التثبيت وما بعد التثبيت تلقائيًا أثناء إنشاء ملف البيان. راجع [دوال ما قبل التثبيت](#pre-install-functions) و[دوال ما بعد التثبيت](#post-install-functions). - -#### الأدوار والصلاحيات - -يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق في تطبيقك. - -* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا. -* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور. -* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا بالأذونات التي تحتاجها وظائفك فقط، ثم أشِر إلى معرّفه الشامل. - -##### الدور الافتراضي للوظيفة (\*.role.ts) - -عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`. بعبارة أخرى: - -* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة. -* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته. - -الملاحظات: - -* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز. -* استبدل `objectPermissions` و`fieldPermissions` بالكائنات/الحقول التي تحتاجها وظائفك. -* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه. -* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### تكوين الوظيفة المنطقية ونقطة الدخول - -كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // مشغّل مسار HTTP عام '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // مُشغّل كرون (نمط CRON) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // مُشغّل حدث قاعدة البيانات - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -}); -``` - -أنواع المشغلات الشائعة: - -* **route**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**: - -> مثال: `path: '/post-card/create',` -> الاستدعاء على `/s/post-card/create` - -* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON. -* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة. - -> مثال: `person.updated` - -الملاحظات: - -* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى. -* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة. - -### دوال ما قبل التثبيت - -دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا قبل تثبيت تطبيقك على مساحة عمل. يفيد ذلك في مهام التحقق، وفحص المتطلبات المسبقة، أو تجهيز حالة مساحة العمل قبل متابعة التثبيت الرئيسي. - -عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما قبل التثبيت لك في `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -النقاط الرئيسية: - -* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). -* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة). -* يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. -* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `preInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`. -* تم ضبط المهلة الافتراضية على 300 ثانية (5 دقائق) للسماح بمهام التحضير الأطول. -* لا تحتاج دوال ما قبل التثبيت إلى مُشغِّلات — إذ يستدعيها النظام الأساسي قبل التثبيت أو يدويًا عبر `function:execute --preInstall`. - -### Post-install functions - -A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings. - -عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما بعد التثبيت لك في `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -النقاط الرئيسية: - -* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). -* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة). -* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. -* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `postInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`. -* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات. -* لا تحتاج دوال ما بعد التثبيت إلى مُشغِّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `function:execute --postInstall`. - -### حمولة مشغل المسار - - -**تغيير غير متوافق (v1.16، يناير 2026):** لقد تغير تنسيق حمولة مشغل المسار. قبل v1.16، كانت معلمات الاستعلام، ومعلمات المسار، وجسم الطلب تُرسل مباشرةً كحمولة. بدءًا من v1.16، أصبحت متداخلة داخل كائن منظَّم `RoutePayload`. - -**قبل v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**بعد v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params. - - -عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -يحتوي نوع `RoutePayload` على البنية التالية: - -| الخاصية | النوع | الوصف | -| ---------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------- | -| `headers` | `Record` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) | -| `pathParameters` | `Record` | معلمات المسار المستخرجة من نمط المسار (على سبيل المثال، `/users/:id` → `{ id: '123' }`) | -| `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) | -| `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | -| `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | المسار الخام للطلب | - -### تمرير رؤوس HTTP - -افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى وظيفتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -في المعالج الخاص بك، يمكنك حينها الوصول إلى هذه الرؤوس: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`). - - -يمكنك إنشاء وظائف جديدة بطريقتين: - -* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة وظيفة منطقية جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين. -* **يدوي**: أنشئ ملفًا جديدًا `*.logic-function.ts` واستخدم `defineLogicFunction()` مع اتباع النمط نفسه. - -### تمييز دالة منطقية كأداة - -يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عندما يتم تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي الخاصة بـ Twenty ويمكن اختيارها كخطوة في أتمتة سير العمل. - -لتمييز دالة منطقية كأداة، عيّن `isTool: true` وقدّم `toolInputSchema` يصف معاملات الإدخال المتوقعة باستخدام [مخطط JSON](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -});},{ -``` - -النقاط الرئيسية: - -* **`isTool`** (`boolean`, الافتراضي: `false`): عند ضبطه على `true`، يتم تسجيل الدالة كأداة وتصبح متاحة لوكلاء الذكاء الاصطناعي ولأتمتة سير العمل. -* **`toolInputSchema`** (`object`, اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يستخدم وكلاء الذكاء الاصطناعي هذا المخطط لفهم المدخلات التي تتوقعها الأداة وللتحقق من صحة الاستدعاءات. إذا تم إغفاله، فالقيمة الافتراضية للمخطط هي `{ type: 'object', properties: {} }` (من دون معلمات). -* الدوال التي لديها `isTool: false` (أو غير معيَّنة) **غير** معروضة كأدوات. لا يزال بالإمكان تنفيذها مباشرةً أو استدعاؤها بواسطة دوال أخرى، لكنها لن تظهر في اكتشاف الأدوات. -* **تسمية الأداة**: عند كشفها كأداة، يتم تطبيع اسم الدالة تلقائيًا إلى `logic_function_` (تحويله إلى أحرف صغيرة، واستبدال المحارف غير الأبجدية الرقمية بشرطات سفلية). على سبيل المثال، `enrich-company` تصبح `logic_function_enrich_company`. -* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة أحداث (cron، وأحداث قاعدة البيانات، والمسارات) في الوقت نفسه. - - -**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها. - - -### المكوّنات الأمامية - -تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -النقاط الرئيسية: - -* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty. -* يشير الحقل `component` إلى مكوّن React الخاص بك. -* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn twenty dev`. - -يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين: - -* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة مكوّن أمامي جديد. -* **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه. - -#### أين يمكن استخدام مكوّنات الواجهة الأمامية - -يمكن عرض مكوّنات الواجهة الأمامية في موقعين داخل Twenty: - -* **اللوحة الجانبية** — المكوّنات غير عديمة الرأس تفتح في اللوحة الجانبية اليمنى. هذا هو السلوك الافتراضي عندما يتم تشغيل مكوّن واجهة أمامية من قائمة الأوامر. -* **الويدجت (لوحات المعلومات وصفحات السجلات)** — يمكن تضمين مكوّنات الواجهة الأمامية كويدجت داخل تخطيطات الصفحات. عند تكوين لوحة معلومات أو تخطيط صفحة سجل، يمكن للمستخدمين إضافة ويدجت لمكوّن واجهة أمامية. - -#### عديم الرأس مقابل غير عديم الرأس - -تأتي مكوّنات الواجهة الأمامية بوضعَي عرض يتحكّم بهما الخيار `isHeadless`: - -**غير عديم الرأس (افتراضي)** — يعرض المكوّن واجهة مستخدم مرئية. عند تشغيله من قائمة الأوامر يفتح في اللوحة الجانبية. هذا هو السلوك الافتراضي عندما تكون `isHeadless` تساوي `false` أو يتم تجاهلها. - -**عديم الرأس** — يتم تركيب المكوّن بشكل غير مرئي في الخلفية. لا يفتح اللوحة الجانبية. تم تصميم المكوّنات عديمة الرأس لإجراءات تنفّذ منطقًا ثم تُزيل تركيبها ذاتيًا — على سبيل المثال، تشغيل مهمة غير متزامنة، أو الانتقال إلى صفحة، أو إظهار نافذة تأكيد منبثقة. تتوافق بشكل طبيعي مع مكوّنات Command في SDK الموصوفة أدناه. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### إضافة عناصر قائمة الأوامر - -لجعل مكوّن واجهة أمامية يظهر كعنصر في قائمة الأوامر في Twenty، أضف الخاصية `command` إلى `defineFrontComponent()`. عند فتح المستخدمين لقائمة الأوامر (Cmd+K / Ctrl+K)، يظهر العنصر ويشغّل مكوّن الواجهة الأمامية عند النقر. - -يقبل كائن `command` الحقول التالية: - -| الحقل | النوع | الوصف | -| --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- | -| `universalIdentifier` | `string` (إلزامي) | معرّف فريد لعنصر قائمة الأوامر | -| `التسمية` | `string` (إلزامي) | التسمية المعروضة في قائمة الأوامر | -| `أيقونة` | `string` (اختياري) | اسم الأيقونة (مثال: `'IconSparkles'`) | -| `isPinned` | `boolean` (اختياري) | ما إذا كان الأمر مثبتًا أعلى القائمة | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (اختياري) | `GLOBAL` يعرض الأمر في كل مكان؛ `RECORD_SELECTION` يعرضه فقط في سياقات السجلّات | -| `availabilityObjectUniversalIdentifier` | `string` (اختياري) | تقييد الأمر بنوع كائن محدّد (مثال: Person) | - -إليك مثالًا من تطبيق تسجيل المكالمات يضيف أمرًا محصورًا بسجلات Person: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -عند مزامنة الأمر، يظهر في قائمة الأوامر. إذا كان مكوّن الواجهة الأمامية غير عديم الرأس، تُفتح اللوحة الجانبية مع عرض المكوّن بداخلها. إذا كان عديم الرأس، فسيتم تركيب المكوّن في الخلفية وتنفيذ منطقه. - -#### مكوّنات Command في SDK - -توفر حزمة `twenty-sdk` أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء. - -استوردها من `twenty-sdk/command`: - -* **`Command`** — يشغّل رد نداء غير متزامن عبر الخاصية `execute`. -* **`CommandLink`** — ينتقل إلى مسار في التطبيق. الخصائص: `to`، `params`، `queryParams`، `options`. -* **`CommandModal`** — يفتح نافذة تأكيد منبثقة. إذا أكّد المستخدم، ينفّذ رد النداء `execute`. الخصائص: `title`، `subtitle`، `execute`، `confirmButtonText`، `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — يفتح صفحة محدّدة في اللوحة الجانبية. الخصائص: `page`، `pageTitle`، `pageIcon`. - -فيما يلي مثال كامل لمكوّن واجهة أمامية عديم الرأس يستخدم `Command` لتشغيل إجراء من قائمة الأوامر: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -ومثال يستخدم `CommandModal` لطلب التأكيد قبل التنفيذ: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### سياق التنفيذ - -يتلقّى كل مكوّن واجهة أمامية سياق تنفيذ يوفّر معلومات حول مكان وكيفية تشغيله. يمكنك الوصول إلى قيم السياق باستخدام الخطافات من `twenty-sdk`: - -| الخطّاف | نوع القيمة المرجعة | الوصف | -| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | المعرّف الفريد لمثيل مكوّن الواجهة الأمامية الحالي | -| `useRecordId()` | `string \| null` | معرّف السجل الحالي، عندما يعمل المكوّن في سياق سجل (مثال: ويدجت صفحة سجل أو أمر محصور بسجل). يُرجع `null` خلاف ذلك. | -| `useUserId()` | `string \| null` | معرّف المستخدم الحالي | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -السياق تفاعلي — إذا تغيّر السجل المحيط، فستُرجع الخطافات القيم المحدّثة تلقائيًا. - -#### دوال واجهة برمجة تطبيقات المضيف - -تعمل مكوّنات الواجهة الأمامية في بيئة معزولة، لكنها تستطيع التفاعل مع واجهة مستخدم Twenty عبر مجموعة من الدوال التي يوفّرها المضيف. استوردها مباشرةً من `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| دالة | التوقيع | الوصف | -| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `التنقل` | `(to, params?, queryParams?, options?) => Promise` | الانتقال إلى مسار تطبيق محدّد النوع داخل Twenty | -| `closeSidePanel` | `() => Promise` | إغلاق اللوحة الجانبية | -| `enqueueSnackbar` | `(params) => Promise` | عرض إشعار Snackbar. المعاملات: `message`، `variant` (`'error'`، `'success'`، `'info'`، `'warning'`)، `duration` اختياري، `detailedMessage`، `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | إلغاء تركيب مكوّن الواجهة الأمامية الحالي (تستخدمه المكوّنات عديمة الرأس للتنظيف بعد التنفيذ) | -| `openSidePanelPage` | `(params) => Promise` | فتح صفحة في اللوحة الجانبية. المعاملات: `page`، `pageTitle`، `pageIcon`، `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | عرض نافذة تأكيد منبثقة والانتظار لرد المستخدم. المعاملات: `title`، `subtitle`، `confirmButtonText`، `confirmButtonAccent` (`'default'`، `'blue'`، `'danger'`) | - -فيما يلي مثال يستخدم واجهة برمجة تطبيقات المضيف لعرض Snackbar وإغلاق اللوحة الجانبية بعد اكتمال الإجراء: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### المهارات - -تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'التواصل البيعي', - description: 'يرشد وكيل الذكاء الاصطناعي خلال عملية منظّمة للتواصل البيعي', - icon: 'IconBrain', - content: `أنت مساعد للتواصل البيعي. عند التواصل مع عميل محتمل: -1. ابحث عن الشركة وآخر الأخبار -2. حدِّد دور العميل المحتمل ونقاط الألم المرجّحة -3. صِغ رسالة مخصّصة تشير إلى تفاصيل محدّدة -4. حافظ على نبرة احترافية ولكن حوارية`, -}); -``` - -النقاط الرئيسية: - -* `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case). -* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم. -* `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي. -* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. -* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة. - -يمكنك إنشاء مهارات جديدة بطريقتين: - -* **مُنشأ بالقالب**: شغِّل `yarn twenty entity:add` واختر خيار إضافة مهارة جديدة. -* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineSkill()` مع اتباع النمط نفسه. - -### الوكلاء - -تمكّنك ميزة الوكلاء من تعريف وكلاء ذكاء اصطناعي قادرين على العمل ضمن مساحة عملك، باستخدام موجهات النظام. استخدم `defineAgent()` لتعريف وكلاء مع تحقق مدمج: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -النقاط الرئيسية: - -* `name` هي سلسلة معرّف فريدة للوكيل (يُنصَح باستخدام kebab-case). -* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم. -* `prompt` يحتوي موجه النظام — وهو نص التعليمات الذي يحدد سلوك الوكيل. -* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. -* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض الوكيل. - -يمكنك إنشاء وكلاء جدد بطريقتين: - -* **مُنشأ بالقالب**: شغِّل `yarn twenty entity:add` واختر خيار إضافة وكيل جديد. -* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineAgent()` مع اتباع النمط نفسه. - -### عملاء مُولَّدون مضبوطو الأنواع - -يتم توليد عميلين مضبوطي الأنواع تلقائيًا بواسطة `yarn twenty dev` وتخزينهما في `node_modules/twenty-sdk/clients` استنادًا إلى مخطط مساحة العمل لديك: - -* **`CoreApiClient`** — يُجري استعلامات إلى نقطة النهاية `/graphql` للحصول على بيانات مساحة العمل -* **`MetadataApiClient`** — يستعلم عن نقطة النهاية `/metadata` لتكوين مساحة العمل وتحميل الملفات. - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });},{ -``` - -`CoreApiClient` يُعاد توليده تلقائيًا بواسطة `yarn twenty dev` كلما تغيّرت كائناتك أو حقولك. `MetadataApiClient` يأتي مُجهزًا مسبقًا مع SDK. - -#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية - -عندما تعمل وظيفتك على Twenty، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئة قبل تنفيذ كودك: - -* `TWENTY_API_URL`: عنوان URL الأساسي لواجهة Twenty البرمجية التي يستهدفها تطبيقك. -* `TWENTY_API_KEY`: مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك. - -الملاحظات: - -* لا تحتاج إلى تمرير عنوان URL أو مفتاح واجهة برمجة التطبيقات إلى العميل المُولَّد. يقوم بقراءة `TWENTY_API_URL` و`TWENTY_API_KEY` من process.env وقت التشغيل. -* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application-config.ts` عبر `defaultRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف المنطقية في تطبيقك. -* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `defaultRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور. - -#### رفع الملفات - -يتضمن `MetadataApiClient` طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية. - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // محتوى الملف كـ Buffer - 'invoice.pdf', // اسم الملف - 'application/pdf', // نوع MIME (القيمة الافتراضية 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // المعرّف العالمي للحقل -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -توقيع الطريقة: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| المعلمة | النوع | الوصف | -| ---------------------------------- | -------- | ---------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | المحتوى الخام للملف | -| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) | -| `contentType` | `string` | نوع MIME للملف (القيمة الافتراضية هي `application/octet-stream` إذا لم يتم تحديده) | -| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك | - -النقاط الرئيسية: - -* تتوفر طريقة `uploadFile` على `MetadataApiClient` لأن عملية الـ mutation الخاصة بالرفع تُعالَج عبر نقطة النهاية `/metadata`. -* تستخدم `universalIdentifier` الخاص بالحقل (وليس المعرّف الخاص بمساحة العمل)، ليعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك — بما يتماشى مع كيفية إشارة التطبيقات إلى الحقول في كل مكان آخر. -* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع. - -### مثال Hello World - -استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## بناء تطبيقك - -بمجرد أن تطوّر تطبيقك باستخدام `app:dev`، استخدم `app:build` لإنشاء حزمة قابلة للتوزيع منه. - -```bash filename="Terminal" -# ابنِ التطبيق (الإخراج يذهب إلى .twenty/output/) -yarn twenty build - -# ابنِ وأنشئ ملف tarball (.tgz) للتوزيع -yarn twenty build --tarball -``` - -عملية البناء: - -1. **يقوم بتحليل ملف البيان والتحقق من صحته** — يقرأ جميع الكيانات `defineX()` من ملفات المصدر لديك ويُتحقّق من بنية ملف البيان. -2. **يُصرِّف دوال المنطق ومكوّنات الواجهة** — يُجمّع مصادر TypeScript إلى ملفات ESM `.mjs` باستخدام esbuild. -3. **يولّد قيم التحقّق** — يحسب تجزئات MD5 لكل ملف مُبنًى، وتُخزَّن في ملف البيان كـ `builtHandlerChecksum` / `builtComponentChecksum`. -4. **ينشئ عميل API مضبوط الأنواع** — يفحص مخطط GraphQL ويُنشئ عميلَي `CoreApiClient` و`MetadataApiClient` مضبوطي الأنواع. -5. **يشغّل فحص الأنواع لـ TypeScript** — يشغّل `tsc --noEmit` لاكتشاف أخطاء الأنواع قبل النشر. -6. **يعيد البناء باستخدام العميل المُولَّد** — يُجري مرحلة ترجمة ثانية بحيث تُدرَج أنواع العميل المُولَّد. -7. **ينشئ أرشيف tar اختياريًا** — إذا تم تمرير `--tarball`، يشغّل `npm pack` لإنشاء ملف `.tgz` جاهز للتوزيع. - -مخرجات البناء في `.twenty/output/` تتضمّن: - -```text -.twenty/output/ -├── manifest.json # Manifest with checksums for all built files -├── package.json # Copied from app root -├── yarn.lock # Copied from app root -├── src/ -│ ├── logic-functions/ # Compiled .mjs logic function files -│ └── front-components/ # Compiled .mjs front component files -├── public/ # Static assets (if any) -└── my-app-1.0.0.tgz # Only with --tarball flag -``` - -| الخيار | الوصف | -| ----------- | -------------------------------------------------- | -| `[appPath]` | المسار إلى دليل التطبيق (افتراضيًا: الدليل الحالي) | -| `--tarball` | قم أيضًا بحزم المخرجات في أرشيف `.tgz` | - -## نشر تطبيقك - -استخدم `app:publish` لتوزيع تطبيقك — إما إلى سجل npm أو مباشرةً إلى خادم Twenty. - -### النشر إلى npm (الإعداد الافتراضي) - -```bash filename="Terminal" -# انشر إلى npm (يتطلب تسجيل الدخول إلى npm) -yarn twenty publish - -# انشر باستخدام وسم توزيع (مثل beta، next) -yarn twenty publish --tag beta -``` - -يقوم هذا ببناء التطبيق وتشغيل `npm publish` من دليل `.twenty/output/`. بعد ذلك يمكن تثبيت الحزمة المنشورة من سوق Twenty بواسطة أي مساحة عمل. - -### النشر إلى خادم Twenty - -```bash filename="Terminal" -# انشر مباشرةً إلى خادم Twenty -yarn twenty publish --server https://app.twenty.com -``` - -يقوم هذا ببناء التطبيق مع أرشيف tar، ويرفعه إلى الخادم عبر العملية `uploadAppTarball` في GraphQL، ويبدأ التثبيت في خطوة واحدة. يكون هذا مفيدًا لعمليات النشر الخاصة أو للاختبار مقابل خادم محدّد. - -| الخيار | الوصف | -| ----------------- | -------------------------------------------------------- | -| `[appPath]` | المسار إلى دليل التطبيق (افتراضيًا: الدليل الحالي) | -| `--server ` | انشر إلى خادم Twenty بدلًا من npm | -| `--token ` | رمز المصادقة للخادم المستهدف | -| `--tag ` | علامة توزيع npm (مثل `beta`، `next`) — للنشر عبر npm فقط | - -## تسجيل التطبيق - -قبل أن يمكن تثبيت تطبيق في مساحة عمل، يجب أن يكون **مسجّلًا**. التسجيل هو سجل بيانات وصفية يوضّح مصدر التطبيق وكيفية مصادقته. يُعالَج هذا تلقائيًا بواسطة CLI في معظم الحالات. - -### أنواع المصادر - -لكل تسجيل **نوع مصدر** يحدّد كيفية تحديد ملفات التطبيق أثناء التثبيت: - -| نوع المصدر | كيفية تحديد الملفات | حالة الاستخدام النموذجية | -| ---------- | ------------------------------------------------------------------------- | --------------------------------------- | -| `LOCAL` | تتم مزامنة الملفات في الوقت الفعلي بواسطة مُراقِب CLI — يتم تخطّي التثبيت | التطوير باستخدام `app:dev` | -| `NPM` | تُجلب من سجل npm عبر الحقل `sourcePackage` | تطبيقات منشورة على npm | -| `TARBALL` | تُستخرَج من ملف `.tgz` مرفوع ومخزَّن على الخادم | تطبيقات خاصة منشورة باستخدام `--server` | - -### كيفية إجراء التسجيل - -* **`app:dev`** — ينشئ تلقائيًا تسجيلًا من نوع `LOCAL` في المرة الأولى التي تشغّل فيها وضع التطوير لمساحة عمل. -* **`app:publish --server`** — يرفع أرشيف tar وينشئ (أو يحدّث) تسجيلًا من نوع `TARBALL`، ثم يثبّت التطبيق. -* **سوق npm** — يتم إنشاء تسجيلات `NPM` عند مزامنة التطبيقات من سجل npm إلى كتالوج سوق Twenty. -* **واجهة برمجة تطبيقات GraphQL** — يمكنك أيضًا إنشاء التسجيلات برمجيًا عبر العملية `createApplicationRegistration`. - -### التسجيل مقابل التثبيت - -**التسجيل** و**التثبيت** مفهومان منفصلان: - -* **التسجيل** (`ApplicationRegistration`) هو سجل بيانات وصفية عام يصف التطبيق: اسمه، نوع المصدر، بيانات اعتماد OAuth، وحالة إدراجه في السوق. وهو موجود بشكل مستقل عن أي مساحة عمل. -* **التثبيت** (`Application`) هو مثيل لكل مساحة عمل. عند قيام مستخدم بتثبيت تطبيق، تقوم Twenty بحلّ الحزمة من مصدر التسجيل، وتكتب الملفات المُبنَاة إلى التخزين، وتزامن البيان التعريفي (إنشاء الكائنات والحقول ودوال المنطق، إلخ) في مساحة العمل تلك. - -يمكن تثبيت تسجيل واحد في العديد من مساحات العمل. تحصل كل مساحة عمل على نسختها الخاصة من ملفات التطبيق ونموذج البيانات. - -### بيانات اعتماد OAuth - -يتضمن كل تسجيل بيانات اعتماد OAuth (`oAuthClientId` و`oAuthClientSecret`) يتم إنشاؤها وقت الإنشاء. يستخدمها التطبيق لمصادقة طلبات واجهة برمجة التطبيقات بالنيابة عن المستخدمين. يُعرَض سر العميل مرةً **واحدة** عند الإنشاء — خزّنه بأمان. يمكنك تدويره لاحقًا عبر العملية `rotateApplicationRegistrationClientSecret`. - -## إعداد يدوي (بدون المهيئ) - -بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -ثم أضف سكربتًا باسم `twenty`: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty `، مثلًا: `yarn twenty dev`، `yarn twenty help`، إلخ. - -## استكشاف الأخطاء وإصلاحها - -* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة. -* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول. -* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty dev` — فهو يولِّد العميل مضبوط الأنواع تلقائيًا. -* وضع التطوير لا يزامن: تأكد من أن `yarn twenty dev` قيد التشغيل وأن التغييرات غير متجاهلة في بيئتك. - -قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/ar/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx index a54e74d088..5d5640b137 100644 --- a/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: Definujte objekty, logické funkce, frontendové komponenty a dalš --- -Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí. + Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí. -## Používejte zdroje SDK (typy a konfiguraci) +Balíček `twenty-sdk` poskytuje typované stavební bloky pro vytváření vaší aplikace. Tato stránka pokrývá všechny typy entit a klienty API dostupné v SDK. -twenty-sdk poskytuje typované stavební bloky a pomocné funkce, které používáte ve své aplikaci. Níže jsou klíčové části, se kterými budete nejčastěji pracovat. +## Funkce DefineEntity -### Pomocné funkce +SDK poskytuje funkce pro definování entit vaší aplikace. Abyste umožnili SDK detekovat vaše entity, musíte použít `export default defineEntity({...})`. Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost. -SDK poskytuje pomocné funkce pro definování entit vaší aplikace. Jak je popsáno v [Detekce entit](/l/cs/developers/extend/apps/getting-started#entity-detection), musíte použít `export default define({...})`, aby byly vaše entity detekovány: + + **Uspořádání souborů je na vás.** + Detekce entit je založená na AST — SDK najde volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází. Seskupování souborů podle typu (např. `logic-functions/`, `roles/`) je pouze konvence, nikoli požadavek. + -| Funkce | Účel | -| -------------------------------- | ------------------------------------------------------------------------------- | -| `defineApplication` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) | -| `defineObject` | Definice vlastních objektů s poli | -| `defineField` | Rozšiřte existující objekty o další pole nebo definujte samostatná relační pole | -| `defineLogicFunction` | Definice logických funkcí s obslužnými funkcemi | -| `definePreInstallLogicFunction` | Definujte předinstalační logickou funkci (jedna na aplikaci) | -| `definePostInstallLogicFunction` | Definujte postinstalační logickou funkci (jedna na aplikaci) | -| `defineFrontComponent` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní | -| `defineRole` | Konfigurace oprávnění rolí a přístupu k objektům | -| `defineView` | Definujte uložená zobrazení pro objekty | -| `defineNavigationMenuItem` | Definujte odkazy postranní navigace | -| `defineSkill` | Definujte dovednosti agenta AI | -| `defineAgent` | Definujte agenty AI | -| `definePageLayout` | Definujte vlastní rozvržení stránek | + + -Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost. +Role zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. -### Definování objektů +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Každá aplikace musí mít právě jedno volání `defineApplication`, které popisuje: + +* **Identita**: identifikátory, zobrazovaný název a popis. +* **Oprávnění**: jakou roli používají její funkce a frontendové komponenty. +* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí. +* **(Volitelné) předinstalační / postinstalační funkce**: logické funkce, které se spouštějí před nebo po instalaci. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +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 (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` musí odkazovat na roli definovanou pomocí `defineRole()` (viz výše). +* Předinstalační a postinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`. + +#### Metadata Marketplace + +Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje na Marketplace: + +| Pole | Popis | +| ------------------ | ------------------------------------------------------------------------------------------------------------- | +| `author` | Jméno autora nebo název společnosti | +| `category` | Kategorie aplikace pro filtrování na Marketplace | +| `logoUrl` | Cesta k logu vaší aplikace (např. `public/logo.png`) | +| `screenshots` | Pole cest ke snímkům obrazovky (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ů | + +#### Role a oprávnění + +Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi a frontendovými komponentami vaší aplikace. Podrobnosti viz výše u `defineRole`. + +* Běhový token vložený jako `TWENTY_APP_ACCESS_TOKEN` je odvozen z této role. +* Typovaný klient bude omezen oprávněními udělenými této roli. +* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují. + +##### Výchozí role funkce + +Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +Na `universalIdentifier` této role se v `application-config.ts` odkazuje jako na `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** definuje, co daná role může dělat. +* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění. + +Poznámky: +* Začněte vygenerovanou rolí a postupně ji omezujte podle principu nejmenších oprávnění. +* Nahraďte `objectPermissions` a `fieldPermissions` objekty a poli, které vaše funkce skutečně potřebují. +* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší. +* Podívejte se na funkční příklad: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Vlastní objekty popisují jak schéma, tak chování záznamů ve vašem pracovním prostoru. K definování objektů s vestavěnou validací použijte `defineObject()`: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ Výchozí pole můžete přepsat definováním pole se stejným názvem v poli ` ale to se nedoporučuje. -### Definování polí u existujících objektů + + Pomocí `defineField()` přidejte pole k objektům, které nevlastníte — například ke standardním objektům Twenty (Person, Company atd.). nebo k objektům z jiných aplikací. Na rozdíl od inline polí v `defineObject()` vyžadují samostatná pole `objectUniversalIdentifier` k určení, který objekt rozšiřují: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` Hlavní body: - * `objectUniversalIdentifier` identifikuje cílový objekt. Pro standardní objekty použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportovaný z `twenty-sdk`. * Při definování polí inline v `defineObject()` `objectUniversalIdentifier` nepotřebujete — dědí se z nadřazeného objektu. * `defineField()` je jediný způsob, jak přidat pole k objektům, které jste nevytvořili pomocí `defineObject()`. -### Vztahy + + Relace propojují objekty. Ve Twenty jsou relace vždy obousměrné — definujete obě strany a každá strana odkazuje na tu druhou. @@ -177,8 +310,7 @@ Předpokládejme, že `PostCard` lze odeslat mnoha záznamům `PostCardRecipient **Krok 1: Definujte stranu ONE_TO_MANY na PostCard** (strana "one"): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **Krok 2: Definujte stranu MANY_TO_ONE na PostCardRecipient** (strana "many" — drží cizí klíč): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ Chcete-li vytvořit relaci s vestavěným objektem Twenty (Person, Company atd.), použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ Relační pole můžete také definovat přímo uvnitř `defineObject()`. V takovém případě vynechejte `objectUniversalIdentifier` — dědí se z nadřazeného objektu: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### Konfigurace aplikace (application-config.ts) - -Každá aplikace má jeden soubor `application-config.ts`, který popisuje: - -* **Identitu aplikace**: identifikátory, zobrazovaný název a popis. -* **Jak běží její funkce**: kterou roli používají pro oprávnění. -* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí. -* **(Volitelná) předinstalační funkce**: logická funkce, která se spouští před instalací aplikace. -* **(Volitelná) postinstalační funkce**: logická funkce, která se spouští po instalaci aplikace. - -K definování konfigurace aplikace použijte `defineApplication()`: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Poznámky: - -* Pole `universalIdentifier` jsou deterministická ID, která vlastníte; vygenerujte je jednou a udržujte je stabilní napříč synchronizacemi. -* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` se musí shodovat se souborem role (viz níže). -* Předinstalační a postinstalační funkce jsou při sestavování manifestu automaticky detekovány. Viz [Předinstalační funkce](#pre-install-functions) a [Postinstalační funkce](#post-install-functions). - -#### Metadata Marketplace - -Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje na Marketplace: - -| Pole | Popis | -| ------------------ | ------------------------------------------------------------------------------------------------------------- | -| `author` | Jméno autora nebo název společnosti | -| `category` | Kategorie aplikace pro filtrování na Marketplace | -| `logoUrl` | Cesta k logu vaší aplikace (relativně k `./assets/`) | -| `screenshots` | Pole cest ke snímkům obrazovky (relativně k `./assets/`) | -| `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ů | - -#### Role a oprávnění - -Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi vaší aplikace. - -* Běhový klíč API vložený jako `TWENTY_API_KEY` je odvozen z této výchozí role funkcí. -* Typovaný klient bude omezen oprávněními udělenými této roli. -* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují, a poté odkazujte na její univerzální identifikátor. - -##### Výchozí role funkce (*.role.ts) - -Když vygenerujete novou aplikaci, CLI také vytvoří výchozí soubor role. K definování rolí s vestavěnou validací použijte `defineRole()`: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Na `universalIdentifier` této role se poté odkazuje v `application-config.ts` jako na `defaultRoleUniversalIdentifier`. Jinými slovy: - -* **\*.role.ts** definuje, co může výchozí role funkce dělat. -* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění. - -Poznámky: - -* Začněte vygenerovanou rolí a postupně ji omezujte podle principu nejmenších oprávnění. -* Nahraďte `objectPermissions` a `fieldPermissions` objekty/poli, která vaše funkce potřebují. -* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Držte je na minimu; přidávejte pouze to, co potřebujete. -* Podívejte se na funkční příklad v aplikaci Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Konfigurace logických funkcí a vstupní bod + + Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Běžné typy spouštěčů: - -* **route**: Zpřístupní vaši funkci na HTTP cestě a metodě **pod koncovým bodem `/s/`**: - -> např. `path: '/post-card/create',` -> volání na `/s/post-card/create` - +Dostupné typy spouštěčů: +* **httpRoute**: Zpřístupní vaši funkci na HTTP cestě a metodě **pod koncovým bodem `/s/`**: +> např. `path: '/post-card/create'` je volatelné na `https://your-twenty-server.com/s/post-card/create` * **cron**: Spouští vaši funkci podle plánu pomocí výrazu CRON. * **databaseEvent**: Spouští se při událostech životního cyklu objektů v pracovním prostoru. Když je operace události `updated`, lze konkrétní sledovaná pole určit v poli `updatedFields`. Pokud zůstane nedefinované nebo prázdné, spustí funkci jakákoli aktualizace. +> např. `person.updated`, `*.created`, `company.*` -> např. `person.updated` - -Poznámky: - -* Pole `triggers` je volitelné. Funkce bez spouštěčů lze použít jako pomocné funkce volané jinými funkcemi. -* V jedné funkci můžete kombinovat více typů spouštěčů. - -### Předinstalační funkce - -Předinstalační funkce je logická funkce, která se automaticky spouští před instalací vaší aplikace v pracovním prostoru. To je užitečné pro validační úlohy, kontrolu předpokladů nebo přípravu stavu pracovního prostoru před zahájením hlavní instalace. - -Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří se pro vás předinstalační funkce v `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: + +Funkci můžete také spustit ručně pomocí CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Hlavní body: - -* Předinstalační funkce používají `definePreInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci). -* Na jednu aplikaci je povolena pouze jedna předinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. -* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `preInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`. -* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší přípravné úlohy. -* Předinstalační funkce nepotřebují spouštěče — platforma je vyvolává před instalací nebo je lze spustit ručně pomocí `exec --preInstall`. - -### Postinstalační funkce - -Postinstalační funkce je logická funkce, která se automaticky spouští po instalaci vaší aplikace do pracovního prostoru. To je užitečné pro jednorázové úlohy nastavení, jako je naplnění výchozími daty, vytvoření počátečních záznamů nebo konfigurace nastavení pracovního prostoru. - -Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří se pro vás postinstalační funkce v `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Hlavní body: +Logy můžete sledovat pomocí: -* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci). -* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. -* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `postInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`. -* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty. -* Postinstalační funkce nepotřebují spouštěče — jsou spouštěny platformou během instalace nebo ručně pomocí `exec --postInstall`. - -### Payload spouštěče trasy - - -**Zpětně nekompatibilní změna (v1.16, leden 2026):** Formát payloadu spouštěče trasy se změnil. Před verzí v1.16 byly parametry dotazu, parametry cesty a tělo odesílány přímo jako payload. Od verze v1.16 jsou zanořeny uvnitř strukturovaného objektu `RoutePayload`. - -**Před v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**Po v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Payload spouštěče trasy -**Jak migrovat existující funkce:** Aktualizujte svůj handler tak, aby destrukturoval z `event.body`, `event.queryStringParameters` nebo `event.pathParameters` místo přímo z objektu params. - +Když spouštěč typu route vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá +[AWS HTTP API v2 formátu](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Importujte typ `RoutePayload` z `twenty-sdk`: -Když spouštěč trasy vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá formátu AWS HTTP API v2. Importujte typ z `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { Typ `RoutePayload` má následující strukturu: -| Vlastnost | Typ | Popis | -| ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `headers` | `Record` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parametry query stringu (více hodnot spojených čárkami) | -| `pathParameters` | `Record` | Parametry cesty extrahované ze vzoru trasy (např. `/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | Parsované tělo požadavku (JSON) | -| `isBase64Encoded` | `boolean` | Zda je tělo kódováno base64 | -| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | + | Vlastnost | Typ | Popis | Příklad | + | ---------------------------- | ------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | viz sekce níže | + | `queryStringParameters` | `Record` | Parametry query stringu (více hodnot spojených čárkami) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Parametry cesty extrahované ze vzoru trasy | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Parsované tělo požadavku (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Zda je tělo kódováno base64 | | + | `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | | -### Přeposílání záhlaví HTTP -Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`: +#### forwardedRequestHeaders -```typescript +Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. +Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -Ve vašem handleru k nim poté můžete přistupovat: +Ve vašem handleru k přeposlaným záhlavím přistupujte takto: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`). +Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`). -Nové funkce můžete vytvářet dvěma způsoby: +#### Zpřístupnění funkce jako nástroje -* **Vygenerované**: Spusťte `yarn twenty add` a zvolte možnost přidat novou logickou funkci. Tím se vygeneruje startovací soubor s obslužnou funkcí a konfigurací. -* **Ruční**: Vytvořte nový soubor `*.logic-function.ts` a použijte `defineLogicFunction()` podle stejného vzoru. +Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji použít v automatizacích pracovních postupů. -### Označení logické funkce jako nástroje +Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true`: -Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji vybrat jako krok v automatizacích pracovních postupů. - -Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true` a poskytněte `toolInputSchema` popisující očekávané vstupní parametry pomocí [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Hlavní body: + +* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) a současně se spouštět událostmi. +* **`toolInputSchema`** (volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Schéma se určuje automaticky ze statické analýzy zdrojového kódu, ale můžete ho nastavit i explicitně: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Hlavní body: - -* **`isTool`** (`boolean`, výchozí: `false`): Když je nastaveno na `true`, funkce je zaregistrována jako nástroj a zpřístupní se agentům AI a automatizacím pracovních postupů. -* **`toolInputSchema`** (`object`, volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Agenti AI používají toto schéma k pochopení toho, jaké vstupy nástroj očekává, a k ověřování volání. Pokud je vynecháno, schéma má výchozí podobu `{ type: 'object', properties: {} }` (žádné parametry). -* Funkce s `isTool: false` (nebo není nastaveno) **nejsou** zpřístupněny jako nástroje. Stále je lze spouštět přímo nebo volat z jiných funkcí, ale neobjeví se ve vyhledávání nástrojů. -* **Pojmenování nástrojů**: Když je funkce zpřístupněna jako nástroj, její název se automaticky normalizuje na `logic_function_` (převedeno na malá písmena, nealfanumerické znaky jsou nahrazeny podtržítky). Například `enrich-company` se změní na `logic_function_enrich_company`. -* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) i spouštěna událostmi (cron, databázové události, routes). - **Napište kvalitní `description`.** Agenti AI se spoléhají na pole funkce `description` při rozhodování, kdy nástroj použít. Buďte konkrétní ohledně toho, co nástroj dělá a kdy se má volat. -### Frontendové komponenty + + -Frontendové komponenty vám umožňují vytvářet vlastní React komponenty, které se vykreslují v rozhraní Twenty. K definování komponent s vestavěnou validací použijte `defineFrontComponent()`: +Předinstalační funkce je logická funkce, která se automaticky spouští před instalací vaší aplikace v pracovním prostoru. To je užitečné pro validační úlohy, kontrolu předpokladů nebo přípravu stavu pracovního prostoru před zahájením hlavní instalace. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Hlavní body: +* Předinstalační funkce používají `definePreInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci). +* Na jednu aplikaci je povolena pouze jedna předinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. +* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `preInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`. +* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší přípravné úlohy. + + + + +Postinstalační funkce je logická funkce, která se automaticky spouští po instalaci vaší aplikace do pracovního prostoru. To je užitečné pro jednorázové úlohy nastavení, jako je naplnění výchozími daty, vytvoření počátečních záznamů nebo konfigurace nastavení pracovního prostoru. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Hlavní body: +* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci). +* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. +* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `postInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`. +* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty. + + + + +Frontendové komponenty jsou React komponenty, které se vykreslují přímo v uživatelském rozhraní Twenty. Běží v **izolovaném Web Workeru** s využitím Remote DOM — váš kód je sandboxovaný, ale vykresluje se nativně na stránce, nikoli v iframu. + +#### Základní příklad + +Nejrychlejší způsob, jak vidět frontendovou komponentu v akci, je zaregistrovat ji jako **příkaz**. Přidáním pole `command` s `isPinned: true` se zobrazí jako tlačítko rychlé akce v pravém horním rohu stránky — není potřeba žádné rozvržení stránky: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Hlavní body: +Po synchronizaci pomocí `yarn twenty dev` se rychlá akce zobrazí v pravém horním rohu stránky: -* Frontendové komponenty jsou React komponenty, které se vykreslují v izolovaných kontextech v rámci Twenty. -* Pole `component` odkazuje na vaši React komponentu. -* Komponenty se během `yarn twenty dev` automaticky sestaví a synchronizují. +
+ Tlačítko rychlé akce v pravém horním rohu +
-Nové frontendové komponenty můžete vytvořit dvěma způsoby: +Kliknutím na něj vykreslíte komponentu přímo ve stránce. -* **Vygenerované**: Spusťte `yarn twenty add` a zvolte možnost přidat novou frontendovou komponentu. -* **Ruční**: Vytvořte nový soubor `.tsx` a použijte `defineFrontComponent()`, podle stejného vzoru. +{/* TODO: add screenshot of the rendered front component */} -### Dovednosti +#### Konfigurační pole + +| Pole | Povinné | Popis | +| --------------------- | ------- | ------------------------------------------------------------------------------------ | +| `universalIdentifier` | Ano | Stabilní jedinečné ID pro tuto komponentu | +| `component` | Ano | Funkce komponenty React | +| `name` | Ne | Zobrazovaný název | +| `description` | Ne | Popis toho, co komponenta dělá | +| `isHeadless` | Ne | Nastavte na `true`, pokud komponenta nemá viditelné UI (viz níže) | +| `command` | Ne | Zaregistrujte komponentu jako příkaz (viz [možnosti příkazu](#command-options) níže) | + +#### Umístění frontendové komponenty na stránku + +Mimo příkazy můžete frontendovou komponentu vložit přímo na stránku záznamu přidáním jako widget v **rozvržení stránky**. Podrobnosti viz sekce [definePageLayout](#definepagelayout). + +#### Headless komponenty (`isHeadless: true`) + +Headless komponenty nevykreslují viditelné UI, ale stále spouštějí logiku Reactu. To je užitečné pro **efektové komponenty** — komponenty, které po připojení provádějí vedlejší efekty, jako je synchronizace dat, spuštění časovače, naslouchání událostem nebo vyvolání oznámení. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Protože komponenta vrací `null`, Twenty přeskočí vykreslení kontejneru — v rozvržení se neobjeví žádné prázdné místo. Komponenta má však stále přístup ke všem hookům a API komunikace s hostitelem. + +#### Přístup k běhovému kontextu + +Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli, záznamu a instanci komponenty: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Dostupné hooky: + +| Hook | Vrací | Popis | +| --------------------------------------------- | -------------------- | ------------------------------------------------------------ | +| `useUserId()` | `string` nebo `null` | ID aktuálního uživatele | +| `useRecordId()` | `string` nebo `null` | ID aktuálního záznamu (pokud je umístěna na stránce záznamu) | +| `useFrontComponentId()` | `string` | ID této instance komponenty | +| `useFrontComponentExecutionContext(selector)` | různé | Přístup k úplnému kontextu běhu pomocí selektorové funkce | + +#### API komunikace s hostitelem + +Frontendové komponenty mohou pomocí funkcí z `twenty-sdk` vyvolávat navigaci, modály a oznámení: + +| Funkce | Popis | +| ----------------------------------------------- | ------------------------------ | +| `navigate(to, params?, queryParams?, options?)` | Přejít na stránku v aplikaci | +| `openSidePanelPage(params)` | Otevřít postranní panel | +| `closeSidePanel()` | Zavře postranní panel | +| `openCommandConfirmationModal(params)` | Zobrazit potvrzovací dialog | +| `enqueueSnackbar(params)` | Zobrazit oznámení typu toast | +| `unmountFrontComponent()` | Odmontovat komponentu | +| `updateProgress(progress)` | Aktualizovat indikátor průběhu | + +#### Možnosti příkazu + +Přidání pole `command` do `defineFrontComponent` zaregistruje komponentu v příkazovém menu (Cmd+K). Pokud je `isPinned` nastaveno na `true`, zobrazí se také jako tlačítko rychlé akce v pravém horním rohu stránky. + +| Pole | Povinné | Popis | +| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz | +| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) | +| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce | +| `icon` | Ne | Název ikony zobrazený vedle popisku (např. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Ne | Pokud je `true`, zobrazí příkaz jako tlačítko rychlé akce v pravém horním rohu stránky | +| `availabilityType` | Ne | Určuje, kde se příkaz zobrazuje: `'GLOBAL'` (vždy dostupné), `'RECORD_SELECTION'` (pouze když jsou vybrány záznamy) nebo `'FALLBACK'` (zobrazeno, když neodpovídají žádné jiné příkazy) | +| `availabilityObjectUniversalIdentifier` | Ne | Omezí příkaz na stránky konkrétního typu objektu (např. pouze u záznamů Company) | +| `conditionalAvailabilityExpression` | Ne | Logický výraz pro dynamické řízení, zda je příkaz viditelný (viz níže) | + +#### Výrazy podmíněné dostupnosti + +Pole `conditionalAvailabilityExpression` vám umožní řídit viditelnost příkazu na základě aktuálního kontextu stránky. Pro sestavení výrazů importujte typované proměnné a operátory z `twenty-sdk`: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Kontextové proměnné** — reprezentují aktuální stav stránky: + +| Proměnná | Typ | Popis | +| ------------------------------ | --------- | -------------------------------------------------------------------- | +| `pageType` | `string` | Aktuální typ stránky (např. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Zda je komponenta vykreslena v postranním panelu | +| `numberOfSelectedRecords` | `number` | Počet aktuálně vybraných záznamů | +| `isSelectAll` | `boolean` | Zda je aktivní "vybrat vše" | +| `selectedRecords` | `array` | Vybrané objekty záznamů | +| `favoriteRecordIds` | `array` | ID oblíbených záznamů | +| `objectPermissions` | `object` | Oprávnění pro aktuální typ objektu | +| `targetObjectReadPermissions` | `object` | Oprávnění ke čtení pro cílový objekt | +| `targetObjectWritePermissions` | `object` | Oprávnění k zápisu pro cílový objekt | +| `featureFlags` | `object` | Aktivní příznaky funkcí | +| `objectMetadataItem` | `object` | Metadata aktuálního typu objektu | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Zda má aktuální zobrazení filtr soft-delete | + +**Operátory** — kombinují proměnné do logických výrazů: + +| Operátor | Popis | +| ----------------------------------- | -------------------------------------------------------------------- | +| `isDefined(value)` | `true`, pokud hodnota není null/undefined | +| `isNonEmptyString(value)` | `true`, pokud je hodnota neprázdný řetězec | +| `includes(array, value)` | `true`, pokud pole obsahuje danou hodnotu | +| `includesEvery(array, prop, value)` | `true`, pokud vlastnost každé položky zahrnuje danou hodnotu | +| `every(array, prop)` | `true`, pokud je vlastnost u každé položky pravdivá (truthy) | +| `everyDefined(array, prop)` | `true`, pokud je vlastnost definována u každé položky | +| `everyEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě u každé položky | +| `some(array, prop)` | `true`, pokud je vlastnost pravdivá (truthy) alespoň u jedné položky | +| `someDefined(array, prop)` | `true`, pokud je vlastnost definována alespoň u jedné položky | +| `someEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě alespoň u jedné položky | +| `someNonEmptyString(array, prop)` | `true`, pokud je vlastnost neprázdný řetězec alespoň u jedné položky | +| `none(array, prop)` | `true`, pokud je vlastnost u všech položek nepravdivá (falsy) | +| `noneDefined(array, prop)` | `true`, pokud je vlastnost u všech položek nedefinovaná | +| `noneEquals(array, prop, value)` | `true`, pokud se vlastnost nerovná hodnotě u žádné položky | + +#### Veřejná aktiva + +Frontendové komponenty mohou přistupovat k souborům ze složky aplikace `public/` pomocí `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +Podrobnosti viz [sekce veřejných aktiv](#accessing-public-assets-with-getpublicasseturl). + +#### Styling + +Frontendové komponenty podporují více přístupů ke stylování. Můžete použít: + +* **Inline styly** — `style={{ color: 'red' }}` +* **Komponenty Twenty UI** — import z `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar a další) +* **Emotion** — CSS-in-JS s `@emotion/react` +* **Styled-components** — vzory `styled.div` +* **Tailwind CSS** — utilitní třídy +* **Jakákoli CSS-in-JS knihovna** kompatibilní s Reactem + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + Dovednosti definují znovupoužitelné pokyny a schopnosti, které mohou agenti AI používat ve vašem pracovním prostoru. K definování dovedností s vestavěnou validací použijte `defineSkill()`: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` Hlavní body: - * `name` je jedinečný identifikátor dovednosti (doporučuje se kebab-case). * `label` je uživatelsky čitelný název zobrazovaný v UI. * `content` obsahuje pokyny dovednosti — je to text, který agent AI používá. * `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. * `description` (volitelné) poskytuje doplňující kontext o účelu dovednosti. -Nové dovednosti můžete vytvářet dvěma způsoby: + + -* **Vygenerované**: Spusťte `yarn twenty add` a zvolte možnost přidat novou dovednost. -* **Ruční**: Vytvořte nový soubor a použijte `defineSkill()` podle stejného vzoru. +Agenti jsou asistenti AI, kteří běží ve vašem pracovním prostoru. K vytvoření agentů s vlastním systémovým promptem použijte `defineAgent()`: -### Typovaní klienti API (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -Balíček `twenty-client-sdk` poskytuje dva typované klienty GraphQL pro práci s Twenty API z vašich logických funkcí a frontendových komponent: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Hlavní body: +* `name` je jedinečný identifikátor agenta (doporučuje se kebab-case). +* `label` je zobrazovaný název v UI. +* `prompt` je systémový prompt, který definuje chování agenta. +* `description` (volitelné) poskytuje kontext o tom, co agent dělá. +* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. +* `modelId` (volitelné) přepíše výchozí model AI používaný agentem. + + + + +Zobrazení jsou uložené konfigurace toho, jak se zobrazují záznamy objektu — včetně toho, která pole jsou viditelná, jejich pořadí a jaké filtry či seskupení jsou použity. Pomocí `defineView()` můžete k aplikaci přidat předkonfigurovaná zobrazení: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Hlavní body: +* `objectUniversalIdentifier` určuje, na který objekt se toto zobrazení vztahuje. +* `key` určuje typ zobrazení (např. `ViewKey.INDEX` pro hlavní seznam). +* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`. +* Pro pokročilejší konfigurace můžete definovat také `filters`, `filterGroups`, `groups` a `fieldGroups`. +* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení. + + + + +Položky navigační nabídky přidávají vlastní položky do postranního panelu pracovního prostoru. Použijte `defineNavigationMenuItem()` k odkazování na zobrazení, externí URL nebo objekty: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Hlavní body: +* `type` určuje, na co položka menu odkazuje: `NavigationMenuItemType.VIEW` pro uložené zobrazení nebo `NavigationMenuItemType.LINK` pro externí URL. +* Pro odkazy na zobrazení nastavte `viewUniversalIdentifier`. Pro externí odkazy nastavte `link`. +* `position` určuje pořadí v postranním panelu. +* `icon` a `color` (volitelné) upravují vzhled. + + + + +Rozvržení stránek vám umožní přizpůsobit vzhled stránky s detailem záznamu — které karty se zobrazí, jaké widgety jsou uvnitř každé karty a jak jsou uspořádány. Pomocí `definePageLayout()` můžete k aplikaci přidat vlastní rozvržení: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Hlavní body: +* `type` je obvykle `'RECORD_PAGE'` pro úpravu detailního zobrazení konkrétního objektu. +* `objectUniversalIdentifier` určuje, na který objekt se toto rozvržení vztahuje. +* Každá `tab` definuje sekci stránky s `title`, `position` a `layoutMode` (`CANVAS` pro volné rozvržení). +* Každý `widget` uvnitř karty může vykreslit frontendovou komponentu, seznam relací nebo jiné vestavěné typy widgetů. +* `position` na kartách určuje jejich pořadí. Použijte vyšší hodnoty (např. 50) pro umístění vlastních karet za vestavěné. + + +
+ +## Veřejné prostředky (složka `public/`) + +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 `logoUrl` a `screenshots` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Tyto se zobrazují v Marketplace, když je vaše aplikace zveřejněna. +* **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 restart. +* **Zahrnuté do buildů** — `yarn twenty 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, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +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. + +## Používání balíčků npm + +Ve své aplikaci můžete nainstalovat a používat libovolný balíček npm. Logické funkce i frontendové komponenty se bundlují pomocí [esbuild](https://esbuild.github.io/), který vloží všechny závislosti přímo do výstupu — za běhu nejsou potřeba žádné `node_modules`. + +### Instalace balíčku + +```bash filename="Terminal" +yarn add axios +``` + +Poté jej importujte ve svém kódu: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +Stejně to funguje i pro frontendové komponenty: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Jak funguje bundlování + +Krok sestavení (`yarn twenty dev` nebo `yarn twenty build`) používá esbuild k vytvoření jediného samostatného souboru pro každou logickou funkci a každou frontendovou komponentu. Všechny importované balíčky jsou vloženy přímo do bundlu. + +**Logické funkce** běží v prostředí Node.js. Vestavěné moduly Node (`fs`, `path`, `crypto`, `http` atd.) jsou k dispozici a není je třeba instalovat. + +**Frontendové komponenty** běží ve Web Workeru. Vestavěné moduly Node nejsou k dispozici — pouze prohlížečová API a balíčky npm, které fungují v prohlížečovém prostředí. + +V obou prostředích jsou jako předpřipravené moduly k dispozici `twenty-client-sdk/core` a `twenty-client-sdk/metadata` — nejsou součástí bundlu, ale server je za běhu načítá. + +## Generování entit pomocí `yarn twenty add` + +Místo ručního vytváření souborů entit můžete použít interaktivní generátor: + +```bash filename="Terminal" +yarn twenty add +``` + +Požádá vás o výběr typu entity a provede vás požadovanými poli. Vygeneruje soubor připravený k použití se stabilním `universalIdentifier` a správným voláním `defineEntity()`. + +Můžete také předat typ entity přímo a přeskočit první dotaz: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Dostupné typy entit + +| Typ entity | Příkaz | Vygenerovaný soubor | +| ------------------------- | ------------------------------------ | ------------------------------------- | +| Objekt | `yarn twenty add object` | `src/objects/.ts` | +| Pole | `yarn twenty add field` | `src/fields/.ts` | +| Logická funkce | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Frontendová komponenta | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Role | `yarn twenty add role` | `src/roles/.ts` | +| Dovednost | `yarn twenty add skill` | `src/skills/.ts` | +| Agent | `yarn twenty add agent` | `src/agents/.ts` | +| Pohled | `yarn twenty add view` | `src/views/.ts` | +| Položka navigační nabídky | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Rozvržení stránky | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### Co generátor vytváří + +Každý typ entity má vlastní šablonu. Například `yarn twenty add object` se zeptá na: + +1. **Název (jednotné číslo)** — např. `invoice` +2. **Název (množné číslo)** — např. `invoices` +3. **Štítek (jednotné číslo)** — automaticky doplněn z názvu (např. `Invoice`) +4. **Štítek (množné číslo)** — automaticky doplněn (např. `Invoices`) +5. **Vytvořit zobrazení a položku navigace?** — pokud odpovíte ano, generátor také vytvoří odpovídající zobrazení a odkaz v postranním panelu pro nový objekt. + +Ostatní typy entit mají jednodušší dotazy — většinou se ptají pouze na název. + +Typ entity `field` je podrobnější: ptá se na název pole, štítek, typ (ze seznamu všech dostupných typů polí jako `TEXT`, `NUMBER`, `SELECT`, `RELATION` atd.) a `universalIdentifier` cílového objektu. + +### Vlastní výstupní cesta + +Pomocí příznaku `--path` umístíte vygenerovaný soubor do vlastního umístění: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typovaní klienti API (twenty-client-sdk) + +Balíček `twenty-client-sdk` poskytuje dva typované klienty GraphQL pro práci s Twenty API z vašich logických funkcí a frontendových komponent. | Klient | Importovat | Koncový bod | Generováno? | | ------------------- | ---------------------------- | ---------------------------------------------------------------- | ------------------------------ | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — data pracovního prostoru (záznamy, objekty) | Ano, při vývoji/sestavení | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — konfigurace pracovního prostoru, nahrávání souborů | Ne, dodává se předem sestavený | -#### CoreApiClient + + `CoreApiClient` je hlavní klient pro dotazování a mutace dat pracovního prostoru. Generuje se z vašeho schématu pracovního prostoru během `yarn twenty dev` nebo `yarn twenty build`, takže je plně typovaný tak, aby odpovídal vašim objektům a polím. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ Klient používá syntaxi výběrové sady (selection-set): předáním `true` zahrnete pole, pro argumenty použijte `__args` a pro relace vnořujte objekty. Získáte plné automatické doplňování a kontrolu typů založené na schématu vašeho pracovního prostoru. -**CoreApiClient je generován při vývoji/sestavení.** Pokud se jej pokusíte použít bez předchozího spuštění `yarn twenty dev` nebo `yarn twenty build`, vyvolá chybu. Generování probíhá automaticky — CLI prozkoumá GraphQL schéma vašeho pracovního prostoru, vygeneruje typovaného klienta pomocí `@genql/cli`, zapíše vygenerované zdrojové soubory do `node_modules/twenty-client-sdk/dist/core/generated/` a nahradí zástupné soubory v `node_modules/twenty-client-sdk/dist/core.mjs` a `node_modules/twenty-client-sdk/dist/core.cjs`. +**CoreApiClient je generován při vývoji/sestavení.** Pokud jej použijete bez předchozího spuštění `yarn twenty dev` nebo `yarn twenty build`, vyvolá chybu. Generování probíhá automaticky — CLI prozkoumá GraphQL schéma vašeho pracovního prostoru a vygeneruje typovaného klienta pomocí `@genql/cli`. #### Použití CoreSchema pro anotace typů -`CoreSchema` poskytuje typy TypeScriptu odpovídající objektům vašeho pracovního prostoru; je užitečný pro typování stavu komponent nebo parametrů funkcí: +`CoreSchema` poskytuje typy TypeScriptu odpovídající objektům vašeho pracovního prostoru — hodí se pro typování stavu komponent nebo parametrů funkcí: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient` je součástí SDK již předem sestavený (není vyžadována žádná generace). Odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru, aplikace a nahrávání souborů: +`MetadataApiClient` je součástí SDK již předem sestavený (není vyžadována žádná generace). Odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru, aplikace a nahrávání souborů. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### Běhové přihlašovací údaje - -Když váš kód běží na Twenty (logické funkce nebo frontendové komponenty), platforma vloží přihlašovací údaje jako proměnné prostředí: - -* `TWENTY_API_URL` — Základní URL Twenty API -* `TWENTY_API_KEY` — Krátkodobý klíč s rozsahem omezeným na výchozí roli funkce vaší aplikace - -Není nutné je předávat klientům — čtou je automaticky z `process.env`. Oprávnění API klíče jsou určena rolí uvedenou v `defaultRoleUniversalIdentifier` ve vašem `application-config.ts`. - #### Nahrávání souborů -`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru. Implementuje [specifikaci GraphQL multipart request](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| Parametr | Typ | Popis | -| ---------------------------------- | -------- | --------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Surový obsah souboru | -| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) | -| `contentType` | `string` | Typ MIME souboru (pokud je vynechán, výchozí je `application/octet-stream`) | -| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu | +| Parametr | Typ | Popis | +| ---------------------------------- | -------- | ------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Surový obsah souboru | +| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) | +| `contentType` | `string` | Typ MIME (pokud je vynechán, výchozí je `application/octet-stream`) | +| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu | Hlavní body: - * Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje v jakémkoli pracovním prostoru, kde je vaše aplikace nainstalována. * Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru. -### Příklad Hello World + + -Prozkoumejte minimalistický end-to-end příklad, který demonstruje objekty, logické funkce, frontendové komponenty a více spouštěčů [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + Když váš kód běží na Twenty (logické funkce nebo frontendové komponenty), platforma vloží přihlašovací údaje jako proměnné prostředí: + + * `TWENTY_API_URL` — Základní URL Twenty API + * `TWENTY_APP_ACCESS_TOKEN` — krátkodobý klíč s rozsahem omezeným na výchozí roli funkce vaší aplikace + + Není nutné je předávat klientům — čtou je automaticky z `process.env`. Oprávnění API klíče jsou určena rolí uvedenou v `defaultRoleUniversalIdentifier` ve vašem `application-config.ts`. + + +## Testování vaší aplikace + +SDK poskytuje programová rozhraní, která vám umožní z testovacího kódu aplikaci sestavit, nasadit, nainstalovat a odinstalovat. V kombinaci s [Vitest](https://vitest.dev/) a typovanými klienty API můžete psát integrační testy, které ověří, že vaše aplikace funguje end-to-end proti reálnému serveru Twenty. + +### Nastavení + +Vygenerovaná aplikace již obsahuje Vitest. Pokud to nastavujete ručně, nainstalujte závislosti: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Vytvořte `vitest.config.ts` v kořeni vaší aplikace: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programová rozhraní SDK + +Subcesta `twenty-sdk/cli` exportuje funkce, které můžete volat přímo z testovacího kódu: + +| Funkce | Popis | +| -------------- | ----------------------------------------------------- | +| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball | +| `appDeploy` | Nahraje tarball na server | +| `appInstall` | Nainstaluje aplikaci do aktivního pracovního prostoru | +| `appUninstall` | Odinstaluje aplikaci z aktivního pracovního prostoru | + +Každá funkce vrací objekt výsledku se `success: boolean` a buď `data`, nebo `error`. + +### Psání integračního testu + +Zde je kompletní příklad, který aplikaci sestaví, nasadí a nainstaluje a poté ověří, že se objeví v pracovním prostoru: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Spuštění testů + +Ujistěte se, že běží váš lokální server Twenty, a poté: + +```bash filename="Terminal" +yarn test +``` + +Nebo v režimu watch během vývoje: + +```bash filename="Terminal" +yarn test:watch +``` + +### Kontrola typů + +Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Spustí se `tsc --noEmit` a nahlásí se případné chyby typů. + +## Referenční dokumentace CLI + +Kromě `dev`, `build`, `add` a `typecheck` poskytuje CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací. + +### Spouštění funkcí (`yarn twenty exec`) + +Spusťte logickou funkci ručně bez vyvolání přes HTTP, cron nebo databázovou událost: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Zobrazení logů funkcí (`yarn twenty logs`) + +Streamujte výstupní logy běhu logických funkcí vaší aplikace: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +To je jiné než `yarn twenty server logs`, které zobrazují logy kontejneru Docker. `yarn twenty logs` zobrazuje logy spuštění funkcí vaší aplikace ze serveru Twenty. + + +### Odinstalace aplikace (`yarn twenty uninstall`) + +Odeberte svou aplikaci z aktivního pracovního prostoru: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx index be66d0d31e..5f653de552 100644 --- a/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: Vytvořte svou první aplikaci Twenty během několika minut. --- -Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí. +Apps are currently in alpha. The feature works but is still evolving. Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, AI schopnosti a komponenty uživatelského rozhraní — vše je spravováno jako kód. -**Co můžete vytvořit:** - -* Vlastní objekty, pole, zobrazení a položky navigace pro utváření vašeho datového modelu -* Logické funkce spouštěné trasami HTTP, plánovačem cron nebo událostmi databáze -* Frontendové komponenty, které se vykreslují přímo uvnitř uživatelského rozhraní Twenty -* Dovednosti, které rozšiřují možnosti AI agentů Twenty -* Nasazení aplikace napříč více pracovními prostory - ## Předpoklady -* Node.js 24+ -* Yarn 4 -* Docker (nebo běžící lokální instance Twenty) +Before you begin, make sure the following is installed on your machine: -## Začínáme +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Vytvořte novou aplikaci pomocí oficiálního scaffolderu, poté se ověřte a začněte vyvíjet: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> Použijte volbu `--minimal` k vygenerování minimální instalace +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -Odtud můžete: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## Struktura projektu (vytvořená scaffolderem) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* Zkopíruje minimální základní aplikaci do `my-twenty-app/` -* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4 -* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI -* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, předinstalační a postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/cs/developers/extend/apps/publishing) for details. + -Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/cs/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -V kostce: +### Key files -* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skript `twenty`, který deleguje na lokální `twenty` CLI. Spusťte `yarn twenty help` pro výpis všech dostupných příkazů. -* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, složky s coverage, soubory s logy a soubory `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem. -* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem. -* **.oxlintrc.json** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu. -* **README.md**: Krátké README v kořeni aplikace se základními pokyny. -* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné. -* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód +| File / Folder | Účel | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Dovednosti, které rozšiřují možnosti AI agentů Twenty. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Detekce entit +## Managing remotes -SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`: - -| Pomocná funkce | Typ entity | -| -------------------------------- | --------------------------------------------------------- | -| `defineObject` | Definice vlastních objektů | -| `defineLogicFunction` | Definice logických funkcí | -| `definePreInstallLogicFunction` | Předinstalační logická funkce (spouští se před instalací) | -| `definePostInstallLogicFunction` | Postinstalační logická funkce (spouští se po instalaci) | -| `defineFrontComponent` | Definice frontendových komponent | -| `defineRole` | Definice rolí | -| `defineField` | Rozšíření polí u existujících objektů | -| `defineView` | Definice uložených zobrazení | -| `defineNavigationMenuItem` | Definice položek navigační nabídky | -| `defineSkill` | Definice dovedností agenta AI | - - -**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek. - - -Příklad detekované entity: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Pozdější příkazy přidají další soubory a složky: - -* `yarn twenty dev` automaticky vygeneruje typovaný `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) do `node_modules/twenty-client-sdk/`. `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`) je dodáván předpřipravený a je okamžitě k dispozici. Importujte je z `twenty-client-sdk/core` a `twenty-client-sdk/metadata` v uvedeném pořadí. -* `yarn twenty add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další. - -## Ověření - -Při prvním spuštění `yarn twenty auth:login` budete vyzváni k zadání: - -* URL API (výchozí je http://localhost:3000 nebo váš aktuální profil pracovního prostoru) -* Klíč API - -Vaše přihlašovací údaje se ukládají pro jednotlivé uživatele do `~/.twenty/config.json`. Můžete spravovat více profilů a přepínat mezi nimi. - -### Správa pracovních prostorů +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -Jakmile přepnete pracovní prostor pomocí `yarn twenty auth:switch`, všechny následující příkazy budou tento pracovní prostor používat jako výchozí. Můžete jej stále dočasně přepsat pomocí `--workspace `. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Spuštění serveru + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Příkaz | Popis | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Ruční nastavení (bez scaffolderu) -Ačkoli pro nejlepší začátky doporučujeme použít `create-twenty-app`, projekt můžete nastavit i ručně. Neinstalujte CLI globálně. Místo toho přidejte `twenty-sdk` jako lokální závislost a přidejte jeden skript do souboru package.json: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Poté přidejte skript `twenty`: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ Poté přidejte skript `twenty`: } ``` -Nyní můžete spouštět všechny příkazy přes `yarn twenty `, např. `yarn twenty dev`, `yarn twenty help` atd. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## Jak používat lokální instanci Twenty - -Pokud již lokálně provozujete instanci Twenty (např. pomocí `npx nx start twenty-server`), můžete se k ní připojit namísto použití Dockeru: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Řešení potíží -* Chyby ověření: spusťte `yarn twenty auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění. -* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný. -* Typy nebo klient chybí nebo jsou zastaralé: restartujte `yarn twenty dev` — automaticky generuje typovaného klienta. -* Režim vývoje se nesynchronizuje: ujistěte se, že běží `yarn twenty dev` a že vaše prostředí změny neignoruje. +If you run into issues: -Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx index 2977f9eade..0d2a4611d7 100644 --- a/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte i --- -Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí. + Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí. ## Přehled Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat: -* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat. * **Nasaďte tarball** — nahrajte svou aplikaci přímo na konkrétní server Twenty pro interní nebo soukromé použití. +* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat. Obě cesty začínají stejným krokem **build**. ## Sestavení vaší aplikace -Příkaz `build` zkompiluje vaše zdrojové soubory TypeScriptu, transpiluje logické funkce a frontendové komponenty a vygeneruje soubor `manifest.json`, který popisuje obsah vaší aplikace: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -Výstup se zapisuje do `.twenty/output/`. Tento adresář obsahuje vše potřebné pro distribuci: zkompilovaný kód, statické soubory, manifest a kopii souboru `package.json`. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -Chcete-li také vytvořit tarball `.tgz` (používaný interně příkazem deploy nebo pro ruční distribuci): +## Nasazení na server (tarball) + +U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty. + +### Předpoklady + +Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`. + +Přidat vzdálený cíl: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### Nasazení + +Sestavte a nahrajte svou aplikaci na server v jednom kroku: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Sdílení nasazené aplikace + +Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci: + +1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci +2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení** +3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace + +Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Správa verzí + +Chcete-li vydat aktualizaci: + +1. Zvyšte hodnotu pole `version` v souboru `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Pracovní prostory, které mají aplikaci nainstalovanou, uvidí dostupnou aktualizaci ve svém nastavení + +{/* TODO: add screenshot of the Upgrade button */} + ## Publikování na npm Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketplace Twenty. Jakýkoli pracovní prostor Twenty může procházet, instalovat a aktualizovat aplikace z Marketplace přímo z UI. @@ -39,41 +81,42 @@ Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketpla ### Požadavky * Účet na [npm](https://www.npmjs.com) -* Klíčové slovo `twenty-app` **musí** být uvedeno v poli `keywords` vašeho `package.json` - -### Přidání požadovaného klíčového slova - -Tržiště Twenty objevuje aplikace hledáním balíčků v registru npm s klíčovým slovem `twenty-app`. Přidejte jej do svého `package.json`: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -Tržiště vyhledává v registru npm výraz `keywords:twenty-app`. Bez tohoto klíčového slova se váš balíček v tržišti neobjeví, i když má jmennou předponu `twenty-app-`. - +### Metadata tržiště -### Postup +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **Sestavení vaší aplikace:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **Publikování na npm:** +See the [defineApplication accordion](/l/cs/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -Tímto se spustí `npm publish` z adresáře `.twenty/output/`. - Chcete-li publikovat pod konkrétním dist-tagem (např. `beta` nebo `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### Jak funguje objevování v tržišti -Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**: +Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**. -1. Vyhledá všechny balíčky na npm s klíčovým slovem `keywords:twenty-app` -2. Pro každý balíček stáhne `manifest.json` z CDN npm -3. Metadata aplikace (název, popis, autor, logo, snímky obrazovky, kategorie) se získají z manifestu a zobrazí se v tržišti - -Po publikování se vaše aplikace může v tržišti objevit až za jednu hodinu. Chcete-li spustit synchronizaci okamžitě místo čekání na další hodinové spuštění: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -Chcete-li zacílit na konkrétní vzdálený cíl: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -Metadata zobrazená v tržišti pocházejí z volání `defineApplication()` ve zdrojovém kódu vaší aplikace — z polí jako `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` a `termsUrl`. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. Pokud vaše aplikace nedefinuje `aboutDescription` v `defineApplication()`, tržiště automaticky použije soubor `README.md` vašeho balíčku z npm jako obsah stránky O aplikaci. To znamená, že můžete spravovat jediný soubor README jak pro npm, tak pro tržiště Twenty. Pokud chcete v tržišti jiný popis, explicitně nastavte `aboutDescription`. @@ -108,7 +143,7 @@ Pokud vaše aplikace nedefinuje `aboutDescription` v `defineApplication()`, trž ### Publikování pomocí CI -Vygenerovaný projekt obsahuje pracovní postup GitHub Actions, který publikuje při každém vydání: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `yarn twenty build` a poté `npm publish` z `.twenty/output`. - + **npm provenance** je volitelné, ale doporučené. Publikování s `--provenance` přidá k vašemu záznamu na npm odznak důvěryhodnosti a umožní uživatelům ověřit, že balíček byl sestaven z konkrétního commitu ve veřejné CI pipeline. Pokyny k nastavení najdete v [dokumentaci k npm provenance](https://docs.npmjs.com/generating-provenance-statements). - - -## Nasazení na server (tarball) - -U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty. - -### Předpoklady - -Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`. - -Přidat vzdálený cíl: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -Pro lokální vývojový server: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -Pro neinteraktivní prostředí se můžete ověřit také pomocí klíče API: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -Spravujte své vzdálené servery: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### Nasazení - -Sestavte a nahrajte svou aplikaci na server v jednom kroku: - -```bash filename="Terminal" -yarn twenty deploy -``` - -Tímto se aplikace sestaví s `--tarball` a poté se tarball nahraje na výchozí vzdálený server prostřednictvím vícedílného (multipart) nahrávání GraphQL. - -Chcete-li nasadit na konkrétní vzdálený server: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### Sdílení nasazené aplikace - -Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci: - -1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci -2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení** -3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace - -Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru. - -### Správa verzí - -Chcete-li vydat aktualizaci: - -1. Zvyšte hodnotu pole `version` v souboru `package.json` -2. Spusťte `yarn twenty deploy` (nebo `yarn twenty deploy -r production`) -3. Pracovní prostory, které mají aplikaci nainstalovanou, uvidí dostupnou aktualizaci ve svém nastavení + ## Instalace aplikací -Jakmile je aplikace publikována (npm) nebo nasazena (tarball), pracovní prostory ji instalují prostřednictvím uživatelského rozhraní: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -Nebo ze stránky **Nastavení > Aplikace** v rozhraní Twenty, kde lze procházet a instalovat jak aplikace z tržiště, tak aplikace nasazené jako tarball. - -## Kategorie distribuce aplikací - -Twenty organizuje aplikace do tří kategorií podle způsobu distribuce: - -| Kategorie | Jak to funguje | Viditelné v Marketplace? | -| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| **Vývoj** | Aplikace v místním vývojářském režimu spuštěné přes `yarn twenty dev`. Slouží k sestavování a testování. | Ne | -| **Publikováno (npm)** | Aplikace publikované na npm s klíčovým slovem `twenty-app`. Uvedeny v Marketplace, aby je mohl kterýkoli pracovní prostor nainstalovat. | Ano | -| **Interní (tarball)** | Aplikace nasazené pomocí tarballu na konkrétní server. Dostupné pouze pro pracovní prostory na tomto serveru prostřednictvím odkazu ke sdílení. | Ne | - - -Začněte v režimu **Development** při sestavování své aplikace. Až bude připravena, zvolte **Published** (npm) pro širokou distribuci nebo **Internal** (tarball) pro soukromé nasazení. - - -## Reference CLI - -| Příkaz | Popis | Klíčové přepínače | -| --------------------------- | ----------------------------------------------------- | --------------------------------------------------- | -| `yarn twenty build` | Sestaví aplikaci a vygeneruje manifest | `--tarball` — také vytvoří balíček `.tgz` | -| `yarn twenty publish` | Sestaví a publikuje na npm | `--tag ` — npm dist-tag (např. `beta`, `next`) | -| `yarn twenty deploy` | Sestaví a nahraje tarball na server | `-r, --remote ` — cílový vzdálený repozitář | -| `yarn twenty catalog-sync` | Spustí synchronizaci katalogu tržiště na serveru | `-r, --remote ` — cílový vzdálený repozitář | -| `yarn twenty install` | Nainstaluje nasazenou aplikaci do pracovního prostoru | `-r, --remote ` — cílový vzdálený server | -| `yarn twenty dev` | Sleduje a synchronizuje lokální změny | Používá výchozí vzdálený server | -| `yarn twenty remote add` | Přidá připojení k serveru | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | Vypíše nakonfigurované vzdálené servery | — | -| `yarn twenty remote switch` | Nastaví výchozí vzdálený server | — | -| `yarn twenty remote status` | Zobrazí stav připojení | — | -| `yarn twenty remote remove` | Odebere vzdálený server | — | diff --git a/packages/twenty-docs/l/cs/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/cs/developers/extend/capabilities/apps.mdx index d393ce6ebe..d91bef459f 100644 --- a/packages/twenty-docs/l/cs/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: Vytvářejte a spravujte přizpůsobení Twenty jako kód. --- -Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí. +Apps are currently in alpha. The feature works but is still evolving. -## Co jsou aplikace? +## What are apps? -Aplikace vám umožňují vytvářet a spravovat přizpůsobení Twenty **jako kód**. Místo konfigurace všeho přes uživatelské rozhraní definujete v kódu svůj datový model a logické funkce — což zrychluje vývoj, údržbu i nasazování do více pracovních prostorů. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**Co můžete dělat už dnes:** +**Co můžete vytvořit:** -* Definujte vlastní objekty a pole jako kód (spravovaný datový model) -* Vytvářejte logické funkce s vlastními spouštěči -* Definujte dovednosti a agenty AI! -* Nasazujte stejnou aplikaci do více pracovních prostorů +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Předpoklady - -* Node.js 24+ a Yarn 4 -* Docker (pro místní vývojový server Twenty) - -## Začínáme - -Vytvořte novou aplikaci pomocí oficiálního generátoru kostry. Může vám automaticky spustit místní instanci Twenty: +## Quick start ```bash filename="Terminal" -# Vygenerujte kostru nové aplikace — CLI nabídne spuštění místního serveru Twenty npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Spusťte vývojový režim: automaticky synchronizuje místní změny s vaším pracovním prostorem yarn twenty dev ``` -### Správa místního serveru +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/cs/developers/extend/apps/getting-started) guide for the full walkthrough. -SDK obsahuje příkazy ke správě místního vývojového serveru Twenty (all-in-one obraz Dockeru s PostgreSQL, Redisem, serverem a workerem na portu 2020). Tyto příkazy se vztahují pouze na vývojový server založený na Dockeru — nespravují instanci Twenty spuštěnou ze zdrojového kódu (např. `npx nx start twenty-server` na portu 3000): +## Detailed guides -```bash filename="Terminal" -# Spusťte místní server (v případě potřeby stáhne obraz) -yarn twenty server start +| Guide | Popis | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/cs/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/cs/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/cs/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Zkontrolujte stav serveru -yarn twenty server status - -# Streamujte logy serveru -yarn twenty server logs - -# Zastavte server -yarn twenty server stop - -# Resetujte všechna data a začněte znovu -yarn twenty server reset -``` - -Lokální server je předem naplněn pracovním prostorem a uživatelem (`tim@apple.dev` / `tim@apple.dev`), takže můžete začít vyvíjet okamžitě bez jakéhokoli ručního nastavení. - -### Ověření - -Připojte svou aplikaci k lokálnímu serveru pomocí OAuth: - -```bash filename="Terminal" -# Ověřte se pomocí OAuth (otevře prohlížeč) -yarn twenty remote add --local -``` - -Nástroj pro generování kostry podporuje dva režimy pro řízení toho, které ukázkové soubory jsou zahrnuty: - -```bash filename="Terminal" -# Výchozí (úplný): všechny příklady (objekt, pole, logická funkce, front-endová komponenta, zobrazení, položka navigační nabídky, dovednost, agent) -npx create-twenty-app@latest my-app - -# Minimální: pouze základní soubory (application-config.ts a default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### Jak používat lokální instanci Twenty - -Pokud již lokálně provozujete instanci Twenty, můžete se k ní připojit místo použití Dockeru. Zadejte port, na kterém váš lokální server naslouchá (výchozí: `3000`): - -```bash filename="Terminal" -# Během vytváření kostry -npx create-twenty-app@latest my-app --port 3000 - -# Nebo po vytvoření kostry -yarn twenty remote add --local --port 3000 -``` - -Odtud můžete: - -```bash filename="Terminal" -# Přidejte do vaší aplikace novou entitu (s průvodcem) -yarn twenty entity:add - -# Sledujte logy funkcí vaší aplikace -yarn twenty function:logs - -# Spusťte funkci podle názvu -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Spusťte předinstalační funkci -yarn twenty function:execute --preInstall - -# Spusťte postinstalační funkci -yarn twenty function:execute --postInstall - -# Sestavte aplikaci pro distribuci -yarn twenty build - -# Publikujte aplikaci na npm nebo na server Twenty -yarn twenty publish - -# Odinstalujte aplikaci z aktuálního pracovního prostoru -yarn twenty uninstall - -# Zobrazte nápovědu k příkazům -yarn twenty help},{ -``` - -Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## Struktura projektu (vytvořená scaffolderem) - -Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder: - -* Zkopíruje minimální základní aplikaci do `my-twenty-app/` -* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4 -* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI -* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, předinstalační a postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry - -Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Složka s veřejnými prostředky (obrázky, písma apod.) - src/ - ├── application-config.ts # Povinné – hlavní konfigurace aplikace - ├── roles/ - │ └── default-role.ts # Výchozí role pro logické funkce - ├── objects/ - │ └── example-object.ts # Ukázková definice vlastního objektu - ├── fields/ - │ └── example-field.ts # Ukázková samostatná definice pole - ├── logic-functions/ - │ ├── hello-world.ts # Ukázková logická funkce - │ ├── pre-install.ts # Předinstalační logická funkce - │ └── post-install.ts # Postinstalační logická funkce - ├── front-components/ - │ └── hello-world.tsx # Ukázková front-endová komponenta - ├── views/ - │ └── example-view.ts # Ukázková definice uloženého zobrazení - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Ukázkový odkaz postranní navigace - ├── skills/ - │ └── example-skill.ts # Ukázková definice dovednosti agenta AI - └── agents/ - └── example-agent.ts # Ukázková definice agenta AI -``` - -S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`). - -V kostce: - -* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skript `twenty`, který deleguje na lokální `twenty` CLI. Spusťte `yarn twenty help` pro výpis všech dostupných příkazů. -* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem. -* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem. -* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources. -* **README.md**: Krátké README v kořeni aplikace se základními pokyny. -* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné. -* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód +## Key concepts ### Detekce entit -SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Pomocná funkce | Typ entity | -| ---------------------------------- | --------------------------------------------------------- | -| `defineObject()` | Definice vlastních objektů | -| `defineLogicFunction()` | Definice logických funkcí | -| `definePreInstallLogicFunction()` | Předinstalační logická funkce (spouští se před instalací) | -| `definePostInstallLogicFunction()` | Postinstalační logická funkce (spouští se po instalaci) | -| `defineFrontComponent()` | Definice frontendových komponent | -| `defineRole()` | Definice rolí | -| `defineField()` | Rozšíření polí u existujících objektů | -| `defineView()` | Definice uložených zobrazení | -| `defineNavigationMenuItem()` | Definice položek navigační nabídky | -| `defineSkill()` | Definice dovedností agenta AI | -| `defineAgent()` | Definice agentů AI | +### Available entity types - -**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek. - +| Funkce | Účel | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Příklad detekované entity: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Pozdější příkazy přidají další soubory a složky: - -* `yarn twenty dev` automaticky vygeneruje dva typované API klienty v `node_modules/twenty-sdk/clients`: `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) a `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`). -* `yarn twenty entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další. - -## Ověření - -Při prvním spuštění `yarn twenty auth:login` budete vyzváni k zadání: - -* URL API (výchozí je http://localhost:3000 nebo váš aktuální profil pracovního prostoru) -* Klíč API - -Vaše přihlašovací údaje se ukládají pro jednotlivé uživatele do `~/.twenty/config.json`. Můžete spravovat více profilů a přepínat mezi nimi. - -### Správa pracovních prostorů +### Referenční dokumentace CLI ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login - -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace - -# List all configured workspaces -yarn twenty auth:list - -# Switch the default workspace (interactive) -yarn twenty auth:switch - -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -Jakmile přepnete pracovní prostor pomocí `yarn twenty auth:switch`, všechny následující příkazy budou tento pracovní prostor používat jako výchozí. Můžete jej stále dočasně přepsat pomocí `--workspace `. - -## Používejte zdroje SDK (typy a konfiguraci) - -twenty-sdk poskytuje typované stavební bloky a pomocné funkce, které používáte ve své aplikaci. Níže jsou klíčové části, se kterými budete nejčastěji pracovat. - -### Pomocné funkce - -SDK poskytuje pomocné funkce pro definování entit vaší aplikace. Jak je popsáno v [Detekce entit](#entity-detection), musíte použít `export default define({...})`, aby byly vaše entity detekovány: - -| Funkce | Účel | -| ---------------------------------- | ----------------------------------------------------------------- | -| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) | -| `defineObject()` | Definice vlastních objektů s poli | -| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi | -| `definePreInstallLogicFunction()` | Definujte předinstalační logickou funkci (jedna na aplikaci) | -| `definePostInstallLogicFunction()` | Definujte postinstalační logickou funkci (jedna na aplikaci) | -| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní | -| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům | -| `defineField()` | Rozšiřte existující objekty o další pole | -| `defineView()` | Definujte uložená zobrazení pro objekty | -| `defineNavigationMenuItem()` | Definujte odkazy postranní navigace | -| `defineSkill()` | Definuje dovednosti agenta AI | -| `defineAgent()` | Definujte AI agenty pomocí systémových promptů | - -Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost. - -### Definování objektů - -Vlastní objekty popisují jak schéma, tak chování záznamů ve vašem pracovním prostoru. K definování objektů s vestavěnou validací použijte `defineObject()`: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Hlavní body: - -* Použijte `defineObject()` pro vestavěnou validaci a lepší podporu v IDE. -* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními. -* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`. -* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí. -* Nové objekty můžete vygenerovat pomocí `yarn twenty entity:add`, který vás provede pojmenováním, poli a vztahy. - - -**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole -jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`. -Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole. -Výchozí pole můžete přepsat definováním pole se stejným názvem v poli `fields`, -ale to se nedoporučuje. - - -### Definování polí u existujících objektů - -Použijte `defineField()` k přidání vlastních polí k existujícím objektům — jak ke standardním objektům (např. `company`, `person`, `opportunity`), tak k vlastním objektům definovaným jinými aplikacemi. Každé pole je ve svém vlastním souboru a odkazuje na cílový objekt pomocí jeho `universalIdentifier`. - -Chcete-li odkazovat na standardní objekty, importujte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` z `twenty-sdk`. Tato konstanta poskytuje stabilní identifikátory pro všechny vestavěné objekty a jejich pole: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Hlavní body: - -* `objectUniversalIdentifier` určuje, ke kterému objektu má Twenty pole připojit. Použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` pro standardní objekty. -* Každé pole vyžaduje svůj vlastní stabilní `universalIdentifier`, `name`, `type`, `label` a cílový `objectUniversalIdentifier`. -* Nová pole můžete vygenerovat pomocí `yarn twenty entity:add` a zvolit možnost pole. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` je pro pohodlí exportován také jako `STANDARD_OBJECT` — obojí odkazuje na stejnou konstantu. - -Mezi dostupné standardní objekty patří: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` a `workspaceMember`. - -Každý standardní objekt také zpřístupňuje identifikátory svých polí. Například chcete-li odkázat na konkrétní pole u standardního objektu v oprávněních pro role: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### Vztahová pole u existujících objektů - -Můžete také definovat vztahová pole, která propojí existující objekty s vašimi vlastními objekty: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### Konfigurace aplikace (application-config.ts) - -Každá aplikace má jeden soubor `application-config.ts`, který popisuje: - -* **Identitu aplikace**: identifikátory, zobrazovaný název a popis. -* **Jak běží její funkce**: kterou roli používají pro oprávnění. -* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí. -* **(Volitelná) předinstalační funkce**: logická funkce, která se spouští před instalací aplikace. -* **(Volitelná) postinstalační funkce**: logická funkce, která se spouští po instalaci aplikace. - -Use `defineApplication()` to define your application configuration: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Poznámky: - -* Pole `universalIdentifier` jsou deterministická ID, která vlastníte; vygenerujte je jednou a udržujte je stabilní napříč synchronizacemi. -* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` se musí shodovat se souborem role (viz níže). -* Předinstalační a postinstalační funkce jsou při sestavování manifestu automaticky detekovány. Viz [Předinstalační funkce](#pre-install-functions) a [Postinstalační funkce](#post-install-functions). - -#### Role a oprávnění - -Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi vaší aplikace. - -* Běhový klíč API vložený jako `TWENTY_API_KEY` je odvozen z této výchozí role funkcí. -* Typovaný klient bude omezen oprávněními udělenými této roli. -* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují, a poté odkazujte na její univerzální identifikátor. - -##### Výchozí role funkce (\*.role.ts) - -Když vygenerujete novou aplikaci, CLI také vytvoří výchozí soubor role. K definování rolí s vestavěnou validací použijte `defineRole()`: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Na `universalIdentifier` této role se poté odkazuje v `application-config.ts` jako na `defaultRoleUniversalIdentifier`. Jinými slovy: - -* **\*.role.ts** definuje, co může výchozí role funkce dělat. -* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění. - -Poznámky: - -* Začněte rolí vytvořenou scaffolderem a postupně ji omezujte podle principu nejmenších oprávnění. -* Nahraďte `objectPermissions` a `fieldPermissions` objekty/poli, která vaše funkce potřebují. -* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Držte je na minimu; přidávejte pouze to, co potřebujete. -* Podívejte se na funkční příklad v aplikaci Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Konfigurace logických funkcí a vstupní bod - -Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -}); -``` - -Běžné typy spouštěčů: - -* **route**: Zpřístupní vaši funkci na HTTP cestě a metodě **pod koncovým bodem `/s/`**: - -> např. `path: '/post-card/create',` -> volání na `/s/post-card/create` - -* **cron**: Spouští vaši funkci podle plánu pomocí výrazu CRON. -* **databaseEvent**: Spouští se při událostech životního cyklu objektů v pracovním prostoru. Když je operace události `updated`, lze konkrétní sledovaná pole určit v poli `updatedFields`. Pokud zůstane nedefinované nebo prázdné, spustí funkci jakákoli aktualizace. - -> např. `person.updated` - -Poznámky: - -* Pole `triggers` je volitelné. Funkce bez spouštěčů lze použít jako pomocné funkce volané jinými funkcemi. -* V jedné funkci můžete kombinovat více typů spouštěčů. - -### Předinstalační funkce - -Předinstalační funkce je logická funkce, která se automaticky spouští před instalací vaší aplikace v pracovním prostoru. To je užitečné pro validační úlohy, kontrolu předpokladů nebo přípravu stavu pracovního prostoru před zahájením hlavní instalace. - -Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří se pro vás předinstalační funkce v `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Hlavní body: - -* Předinstalační funkce používají `definePreInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci). -* Na jednu aplikaci je povolena pouze jedna předinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. -* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `preInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`. -* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší přípravné úlohy. -* Předinstalační funkce nepotřebují spouštěče — platforma je vyvolává před instalací nebo je lze spustit ručně pomocí `function:execute --preInstall`. - -### Postinstalační funkce - -Postinstalační funkce je logická funkce, která se automaticky spouští po instalaci vaší aplikace do pracovního prostoru. To je užitečné pro jednorázové úlohy nastavení, jako je naplnění výchozími daty, vytvoření počátečních záznamů nebo konfigurace nastavení pracovního prostoru. - -Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří se pro vás postinstalační funkce v `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Hlavní body: - -* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci). -* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. -* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `postInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`. -* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty. -* Postinstalační funkce nepotřebují spouštěče — jsou spouštěny platformou během instalace nebo ručně pomocí `function:execute --postInstall`. - -### Payload spouštěče trasy - - -**Zpětně nekompatibilní změna (v1.16, leden 2026):** Formát payloadu spouštěče trasy se změnil. Před verzí v1.16 byly parametry dotazu, parametry cesty a tělo odesílány přímo jako payload. Od verze v1.16 jsou zanořeny uvnitř strukturovaného objektu `RoutePayload`. - -**Před v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**Po v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**Jak migrovat existující funkce:** Aktualizujte svůj handler tak, aby destrukturoval z `event.body`, `event.queryStringParameters` nebo `event.pathParameters` místo přímo z objektu params. - - -Když spouštěč trasy vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá formátu AWS HTTP API v2. Importujte typ z `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Typ `RoutePayload` má následující strukturu: - -| Vlastnost | Typ | Popis | -| ---------------------------- | ------------------------------------- | --------------------------------------------------------------------------------- | -| `headers` | `Record` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parametry query stringu (více hodnot spojených čárkami) | -| `pathParameters` | `Record` | Parametry cesty extrahované ze vzoru trasy (např. `/users/:id` → `{ id: '123' }`) | -| `text zprávy` | `object \| null` | Parsované tělo požadavku (JSON) | -| `isBase64Encoded` | `booleovská hodnota` | Zda je tělo kódováno base64 | -| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | - -### Přeposílání záhlaví HTTP - -Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -Ve vašem handleru k nim poté můžete přistupovat: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`). - - -Nové funkce můžete vytvářet dvěma způsoby: - -* **Vygenerované**: Spusťte `yarn twenty entity:add` a zvolte možnost přidat novou logickou funkci. Tím se vygeneruje startovací soubor s obslužnou funkcí a konfigurací. -* **Ruční**: Vytvořte nový soubor `*.logic-function.ts` a použijte `defineLogicFunction()` podle stejného vzoru. - -### Označení logické funkce jako nástroje - -Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji vybrat jako krok v automatizacích pracovních postupů. - -Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true` a poskytněte `toolInputSchema` popisující očekávané vstupní parametry pomocí [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Hlavní body: - -* **`isTool`** (`boolean`, výchozí: `false`): Když je nastaveno na `true`, funkce je zaregistrována jako nástroj a zpřístupní se agentům AI a automatizacím pracovních postupů. -* **`toolInputSchema`** (`object`, volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Agenti AI používají toto schéma k pochopení toho, jaké vstupy nástroj očekává, a k ověřování volání. Pokud je vynecháno, schéma má výchozí podobu `{ type: 'object', properties: {} }` (žádné parametry). -* Funkce s `isTool: false` (nebo není nastaveno) **nejsou** zpřístupněny jako nástroje. Stále je lze spouštět přímo nebo volat z jiných funkcí, ale neobjeví se ve vyhledávání nástrojů. -* **Pojmenování nástrojů**: Když je funkce zpřístupněna jako nástroj, její název se automaticky normalizuje na `logic_function_` (převedeno na malá písmena, nealfanumerické znaky jsou nahrazeny podtržítky). Například `enrich-company` se změní na `logic_function_enrich_company`. -* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) i spouštěna událostmi (cron, databázové události, routes). - - -**Napište kvalitní `description`.** Agenti AI se spoléhají na pole funkce `description` při rozhodování, kdy nástroj použít. Buďte konkrétní ohledně toho, co nástroj dělá a kdy se má volat. - - -### Frontendové komponenty - -Frontendové komponenty vám umožňují vytvářet vlastní React komponenty, které se vykreslují v rozhraní Twenty. K definování komponent s vestavěnou validací použijte `defineFrontComponent()`: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -Hlavní body: - -* Frontendové komponenty jsou React komponenty, které se vykreslují v izolovaných kontextech v rámci Twenty. -* Pole `component` odkazuje na vaši React komponentu. -* Komponenty se během `yarn twenty dev` automaticky sestaví a synchronizují. - -Nové frontendové komponenty můžete vytvořit dvěma způsoby: - -* **Vygenerované**: Spusťte `yarn twenty entity:add` a zvolte možnost přidat novou frontendovou komponentu. -* **Ruční**: Vytvořte nový soubor `.tsx` a použijte `defineFrontComponent()`, podle stejného vzoru. - -#### Kde lze použít front komponenty - -Front komponenty se mohou vykreslovat na dvou místech v rámci Twenty: - -* **Postranní panel** — Ne-headless front komponenty se otevírají v pravém postranním panelu. Toto je výchozí chování, když je front komponenta vyvolána z menu příkazů. -* **Widgety (nástěnky a stránky záznamů)** — Front komponenty lze vkládat jako widgety do rozložení stránek. Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget front komponenty. - -#### Headless vs. ne-headless - -Front komponenty existují ve dvou režimech vykreslování řízených volbou `isHeadless`: - -**Ne-headless (výchozí)** — Komponenta vykreslí viditelné uživatelské rozhraní. Po vyvolání z menu příkazů se otevře v postranním panelu. Toto je výchozí chování, když je `isHeadless` `false` nebo když tato volba není uvedena. - -**Headless** — Komponenta se neviditelně inicializuje na pozadí. Neotevírá postranní panel. Headless komponenty jsou určené pro akce, které provedou logiku a poté se odpojí — například spuštění asynchronního úkolu, navigaci na stránku nebo zobrazení potvrzovacího modálního okna. Přirozeně se hodí ke komponentám SDK Command popsaným níže. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Přidávání položek menu příkazů - -Aby se front komponenta zobrazila jako položka v menu příkazů Twenty, přidejte vlastnost `command` k `defineFrontComponent()`. Když uživatelé otevřou menu příkazů (Cmd+K / Ctrl+K), položka se zobrazí a po kliknutí spustí front komponentu. - -Objekt `command` přijímá následující pole: - -| Pole | Typ | Popis | -| --------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `universalIdentifier` | `string` (povinné) | Jedinečné ID položky menu příkazů | -| `štítek` | `string` (povinné) | Text zobrazený v menu příkazů | -| `ikona` | `string` (nepovinné) | Název ikony (např. `'IconSparkles'`) | -| `isPinned` | `boolean` (nepovinné) | Zda je příkaz připnutý nahoře v menu | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (nepovinné) | `GLOBAL` zobrazuje příkaz všude; `RECORD_SELECTION` jej zobrazuje pouze v kontextech záznamů | -| `availabilityObjectUniversalIdentifier` | `string` (nepovinné) | Omezí příkaz na konkrétní typ objektu (např. Person) | - -Zde je příklad z aplikace pro nahrávání hovorů, který přidává příkaz omezený na záznamy typu Person: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -Když se příkaz synchronizuje, objeví se v menu příkazů. Pokud je front komponenta ne-headless, otevře se postranní panel s komponentou vykreslenou uvnitř. Pokud je headless, komponenta se inicializuje na pozadí a provede svou logiku. - -#### Komponenty SDK Command - -Balíček `twenty-sdk` poskytuje čtyři pomocné komponenty Command navržené pro headless front komponenty. Každá komponenta při připojení provede akci, chyby zpracuje zobrazením oznámení ve snackbaru a po dokončení automaticky odpojí front komponentu. - -Importujte je z `twenty-sdk/command`: - -* **`Command`** — Spustí asynchronní callback přes prop `execute`. -* **`CommandLink`** — Naviguje na cestu v aplikaci. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Otevře potvrzovací modální okno. Pokud uživatel potvrdí, provede callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Otevře konkrétní stránku postranního panelu. Props: `page`, `pageTitle`, `pageIcon`. - -Zde je kompletní příklad headless front komponenty, která pomocí `Command` spouští akci z menu příkazů: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -A příklad s použitím `CommandModal` k vyžádání potvrzení před provedením: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Kontext provádění - -Každá front komponenta získá kontext provádění, který poskytuje informace o tom, kde a jak běží. K hodnotám kontextu přistupujte pomocí hooků z `twenty-sdk`: - -| Hook | Návratový typ | Popis | -| ----------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | Jedinečné ID aktuální instance front komponenty | -| `useRecordId()` | `string \| null` | ID aktuálního záznamu, když komponenta běží v kontextu záznamu (např. widget na stránce záznamu nebo příkaz omezený na záznam). V opačném případě vrací `null`. | -| `useUserId()` | `string \| null` | ID aktuálního uživatele | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -Kontext je reaktivní — pokud se okolní záznam změní, hooky automaticky vrátí aktualizované hodnoty. - -#### Funkce hostitelského API - -Front komponenty běží v izolovaném sandboxu, ale mohou interagovat s UI Twenty prostřednictvím sady funkcí poskytovaných hostitelem. Importujte je přímo z `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Funkce | Signatura | Popis | -| ------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `navigovat` | `(to, params?, queryParams?, options?) => Promise` | Přejde na typovanou cestu aplikace v rámci Twenty | -| `closeSidePanel` | `() => Promise` | Zavře postranní panel | -| `enqueueSnackbar` | `(params) => Promise` | Zobrazí oznámení ve snackbaru. Parametry: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), volitelně `duration`, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Odpojí aktuální front komponentu (používají headless komponenty k úklidu po vykonání) | -| `openSidePanelPage` | `(params) => Promise` | Otevře stránku v postranním panelu. Parametry: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Zobrazí potvrzovací modální okno a počká na reakci uživatele. Parametry: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Zde je příklad, který používá hostitelské API k zobrazení snackbaru a zavření postranního panelu po dokončení akce: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Dovednosti - -Dovednosti definují znovupoužitelné pokyny a schopnosti, které mohou agenti AI používat ve vašem pracovním prostoru. K definování dovedností s vestavěnou validací použijte `defineSkill()`: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Hlavní body: - -* `name` je jedinečný identifikátor dovednosti (doporučuje se kebab-case). -* `label` je uživatelsky čitelný název zobrazovaný v UI. -* `content` obsahuje pokyny dovednosti — je to text, který agent AI používá. -* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. -* `description` (volitelné) poskytuje doplňující kontext o účelu dovednosti. - -Nové dovednosti můžete vytvářet dvěma způsoby: - -* **Vygenerované**: Spusťte `yarn twenty entity:add` a zvolte možnost přidat novou dovednost. -* **Ruční**: Vytvořte nový soubor a použijte `defineSkill()` podle stejného vzoru. - -### Agenti - -Agenti jsou AI agenti se systémovými prompty, kteří mohou fungovat ve vašem pracovním prostoru. K definování agentů s vestavěnou validací použijte `defineAgent()`: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Hlavní body: - -* `name` je jedinečný identifikátor agenta (doporučuje se kebab-case). -* `label` je uživatelsky čitelný název zobrazovaný v UI. -* `prompt` obsahuje systémový prompt — jde o instrukční text, který určuje chování agenta. -* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. -* `description` (volitelné) poskytuje doplňující kontext o účelu agenta. - -Nové agenty můžete vytvářet dvěma způsoby: - -* **Vygenerované**: Spusťte `yarn twenty entity:add` a zvolte možnost přidat nového agenta. -* **Ruční**: Vytvořte nový soubor a použijte `defineAgent()` podle stejného vzoru. - -### Generované typované klienty - -Dva typované klienty jsou automaticky vygenerovány pomocí `yarn twenty dev` a uloženy do `node_modules/twenty-sdk/clients` podle schématu vašeho pracovního prostoru: - -* **`CoreApiClient`** — provádí dotazy na endpoint `/graphql` za účelem získání dat pracovního prostoru -* **`MetadataApiClient`** — odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru a nahrávání souborů - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); -``` - -`CoreApiClient` se automaticky znovu generuje pomocí `yarn twenty dev` kdykoli se změní vaše objekty nebo pole. `MetadataApiClient` je v SDK k dispozici již předem sestavený. - -#### Běhové přihlašovací údaje v logických funkcích - -Když vaše funkce běží na Twenty, platforma před spuštěním kódu vloží přihlašovací údaje jako proměnné prostředí: - -* `TWENTY_API_URL`: Základní URL Twenty API, na které vaše aplikace cílí. -* `TWENTY_API_KEY`: Krátkodobý klíč s rozsahem omezeným na výchozí roli funkce vaší aplikace. - -Poznámky: - -* Není nutné předávat URL ani klíč API vygenerovanému klientovi. Za běhu čte `TWENTY_API_URL` a `TWENTY_API_KEY` z process.env. -* Oprávnění klíče API jsou určena rolí odkazovanou ve vašem `application-config.ts` prostřednictvím `defaultRoleUniversalIdentifier`. Toto je výchozí role používaná logickými funkcemi vaší aplikace. -* Aplikace mohou definovat role podle principu nejmenších oprávnění. Udělte pouze oprávnění, která vaše funkce potřebují, a poté nastavte `defaultRoleUniversalIdentifier` na univerzální identifikátor této role. - -#### Nahrávání souborů - -`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru u objektů ve vašem pracovním prostoru. Protože standardní klienti GraphQL nativně nepodporují nahrávání souborů pomocí multipart, klient poskytuje tuto speciální metodu, která interně implementuje [specifikaci multipart požadavků GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec). - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type (defaults to 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -Signatura metody: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parametr | Typ | Popis | -| ---------------------------------- | -------- | --------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Surový obsah souboru | -| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) | -| `contentType` | `string` | Typ MIME souboru (pokud je vynechán, výchozí je `application/octet-stream`) | -| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu | - -Hlavní body: - -* Metoda `uploadFile` je k dispozici v `MetadataApiClient`, protože mutaci nahrávání obsluhuje endpoint `/metadata`. -* Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje ve všech pracovních prostorech, kde je vaše aplikace nainstalována — v souladu s tím, jak aplikace odkazují na pole všude jinde. -* Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru. - -### Příklad Hello World - -Prozkoumejte minimalistický end-to-end příklad, který demonstruje objekty, logické funkce, frontendové komponenty a více spouštěčů [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## Sestavení vaší aplikace - -Jakmile vyvinete svou aplikaci pomocí `app:dev`, použijte `app:build` k jejímu zkompilování do distribučního balíčku. - -```bash filename="Terminal" -# Sestavte aplikaci (výstup se uloží do .twenty/output/) -yarn twenty build - -# Sestavte a vytvořte tarball (.tgz) pro distribuci -yarn twenty build --tarball -``` - -Proces sestavení: - -1. **Parsuje a ověřuje manifest** — čte všechny entity `defineX()` z vašich zdrojových souborů a ověřuje strukturu manifestu. -2. **Kompiluje logické funkce a frontendové komponenty** — slučuje zdrojové soubory TypeScriptu do ESM souborů `.mjs` pomocí esbuild. -3. **Generuje kontrolní součty** — vypočítá MD5 hashe pro každý sestavený soubor, které jsou v manifestu uloženy jako `builtHandlerChecksum` / `builtComponentChecksum`. -4. **Vygeneruje typovaného klienta API** — prozkoumá schéma GraphQL a vygeneruje typované klienty `CoreApiClient` a `MetadataApiClient`. -5. **Spustí kontrolu typů TypeScriptu** — spustí `tsc --noEmit`, aby zachytil chyby typů před publikováním. -6. **Znovu sestaví s vygenerovaným klientem** — provede druhý průchod kompilace, aby byly zahrnuty typy vygenerovaného klienta. -7. **Volitelně vytvoří tarball** — pokud je předán `--tarball`, spustí `npm pack` a vytvoří soubor `.tgz` připravený k distribuci. - -Výstup sestavení v `.twenty/output/` obsahuje: - -```text -.twenty/output/ -├── manifest.json # Manifest with checksums for all built files -├── package.json # Copied from app root -├── yarn.lock # Copied from app root -├── src/ -│ ├── logic-functions/ # Compiled .mjs logic function files -│ └── front-components/ # Compiled .mjs front component files -├── public/ # Static assets (if any) -└── my-app-1.0.0.tgz # Only with --tarball flag -``` - -| Možnost | Popis | -| ----------- | ----------------------------------------------------- | -| `[appPath]` | Cesta k adresáři aplikace (výchozí: aktuální adresář) | -| `--tarball` | Také zabalí výstup do tarballu `.tgz` | - -## Publikování vaší aplikace - -Použijte `app:publish` k distribuci své aplikace — buď do registru npm, nebo přímo na server Twenty. - -### Publikovat na npm (výchozí) - -```bash filename="Terminal" -# Publikujte na npm (vyžaduje přihlášení k npm) -yarn twenty publish - -# Publikujte s dist-tagem (např. beta, next) -yarn twenty publish --tag beta -``` - -Tímto se aplikace sestaví a spustí se `npm publish` z adresáře `.twenty/output/`. Publikovaný balíček pak může být nainstalován z tržiště Twenty jakýmkoli pracovním prostorem. - -### Publikovat na server Twenty - -```bash filename="Terminal" -# Publikujte přímo na server Twenty -yarn twenty publish --server https://app.twenty.com -``` - -Tímto se aplikace sestaví s tarballem, nahraje se na server pomocí GraphQL mutace `uploadAppTarball` a v jednom kroku se spustí instalace. To je užitečné pro soukromá nasazení nebo testování proti konkrétnímu serveru. - -| Možnost | Popis | -| ----------------- | ------------------------------------------------------------------ | -| `[appPath]` | Cesta k adresáři aplikace (výchozí: aktuální adresář) | -| `--server ` | Publikovat na server Twenty místo npm | -| `--token ` | Autentizační token pro cílový server | -| `--tag ` | npm dist-tag (např. `beta`, `next`) — pouze pro publikování na npm | - -## Registrace aplikace - -Než může být aplikace nainstalována v pracovním prostoru, musí být **zaregistrována**. Registrace je záznam metadat, který popisuje, odkud aplikace pochází a jak ji autentizovat. Ve většině případů to CLI zpracuje automaticky. - -### Typy zdrojů - -Každá registrace má **typ zdroje**, který určuje, jak se při instalaci získávají soubory aplikace: - -| Typ zdroje | Jak se získávají soubory | Typický případ použití | -| ---------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `LOCAL` | Soubory jsou průběžně synchronizovány nástrojem CLI watcher v reálném čase — instalace se přeskočí | Vývoj s `app:dev` | -| `NPM` | Získáváno z registru npm prostřednictvím pole `sourcePackage` | Publikované aplikace na npm | -| `TARBALL` | Extrahováno z nahraného souboru `.tgz` uloženého na serveru | Soukromé aplikace publikované pomocí `--server` | - -### Jak probíhá registrace - -* **`app:dev`** — při prvním spuštění vývojového režimu pro pracovní prostor automaticky vytvoří registraci `LOCAL`. -* **`app:publish --server`** — nahraje tarball a vytvoří (nebo aktualizuje) registraci `TARBALL` a poté nainstaluje aplikaci. -* **tržiště npm** — registrace `NPM` se vytvářejí, když jsou aplikace synchronizovány z registru npm do katalogu tržiště Twenty. -* **GraphQL API** — registrace můžete vytvářet také programově pomocí mutace `createApplicationRegistration`. - -### Registrace vs instalace - -**Registrace** a **instalace** jsou odlišné pojmy: - -* **Registrace** (`ApplicationRegistration`) je globální záznam metadat popisující aplikaci: její název, typ zdroje, přihlašovací údaje OAuth a stav zařazení na tržišti. Existuje nezávisle na jakémkoli pracovním prostoru. -* **Instalace** (`Application`) je instancí na úrovni pracovního prostoru. Když uživatel nainstaluje aplikaci, Twenty načte balíček ze zdroje uvedeného v registraci, zapíše sestavené soubory do úložiště a synchronizuje manifest (vytváření objektů, polí, logických funkcí atd.) v daném pracovním prostoru. - -Jedna registrace může být nainstalována v mnoha pracovních prostorech. Každý pracovní prostor získá svou vlastní kopii souborů a datového modelu aplikace. - -### Přihlašovací údaje OAuth - -Každá registrace obsahuje přihlašovací údaje OAuth (`oAuthClientId` a `oAuthClientSecret`) vygenerované při vytvoření. Tyto údaje aplikace používá k autentizaci požadavků na API jménem uživatelů. Tajný klíč klienta je při vytvoření vrácen **pouze jednou** — uložte jej bezpečně. Později jej můžete rotovat prostřednictvím mutace `rotateApplicationRegistrationClientSecret`. - -## Ruční nastavení (bez scaffolderu) - -Ačkoli pro nejlepší začátky doporučujeme použít `create-twenty-app`, projekt můžete nastavit i ručně. Neinstalujte CLI globálně. Místo toho přidejte `twenty-sdk` jako lokální závislost a přidejte jeden skript do souboru package.json: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Poté přidejte skript `twenty`: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Nyní můžete spouštět všechny příkazy přes `yarn twenty `, např. `yarn twenty dev`, `yarn twenty help` atd. - -## Řešení potíží - -* Chyby ověření: spusťte `yarn twenty auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění. -* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný. -* Typy nebo klient chybí nebo jsou zastaralé: restartujte `yarn twenty dev` — automaticky generuje typovaného klienta. -* Režim vývoje se nesynchronizuje: ujistěte se, že běží `yarn twenty dev` a že vaše prostředí změny neignoruje. - -Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/cs/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/de/developers/extend/apps/building.mdx b/packages/twenty-docs/l/de/developers/extend/apps/building.mdx index 3e75f03a6c..b44951b8ef 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: Definieren Sie Objekte, Logikfunktionen, Frontend-Komponenten und m --- -Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. + Apps are currently in alpha. The feature works but is still evolving. -## SDK-Ressourcen verwenden (Typen & Konfiguration) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -Das twenty-sdk stellt typisierte Bausteine und Hilfsfunktionen bereit, die Sie in Ihrer App verwenden. Im Folgenden finden Sie die wichtigsten Bausteine, mit denen Sie am häufigsten arbeiten. +## DefineEntity functions -### Hilfsfunktionen +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit. -Das SDK stellt Hilfsfunktionen bereit, um die Entitäten Ihrer App zu definieren. Wie in [Entitätserkennung](/l/de/developers/extend/apps/getting-started#entity-detection) beschrieben, müssen Sie `export default define({...})` verwenden, damit Ihre Entitäten erkannt werden: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| Funktion | Zweck | -| -------------------------------- | -------------------------------------------------------------------------------------------------------- | -| `defineApplication` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) | -| `defineObject` | Benutzerdefinierte Objekte mit Feldern definieren | -| `defineField` | Erweitern Sie bestehende Objekte um zusätzliche Felder oder definieren Sie eigenständige Relationsfelder | -| `defineLogicFunction` | Logikfunktionen mit Handlern definieren | -| `definePreInstallLogicFunction` | Eine Pre-Installations-Logikfunktion definieren (eine pro App) | -| `definePostInstallLogicFunction` | Eine Post-Installations-Logikfunktion definieren (eine pro App) | -| `defineFrontComponent` | Frontend-Komponenten für benutzerdefinierte UI definieren | -| `defineRole` | Rollenberechtigungen und Objektzugriff konfigurieren | -| `defineView` | Gespeicherte Views für Objekte definieren | -| `defineNavigationMenuItem` | Seitenleisten-Navigationslinks definieren | -| `defineSkill` | Skills für KI-Agenten definieren | -| `defineAgent` | KI-Agenten definieren | -| `definePageLayout` | Benutzerdefinierte Seitenlayouts definieren | + + -Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit. +Roles encapsulate permissions on your workspace's objects and actions. -### Objekte definieren +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notizen: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### Marktplatz-Metadaten + +If you plan to [publish your app](/l/de/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| Feld | Beschreibung | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `author` | Name des Autors oder des Unternehmens | +| `category` | App-Kategorie für die Filterung im Marktplatz | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | Längere Markdown-Beschreibung für den Tab "Info". Wenn weggelassen, verwendet der Marketplace die `README.md` des Pakets von npm | +| `websiteUrl` | Link zu Ihrer Website | +| `termsUrl` | Link zu den Nutzungsbedingungen | +| `emailSupport` | Support-E-Mail-Adresse | +| `issueReportUrl` | Link zum Issue-Tracker | + +#### Rollen und Berechtigungen + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben. + +Notizen: +* Beginnen Sie mit der vorab erstellten Rolle und schränken Sie sie schrittweise gemäß dem Least-Privilege-Prinzip ein. +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Benutzerdefinierte Objekte beschreiben sowohl Schema als auch Verhalten für Datensätze in Ihrem Workspace. Verwenden Sie `defineObject()`, um Objekte mit eingebauter Validierung zu definieren: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ Sie können Standardfelder überschreiben, indem Sie in Ihrem `fields`-Array ein dies wird jedoch nicht empfohlen. -### Felder für bestehende Objekte definieren + + Verwenden Sie `defineField()`, um Objekten, die Ihnen nicht gehören — etwa Standardobjekten von Twenty (Person, Company usw.) — Felder hinzuzufügen oder Objekten aus anderen Apps. Im Gegensatz zu Inline-Feldern in `defineObject()` benötigen eigenständige Felder einen `objectUniversalIdentifier`, um anzugeben, welches Objekt sie erweitern: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` Hauptpunkte: - * Der `objectUniversalIdentifier` identifiziert das Zielobjekt. Für Standardobjekte verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, die aus `twenty-sdk` exportiert werden. * Wenn Sie Felder inline in `defineObject()` definieren, benötigen Sie `objectUniversalIdentifier` **nicht** — er wird vom übergeordneten Objekt geerbt. * `defineField()` ist die einzige Möglichkeit, Felder zu Objekten hinzuzufügen, die Sie nicht mit `defineObject()` erstellt haben. -### Beziehungen + + Relationen verbinden Objekte miteinander. In Twenty sind Relationen stets **bidirektional** — Sie definieren beide Seiten, und jede Seite referenziert die andere. @@ -177,8 +310,7 @@ Angenommen, eine `PostCard` kann an viele `PostCardRecipient`-Datensätze gesend **Schritt 1: Definieren Sie die ONE_TO_MANY-Seite auf PostCard** (die "eine" Seite): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **Schritt 2: Definieren Sie die MANY_TO_ONE-Seite auf PostCardRecipient** (die "viele" Seite — hält den Fremdschlüssel): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ Um eine Relation mit einem integrierten Twenty-Objekt (Person, Company usw.) zu erstellen, verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ Sie können Relationsfelder auch direkt innerhalb von `defineObject()` definieren. In diesem Fall lassen Sie `objectUniversalIdentifier` weg — er wird vom übergeordneten Objekt geerbt: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### Anwendungskonfiguration (application-config.ts) - -Jede App hat eine einzelne Datei `application-config.ts`, die Folgendes beschreibt: - -* **Was die App ist**: Bezeichner, Anzeigename und Beschreibung. -* **Wie ihre Funktionen ausgeführt werden**: welche Rolle sie für Berechtigungen verwenden. -* **(Optional) Variablen**: Schlüssel–Wert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden. -* **(Optional) Pre-Installationsfunktion**: eine Logikfunktion, die vor der Installation der App ausgeführt wird. -* **(Optional) Post-Installationsfunktion**: eine Logikfunktion, die nach der Installation der App ausgeführt wird. - -Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definieren: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notizen: - -* `universalIdentifier`-Felder sind deterministische IDs, die Sie besitzen; generieren Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil. -* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen (zum Beispiel ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar). -* `defaultRoleUniversalIdentifier` muss mit der Rollendatei übereinstimmen (siehe unten). -* Pre-Installations- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt. Siehe [Pre-Installationsfunktionen](#pre-install-functions) und [Post-Installationsfunktionen](#post-install-functions). - -#### Marktplatz-Metadaten - -Wenn Sie planen, [Ihre App zu veröffentlichen](/l/de/developers/extend/apps/publishing), steuern diese optionalen Felder, wie Ihre App im Marktplatz erscheint: - -| Feld | Beschreibung | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | -| `author` | Name des Autors oder des Unternehmens | -| `category` | App-Kategorie für die Filterung im Marktplatz | -| `logoUrl` | Pfad zu Ihrem App-Logo (relativ zu `./assets/`) | -| `screenshots` | Array von Screenshot-Pfaden (relativ zu `./assets/`) | -| `aboutDescription` | Längere Markdown-Beschreibung für den Tab "Info". Wenn weggelassen, verwendet der Marketplace die `README.md` des Pakets von npm | -| `websiteUrl` | Link zu Ihrer Website | -| `termsUrl` | Link zu den Nutzungsbedingungen | -| `emailSupport` | Support-E-Mail-Adresse | -| `issueReportUrl` | Link zum Issue-Tracker | - -#### Rollen und Berechtigungen - -Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und Aktionen Ihres Workspaces kapseln. Das Feld `defaultRoleUniversalIdentifier` in `application-config.ts` legt die Standardrolle fest, die von den Logikfunktionen Ihrer App verwendet wird. - -* Der zur Laufzeit als `TWENTY_API_KEY` injizierte API-Schlüssel wird von dieser Standard-Funktionsrolle abgeleitet. -* Der typisierte Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt. -* Befolgen Sie das Least-Privilege-Prinzip: Erstellen Sie eine dedizierte Rolle nur mit den Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann auf deren universellen Bezeichner. - -##### Standard-Funktionsrolle (*.role.ts) - -Wenn Sie eine neue App erzeugen, erstellt die CLI auch eine Standard-Rolldatei. Verwenden Sie `defineRole()`, um Rollen mit eingebauter Validierung zu definieren: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Der `universalIdentifier` dieser Rolle wird anschließend in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert. Anders ausgedrückt: - -* **\*.role.ts** definiert, was die Standard-Funktionsrolle darf. -* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben. - -Notizen: - -* Beginnen Sie mit der vorab erstellten Rolle und schränken Sie sie schrittweise gemäß dem Least-Privilege-Prinzip ein. -* Ersetzen Sie `objectPermissions` und `fieldPermissions` durch die Objekte/Felder, die Ihre Funktionen benötigen. -* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Halten Sie sie minimal; fügen Sie nur hinzu, was Sie benötigen. -* Ein funktionierendes Beispiel finden Sie in der Hello-World-App: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Konfiguration von Logikfunktionen und Einstiegspunkt + + Jede Funktionsdatei verwendet `defineLogicFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Häufige Trigger-Typen: - -* **route**: Stellt Ihre Funktion unter einem HTTP-Pfad und einer Methode **unter dem Endpunkt `/s/`** bereit: - -> z. B. `path: '/post-card/create',` -> Aufruf unter `/s/post-card/create` - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**: Führt Ihre Funktion nach Zeitplan mithilfe eines CRON-Ausdrucks aus. * **databaseEvent**: Wird bei Lebenszyklusereignissen von Workspace-Objekten ausgeführt. Wenn die Ereignisoperation `updated` ist, können bestimmte zu überwachende Felder im Array `updatedFields` angegeben werden. Wenn das Array undefiniert oder leer ist, löst jede Aktualisierung die Funktion aus. +> e.g. `person.updated`, `*.created`, `company.*` -> z. B. `person.updated` - -Notizen: - -* Das Array `triggers` ist optional. Funktionen ohne Trigger können als von anderen Funktionen aufgerufene Utility-Funktionen verwendet werden. -* Sie können mehrere Trigger-Typen in einer Funktion kombinieren. - -### Pre-Installationsfunktionen - -Eine Pre-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, bevor Ihre App in einem Arbeitsbereich installiert wird. Dies ist nützlich für Validierungsaufgaben, Überprüfungen von Voraussetzungen oder die Vorbereitung des Status des Arbeitsbereichs, bevor die Hauptinstallation fortgesetzt wird. - -Wenn Sie mit `create-twenty-app` eine neue App erstellen, wird für Sie eine Pre-Installationsfunktion unter `src/logic-functions/pre-install.ts` erzeugt: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Sie können die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Hauptpunkte: - -* Pre-Installationsfunktionen verwenden `definePreInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. -* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen). -* Pro Anwendung ist nur eine Pre-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. -* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `preInstallLogicFunctionUniversalIdentifier` gesetzt — Sie müssen ihn nicht in `defineApplication()` referenzieren. -* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Vorbereitungsvorgänge zu ermöglichen. -* Pre-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform vor der Installation oder manuell über `exec --preInstall` aufgerufen. - -### Post-Installationsfunktionen - -Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Dies ist nützlich für einmalige Einrichtungsvorgänge wie das Befüllen mit Standarddaten, das Erstellen erster Datensätze oder das Konfigurieren von Arbeitsbereichseinstellungen. - -Wenn Sie mit `create-twenty-app` eine neue App erstellen, wird für Sie eine Post-Installationsfunktion unter `src/logic-functions/post-install.ts` erzeugt: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Sie können die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Hauptpunkte: +You can watch logs with: -* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. -* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen). -* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. -* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `postInstallLogicFunctionUniversalIdentifier` gesetzt — Sie müssen ihn nicht in `defineApplication()` referenzieren. -* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen. -* Post-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform während der Installation oder manuell über `exec --postInstall` aufgerufen. - -### Routen-Trigger-Payload - - -**Breaking Change (v1.16, Januar 2026):** Das Format der Routen-Trigger-Payload hat sich geändert. Vor v1.16 wurden Query-Parameter, Pfadparameter und der Body direkt als Payload gesendet. Ab v1.16 sind sie innerhalb eines strukturierten `RoutePayload`-Objekts verschachtelt. - -**Vor v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**Nach v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Routen-Trigger-Payload -**So migrieren Sie bestehende Funktionen:** Aktualisieren Sie Ihren Handler, sodass er nicht mehr direkt aus dem params-Objekt destrukturiert, sondern aus `event.body`, `event.queryStringParameters` oder `event.pathParameters`. - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -Wenn ein Routen-Trigger Ihre Logikfunktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem AWS HTTP API v2-Format entspricht. Importieren Sie den Typ aus `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { Der Typ `RoutePayload` hat die folgende Struktur: -| Eigenschaft | Typ | Beschreibung | -| ---------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------- | -| `headers` | `Record` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | -| `queryStringParameters` | `Record` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | -| `pathParameters` | `Record` | Aus dem Routenmuster extrahierte Pfadparameter (z. B. `/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | Geparster Request-Body (JSON) | -| `isBase64Encoded` | `boolean` | Gibt an, ob der Body Base64-codiert ist | -| `requestContext.http.method` | `string` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Rohpfad der Anfrage | + | Eigenschaft | Typ | Beschreibung | Beispiel | + | ---------------------------- | ------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | see section below | + | `queryStringParameters` | `Record` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Geparster Request-Body (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Gibt an, ob der Body Base64-codiert ist | | + | `requestContext.http.method` | `string` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Rohpfad der Anfrage | | -### Weiterleiten von HTTP-Headern -Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. Um auf bestimmte Header zuzugreifen, listen Sie diese explizit im Array `forwardedRequestHeaders` auf: +#### forwardedRequestHeaders -```typescript +Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -In Ihrem Handler können Sie anschließend auf diese Header zugreifen: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - Header-Namen werden in Kleinbuchstaben normalisiert. Greifen Sie mit Schlüsseln in Kleinbuchstaben darauf zu (zum Beispiel `event.headers['content-type']`). +Header-Namen werden in Kleinbuchstaben normalisiert. Access them using lowercase keys (e.g., `event.headers['content-type']`). -Sie können neue Funktionen auf zwei Arten erstellen: +#### Exposing a function as a tool -* **Generiert**: Führen Sie `yarn twenty add` aus und wählen Sie die Option zum Hinzufügen einer neuen Logikfunktion. Dadurch wird eine Starterdatei mit Handler und Konfiguration erzeugt. -* **Manuell**: Erstellen Sie eine neue `*.logic-function.ts`-Datei und verwenden Sie `defineLogicFunction()` nach demselben Muster. +Logikfunktionen können als **Tools** für KI-Agenten und Workflows verfügbar gemacht werden. When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### Eine Logikfunktion als Tool markieren +To mark a logic function as a tool, set `isTool: true`: -Logikfunktionen können als **Tools** für KI-Agenten und Workflows verfügbar gemacht werden. Wenn eine Funktion als Tool markiert ist, wird sie von den KI-Funktionen von Twenty auffindbar und kann als Schritt in Workflow-Automatisierungen ausgewählt werden. - -Um eine Logikfunktion als Tool zu markieren, setzen Sie `isTool: true` und geben Sie ein `toolInputSchema` an, das die erwarteten Eingabeparameter mithilfe von [JSON Schema](https://json-schema.org/) beschreibt: - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Hauptpunkte: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Hauptpunkte: - -* **`isTool`** (`boolean`, Standard: `false`): Wenn auf `true` gesetzt, wird die Funktion als Tool registriert und steht KI-Agenten und Workflow-Automatisierungen zur Verfügung. -* **`toolInputSchema`** (`object`, optional): Ein JSON-Schema-Objekt, das die Parameter beschreibt, die Ihre Funktion akzeptiert. KI-Agenten verwenden dieses Schema, um zu verstehen, welche Eingaben das Tool erwartet, und um Aufrufe zu validieren. Falls weggelassen, lautet der Standardwert für das Schema `{ type: 'object', properties: {} }` (keine Parameter). -* Funktionen mit `isTool: false` (oder nicht gesetzt) werden **nicht** als Tools bereitgestellt. Sie können weiterhin direkt ausgeführt oder von anderen Funktionen aufgerufen werden, erscheinen jedoch nicht in der Tool-Erkennung. -* **Tool-Benennung**: Wenn als Tool bereitgestellt, wird der Funktionsname automatisch zu `logic_function_` normalisiert (in Kleinbuchstaben umgewandelt, nicht alphanumerische Zeichen durch Unterstriche ersetzt). Beispielsweise wird `enrich-company` zu `logic_function_enrich_company`. -* Sie können `isTool` mit Triggern kombinieren — eine Funktion kann gleichzeitig sowohl ein Tool (von KI-Agenten aufrufbar) als auch durch Ereignisse (Cron, Datenbankereignisse, Routen) ausgelöst werden. - **Schreiben Sie eine gute `description`.** KI-Agenten verlassen sich auf das `description`-Feld der Funktion, um zu entscheiden, wann das Tool verwendet werden soll. Seien Sie konkret darin, was das Tool tut und wann es aufgerufen werden soll. -### Frontend-Komponenten + + -Frontend-Komponenten ermöglichen es Ihnen, benutzerdefinierte React-Komponenten zu erstellen, die innerhalb der Twenty-UI gerendert werden. Verwenden Sie `defineFrontComponent()`, um Komponenten mit eingebauter Validierung zu definieren: +Eine Pre-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, bevor Ihre App in einem Arbeitsbereich installiert wird. Dies ist nützlich für Validierungsaufgaben, Überprüfungen von Voraussetzungen oder die Vorbereitung des Status des Arbeitsbereichs, bevor die Hauptinstallation fortgesetzt wird. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Sie können die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Hauptpunkte: +* Pre-Installationsfunktionen verwenden `definePreInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. +* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen). +* Pro Anwendung ist nur eine Pre-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. +* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `preInstallLogicFunctionUniversalIdentifier` gesetzt — Sie müssen ihn nicht in `defineApplication()` referenzieren. +* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Vorbereitungsvorgänge zu ermöglichen. + + + + +Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Dies ist nützlich für einmalige Einrichtungsvorgänge wie das Befüllen mit Standarddaten, das Erstellen erster Datensätze oder das Konfigurieren von Arbeitsbereichseinstellungen. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Sie können die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Hauptpunkte: +* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. +* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen). +* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. +* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `postInstallLogicFunctionUniversalIdentifier` gesetzt — Sie müssen ihn nicht in `defineApplication()` referenzieren. +* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen. + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Hauptpunkte: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* Frontend-Komponenten sind React-Komponenten, die in isolierten Kontexten innerhalb von Twenty gerendert werden. -* Das Feld `component` verweist auf Ihre React-Komponente. -* Komponenten werden während `yarn twenty dev` automatisch gebaut und synchronisiert. +
+ Quick action button in the top-right corner +
-Sie können neue Frontend-Komponenten auf zwei Arten erstellen: +Click it to render the component inline. -* **Generiert**: Führen Sie `yarn twenty add` aus und wählen Sie die Option zum Hinzufügen einer neuen Frontend-Komponente. -* **Manuell**: Erstellen Sie eine neue `.tsx`-Datei und verwenden Sie `defineFrontComponent()` nach demselben Muster. +{/* TODO: add screenshot of the rendered front component */} -### Fähigkeiten +#### Configuration fields + +| Feld | Erforderlich | Beschreibung | +| --------------------- | ------------ | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | Ja | Stable unique ID for this component | +| `component` | Ja | A React component function | +| `name` | Nein | Display name | +| `description` | Nein | Description of what the component does | +| `isHeadless` | Nein | Set to `true` if the component has no visible UI (see below) | +| `command` | Nein | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| Hook | Returns | Beschreibung | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | variiert | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| Funktion | Beschreibung | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | Seitenpanel schließen | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| Feld | Erforderlich | Beschreibung | +| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Ja | Stable unique ID for the command | +| `label` | Ja | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | Nein | Shorter label displayed on the pinned quick-action button | +| `icon` | Nein | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Nein | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | Nein | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | Nein | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | Nein | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| Variable | Typ | Beschreibung | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `number` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `object` | Permissions for the current object type | +| `targetObjectReadPermissions` | `object` | Read permissions for the target object | +| `targetObjectWritePermissions` | `object` | Write permissions for the target object | +| `featureFlags` | `object` | Active feature flags | +| `objectMetadataItem` | `object` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | Beschreibung | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### Styling + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + Skills definieren wiederverwendbare Anweisungen und Fähigkeiten, die KI-Agenten in Ihrem Arbeitsbereich verwenden können. Verwenden Sie `defineSkill()`, um Skills mit eingebauter Validierung zu definieren: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` Hauptpunkte: - * `name` ist eine eindeutige Kennung (als Zeichenfolge) für den Skill (kebab-case empfohlen). * `label` ist der menschenlesbare Anzeigename, der in der UI angezeigt wird. * `content` enthält die Skill-Anweisungen — dies ist der Text, den der KI-Agent verwendet. * `icon` (optional) legt das in der UI angezeigte Symbol fest. * `description` (optional) liefert zusätzlichen Kontext zum Zweck des Skills. -Sie können neue Skills auf zwei Arten erstellen: + + -* **Generiert**: Führen Sie `yarn twenty add` aus und wählen Sie die Option zum Hinzufügen eines neuen Skills. -* **Manuell**: Erstellen Sie eine neue Datei und verwenden Sie `defineSkill()` nach demselben Muster. +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### Typisierte API-Clients (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -Das Paket `twenty-client-sdk` stellt zwei typisierte GraphQL-Clients bereit, um aus Ihren Logikfunktionen und Frontend-Komponenten mit der Twenty-API zu interagieren: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Hauptpunkte: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon` (optional) legt das in der UI angezeigte Symbol fest. +* `modelId` (optional) overrides the default AI model used by the agent. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Hauptpunkte: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Hauptpunkte: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Hauptpunkte: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| Entitätstyp | Befehl | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| Objekt | `yarn twenty add object` | `src/objects/.ts` | +| Feld | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Rolle | `yarn twenty add role` | `src/roles/.ts` | +| Skill | `yarn twenty add skill` | `src/skills/.ts` | +| Agent | `yarn twenty add agent` | `src/agents/.ts` | +| Ansicht | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +Das Paket `twenty-client-sdk` stellt zwei typisierte GraphQL-Clients bereit, um aus Ihren Logikfunktionen und Frontend-Komponenten mit der Twenty-API zu interagieren. | Client | Importieren | Endpunkt | Generiert? | | ------------------- | ---------------------------- | --------------------------------------------------------- | ------------------------------------ | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — Arbeitsbereichsdaten (Datensätze, Objekte) | Ja, zur Entwicklungs-/Build-Zeit | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — Arbeitsbereichskonfiguration, Datei-Uploads | Nein, wird vorgefertigt ausgeliefert | -#### CoreApiClient + + -Der `CoreApiClient` ist der Haupt-Client zum Abfragen und Ändern von Arbeitsbereichsdaten. Er wird während `yarn twenty dev` oder `yarn twenty build` **aus Ihrem Arbeitsbereichsschema generiert** und ist daher vollständig typisiert, passend zu Ihren Objekten und Feldern. +Der `CoreApiClient` ist der Haupt-Client zum Abfragen und Ändern von Arbeitsbereichsdaten. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ Der Client verwendet eine Selection-Set-Syntax: Übergeben Sie `true`, um ein Feld einzuschließen, verwenden Sie `__args` für Argumente, und verschachteln Sie Objekte für Relationen. Sie erhalten vollständige Autovervollständigung und Typprüfung basierend auf Ihrem Arbeitsbereichsschema. -**Der CoreApiClient wird zur Entwicklungs-/Build-Zeit generiert.** Wenn Sie versuchen, ihn zu verwenden, ohne zuvor `yarn twenty dev` oder `yarn twenty build` ausgeführt zu haben, wird ein Fehler ausgelöst. Die Generierung erfolgt automatisch — die CLI inspiziert das GraphQL-Schema Ihres Arbeitsbereichs, erzeugt mit `@genql/cli` einen typisierten Client, schreibt die generierten Quellen nach `node_modules/twenty-client-sdk/dist/core/generated/` und ersetzt die Platzhalter in `node_modules/twenty-client-sdk/dist/core.mjs` und `node_modules/twenty-client-sdk/dist/core.cjs`. +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### Verwendung von CoreSchema für Typannotationen -`CoreSchema` stellt TypeScript-Typen bereit, die Ihren Arbeitsbereichsobjekten entsprechen; nützlich zum Typisieren von Komponentenzustand oder Funktionsparametern: +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient` ist im SDK bereits vorgefertigt enthalten (keine Generierung erforderlich). Er fragt den Endpunkt `/metadata` nach Arbeitsbereichskonfiguration, Anwendungen und Datei-Uploads ab: +`MetadataApiClient` ist im SDK bereits vorgefertigt enthalten (keine Generierung erforderlich). Er fragt den Endpunkt `/metadata` nach Arbeitsbereichskonfiguration, Anwendungen und Datei-Uploads ab. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### Laufzeit-Anmeldedaten - -Wenn Ihr Code auf Twenty ausgeführt wird (Logikfunktionen oder Frontend-Komponenten), injiziert die Plattform Anmeldedaten als Umgebungsvariablen: - -* `TWENTY_API_URL` — Basis-URL der Twenty-API -* `TWENTY_API_KEY` — Kurzlebiger Schlüssel, der auf die Standard-Funktionsrolle Ihrer Anwendung begrenzt ist - -Sie müssen diese **nicht** an die Clients übergeben — sie lesen automatisch aus `process.env`. Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in `defaultRoleUniversalIdentifier` in Ihrer `application-config.ts` verwiesen wird. - #### Dateien hochladen -Der `MetadataApiClient` enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei anzuhängen. Sie implementiert die [GraphQL-Multipart-Request-Spezifikation](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| Parameter | Typ | Beschreibung | -| ---------------------------------- | -------- | ------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | -| `filename` | `string` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | -| `contentType` | `string` | MIME-Typ der Datei (standardmäßig `application/octet-stream`, wenn weggelassen) | -| `fieldMetadataUniversalIdentifier` | `string` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | +| Parameter | Typ | Beschreibung | +| ---------------------------------- | -------- | --------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | +| `filename` | `string` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | Hauptpunkte: - * Sie verwendet den `universalIdentifier` des Feldes (nicht dessen arbeitsbereichsspezifische ID), sodass Ihr Upload-Code in jedem Arbeitsbereich funktioniert, in dem Ihre App installiert ist. * Die zurückgegebene `url` ist eine signierte URL, mit der Sie auf die hochgeladene Datei zugreifen können. -### Hello-World-Beispiel + + -Ein minimales End-to-End-Beispiel, das Objekte, Logikfunktionen, Frontend-Komponenten und mehrere Trigger demonstriert, finden Sie [hier](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + Wenn Ihr Code auf Twenty ausgeführt wird (Logikfunktionen oder Frontend-Komponenten), injiziert die Plattform Anmeldedaten als Umgebungsvariablen: + + * `TWENTY_API_URL` — Basis-URL der Twenty-API + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + Sie müssen diese **nicht** an die Clients übergeben — sie lesen automatisch aus `process.env`. Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in `defaultRoleUniversalIdentifier` in Ihrer `application-config.ts` verwiesen wird. + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### Einrichtung + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| Funktion | Beschreibung | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## CLI-Referenz + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx index 7c8c662cfc..de81d63f40 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App. --- -Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. +Apps are currently in alpha. The feature works but is still evolving. Apps ermöglichen es Ihnen, Twenty mit benutzerdefinierten Objekten, Feldern, Logikfunktionen, KI-Fähigkeiten und UI-Komponenten zu erweitern — alles als Code verwaltet. -**Was Sie erstellen können:** - -* Benutzerdefinierte Objekte, Felder, Ansichten und Navigationselemente, um Ihr Datenmodell zu gestalten -* Logikfunktionen, die durch HTTP-Routen, Cron-Zeitpläne oder Datenbankereignisse ausgelöst werden -* Frontend-Komponenten, die direkt innerhalb der Twenty-UI gerendert werden -* Skills, die die KI-Agenten von Twenty erweitern -* Eine App in mehreren Workspaces bereitstellen - ## Voraussetzungen -* Node.js 24+ -* Yarn 4 -* Docker (oder eine lokal laufende Twenty-Instanz) +Before you begin, make sure the following is installed on your machine: -## Erste Schritte +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Erstellen Sie mit dem offiziellen Scaffolder eine neue App, authentifizieren Sie sich und beginnen Sie mit der Entwicklung: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> Verwenden Sie die Option `--minimal`, um eine minimale Installation zu erstellen +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -Von hier aus können Sie: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## Projektstruktur (vom Scaffolder erzeugt) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der Scaffolder Folgendes: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* Kopiert eine minimale Basisanwendung nach `my-twenty-app/` -* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu -* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind -* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Pre-Installations- und Post-Installationsfunktionen) sowie Beispieldateien entsprechend dem Scaffolding-Modus + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/de/developers/extend/apps/publishing) for details. + -Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/de/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -Auf hoher Ebene: +### Key files -* **package.json**: Deklariert den App-Namen, die Version und die Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie ein `twenty`-Skript hinzu, das an die lokale `twenty`-CLI delegiert. Führen Sie `yarn twenty help` aus, um alle verfügbaren Befehle aufzulisten. -* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain. -* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest. -* **.oxlintrc.json** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit. -* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen. -* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich. -* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren +| File / Folder | Zweck | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Skills, die die KI-Agenten von Twenty erweitern. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Entitätserkennung +## Managing remotes -Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird: - -| Hilfsfunktion | Entitätstyp | -| -------------------------------- | ------------------------------------------------------------------------ | -| `defineObject` | Benutzerdefinierte Objektdefinitionen | -| `defineLogicFunction` | Definitionen von Logikfunktionen | -| `definePreInstallLogicFunction` | Pre-Installations-Logikfunktion (wird vor der Installation ausgeführt) | -| `definePostInstallLogicFunction` | Post-Installations-Logikfunktion (wird nach der Installation ausgeführt) | -| `defineFrontComponent` | Definitionen von Frontend-Komponenten | -| `defineRole` | Rollendefinitionen | -| `defineField` | Felderweiterungen für bestehende Objekte | -| `defineView` | Gespeicherte View-Definitionen | -| `defineNavigationMenuItem` | Definitionen von Navigationsmenüeinträgen | -| `defineSkill` | Skill-Definitionen für KI-Agenten | - - -**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung. - - -Beispiel für eine erkannte Entität: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Spätere Befehle fügen weitere Dateien und Ordner hinzu: - -* `yarn twenty dev` generiert den typisierten `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) automatisch in `node_modules/twenty-client-sdk/`. Der `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`) wird vorkompiliert ausgeliefert und ist sofort verfügbar. Importieren Sie sie jeweils aus `twenty-client-sdk/core` und `twenty-client-sdk/metadata`. -* `yarn twenty add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu. - -## Authentifizierung - -Wenn Sie `yarn twenty auth:login` zum ersten Mal ausführen, werden Sie nach Folgendem gefragt: - -* API-URL (standardmäßig http://localhost:3000 oder Ihr aktuelles Workspace-Profil) -* API-Schlüssel - -Ihre Anmeldedaten werden pro Benutzer in `~/.twenty/config.json` gespeichert. Sie können mehrere Profile verwalten und zwischen ihnen wechseln. - -### Arbeitsbereiche verwalten +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -Sobald Sie mit `yarn twenty auth:switch` den Arbeitsbereich gewechselt haben, verwenden alle nachfolgenden Befehle standardmäßig diesen Arbeitsbereich. Sie können es weiterhin vorübergehend mit `--workspace ` überschreiben. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Server starten + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Befehl | Beschreibung | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Manuelle Einrichtung (ohne Scaffolder) -Wir empfehlen zwar `create-twenty-app` für das beste Einstiegserlebnis, Sie können ein Projekt aber auch manuell einrichten. Installieren Sie die CLI nicht global. Fügen Sie stattdessen `twenty-sdk` als lokale Abhängigkeit hinzu und binden Sie ein einzelnes Skript in Ihrer package.json ein: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Fügen Sie dann ein `twenty`-Skript hinzu: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ Fügen Sie dann ein `twenty`-Skript hinzu: } ``` -Jetzt können Sie alle Befehle über `yarn twenty ` ausführen, z. B. `yarn twenty dev`, `yarn twenty help` usw. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## So verwenden Sie eine lokale Twenty-Instanz - -Wenn Sie bereits lokal eine Twenty-Instanz ausführen (z. B. über `npx nx start twenty-server`), können Sie sich damit verbinden, anstatt Docker zu verwenden: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Fehlerbehebung -* Authentifizierungsfehler: Führen Sie `yarn twenty auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat. -* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist. -* Typen oder Client fehlen oder sind veraltet: Starten Sie `yarn twenty dev` neu — der typisierte Client wird automatisch generiert. -* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn twenty dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden. +If you run into issues: -Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx index a1d586ffa1..6ac1fa5805 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: Veröffentlichen Sie Ihre Twenty-App auf dem Twenty-Marktplatz oder --- -Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. + Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. ## Übersicht Sobald Ihre App [lokal gebaut und getestet](/l/de/developers/extend/apps/building) wurde, haben Sie zwei Möglichkeiten, sie zu verteilen: -* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann. * **Einen Tarball bereitstellen** — Laden Sie Ihre App direkt auf einen bestimmten Twenty-Server für die interne oder private Nutzung hoch. +* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann. Beide Pfade beginnen mit demselben **Build**-Schritt. ## Erstellen Ihrer App -Der Befehl `build` kompiliert Ihre TypeScript-Quelltexte, transpiliert Logikfunktionen und Frontend-Komponenten und erzeugt eine `manifest.json`, die die Inhalte Ihrer App beschreibt: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -Die Ausgabe wird in `.twenty/output/` geschrieben. Dieses Verzeichnis enthält alles, was für die Verteilung benötigt wird: kompilierter Code, Assets, das Manifest und eine Kopie Ihrer `package.json`. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -Um zusätzlich ein `.tgz`-Tarball zu erstellen (wird intern vom Deploy-Befehl verwendet oder für die manuelle Verteilung): +## Bereitstellung auf einem Server (Tarball) + +Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, ausschließlich für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einem Twenty-Server bereitstellen. + +### Voraussetzungen + +Bevor Sie bereitstellen, benötigen Sie ein konfiguriertes Remote, das auf den Zielserver zeigt. Remotes speichern die Server-URL und Anmeldeinformationen lokal in `~/.twenty/config.json`. + +Ein Remote hinzufügen: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### Bereitstellen + +Bauen und laden Sie Ihre App in einem Schritt auf den Server hoch: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Eine bereitgestellte App freigeben + +Tarball-Apps werden nicht im öffentlichen Marktplatz gelistet, daher entdecken andere Arbeitsbereiche auf demselben Server sie nicht durch Stöbern. So geben Sie eine bereitgestellte App frei: + +1. Gehen Sie zu **Einstellungen > Anwendungen > Registrierungen** und öffnen Sie Ihre App +2. Klicken Sie im Tab **Distribution** auf **Freigabelink kopieren** +3. Teilen Sie diesen Link mit Nutzern in anderen Arbeitsbereichen — er führt sie direkt zur Installationsseite der App + +Der Freigabelink verwendet die Basis-URL des Servers (ohne Workspace-Subdomain), sodass er für jeden Arbeitsbereich auf dem Server funktioniert. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Versionsverwaltung + +So veröffentlichen Sie ein Update: + +1. Erhöhen Sie das Feld `version` in Ihrer `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Arbeitsbereiche, die die App installiert haben, sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist. + +{/* TODO: add screenshot of the Upgrade button */} + ## Auf npm veröffentlichen Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Jeder Twenty-Arbeitsbereich kann Marktplatz-Apps direkt über die Benutzeroberfläche durchsuchen, installieren und aktualisieren. @@ -39,41 +81,42 @@ Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Je ### Anforderungen * Ein [npm](https://www.npmjs.com)-Konto -* Das Schlüsselwort `twenty-app` **muss** in Ihrem `package.json`-`keywords`-Array aufgeführt sein - -### Das erforderliche Schlüsselwort hinzufügen - -Der Twenty-Marktplatz entdeckt Apps, indem er die npm-Registry nach Paketen mit dem Schlüsselwort `twenty-app` durchsucht. Fügen Sie es zu Ihrer `package.json` hinzu: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -Der Marktplatz sucht in der npm-Registry nach `keywords:twenty-app`. Ohne dieses Schlüsselwort erscheint Ihr Paket nicht im Marktplatz, selbst wenn es das Namenspräfix `twenty-app-` hat. - +### Marktplatz-Metadaten -### Schritte +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **Erstellen Ihrer App:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **Auf npm veröffentlichen:** +See the [defineApplication accordion](/l/de/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -Dies führt `npm publish` aus dem Verzeichnis `.twenty/output/` aus. - Um unter einem bestimmten dist-tag zu veröffentlichen (z. B. `beta` oder `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### So funktioniert die Marktplatz-Erkennung -Der Twenty-Server synchronisiert seinen Marktplatzkatalog **stündlich** mit der npm-Registry: +The Twenty server syncs its marketplace catalog from the npm registry **every hour**. -1. Er sucht nach allen npm-Paketen mit dem Schlüsselwort `keywords:twenty-app` -2. Für jedes Paket ruft er die `manifest.json` vom npm-CDN ab -3. Die Metadaten der App (Name, Beschreibung, Autor, Logo, Screenshots, Kategorie) werden aus dem Manifest extrahiert und im Marktplatz angezeigt - -Nach der Veröffentlichung kann es bis zu einer Stunde dauern, bis Ihre App im Marktplatz erscheint. Um die Synchronisierung sofort auszulösen, statt auf den nächsten stündlichen Lauf zu warten: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -Um ein bestimmtes Remote anzusteuern: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -Die im Marktplatz angezeigten Metadaten stammen aus Ihrem `defineApplication()`-Aufruf im Quellcode Ihrer App — Felder wie `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` und `termsUrl`. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. Wenn deine App keine `aboutDescription` in `defineApplication()` definiert, verwendet der Marktplatz automatisch die `README.md` deines Pakets von npm als Inhalt der Über-uns-Seite. Das bedeutet, dass du eine einzige README sowohl für npm als auch für den Twenty-Marktplatz pflegen kannst. Wenn du im Marktplatz eine andere Beschreibung möchtest, setze `aboutDescription` explizit. @@ -108,7 +143,7 @@ Wenn deine App keine `aboutDescription` in `defineApplication()` definiert, verw ### CI-Veröffentlichung -Das vorgefertigte Projekt enthält einen GitHub-Actions-Workflow, der bei jedem Release eine Veröffentlichung durchführt: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `yarn twenty build` und anschließend `npm publish` aus `.twenty/output`. - + **npm-Provenance** ist optional, wird jedoch empfohlen. Das Veröffentlichen mit `--provenance` fügt Ihrem npm-Eintrag ein Vertrauensabzeichen hinzu, sodass Nutzer überprüfen können, dass das Paket aus einem bestimmten Commit in einer öffentlichen CI-Pipeline gebaut wurde. Siehe die [npm-Provenance-Dokumentation](https://docs.npmjs.com/generating-provenance-statements) für Einrichtungshinweise. - - -## Bereitstellung auf einem Server (Tarball) - -Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, ausschließlich für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einem Twenty-Server bereitstellen. - -### Voraussetzungen - -Bevor Sie bereitstellen, benötigen Sie ein konfiguriertes Remote, das auf den Zielserver zeigt. Remotes speichern die Server-URL und Anmeldeinformationen lokal in `~/.twenty/config.json`. - -Ein Remote hinzufügen: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -Für einen lokalen Entwicklungsserver: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -Sie können sich in nicht interaktiven Umgebungen auch mit einem API-Schlüssel authentifizieren: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -Ihre Remotes verwalten: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### Bereitstellen - -Bauen und laden Sie Ihre App in einem Schritt auf den Server hoch: - -```bash filename="Terminal" -yarn twenty deploy -``` - -Dies baut die App mit `--tarball` und lädt anschließend den Tarball per GraphQL-Multipart-Upload auf das Standard-Remote hoch. - -Um auf ein bestimmtes Remote bereitzustellen: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### Eine bereitgestellte App freigeben - -Tarball-Apps werden nicht im öffentlichen Marktplatz gelistet, daher entdecken andere Arbeitsbereiche auf demselben Server sie nicht durch Stöbern. So geben Sie eine bereitgestellte App frei: - -1. Gehen Sie zu **Einstellungen > Anwendungen > Registrierungen** und öffnen Sie Ihre App -2. Klicken Sie im Tab **Distribution** auf **Freigabelink kopieren** -3. Teilen Sie diesen Link mit Nutzern in anderen Arbeitsbereichen — er führt sie direkt zur Installationsseite der App - -Der Freigabelink verwendet die Basis-URL des Servers (ohne Workspace-Subdomain), sodass er für jeden Arbeitsbereich auf dem Server funktioniert. - -### Versionsverwaltung - -So veröffentlichen Sie ein Update: - -1. Erhöhen Sie das Feld `version` in Ihrer `package.json` -2. Führen Sie `yarn twenty deploy` aus (oder `yarn twenty deploy -r production`) -3. Arbeitsbereiche, die die App installiert haben, sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist. + ## Apps installieren -Sobald eine App veröffentlicht (npm) oder bereitgestellt (Tarball) wurde, installieren Arbeitsbereiche sie über die Benutzeroberfläche: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -Oder über die Seite **Einstellungen > Anwendungen** in der Twenty-Oberfläche, wo sowohl Marktplatz- als auch per Tarball bereitgestellte Apps durchsucht und installiert werden können. - -## Kategorien der App-Verteilung - -Twenty organisiert Apps in drei Kategorien, basierend auf ihrer Vertriebsart: - -| Kategorie | Wie es funktioniert | Im Marktplatz sichtbar? | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| **Entwicklung** | Lokale Apps im Entwicklungsmodus, die über `yarn twenty dev` ausgeführt werden. Zum Erstellen und Testen verwendet. | Nein | -| **Veröffentlicht (npm)** | Auf npm veröffentlichte Apps mit dem Schlüsselwort `twenty-app`. Im Marktplatz gelistet, damit jeder Arbeitsbereich sie installieren kann. | Ja | -| **Intern (Tarball)** | Apps, die per Tarball auf einen bestimmten Server bereitgestellt werden. Nur für Arbeitsbereiche auf diesem Server per Freigabelink verfügbar. | Nein | - - -Beginnen Sie im **Entwicklungsmodus**, während Sie Ihre App erstellen. Wenn sie bereit ist, wählen Sie **Veröffentlicht** (npm) für die breite Verteilung oder **Intern** (Tarball) für die private Bereitstellung. - - -## CLI-Referenz - -| Befehl | Beschreibung | Wichtige Flags | -| --------------------------- | --------------------------------------------------------------- | --------------------------------------------------- | -| `yarn twenty build` | App kompilieren und Manifest erzeugen | `--tarball` — zusätzlich ein `.tgz`-Paket erstellen | -| `yarn twenty publish` | Bauen und auf npm veröffentlichen | `--tag ` — npm-dist-tag (z. B. `beta`, `next`) | -| `yarn twenty deploy` | Tarball bauen und auf einen Server hochladen | `-r, --remote ` — Ziel-Remote | -| `yarn twenty catalog-sync` | Synchronisierung des Marktplatzkatalogs auf dem Server auslösen | `-r, --remote ` — Ziel-Remote | -| `yarn twenty install` | Eine bereitgestellte App in einem Arbeitsbereich installieren | `-r, --remote ` — Ziel-Remote | -| `yarn twenty dev` | Lokale Änderungen beobachten und synchronisieren | Verwendet das Standard-Remote | -| `yarn twenty remote add` | Eine Serververbindung hinzufügen | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | Konfigurierte Remotes auflisten | — | -| `yarn twenty remote switch` | Standard-Remote festlegen | — | -| `yarn twenty remote status` | Verbindungsstatus anzeigen | — | -| `yarn twenty remote remove` | Ein Remote entfernen | — | diff --git a/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx index a1c0018818..405baa2eaf 100644 --- a/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: Twenty-Anpassungen als Code erstellen und verwalten. --- -Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. +Apps are currently in alpha. The feature works but is still evolving. -## Was sind Apps? +## What are apps? -Mit Apps können Sie Twenty-Anpassungen **als Code** erstellen und verwalten. Anstatt alles über die UI zu konfigurieren, definieren Sie Ihr Datenmodell und Logikfunktionen im Code — das beschleunigt Entwicklung, Wartung und den Rollout auf mehrere Workspaces. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**Was Sie heute tun können:** +**Was Sie erstellen können:** -* Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell) -* Logikfunktionen mit benutzerdefinierten Triggern erstellen -* Skills und Agenten für KI definieren -* Dieselbe App in mehreren Workspaces bereitstellen +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Voraussetzungen - -* Node.js 24+ und Yarn 4 -* Docker (für den lokalen Twenty-Dev-Server) - -## Erste Schritte - -Erstelle eine neue App mit dem offiziellen Scaffolder. Der Scaffolder kann für dich automatisch eine lokale Twenty-Instanz starten: +## Quick start ```bash filename="Terminal" -# Eine neue App erstellen — die CLI bietet an, einen lokalen Twenty-Server zu starten npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Dev-Modus starten: synchronisiert lokale Änderungen automatisch mit deinem Arbeitsbereich yarn twenty dev ``` -### Lokale Serververwaltung +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/de/developers/extend/apps/getting-started) guide for the full walkthrough. -Das SDK enthält Befehle zur Verwaltung eines lokalen Twenty-Dev-Servers (All-in-One-Docker-Image mit PostgreSQL, Redis, Server und Worker auf Port 2020). Diese Befehle gelten nur für den Docker-basierten Dev-Server — sie verwalten keine aus dem Quellcode gestartete Twenty-Instanz (z. B. `npx nx start twenty-server` auf Port 3000): +## Detailed guides -```bash filename="Terminal" -# Den lokalen Server starten (lädt das Image bei Bedarf herunter) -yarn twenty server start +| Guide | Beschreibung | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/de/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/de/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/de/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Serverstatus prüfen -yarn twenty server status - -# Serverprotokolle streamen -yarn twenty server logs - -# Server stoppen -yarn twenty server stop - -# Alle Daten zurücksetzen und neu starten -yarn twenty server reset -``` - -Der lokale Server ist bereits mit einem Arbeitsbereich und einem Benutzer (`tim@apple.dev` / `tim@apple.dev`) vorbefüllt, sodass Sie ohne manuelle Einrichtung sofort mit der Entwicklung beginnen können. - -### Authentifizierung - -Verbinden Sie Ihre App mithilfe von OAuth mit dem lokalen Server: - -```bash filename="Terminal" -# Authenticate via OAuth (opens browser) -yarn twenty remote add --local -``` - -Das Scaffolding-Tool unterstützt zwei Modi, um zu steuern, welche Beispieldateien enthalten sind: - -```bash filename="Terminal" -# Standard (umfassend): alle Beispiele (Objekt, Feld, Logikfunktion, Frontend-Komponente, View, Navigationsmenüeintrag, Skill, Agent) -npx create-twenty-app@latest my-app - -# Minimal: nur Kerndateien (application-config.ts und default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### So verwenden Sie eine lokale Twenty-Instanz - -Wenn Sie bereits eine lokale Twenty-Instanz ausführen, können Sie sich damit verbinden, anstatt Docker zu verwenden. Geben Sie den Port an, auf dem Ihr lokaler Server lauscht (Standard: `3000`): - -```bash filename="Terminal" -# Während des Scaffoldings -npx create-twenty-app@latest my-app --port 3000 - -# Oder nach dem Scaffolding -yarn twenty remote add --local --port 3000 -``` - -Von hier aus können Sie: - -```bash filename="Terminal" -# Eine neue Entität zu Ihrer App hinzufügen (geführt) -yarn twenty entity:add - -# Die Funktionsprotokolle Ihrer App überwachen -yarn twenty function:logs - -# Eine Funktion anhand ihres Namens ausführen -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Die Pre-Installationsfunktion ausführen -yarn twenty function:execute --preInstall - -# Die Post-Installationsfunktion ausführen -yarn twenty function:execute --postInstall - -# Die App für die Verteilung erstellen -yarn twenty build - -# Die App auf npm oder einen Twenty-Server veröffentlichen -yarn twenty publish - -# Die App aus dem aktuellen Arbeitsbereich deinstallieren -yarn twenty uninstall - -# Hilfe zu Befehlen anzeigen -yarn twenty help -``` - -Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## Projektstruktur (vom Scaffolder erzeugt) - -Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der Scaffolder Folgendes: - -* Kopiert eine minimale Basisanwendung nach `my-twenty-app/` -* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu -* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind -* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Pre-Installations- und Post-Installationsfunktionen) sowie Beispieldateien entsprechend dem Scaffolding-Modus - -Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Ordner für öffentliche Assets (Bilder, Schriftarten usw.) - src/ - ├── application-config.ts # Erforderlich - Hauptkonfiguration der Anwendung - ├── roles/ - │ └── default-role.ts # Standardrolle für Logikfunktionen - ├── objects/ - │ └── example-object.ts # Beispiel für eine benutzerdefinierte Objektdefinition - ├── fields/ - │ └── example-field.ts # Beispiel für eine eigenständige Felddefinition - ├── logic-functions/ - │ ├── hello-world.ts # Beispiel für eine Logikfunktion - │ ├── pre-install.ts # Pre-Installations-Logikfunktion - │ └── post-install.ts # Post-Installations-Logikfunktion - ├── front-components/ - │ └── hello-world.tsx # Beispiel für eine Frontend-Komponente - ├── views/ - │ └── example-view.ts # Beispiel für eine gespeicherte View-Definition - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Beispiel für einen Navigationslink in der Seitenleiste - ├── skills/ - │ └── example-skill.ts # Beispiel für eine Skill-Definition eines KI-Agenten - └── agents/ - └── example-agent.ts # Beispiel für eine KI-Agenten-Definition -``` - -Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`). - -Auf hoher Ebene: - -* **package.json**: Deklariert den App-Namen, die Version und die Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie ein `twenty`-Skript hinzu, das an die lokale `twenty`-CLI delegiert. Führe `yarn twenty help` aus, um alle verfügbaren Befehle aufzulisten. -* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain. -* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest. -* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources. -* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen. -* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich. -* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren +## Key concepts ### Entitätserkennung -Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Hilfsfunktion | Entitätstyp | -| ---------------------------------- | ------------------------------------------------------------------------ | -| `defineObject()` | Benutzerdefinierte Objektdefinitionen | -| `defineLogicFunction()` | Definitionen von Logikfunktionen | -| `definePreInstallLogicFunction()` | Pre-Installations-Logikfunktion (wird vor der Installation ausgeführt) | -| `definePostInstallLogicFunction()` | Post-Installations-Logikfunktion (wird nach der Installation ausgeführt) | -| `defineFrontComponent()` | Definitionen von Frontend-Komponenten | -| `defineRole()` | Rollendefinitionen | -| `defineField()` | Felderweiterungen für bestehende Objekte | -| `defineView()` | Gespeicherte View-Definitionen | -| `defineNavigationMenuItem()` | Definitionen von Navigationsmenüeinträgen | -| `defineSkill()` | Skill-Definitionen für KI-Agenten | -| `defineAgent()` | KI-Agenten-Definitionen | +### Available entity types - -**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung. - +| Funktion | Zweck | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Beispiel für eine erkannte Entität: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Spätere Befehle fügen weitere Dateien und Ordner hinzu: - -* `yarn twenty dev` generiert automatisch zwei typisierte API-Clients in `node_modules/twenty-sdk/clients`: `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) und `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`). -* `yarn twenty entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu. - -## Authentifizierung - -Wenn Sie `yarn twenty auth:login` zum ersten Mal ausführen, werden Sie nach Folgendem gefragt: - -* API-URL (standardmäßig http://localhost:3000 oder Ihr aktuelles Workspace-Profil) -* API-Schlüssel - -Ihre Anmeldedaten werden pro Benutzer in `~/.twenty/config.json` gespeichert. Sie können mehrere Profile verwalten und zwischen ihnen wechseln. - -### Arbeitsbereiche verwalten +### CLI-Referenz ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login - -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace - -# List all configured workspaces -yarn twenty auth:list - -# Switch the default workspace (interactive) -yarn twenty auth:switch - -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -Sobald Sie mit `yarn twenty auth:switch` den Arbeitsbereich gewechselt haben, verwenden alle nachfolgenden Befehle standardmäßig diesen Arbeitsbereich. Sie können es weiterhin vorübergehend mit `--workspace ` überschreiben. - -## SDK-Ressourcen verwenden (Typen & Konfiguration) - -Das twenty-sdk stellt typisierte Bausteine und Hilfsfunktionen bereit, die Sie in Ihrer App verwenden. Im Folgenden finden Sie die wichtigsten Bausteine, mit denen Sie am häufigsten arbeiten. - -### Hilfsfunktionen - -Das SDK stellt Hilfsfunktionen bereit, um die Entitäten Ihrer App zu definieren. Wie in [Entitätserkennung](#entity-detection) beschrieben, müssen Sie `export default define({...})` verwenden, damit Ihre Entitäten erkannt werden: - -| Funktion | Zweck | -| ---------------------------------- | --------------------------------------------------------------- | -| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) | -| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren | -| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren | -| `definePreInstallLogicFunction()` | Eine Pre-Installations-Logikfunktion definieren (eine pro App) | -| `definePostInstallLogicFunction()` | Eine Post-Installations-Logikfunktion definieren (eine pro App) | -| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren | -| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren | -| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern | -| `defineView()` | Gespeicherte Views für Objekte definieren | -| `defineNavigationMenuItem()` | Seitenleisten-Navigationslinks definieren | -| `defineSkill()` | Definieren Sie Skills für KI-Agenten | -| `defineAgent()` | Definieren Sie KI-Agenten mit System-Prompts | - -Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit. - -### Objekte definieren - -Benutzerdefinierte Objekte beschreiben sowohl Schema als auch Verhalten für Datensätze in Ihrem Workspace. Verwenden Sie `defineObject()`, um Objekte mit eingebauter Validierung zu definieren: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Hauptpunkte: - -* Verwenden Sie `defineObject()` für eingebaute Validierung und bessere IDE-Unterstützung. -* Der `universalIdentifier` muss eindeutig und über Deployments hinweg stabil sein. -* Jedes Feld benötigt `name`, `type`, `label` und einen eigenen stabilen `universalIdentifier`. -* Das Array `fields` ist optional — Sie können Objekte ohne benutzerdefinierte Felder definieren. -* Sie können mit `yarn twenty entity:add` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen. - - -**Basisfelder werden automatisch erstellt.** Wenn Sie ein benutzerdefiniertes Objekt definieren, fügt Twenty automatisch Standardfelder hinzu -wie `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` und `deletedAt`. -Sie müssen diese nicht in Ihrem `fields`-Array definieren — fügen Sie nur Ihre benutzerdefinierten Felder hinzu. -Sie können Standardfelder überschreiben, indem Sie in Ihrem `fields`-Array ein Feld mit demselben Namen definieren, -dies wird jedoch nicht empfohlen. - - -### Felder für bestehende Objekte definieren - -Verwenden Sie `defineField()`, um benutzerdefinierte Felder zu bestehenden Objekten hinzuzufügen — sowohl zu Standardobjekten (wie `company`, `person`, `opportunity`) als auch zu benutzerdefinierten Objekten, die von anderen Apps definiert werden. Jedes Feld befindet sich in einer eigenen Datei und verweist auf das Zielobjekt über dessen `universalIdentifier`. - -Um auf Standardobjekte zu verweisen, importieren Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` aus `twenty-sdk`. Diese Konstante stellt stabile Bezeichner für alle integrierten Objekte und deren Felder bereit: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Hauptpunkte: - -* `objectUniversalIdentifier` teilt Twenty mit, an welches Objekt das Feld angehängt werden soll. Verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` für Standardobjekte. -* Jedes Feld benötigt einen eigenen stabilen `universalIdentifier`, `name`, `type`, `label` und den Ziel-`objectUniversalIdentifier`. -* Sie können mit `yarn twenty entity:add` neue Felder anlegen, indem Sie die Feldoption wählen. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` wird der Einfachheit halber auch als `STANDARD_OBJECT` exportiert — beide verweisen auf dieselbe Konstante. - -Verfügbare Standardobjekte sind unter anderem: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` und `workspaceMember`. - -Jedes Standardobjekt stellt außerdem seine Feldbezeichner bereit. Beispielsweise, um in Rollenberechtigungen auf ein bestimmtes Feld eines Standardobjekts zu verweisen: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### Beziehungsfelder bei bestehenden Objekten - -Sie können auch Beziehungsfelder definieren, die bestehende Objekte mit Ihren benutzerdefinierten Objekten verknüpfen: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### Anwendungskonfiguration (application-config.ts) - -Jede App hat eine einzelne Datei `application-config.ts`, die Folgendes beschreibt: - -* **Was die App ist**: Bezeichner, Anzeigename und Beschreibung. -* **Wie ihre Funktionen ausgeführt werden**: welche Rolle sie für Berechtigungen verwenden. -* **(Optional) Variablen**: Schlüssel–Wert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden. -* **(Optional) Pre-Installationsfunktion**: eine Logikfunktion, die vor der Installation der App ausgeführt wird. -* **(Optional) Post-Installationsfunktion**: eine Logikfunktion, die nach der Installation der App ausgeführt wird. - -Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definieren: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notizen: - -* `universalIdentifier`-Felder sind deterministische IDs, die Sie besitzen; generieren Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil. -* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen (zum Beispiel ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar). -* `defaultRoleUniversalIdentifier` muss mit der Rollendatei übereinstimmen (siehe unten). -* Pre-Installations- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt. Siehe [Pre-Installationsfunktionen](#pre-install-functions) und [Post-Installationsfunktionen](#post-install-functions). - -#### Rollen und Berechtigungen - -Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und Aktionen Ihres Workspaces kapseln. Das Feld `defaultRoleUniversalIdentifier` in `application-config.ts` legt die Standardrolle fest, die von den Logikfunktionen Ihrer App verwendet wird. - -* Der zur Laufzeit als `TWENTY_API_KEY` injizierte API-Schlüssel wird von dieser Standard-Funktionsrolle abgeleitet. -* Der typisierte Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt. -* Befolgen Sie das Least-Privilege-Prinzip: Erstellen Sie eine dedizierte Rolle nur mit den Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann auf deren universellen Bezeichner. - -##### Standard-Funktionsrolle (\*.role.ts) - -Wenn Sie eine neue App erzeugen, erstellt die CLI auch eine Standard-Rolldatei. Verwenden Sie `defineRole()`, um Rollen mit eingebauter Validierung zu definieren: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Der `universalIdentifier` dieser Rolle wird anschließend in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert. Anders ausgedrückt: - -* **\*.role.ts** definiert, was die Standard-Funktionsrolle darf. -* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben. - -Notizen: - -* Beginnen Sie mit der vorab erstellten Rolle und schränken Sie sie schrittweise gemäß dem Least-Privilege-Prinzip ein. -* Ersetzen Sie `objectPermissions` und `fieldPermissions` durch die Objekte/Felder, die Ihre Funktionen benötigen. -* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Halten Sie sie minimal; fügen Sie nur hinzu, was Sie benötigen. -* Ein funktionierendes Beispiel finden Sie in der Hello-World-App: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Konfiguration von Logikfunktionen und Einstiegspunkt - -Jede Funktionsdatei verwendet `defineLogicFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Öffentlicher HTTP-Routen-Trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron-Trigger (CRON-Muster) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Datenbank-Ereignis-Trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -}); -``` - -Häufige Trigger-Typen: - -* **route**: Stellt Ihre Funktion unter einem HTTP-Pfad und einer Methode **unter dem Endpunkt `/s/`** bereit: - -> z. B. `path: '/post-card/create',` -> Aufruf unter `/s/post-card/create` - -* **cron**: Führt Ihre Funktion nach Zeitplan mithilfe eines CRON-Ausdrucks aus. -* **databaseEvent**: Wird bei Lebenszyklusereignissen von Workspace-Objekten ausgeführt. Wenn die Ereignisoperation `updated` ist, können bestimmte zu überwachende Felder im Array `updatedFields` angegeben werden. Wenn das Array undefiniert oder leer ist, löst jede Aktualisierung die Funktion aus. - -> z. B. `person.updated` - -Notizen: - -* Das Array `triggers` ist optional. Funktionen ohne Trigger können als von anderen Funktionen aufgerufene Utility-Funktionen verwendet werden. -* Sie können mehrere Trigger-Typen in einer Funktion kombinieren. - -### Pre-Installationsfunktionen - -Eine Pre-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, bevor deine App in einem Arbeitsbereich installiert wird. Dies ist nützlich für Validierungsaufgaben, Überprüfungen von Voraussetzungen oder die Vorbereitung des Status des Arbeitsbereichs, bevor die Hauptinstallation fortgesetzt wird. - -Wenn du mit `create-twenty-app` eine neue App erstellst, wird für dich eine Pre-Installationsfunktion unter `src/logic-functions/pre-install.ts` erzeugt: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Du kannst die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Hauptpunkte: - -* Pre-Installationsfunktionen verwenden `definePreInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. -* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen). -* Pro Anwendung ist nur eine Pre-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. -* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `preInstallLogicFunctionUniversalIdentifier` gesetzt — du musst ihn nicht in `defineApplication()` referenzieren. -* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Vorbereitungsvorgänge zu ermöglichen. -* Pre-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform vor der Installation oder manuell über `function:execute --preInstall` aufgerufen. - -### Post-Installationsfunktionen - -Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Dies ist nützlich für einmalige Einrichtungsvorgänge wie das Befüllen mit Standarddaten, das Erstellen erster Datensätze oder das Konfigurieren von Arbeitsbereichseinstellungen. - -Wenn du mit `create-twenty-app` eine neue App erstellst, wird für dich eine Post-Installationsfunktion unter `src/logic-functions/post-install.ts` erzeugt: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Du kannst die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Hauptpunkte: - -* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. -* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen). -* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. -* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `postInstallLogicFunctionUniversalIdentifier` gesetzt — du musst ihn nicht in `defineApplication()` referenzieren. -* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen. -* Post-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform während der Installation oder manuell über `function:execute --postInstall` aufgerufen. - -### Routen-Trigger-Payload - - -**Breaking Change (v1.16, Januar 2026):** Das Format der Routen-Trigger-Payload hat sich geändert. Vor v1.16 wurden Query-Parameter, Pfadparameter und der Body direkt als Payload gesendet. Ab v1.16 sind sie innerhalb eines strukturierten `RoutePayload`-Objekts verschachtelt. - -**Vor v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**Nach v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**So migrieren Sie bestehende Funktionen:** Aktualisieren Sie Ihren Handler, sodass er nicht mehr direkt aus dem params-Objekt destrukturiert, sondern aus `event.body`, `event.queryStringParameters` oder `event.pathParameters`. - - -Wenn ein Routen-Trigger Ihre Logikfunktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem AWS HTTP API v2-Format entspricht. Importieren Sie den Typ aus `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Der Typ `RoutePayload` hat die folgende Struktur: - -| Eigenschaft | Typ | Beschreibung | -| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------- | -| `headers` | `Record` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | -| `queryStringParameters` | `Record` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | -| `pathParameters` | `Record` | Aus dem Routenmuster extrahierte Pfadparameter (z. B. `/users/:id` → `{ id: '123' }`) | -| `inhalt` | `object \| null` | Geparster Request-Body (JSON) | -| `isBase64Encoded` | `boolesch` | Gibt an, ob der Body Base64-codiert ist | -| `requestContext.http.method` | `string` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Rohpfad der Anfrage | - -### Weiterleiten von HTTP-Headern - -Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. Um auf bestimmte Header zuzugreifen, listen Sie diese explizit im Array `forwardedRequestHeaders` auf: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -In Ihrem Handler können Sie anschließend auf diese Header zugreifen: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - Header-Namen werden in Kleinbuchstaben normalisiert. Greifen Sie mit Schlüsseln in Kleinbuchstaben darauf zu (zum Beispiel `event.headers['content-type']`). - - -Sie können neue Funktionen auf zwei Arten erstellen: - -* **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Logikfunktion. Dadurch wird eine Starterdatei mit Handler und Konfiguration erzeugt. -* **Manuell**: Erstellen Sie eine neue `*.logic-function.ts`-Datei und verwenden Sie `defineLogicFunction()` nach demselben Muster. - -### Eine Logikfunktion als Tool markieren - -Logikfunktionen können als **Tools** für KI-Agenten und Workflows verfügbar gemacht werden. Wenn eine Funktion als Tool markiert ist, wird sie von den KI-Funktionen von Twenty auffindbar und kann als Schritt in Workflow-Automatisierungen ausgewählt werden. - -Um eine Logikfunktion als Tool zu markieren, setzen Sie `isTool: true` und geben Sie ein `toolInputSchema` an, das die erwarteten Eingabeparameter mithilfe von [JSON Schema](https://json-schema.org/) beschreibt: - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Daten für ${params.companyName} anreichern`, - body: `Domain: ${params.domain ?? 'unbekannt'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Einen Unternehmensdatensatz mit externen Daten anreichern', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'Name des Unternehmens, das angereichert werden soll', - }, - domain: { - type: 'string', - description: 'Website-Domain des Unternehmens (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Hauptpunkte: - -* **`isTool`** (`boolean`, Standard: `false`): Wenn auf `true` gesetzt, wird die Funktion als Tool registriert und steht KI-Agenten und Workflow-Automatisierungen zur Verfügung. -* **`toolInputSchema`** (`object`, optional): Ein JSON-Schema-Objekt, das die Parameter beschreibt, die Ihre Funktion akzeptiert. KI-Agenten verwenden dieses Schema, um zu verstehen, welche Eingaben das Tool erwartet, und um Aufrufe zu validieren. Falls weggelassen, lautet der Standardwert für das Schema `{ type: 'object', properties: {} }` (keine Parameter). -* Funktionen mit `isTool: false` (oder nicht gesetzt) werden **nicht** als Tools bereitgestellt. Sie können weiterhin direkt ausgeführt oder von anderen Funktionen aufgerufen werden, erscheinen jedoch nicht in der Tool-Erkennung. -* **Tool-Benennung**: Wenn als Tool bereitgestellt, wird der Funktionsname automatisch zu `logic_function_` normalisiert (in Kleinbuchstaben umgewandelt, nicht alphanumerische Zeichen durch Unterstriche ersetzt). Beispielsweise wird `enrich-company` zu `logic_function_enrich_company`. -* Sie können `isTool` mit Triggern kombinieren — eine Funktion kann gleichzeitig sowohl ein Tool (von KI-Agenten aufrufbar) als auch durch Ereignisse (Cron, Datenbankereignisse, Routen) ausgelöst werden. - - -**Schreiben Sie eine gute `description`.** KI-Agenten verlassen sich auf das `description`-Feld der Funktion, um zu entscheiden, wann das Tool verwendet werden soll. Seien Sie konkret darin, was das Tool tut und wann es aufgerufen werden soll. - - -### Frontend-Komponenten - -Frontend-Komponenten ermöglichen es Ihnen, benutzerdefinierte React-Komponenten zu erstellen, die innerhalb der Twenty-UI gerendert werden. Verwenden Sie `defineFrontComponent()`, um Komponenten mit eingebauter Validierung zu definieren: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -Hauptpunkte: - -* Frontend-Komponenten sind React-Komponenten, die in isolierten Kontexten innerhalb von Twenty gerendert werden. -* Das Feld `component` verweist auf Ihre React-Komponente. -* Komponenten werden während `yarn twenty dev` automatisch gebaut und synchronisiert. - -Sie können neue Frontend-Komponenten auf zwei Arten erstellen: - -* **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Frontend-Komponente. -* **Manuell**: Erstellen Sie eine neue `.tsx`-Datei und verwenden Sie `defineFrontComponent()` nach demselben Muster. - -#### Wo Front-Komponenten verwendet werden können - -Front-Komponenten können an zwei Stellen innerhalb von Twenty gerendert werden: - -* **Seitenpanel** — Nicht-Headless-Front-Komponenten werden im rechten Seitenpanel geöffnet. Dies ist das Standardverhalten, wenn eine Front-Komponente über das Befehlsmenü ausgelöst wird. -* **Widgets (Dashboards und Datensatzseiten)** — Front-Komponenten können als Widgets in Seitenlayouts eingebettet werden. Beim Konfigurieren eines Dashboards oder eines Datensatzseiten-Layouts können Benutzer ein Front-Komponenten-Widget hinzufügen. - -#### Headless vs. Nicht-Headless - -Front-Komponenten gibt es in zwei Rendering-Modi, die durch die Option `isHeadless` gesteuert werden: - -**Nicht-Headless (Standard)** — Die Komponente rendert eine sichtbare UI. Wird sie über das Befehlsmenü ausgelöst, öffnet sie sich im Seitenpanel. Dies ist das Standardverhalten, wenn `isHeadless` `false` ist oder weggelassen wird. - -**Headless** — Die Komponente wird unsichtbar im Hintergrund gemountet. Sie öffnet das Seitenpanel nicht. Headless-Komponenten sind für Aktionen konzipiert, die Logik ausführen und sich anschließend selbst unmounten — zum Beispiel das Ausführen einer asynchronen Aufgabe, das Navigieren zu einer Seite oder das Anzeigen eines Bestätigungsdialogs. Sie lassen sich gut mit den unten beschriebenen SDK-Command-Komponenten kombinieren. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Befehlsmenü-Einträge hinzufügen - -Damit eine Front-Komponente als Eintrag im Befehlsmenü von Twenty erscheint, fügen Sie die Eigenschaft `command` zu `defineFrontComponent()` hinzu. Wenn Benutzer das Befehlsmenü öffnen (Cmd+K / Ctrl+K), erscheint der Eintrag und löst beim Klicken die Front-Komponente aus. - -Das Objekt `command` akzeptiert die folgenden Felder: - -| Feld | Typ | Beschreibung | -| --------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `universalIdentifier` | `string` (erforderlich) | Eindeutige ID für den Befehlsmenü-Eintrag | -| `beschriftung` | `string` (erforderlich) | Angezeigtes Label im Befehlsmenü | -| `symbol` | `string` (optional) | Iconname (z. B. 'IconSparkles') | -| `isPinned` | `boolean` (optional) | Ob der Befehl oben im Menü angeheftet ist | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` zeigt den Befehl überall an; `RECORD_SELECTION` zeigt ihn nur in Datensatzkontexten an | -| `availabilityObjectUniversalIdentifier` | `string` (optional) | Beschränkt den Befehl auf einen bestimmten Objekttyp (z. B. Person) | - -Hier ist ein Beispiel aus der Call-Recording-App, das einen auf Person-Datensätze beschränkten Befehl hinzufügt: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -Wenn der Befehl synchronisiert wird, erscheint er im Befehlsmenü. Wenn die Front-Komponente Nicht-Headless ist, öffnet sich das Seitenpanel und die Komponente wird darin gerendert. Wenn sie Headless ist, wird die Komponente im Hintergrund gemountet und führt ihre Logik aus. - -#### SDK-Command-Komponenten - -Das Paket `twenty-sdk` stellt vier Command-Hilfskomponenten bereit, die für Headless-Front-Komponenten ausgelegt sind. Jede Komponente führt beim Mounten eine Aktion aus, behandelt Fehler durch Anzeige einer Snackbar-Benachrichtigung und unmountet die Front-Komponente nach Abschluss automatisch. - -Importieren Sie sie aus `twenty-sdk/command`: - -* **`Command`** — Führt einen asynchronen Callback über das Prop `execute` aus. -* **`CommandLink`** — Navigiert zu einem App-Pfad. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Öffnet einen Bestätigungsdialog. Bestätigt der Benutzer, wird der Callback `execute` ausgeführt. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Öffnet eine bestimmte Seite im Seitenpanel. Props: `page`, `pageTitle`, `pageIcon`. - -Hier ist ein vollständiges Beispiel einer Headless-Front-Komponente, die `Command` verwendet, um eine Aktion aus dem Befehlsmenü auszuführen: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -Und ein Beispiel, das `CommandModal` verwendet, um vor der Ausführung um Bestätigung zu bitten: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Ausführungskontext - -Jede Front-Komponente erhält einen Ausführungskontext, der Informationen darüber liefert, wo und wie sie ausgeführt wird. Greifen Sie mit Hooks aus `twenty-sdk` auf Kontextwerte zu: - -| Hook | Rückgabetyp | Beschreibung | -| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `useFrontComponentId()` | `string` | Die eindeutige ID der aktuellen Front-Komponenteninstanz | -| `useRecordId()` | `string \| null` | Die ID des aktuellen Datensatzes, wenn die Komponente in einem Datensatzkontext ausgeführt wird (z. B. ein Widget auf einer Datensatzseite oder ein auf einen Datensatz beschränkter Befehl). Andernfalls wird `null` zurückgegeben. | -| `useUserId()` | `string \| null` | Die ID des aktuellen Benutzers | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -Der Kontext ist reaktiv — ändert sich der umgebende Datensatz, liefern die Hooks automatisch die aktualisierten Werte. - -#### Host-API-Funktionen - -Front-Komponenten laufen in einer isolierten Sandbox, können jedoch über eine Reihe vom Host bereitgestellter Funktionen mit der UI von Twenty interagieren. Importieren Sie sie direkt aus `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Funktion | Signatur | Beschreibung | -| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `navigieren` | `(to, params?, queryParams?, options?) => Promise` | Navigieren Sie zu einem typisierten App-Pfad innerhalb von Twenty | -| `closeSidePanel` | `() => Promise` | Seitenpanel schließen | -| `enqueueSnackbar` | `(params) => Promise` | Eine Snackbar-Benachrichtigung anzeigen. Parameter: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Die aktuelle Front-Komponente unmounten (wird von Headless-Komponenten verwendet, um nach der Ausführung aufzuräumen) | -| `openSidePanelPage` | `(params) => Promise` | Eine Seite im Seitenpanel öffnen. Parameter: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Einen Bestätigungsdialog anzeigen und auf die Antwort des Benutzers warten. Parameter: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Hier ist ein Beispiel, das die Host-API verwendet, um nach Abschluss einer Aktion eine Snackbar anzuzeigen und das Seitenpanel zu schließen: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Fähigkeiten - -Skills definieren wiederverwendbare Anweisungen und Fähigkeiten, die KI-Agenten in Ihrem Arbeitsbereich verwenden können. Verwenden Sie `defineSkill()`, um Skills mit eingebauter Validierung zu definieren: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Hauptpunkte: - -* `name` ist eine eindeutige Kennung (als Zeichenfolge) für den Skill (kebab-case empfohlen). -* `label` ist der menschenlesbare Anzeigename, der in der UI angezeigt wird. -* `content` enthält die Skill-Anweisungen — dies ist der Text, den der KI-Agent verwendet. -* `icon` (optional) legt das in der UI angezeigte Symbol fest. -* `description` (optional) liefert zusätzlichen Kontext zum Zweck des Skills. - -Sie können neue Skills auf zwei Arten erstellen: - -* **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen eines neuen Skills. -* **Manuell**: Erstellen Sie eine neue Datei und verwenden Sie `defineSkill()` nach demselben Muster. - -### Agenten - -Mit Agents definieren Sie KI-Agenten mit System-Prompts, die in Ihrem Arbeitsbereich arbeiten können. Verwenden Sie `defineAgent()`, um Agenten mit eingebauter Validierung zu definieren: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Hauptpunkte: - -* `name` ist eine eindeutige Kennung (als Zeichenfolge) für den Agenten (kebab-case empfohlen). -* `label` ist der menschenlesbare Anzeigename, der in der UI angezeigt wird. -* `prompt` enthält den System-Prompt — dies ist der Anweisungstext, der das Verhalten des Agenten definiert. -* `icon` (optional) legt das in der UI angezeigte Symbol fest. -* `description` (optional) liefert zusätzlichen Kontext zum Zweck des Agenten. - -Sie können neue Agenten auf zwei Arten erstellen: - -* **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen eines neuen Agenten. -* **Manuell**: Erstellen Sie eine neue Datei und verwenden Sie `defineAgent()` nach demselben Muster. - -### Generierte typisierte Clients - -Zwei typisierte Clients werden von `yarn twenty dev` automatisch generiert und basierend auf Ihrem Arbeitsbereichs-Schema in `node_modules/twenty-sdk/clients` gespeichert: - -* **`CoreApiClient`** — fragt den `/graphql`-Endpunkt nach Arbeitsbereichsdaten ab -* **`MetadataApiClient`** — ruft über den Endpunkt `/metadata` die Arbeitsbereichskonfiguration und Datei-Uploads ab. - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });},{ -``` - -`CoreApiClient` wird von `yarn twenty dev` automatisch neu generiert, sobald sich Ihre Objekte oder Felder ändern. `MetadataApiClient` ist im SDK bereits enthalten. - -#### Laufzeit-Anmeldedaten in Logikfunktionen - -Wenn Ihre Funktion auf Twenty läuft, injiziert die Plattform vor der Ausführung Ihres Codes Anmeldedaten als Umgebungsvariablen: - -* `TWENTY_API_URL`: Basis-URL der Twenty-API, auf die Ihre App abzielt. -* `TWENTY_API_KEY`: Kurzlebiger Schlüssel, der auf die Standard-Funktionsrolle Ihrer Anwendung begrenzt ist. - -Notizen: - -* Sie müssen dem generierten Client weder URL noch API-Schlüssel übergeben. Er liest `TWENTY_API_URL` und `TWENTY_API_KEY` zur Laufzeit aus process.env. -* Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in Ihrer `application-config.ts` über `defaultRoleUniversalIdentifier` verwiesen wird. Dies ist die Standardrolle, die von den Logikfunktionen Ihrer Anwendung verwendet wird. -* Anwendungen können Rollen definieren, um das Least-Privilege-Prinzip einzuhalten. Gewähren Sie nur die Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann mit `defaultRoleUniversalIdentifier` auf den universellen Bezeichner dieser Rolle. - -#### Dateien hochladen - -Der `MetadataApiClient` enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei in Ihren Arbeitsbereichsobjekten anzuhängen. Da Standard-GraphQL-Clients Multipart-Datei-Uploads nicht nativ unterstützen, stellt der Client diese dedizierte Methode bereit, die unter der Haube die [GraphQL-Multipart-Anfragespezifikation](https://github.com/jaydenseric/graphql-multipart-request-spec) implementiert. - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // Dateiinhalte als Buffer - 'invoice.pdf', // Dateiname - 'application/pdf', // MIME-Typ (Standard: 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // Universeller Feldbezeichner -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -Die Methodensignatur: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parameter | Typ | Beschreibung | -| ---------------------------------- | -------------- | ------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | -| `filename` | `string` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | -| `contentType` | `string` | MIME-Typ der Datei (standardmäßig `application/octet-stream`, wenn weggelassen) | -| `fieldMetadataUniversalIdentifier` | `Zeichenkette` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | - -Hauptpunkte: - -* Die Methode `uploadFile` ist auf dem `MetadataApiClient` verfügbar, weil die Upload-Mutation vom Endpunkt `/metadata` aufgelöst wird. -* Sie verwendet den `universalIdentifier` des Feldes (nicht dessen arbeitsbereichsspezifische ID), sodass Ihr Upload-Code in jedem Arbeitsbereich funktioniert, in dem Ihre App installiert ist — im Einklang damit, wie Apps Felder überall sonst referenzieren. -* Die zurückgegebene `url` ist eine signierte URL, mit der Sie auf die hochgeladene Datei zugreifen können. - -### Hello-World-Beispiel - -Ein minimales End-to-End-Beispiel, das Objekte, Logikfunktionen, Frontend-Komponenten und mehrere Trigger demonstriert, finden Sie [hier](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## Erstellen Ihrer App - -Sobald Sie Ihre App mit `app:dev` entwickelt haben, verwenden Sie `app:build`, um sie in ein verteilbares Paket zu kompilieren. - -```bash filename="Terminal" -# Die App erstellen (Ausgabe nach .twenty/output/) -yarn twenty build - -# Build ausführen und ein Tarball (.tgz) für die Verteilung erstellen -yarn twenty build --tarball -``` - -Der Build-Prozess: - -1. **Parst und validiert das Manifest** — liest alle `defineX()`-Entitäten aus Ihren Quelldateien und validiert die Manifeststruktur. -2. **Kompiliert Logikfunktionen und Front-Komponenten** — bündelt TypeScript-Quellcode in ESM `.mjs`-Dateien mit esbuild. -3. **Erzeugt Checksummen** — berechnet MD5-Hashes für jede erstellte Datei, die im Manifest als `builtHandlerChecksum` / `builtComponentChecksum` gespeichert werden. -4. **Generiert den typisierten API-Client** — führt eine Introspektion des GraphQL-Schemas durch und generiert die typisierten Clients `CoreApiClient` und `MetadataApiClient`. -5. **Führt eine TypeScript-Typprüfung aus** — führt `tsc --noEmit` aus, um Typfehler vor der Veröffentlichung zu erkennen. -6. **Baut mit dem generierten Client neu** — führt einen zweiten Kompiliervorgang durch, damit die generierten Client-Typen enthalten sind. -7. **Erstellt optional einen Tarball** — wenn `--tarball` übergeben wird, wird `npm pack` ausgeführt, um eine `.tgz`-Datei zu erstellen, die für die Verteilung bereit ist. - -Der Build-Output in `.twenty/output/` enthält: - -```text -.twenty/output/ -├── manifest.json # Manifest with checksums for all built files -├── package.json # Copied from app root -├── yarn.lock # Copied from app root -├── src/ -│ ├── logic-functions/ # Compiled .mjs logic function files -│ └── front-components/ # Compiled .mjs front component files -├── public/ # Static assets (if any) -└── my-app-1.0.0.tgz # Only with --tarball flag -``` - -| Option | Beschreibung | -| ----------- | -------------------------------------------------------------- | -| `[appPath]` | Pfad zum App-Verzeichnis (standardmäßig aktuelles Verzeichnis) | -| `--tarball` | Den Output zusätzlich in einen `.tgz`-Tarball packen | - -## Veröffentlichen Ihrer App - -Verwenden Sie `app:publish`, um Ihre App zu verteilen — entweder zur npm-Registry oder direkt zu einem Twenty-Server. - -### Bei npm veröffentlichen (Standard) - -```bash filename="Terminal" -# Auf npm veröffentlichen (erfordert npm login) -yarn twenty publish - -# Mit einem dist-tag veröffentlichen (z. B. beta, next) -yarn twenty publish --tag beta -``` - -Dies baut die App und führt `npm publish` aus dem Verzeichnis `.twenty/output/` aus. Das veröffentlichte Paket kann dann von jedem Arbeitsbereich über den Twenty-Marktplatz installiert werden. - -### Auf einem Twenty-Server veröffentlichen - -```bash filename="Terminal" -# Direkt auf einen Twenty-Server veröffentlichen -yarn twenty publish --server https://app.twenty.com -``` - -Dies erstellt beim Build einen Tarball, lädt ihn über die GraphQL-Mutation `uploadAppTarball` auf den Server hoch und stößt die Installation in einem Schritt an. Dies ist nützlich für private Bereitstellungen oder Tests gegen einen bestimmten Server. - -| Option | Beschreibung | -| ----------------- | ------------------------------------------------------------------ | -| `[appPath]` | Pfad zum App-Verzeichnis (standardmäßig aktuelles Verzeichnis) | -| `--server ` | Auf einen Twenty-Server anstelle von npm veröffentlichen | -| `--token ` | Authentifizierungstoken für den Zielserver | -| `--tag ` | npm dist-tag (z. B. `beta`, `next`) — nur für npm-Veröffentlichung | - -## Anwendungsregistrierung - -Bevor eine App in einem Arbeitsbereich installiert werden kann, muss sie **registriert** werden. Eine Registrierung ist ein Metadatensatz, der beschreibt, woher die App stammt und wie sie authentifiziert wird. Dies wird in den meisten Fällen automatisch durch die CLI erledigt. - -### Quelltypen - -Jede Registrierung hat einen **Quelltyp**, der bestimmt, wie die Dateien der App während der Installation aufgelöst werden: - -| Quelltyp | Wie Dateien aufgelöst werden | Typischer Anwendungsfall | -| --------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| `LOCAL` | Dateien werden in Echtzeit vom CLI-Watcher synchronisiert — die Installation wird übersprungen | Entwicklung mit `app:dev` | -| `NPM` | Über das Feld `sourcePackage` aus der npm-Registry abgerufen | Veröffentlichte Apps auf npm | -| `TARBALL` | Aus einer hochgeladenen, auf dem Server gespeicherten `.tgz`-Datei extrahiert | Private Apps, die mit `--server` veröffentlicht wurden | - -### Wie die Registrierung erfolgt - -* **`app:dev`** — erstellt beim ersten Ausführen des Dev-Modus für einen Arbeitsbereich automatisch eine `LOCAL`-Registrierung. -* **`app:publish --server`** — lädt einen Tarball hoch und erstellt (oder aktualisiert) eine `TARBALL`-Registrierung und installiert anschließend die App. -* **npm-Marktplatz** — `NPM`-Registrierungen werden erstellt, wenn Apps aus der npm-Registry in den Twenty-Marktplatzkatalog synchronisiert werden. -* **GraphQL-API** — Sie können Registrierungen auch programmgesteuert über die Mutation `createApplicationRegistration` erstellen. - -### Registrierung vs. Installation - -**Registrierung** und **Installation** sind unterschiedliche Konzepte: - -* Eine **Registrierung** (`ApplicationRegistration`) ist ein globaler Metadatensatz, der die App beschreibt: ihren Namen, den Quelltyp, die OAuth-Anmeldedaten und den Status der Marktplatzlistung. Sie existiert unabhängig von jedem Arbeitsbereich. -* Eine **Installation** (`Application`) ist eine Instanz pro Arbeitsbereich. Wenn ein Benutzer eine App installiert, ermittelt Twenty das Paket aus der Quelle der Registrierung, schreibt die erstellten Dateien in den Speicher und synchronisiert das Manifest (wobei Objekte, Felder, Logikfunktionen usw. erstellt werden) in diesem Arbeitsbereich. - -Eine Registrierung kann in vielen Arbeitsbereichen installiert werden. Jeder Arbeitsbereich erhält seine eigene Kopie der Dateien und des Datenmodells der App. - -### OAuth-Anmeldedaten - -Jede Registrierung enthält OAuth-Anmeldedaten (`oAuthClientId` und `oAuthClientSecret`), die bei der Erstellung generiert werden. Diese werden von der App verwendet, um API-Anfragen im Namen der Benutzer zu authentifizieren. Das Client-Secret wird bei der Erstellung **einmalig** zurückgegeben — bewahren Sie es sicher auf. Sie können es später über die Mutation `rotateApplicationRegistrationClientSecret` rotieren. - -## Manuelle Einrichtung (ohne Scaffolder) - -Wir empfehlen zwar `create-twenty-app` für das beste Einstiegserlebnis, Sie können ein Projekt aber auch manuell einrichten. Installieren Sie die CLI nicht global. Fügen Sie stattdessen `twenty-sdk` als lokale Abhängigkeit hinzu und binden Sie ein einzelnes Skript in Ihrer package.json ein: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Fügen Sie dann ein `twenty`-Skript hinzu: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Jetzt können Sie alle Befehle über `yarn twenty ` ausführen, z. B. `yarn twenty dev`, `yarn twenty help` usw. - -## Fehlerbehebung - -* Authentifizierungsfehler: Führen Sie `yarn twenty auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat. -* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist. -* Typen oder Client fehlen/veraltet: Starten Sie `yarn twenty dev` neu — der typisierte Client wird automatisch generiert. -* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn twenty dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden. - -Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/de/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/it/developers/extend/apps/building.mdx b/packages/twenty-docs/l/it/developers/extend/apps/building.mdx index f154053e50..4bf6b4f513 100644 --- a/packages/twenty-docs/l/it/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/it/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: Definisci oggetti, funzioni logiche, componenti front-end e molto a --- -Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione. + Apps are currently in alpha. The feature works but is still evolving. -## Usa le risorse dell'SDK (tipi e configurazione) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -Il pacchetto twenty-sdk fornisce blocchi tipizzati e funzioni helper da usare nella tua app. Di seguito gli elementi principali con cui interagirai più spesso. +## DefineEntity functions -### Funzioni helper +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi. -L'SDK fornisce funzioni helper per definire le entità della tua app. Come descritto in [Rilevamento delle entità](/l/it/developers/extend/apps/getting-started#entity-detection), devi usare `export default define({...})` affinché le tue entità vengano rilevate: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| Funzione | Scopo | -| -------------------------------- | ----------------------------------------------------------------------------------------------- | -| `defineApplication` | Configura i metadati dell'applicazione (obbligatorio, uno per app) | -| `defineObject` | Definisci oggetti personalizzati con campi | -| `defineField` | Estendi gli oggetti esistenti con campi aggiuntivi oppure definisci campi di relazione autonomi | -| `defineLogicFunction` | Definisci funzioni logiche con handler | -| `definePreInstallLogicFunction` | Definisci una funzione logica di pre-installazione (una per app) | -| `definePostInstallLogicFunction` | Definisci una funzione logica di post-installazione (una per app) | -| `defineFrontComponent` | Definisci componenti front-end per un'interfaccia utente personalizzata | -| `defineRole` | Configura i permessi dei ruoli e l'accesso agli oggetti | -| `defineView` | Definisci viste salvate per gli oggetti | -| `defineNavigationMenuItem` | Definisci i link di navigazione della barra laterale | -| `defineSkill` | Definisci le competenze dell'agente IA | -| `defineAgent` | Definisci gli agenti IA | -| `definePageLayout` | Definisci layout di pagina personalizzati | + + -Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi. +Roles encapsulate permissions on your workspace's objects and actions. -### Definizione degli oggetti +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Note: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### Metadati del marketplace + +If you plan to [publish your app](/l/it/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| Campo | Descrizione | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | +| `autore` | Nome dell'autore o dell'azienda | +| `categoria` | Categoria dell'app per il filtraggio nel marketplace | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | Descrizione markdown più lunga per la scheda "Informazioni". Se omesso, il marketplace utilizza il `README.md` del pacchetto da npm | +| `websiteUrl` | Link al tuo sito web | +| `termsUrl` | Link ai Termini di servizio | +| `emailSupport` | Indirizzo email di supporto | +| `issueReportUrl` | Link al sistema di tracciamento dei problemi | + +#### Ruoli e permessi + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts** punta a quel ruolo in modo che le tue funzioni ne ereditino i permessi. + +Note: +* Parti dal ruolo generato dallo scaffolder, quindi restringilo progressivamente seguendo il principio del privilegio minimo. +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags` controllano l'accesso alle funzionalità a livello di piattaforma. Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Gli oggetti personalizzati descrivono sia lo schema sia il comportamento per i record nel tuo spazio di lavoro. Usa `defineObject()` per definire oggetti con convalida integrata: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ Puoi sovrascrivere i campi predefiniti definendo un campo con lo stesso nome nel ma non è consigliato. -### Definire campi sugli oggetti esistenti + + Usa `defineField()` per aggiungere campi a oggetti che non possiedi — come gli oggetti standard di Twenty (Person, Company, ecc.) o oggetti di altre app. A differenza dei campi inline in `defineObject()`, i campi autonomi richiedono un `objectUniversalIdentifier` per specificare quale oggetto estendono: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` Punti chiave: - * `objectUniversalIdentifier` identifica l'oggetto di destinazione. Per gli oggetti standard, usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` esportati da `twenty-sdk`. * Quando definisci campi inline in `defineObject()`, **non** hai bisogno di `objectUniversalIdentifier` — viene ereditato dall'oggetto padre. * `defineField()` è l'unico modo per aggiungere campi a oggetti che non hai creato con `defineObject()`. -### Relazioni + + Le relazioni collegano gli oggetti tra loro. In Twenty, le relazioni sono sempre **bidirezionali** — definisci entrambi i lati e ciascun lato fa riferimento all'altro. @@ -177,8 +310,7 @@ Supponiamo che un `PostCard` possa essere inviato a molti record `PostCardRecipi **Passaggio 1: definisci il lato ONE_TO_MANY su PostCard** (il lato "uno"): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **Passaggio 2: definisci il lato MANY_TO_ONE su PostCardRecipient** (il lato "molti" — contiene la chiave esterna): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ Per creare una relazione con un oggetto Twenty integrato (Person, Company, ecc.), usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ Puoi anche definire i campi di relazione direttamente all'interno di `defineObject()`. In tal caso, ometti `objectUniversalIdentifier` — viene ereditato dall'oggetto padre: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### Configurazione dell'applicazione (application-config.ts) - -Ogni app ha un singolo file `application-config.ts` che descrive: - -* **Identità dell'app**: identificatori, nome visualizzato e descrizione. -* **Come vengono eseguite le sue funzioni**: quale ruolo usano per i permessi. -* **Variabili (opzionali)**: coppie chiave–valore esposte alle funzioni come variabili d'ambiente. -* **(Opzionale) funzione di pre-installazione**: una funzione logica che viene eseguita prima che l'app venga installata. -* **(Opzionale) funzione post-installazione**: una funzione logica che viene eseguita dopo l'installazione dell'app. - -Usa `defineApplication()` per definire la configurazione della tua applicazione: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Note: - -* I campi `universalIdentifier` sono ID deterministici sotto il tuo controllo; generali una volta e mantienili stabili tra le sincronizzazioni. -* `applicationVariables` diventano variabili d'ambiente per le tue funzioni (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` deve corrispondere al file del ruolo (vedi sotto). -* Le funzioni di pre-installazione e post-installazione vengono rilevate automaticamente durante la build del manifesto. Vedi [Funzioni di pre-installazione](#pre-install-functions) e [Funzioni di post-installazione](#post-install-functions). - -#### Metadati del marketplace - -Se prevedi di [pubblicare la tua app](/l/it/developers/extend/apps/publishing), questi campi opzionali controllano come la tua app appare nel marketplace: - -| Campo | Descrizione | -| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -| `autore` | Nome dell'autore o dell'azienda | -| `categoria` | Categoria dell'app per il filtraggio nel marketplace | -| `logoUrl` | Percorso del logo della tua app (relativo a `./assets/`) | -| `screenshots` | Array di percorsi degli screenshot (relativi a `./assets/`) | -| `aboutDescription` | Descrizione markdown più lunga per la scheda "Informazioni". Se omesso, il marketplace utilizza il `README.md` del pacchetto da npm | -| `websiteUrl` | Link al tuo sito web | -| `termsUrl` | Link ai Termini di servizio | -| `emailSupport` | Indirizzo email di supporto | -| `issueReportUrl` | Link al sistema di tracciamento dei problemi | - -#### Ruoli e permessi - -Le applicazioni possono definire ruoli che incapsulano i permessi sugli oggetti e sulle azioni del tuo spazio di lavoro. Il campo `defaultRoleUniversalIdentifier` in `application-config.ts` indica il ruolo predefinito utilizzato dalle funzioni logiche della tua app. - -* La chiave API di runtime iniettata come `TWENTY_API_KEY` è derivata da questo ruolo funzione predefinito. -* Il client tipizzato sarà limitato ai permessi concessi a quel ruolo. -* Segui il principio del privilegio minimo: crea un ruolo dedicato con solo i permessi necessari alle tue funzioni, quindi fai riferimento al suo identificatore universale. - -##### Ruolo funzione predefinito (*.role.ts) - -Quando generi una nuova app con lo scaffolder, la CLI crea anche un file di ruolo predefinito. Usa `defineRole()` per definire ruoli con convalida integrata: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -L'`universalIdentifier` di questo ruolo viene quindi referenziato in `application-config.ts` come `defaultRoleUniversalIdentifier`. In altre parole: - -* **\*.role.ts** definisce ciò che il ruolo funzione predefinito può fare. -* **application-config.ts** punta a quel ruolo in modo che le tue funzioni ne ereditino i permessi. - -Note: - -* Parti dal ruolo generato dallo scaffolder, quindi restringilo progressivamente seguendo il principio del privilegio minimo. -* Sostituisci `objectPermissions` e `fieldPermissions` con gli oggetti/campi di cui le tue funzioni hanno bisogno. -* `permissionFlags` controllano l'accesso alle funzionalità a livello di piattaforma. Mantienili al minimo; aggiungi solo ciò che ti serve. -* Vedi un esempio funzionante nell'app Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Configurazione e punto di ingresso della funzione logica + + Ogni file di funzione usa `defineLogicFunction()` per esportare una configurazione con un handler e trigger opzionali. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Tipi di trigger comuni: - -* **route**: Espone la funzione su un percorso e metodo HTTP **sotto l'endpoint `/s/`**: - -> es. `path: '/post-card/create',` -> chiamata su `/s/post-card/create` - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**: Esegue la tua funzione secondo una pianificazione utilizzando un'espressione CRON. * **databaseEvent**: Viene eseguito sugli eventi del ciclo di vita degli oggetti dello spazio di lavoro. Quando l'operazione dell'evento è `updated`, è possibile specificare campi specifici da monitorare nell'array `updatedFields`. Se lasciato non definito o vuoto, qualsiasi aggiornamento attiverà la funzione. +> e.g. `person.updated`, `*.created`, `company.*` -> es. `person.updated` - -Note: - -* L'array `triggers` è facoltativo. Le funzioni senza trigger possono essere utilizzate come funzioni di utilità richiamate da altre funzioni. -* Puoi combinare più tipi di trigger in un'unica funzione. - -### Funzioni di pre-installazione - -Una funzione di pre-installazione è una funzione logica che viene eseguita automaticamente prima che la tua app venga installata in uno spazio di lavoro. È utile per attività di convalida, controlli dei prerequisiti o per preparare lo stato dello spazio di lavoro prima che proceda l'installazione principale. - -Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata una funzione di pre-installazione in `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Punti chiave: - -* Le funzioni di pre-installazione utilizzano `definePreInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni). -* È consentita una sola funzione di pre-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. -* L'`universalIdentifier` della funzione viene impostato automaticamente come `preInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`. -* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di preparazione più lunghe. -* Le funzioni di pre-installazione non necessitano di trigger — vengono invocate dalla piattaforma prima dell'installazione o manualmente tramite `exec --preInstall`. - -### Funzioni post-installazione - -Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Questo è utile per attività di configurazione una tantum come il popolamento di dati predefiniti, la creazione di record iniziali o la configurazione delle impostazioni dello spazio di lavoro. - -Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata automaticamente una funzione di post-installazione in `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Punti chiave: +You can watch logs with: -* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni). -* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. -* L'`universalIdentifier` della funzione viene impostato automaticamente come `postInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`. -* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati. -* Le funzioni di post-installazione non necessitano di trigger — vengono invocate dalla piattaforma durante l'installazione o manualmente tramite `exec --postInstall`. - -### Payload del trigger di route - - -**Modifica non retrocompatibile (v1.16, gennaio 2026):** Il formato del payload del trigger di route è cambiato. Prima della v1.16, i parametri di query, i parametri di percorso e il corpo venivano inviati direttamente come payload. A partire dalla v1.16, sono annidati all'interno di un oggetto `RoutePayload` strutturato. - -**Prima della v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**Dopo la v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Payload del trigger di route -**Per migrare le funzioni esistenti:** Aggiorna l'handler per estrarre i dati da `event.body`, `event.queryStringParameters` o `event.pathParameters` invece che direttamente dall'oggetto `params`. - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -Quando un trigger di route invoca la tua funzione logica, questa riceve un oggetto `RoutePayload` che segue il formato AWS HTTP API v2. Importa il tipo da `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { Il tipo `RoutePayload` ha la seguente struttura: -| Proprietà | Tipo | Descrizione | -| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------ | -| `headers` | `Record` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parametri della query string (valori multipli uniti da virgole) | -| `pathParameters` | `Record` | Parametri di percorso estratti dal pattern della route (ad es., `/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | Corpo della richiesta analizzato (JSON) | -| `isBase64Encoded` | `boolean` | Indica se il corpo è codificato in base64 | -| `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | + | Proprietà | Tipo | Descrizione | Esempio | + | ---------------------------- | ------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | see section below | + | `queryStringParameters` | `Record` | Parametri della query string (valori multipli uniti da virgole) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Corpo della richiesta analizzato (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Indica se il corpo è codificato in base64 | | + | `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | | -### Inoltro delle intestazioni HTTP -Per impostazione predefinita, le intestazioni HTTP delle richieste in ingresso **non** vengono passate alla tua funzione logica per motivi di sicurezza. Per accedere a intestazioni specifiche, elencale esplicitamente nell'array `forwardedRequestHeaders`: +#### forwardedRequestHeaders -```typescript +Per impostazione predefinita, le intestazioni HTTP delle richieste in ingresso **non** vengono passate alla tua funzione logica per motivi di sicurezza. +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -Nel tuo handler, puoi quindi accedere a queste intestazioni: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - I nomi delle intestazioni vengono normalizzati in minuscolo. Accedile usando chiavi in minuscolo (ad esempio, `event.headers['content-type']`). +I nomi delle intestazioni vengono normalizzati in minuscolo. Access them using lowercase keys (e.g., `event.headers['content-type']`). -Puoi creare nuove funzioni in due modi: +#### Exposing a function as a tool -* **Generata dallo scaffolder**: Esegui `yarn twenty add` e scegli l'opzione per aggiungere una nuova funzione logica. Questo genera un file iniziale con un handler e una configurazione. -* **Manuale**: Crea un nuovo file `*.logic-function.ts` e usa `defineLogicFunction()`, seguendo lo stesso schema. +Le funzioni logiche possono essere esposte come **strumenti** per gli agenti di IA e i flussi di lavoro. When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### Contrassegnare una funzione logica come strumento +To mark a logic function as a tool, set `isTool: true`: -Le funzioni logiche possono essere esposte come **strumenti** per gli agenti di IA e i flussi di lavoro. Quando una funzione è contrassegnata come strumento, diventa individuabile dalle funzionalità di IA di Twenty e può essere selezionata come passaggio nelle automazioni dei flussi di lavoro. - -Per contrassegnare una funzione logica come strumento, imposta `isTool: true` e fornisci un `toolInputSchema` che descriva i parametri di input attesi utilizzando [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Punti chiave: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Punti chiave: - -* **`isTool`** (`boolean`, predefinito: `false`): Quando impostato su `true`, la funzione viene registrata come strumento e diventa disponibile per gli agenti IA e le automazioni dei flussi di lavoro. -* **`toolInputSchema`** (`object`, opzionale): Un oggetto JSON Schema che descrive i parametri accettati dalla funzione. Gli agenti IA utilizzano questo schema per capire quali input si aspetta lo strumento e per convalidare le chiamate. Se omesso, lo schema assume il valore predefinito `{ type: 'object', properties: {} }` (nessun parametro). -* Le funzioni con `isTool: false` (o non impostato) **non** vengono esposte come strumenti. Possono comunque essere eseguite direttamente o chiamate da altre funzioni, ma non compariranno nell'individuazione degli strumenti. -* **Denominazione dello strumento**: Quando esposta come strumento, il nome della funzione viene normalizzato automaticamente in `logic_function_` (in minuscolo, i caratteri non alfanumerici vengono sostituiti da trattini bassi). Ad esempio, `enrich-company` diventa `logic_function_enrich_company`. -* È possibile combinare `isTool` con i trigger — una funzione può essere sia uno strumento (invocabile dagli agenti IA) sia attivata da eventi (cron, eventi del database, routes) contemporaneamente. - **Scrivi una buona `description`.** Gli agenti IA fanno affidamento sul campo `description` della funzione per decidere quando usare lo strumento. Sii specifico su cosa fa lo strumento e quando dovrebbe essere invocato. -### Componenti front-end + + -I componenti front-end ti consentono di creare componenti React personalizzati che vengono renderizzati all'interno dell'interfaccia di Twenty. Usa `defineFrontComponent()` per definire componenti con convalida integrata: +Una funzione di pre-installazione è una funzione logica che viene eseguita automaticamente prima che la tua app venga installata in uno spazio di lavoro. È utile per attività di convalida, controlli dei prerequisiti o per preparare lo stato dello spazio di lavoro prima che proceda l'installazione principale. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Punti chiave: +* Le funzioni di pre-installazione utilizzano `definePreInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni). +* È consentita una sola funzione di pre-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. +* L'`universalIdentifier` della funzione viene impostato automaticamente come `preInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`. +* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di preparazione più lunghe. + + + + +Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Questo è utile per attività di configurazione una tantum come il popolamento di dati predefiniti, la creazione di record iniziali o la configurazione delle impostazioni dello spazio di lavoro. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Punti chiave: +* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni). +* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. +* L'`universalIdentifier` della funzione viene impostato automaticamente come `postInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`. +* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati. + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Punti chiave: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* I componenti front-end sono componenti React che eseguono il rendering in contesti isolati all'interno di Twenty. -* Il campo `component` fa riferimento al tuo componente React. -* I componenti vengono compilati e sincronizzati automaticamente durante `yarn twenty dev`. +
+ Quick action button in the top-right corner +
-Puoi creare nuovi componenti front-end in due modi: +Click it to render the component inline. -* **Generata dallo scaffolder**: Esegui `yarn twenty add` e scegli l'opzione per aggiungere un nuovo componente front-end. -* **Manuale**: Crea un nuovo file `.tsx` e usa `defineFrontComponent()`, seguendo lo stesso schema. +{/* TODO: add screenshot of the rendered front component */} -### Abilità +#### Configuration fields + +| Campo | Obbligatorio | Descrizione | +| --------------------- | ------------ | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | Sì | Stable unique ID for this component | +| `component` | Sì | A React component function | +| `name` | No | Display name | +| `descrizione` | No | Description of what the component does | +| `isHeadless` | No | Set to `true` if the component has no visible UI (see below) | +| `comando` | No | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| Hook | Returns | Descrizione | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | varia | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| Funzione | Descrizione | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | Chiudi il pannello laterale | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| Campo | Obbligatorio | Descrizione | +| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Sì | Stable unique ID for the command | +| `etichetta` | Sì | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | No | Shorter label displayed on the pinned quick-action button | +| `icona` | No | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | No | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | No | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | No | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | No | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| Variabile | Tipo | Descrizione | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `numero` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `oggetto` | Permissions for the current object type | +| `targetObjectReadPermissions` | `oggetto` | Read permissions for the target object | +| `targetObjectWritePermissions` | `oggetto` | Write permissions for the target object | +| `featureFlags` | `oggetto` | Active feature flags | +| `objectMetadataItem` | `oggetto` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | Descrizione | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### Stile + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + Le skill definiscono istruzioni e capacità riutilizzabili che gli agenti IA possono utilizzare all'interno del tuo spazio di lavoro. Usa `defineSkill()` per definire skill con convalida integrata: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` Punti chiave: - * `name` è una stringa identificativa univoca per la skill (kebab-case consigliato). * `label` è il nome di visualizzazione leggibile mostrato nell'UI. * `content` contiene le istruzioni della skill — questo è il testo che l'agente IA utilizza. * `icon` (opzionale) imposta l'icona visualizzata nell'UI. * `description` (opzionale) fornisce contesto aggiuntivo sullo scopo della skill. -Puoi creare nuove skill in due modi: + + -* **Generata dallo scaffolder**: Esegui `yarn twenty add` e scegli l'opzione per aggiungere una nuova skill. -* **Manuale**: Crea un nuovo file e usa `defineSkill()`, seguendo lo stesso schema. +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### Client API tipizzati (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -Il pacchetto `twenty-client-sdk` fornisce due client GraphQL tipizzati per interagire con l'API di Twenty dalle tue funzioni logiche e dai componenti front-end: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Punti chiave: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon` (opzionale) imposta l'icona visualizzata nell'UI. +* `modelId` (optional) overrides the default AI model used by the agent. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Punti chiave: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Punti chiave: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Punti chiave: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| Tipo di entità | Comando | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| Oggetto | `yarn twenty add object` | `src/objects/.ts` | +| Campo | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Ruolo | `yarn twenty add role` | `src/roles/.ts` | +| Abilità | `yarn twenty add skill` | `src/skills/.ts` | +| Agente | `yarn twenty add agent` | `src/agents/.ts` | +| Vista | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +Il pacchetto `twenty-client-sdk` fornisce due client GraphQL tipizzati per interagire con l'API di Twenty dalle tue funzioni logiche e dai componenti front-end. | Client | Importa | Endpoint | Generato? | | ------------------- | ---------------------------- | ------------------------------------------------------------------------ | -------------------------- | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — dati dello spazio di lavoro (record, oggetti) | Sì, in fase di dev/build | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configurazione dello spazio di lavoro, caricamenti di file | No, fornito pronto all'uso | -#### CoreApiClient + + -`CoreApiClient` è il client principale per interrogare e modificare i dati dello spazio di lavoro. Viene **generato dallo schema del tuo spazio di lavoro** durante `yarn twenty dev` o `yarn twenty build`, quindi è completamente tipizzato per corrispondere ai tuoi oggetti e campi. +`CoreApiClient` è il client principale per interrogare e modificare i dati dello spazio di lavoro. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ Il client utilizza una sintassi a selection-set: passa `true` per includere un campo, usa `__args` per gli argomenti e annida oggetti per le relazioni. Ottieni completamento automatico e controllo dei tipi completi basati sullo schema del tuo spazio di lavoro. -**CoreApiClient viene generato in fase di dev/build.** Se provi a usarlo senza eseguire prima `yarn twenty dev` o `yarn twenty build`, genererà un errore. La generazione avviene automaticamente — la CLI esegue l'introspezione dello schema GraphQL del tuo spazio di lavoro, genera un client tipizzato usando `@genql/cli`, scrive le sorgenti generate in `node_modules/twenty-client-sdk/dist/core/generated/` e sostituisce gli stub in `node_modules/twenty-client-sdk/dist/core.mjs` e `node_modules/twenty-client-sdk/dist/core.cjs`. +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### Utilizzo di CoreSchema per le annotazioni di tipo -`CoreSchema` fornisce tipi TypeScript corrispondenti agli oggetti del tuo spazio di lavoro, utili per tipizzare lo stato dei componenti o i parametri delle funzioni: +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient` è fornito pronto all'uso con l'SDK (nessuna generazione richiesta). Interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro, le applicazioni e i caricamenti di file: +`MetadataApiClient` è fornito pronto all'uso con l'SDK (nessuna generazione richiesta). Interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro, le applicazioni e i caricamenti di file. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### Credenziali di runtime - -Quando il tuo codice viene eseguito su Twenty (funzioni logiche o componenti front-end), la piattaforma inietta le credenziali come variabili d'ambiente: - -* `TWENTY_API_URL` — URL di base dell'API di Twenty -* `TWENTY_API_KEY` — Chiave a breve durata con ambito al ruolo funzione predefinito della tua applicazione - -Non è **necessario** passarle ai client — vengono lette automaticamente da `process.env`. I permessi della chiave API sono determinati dal ruolo referenziato in `defaultRoleUniversalIdentifier` nel tuo `application-config.ts`. - #### Caricamento dei file -`MetadataApiClient` include un metodo `uploadFile` per allegare file ai campi di tipo file. Implementa la [specifica delle richieste GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` include un metodo `uploadFile` per allegare file ai campi di tipo file: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| Parametro | Tipo | Descrizione | -| ---------------------------------- | -------- | ------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Il contenuto grezzo del file | -| `filename` | `string` | Il nome del file (utilizzato per l'archiviazione e la visualizzazione) | -| `contentType` | `string` | Tipo MIME del file (predefinito su `application/octet-stream` se omesso) | -| `fieldMetadataUniversalIdentifier` | `string` | L'`universalIdentifier` del campo di tipo file nel tuo oggetto | +| Parametro | Tipo | Descrizione | +| ---------------------------------- | -------- | ---------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Il contenuto grezzo del file | +| `filename` | `string` | Il nome del file (utilizzato per l'archiviazione e la visualizzazione) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | L'`universalIdentifier` del campo di tipo file nel tuo oggetto | Punti chiave: - * Usa l'`universalIdentifier` del campo (non il suo ID specifico dello spazio di lavoro), quindi il tuo codice di upload funziona in qualsiasi spazio di lavoro in cui la tua app è installata. * L'`url` restituito è un URL firmato che puoi usare per accedere al file caricato. -### Esempio Hello World + + -Esplora un esempio minimale end-to-end che dimostra oggetti, funzioni logiche, componenti front-end e trigger multipli [qui](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + Quando il tuo codice viene eseguito su Twenty (funzioni logiche o componenti front-end), la piattaforma inietta le credenziali come variabili d'ambiente: + + * `TWENTY_API_URL` — URL di base dell'API di Twenty + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + Non è **necessario** passarle ai client — vengono lette automaticamente da `process.env`. I permessi della chiave API sono determinati dal ruolo referenziato in `defaultRoleUniversalIdentifier` nel tuo `application-config.ts`. + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### Impostazione + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| Funzione | Descrizione | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## Riferimento CLI + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx index 0de94985d6..781b247928 100644 --- a/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: Crea la tua prima app Twenty in pochi minuti. --- -Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione. +Apps are currently in alpha. The feature works but is still evolving. Le app ti permettono di estendere Twenty con oggetti, campi, funzioni logiche, competenze IA e componenti UI personalizzati — il tutto gestito come codice. -**Cosa puoi creare:** - -* Oggetti, campi, viste ed elementi di navigazione personalizzati per definire il tuo modello di dati -* Funzioni logiche attivate da route HTTP, pianificazioni cron o eventi del database -* Componenti front-end che vengono renderizzati direttamente all'interno della UI di Twenty -* Abilità che estendono gli agenti IA di Twenty -* Distribuisci un'app su più spazi di lavoro - ## Prerequisiti -* Node.js 24+ -* Yarn 4 -* Docker (o un'istanza locale di Twenty in esecuzione) +Before you begin, make sure the following is installed on your machine: -## Per iniziare +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Crea una nuova app utilizzando lo scaffolder ufficiale, quindi autenticati e inizia a sviluppare: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> Usa l'opzione `--minimal` per creare un'installazione minima +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -Da qui puoi: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -Vedi anche: le pagine di riferimento della CLI per [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## Struttura del progetto (generata dallo scaffolder) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* Copia un'applicazione base minimale in `my-twenty-app/` -* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4 -* Crea file di configurazione e script collegati alla CLI `twenty` -* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/it/developers/extend/apps/publishing) for details. + -Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/it/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -A livello generale: +### Key files -* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili. -* **.gitignore**: Ignora i file generati comuni come `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto. -* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto. -* **.oxlintrc.json** e **tsconfig.json**: Forniscono linting e configurazione TypeScript per i sorgenti TypeScript della tua app. -* **README.md**: Un breve README nella radice dell'app con istruzioni di base. -* **public/**: Una cartella per archiviare risorse pubbliche (immagini, font, file statici) che saranno servite con la tua applicazione. I file collocati qui vengono caricati durante la sincronizzazione e sono accessibili in fase di esecuzione. -* **src/**: Il luogo principale in cui definisci la tua applicazione come codice +| File / Folder | Scopo | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Abilità che estendono gli agenti IA di Twenty. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Rilevamento delle entità +## Managing remotes -L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`: - -| Funzione helper | Tipo di entità | -| -------------------------------- | ------------------------------------------------------------------------------ | -| `defineObject` | Definizioni di oggetti personalizzati | -| `defineLogicFunction` | Definizioni di funzioni logiche | -| `definePreInstallLogicFunction` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) | -| `definePostInstallLogicFunction` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) | -| `defineFrontComponent` | Definizioni dei componenti front-end | -| `defineRole` | Definizioni di ruoli | -| `defineField` | Estensioni di campo per oggetti esistenti | -| `defineView` | Definizioni di viste salvate | -| `defineNavigationMenuItem` | Definizioni delle voci del menu di navigazione | -| `defineSkill` | Definizioni delle competenze degli agenti IA | - - -**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito. - - -Esempio di entità rilevata: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Comandi successivi aggiungeranno altri file e cartelle: - -* `yarn twenty dev` genererà automaticamente il `CoreApiClient` tipizzato (per i dati dell'area di lavoro via `/graphql`) in `node_modules/twenty-client-sdk/`. Il `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file via `/metadata`) è fornito precompilato ed è disponibile immediatamente. Importali da `twenty-client-sdk/core` e `twenty-client-sdk/metadata` rispettivamente. -* `yarn twenty add` aggiungerà file di definizione delle entità sotto `src/` per i tuoi oggetti personalizzati, funzioni, componenti front-end, ruoli, competenze e altro ancora. - -## Autenticazione - -La prima volta che esegui `yarn twenty auth:login`, ti verranno richiesti: - -* URL dell'API (predefinito a http://localhost:3000 o al profilo dello spazio di lavoro corrente) -* Chiave API - -Le tue credenziali sono archiviate per utente in `~/.twenty/config.json`. Puoi mantenere più profili e passare da uno all'altro. - -### Gestione delle aree di lavoro +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -Una volta che hai cambiato area di lavoro con `yarn twenty auth:switch`, tutti i comandi successivi utilizzeranno quell'area di lavoro per impostazione predefinita. Puoi comunque sovrascriverla temporaneamente con `--workspace `. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Avvio del server + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Comando | Descrizione | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Configurazione manuale (senza lo scaffolder) -Sebbene consigliamo di utilizzare `create-twenty-app` per la migliore esperienza iniziale, puoi anche configurare un progetto manualmente. Non installare la CLI globalmente. Invece, aggiungi `twenty-sdk` come dipendenza locale e collega un unico script nel tuo package.json: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Quindi aggiungi uno script `twenty`: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ Quindi aggiungi uno script `twenty`: } ``` -Ora puoi eseguire tutti i comandi tramite `yarn twenty `, ad es. `yarn twenty dev`, `yarn twenty help`, ecc. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## Come utilizzare un'istanza locale di Twenty - -Se stai già eseguendo un'istanza di Twenty in locale (ad es. tramite `npx nx start twenty-server`), puoi connetterti ad essa invece di usare Docker: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Risoluzione dei problemi -* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti. -* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile. -* Tipi o client mancanti/obsoleti: riavvia `yarn twenty dev` — genera automaticamente il client tipizzato. -* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente. +If you run into issues: -Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx index 61dc92d572..e4ab5f9cb6 100644 --- a/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: Distribuisci la tua app Twenty nel marketplace oppure distribuiscil --- -Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione. + Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione. ## Panoramica Una volta che la tua app è stata [compilata e testata localmente](/l/it/developers/extend/apps/building), hai due modalità per distribuirla: -* **Pubblica su npm** — elenca la tua app nel marketplace di Twenty affinché qualsiasi spazio di lavoro possa scoprirla e installarla. * **Distribuisci un tarball** — carica la tua app direttamente su un server Twenty specifico per uso interno o privato. +* **Pubblica su npm** — elenca la tua app nel marketplace di Twenty affinché qualsiasi spazio di lavoro possa scoprirla e installarla. Entrambi i percorsi partono dalla stessa fase di **build**. ## Compilazione della tua app -Il comando `build` compila i tuoi sorgenti TypeScript, transpila le funzioni di logica e i componenti front-end e genera un `manifest.json` che descrive i contenuti della tua app: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -L'output viene scritto in `.twenty/output/`. Questa directory contiene tutto il necessario per la distribuzione: codice compilato, risorse, il manifest e una copia del tuo `package.json`. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -Per creare anche un tarball `.tgz` (usato internamente dal comando di deploy o per la distribuzione manuale): +## Distribuzione su un server (tarball) + +Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo aziendali o build sperimentali — puoi distribuire un tarball direttamente su un server Twenty. + +### Prerequisiti + +Prima della distribuzione, ti serve un remote configurato che punti al server di destinazione. I remote memorizzano localmente l'URL del server e le credenziali di autenticazione in `~/.twenty/config.json`. + +Aggiungi un remote: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### Distribuzione + +Compila e carica la tua app sul server in un solo passaggio: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Condivisione di un'app distribuita + +Le app in formato tarball non sono elencate nel marketplace pubblico, quindi altri spazi di lavoro sullo stesso server non le troveranno navigando. Per condividere un'app distribuita: + +1. Vai su **Impostazioni > Applicazioni > Registrazioni** e apri la tua app +2. Nella scheda **Distribuzione**, fai clic su **Copia link di condivisione** +3. Condividi questo link con utenti su altri spazi di lavoro — li porterà direttamente alla pagina di installazione dell'app + +Il link di condivisione utilizza l'URL di base del server (senza alcun sottodominio dello spazio di lavoro) così funziona per qualsiasi spazio di lavoro sul server. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Gestione delle versioni + +Per rilasciare un aggiornamento: + +1. Incrementa il campo `version` nel tuo `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Gli spazi di lavoro che hanno l'app installata vedranno l'aggiornamento disponibile nelle proprie impostazioni + +{/* TODO: add screenshot of the Upgrade button */} + ## Pubblicazione su npm La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Qualsiasi spazio di lavoro Twenty può sfogliare, installare e aggiornare le app del marketplace direttamente dall'interfaccia utente. @@ -39,41 +81,42 @@ La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Q ### Requisiti * Un account [npm](https://www.npmjs.com) -* La parola chiave `twenty-app` **deve** essere elencata nell'array `keywords` del tuo `package.json` - -### Aggiunta della parola chiave richiesta - -Il marketplace di Twenty individua le app cercando nel registro npm i pacchetti con la parola chiave `twenty-app`. Aggiungila al tuo `package.json`: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -Il marketplace cerca `keywords:twenty-app` sul registro npm. Senza questa parola chiave, il tuo pacchetto non apparirà nel marketplace anche se ha il prefisso nel nome `twenty-app-`. - +### Metadati del marketplace -### Passaggi +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **Compila la tua app:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **Pubblica su npm:** +See the [defineApplication accordion](/l/it/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -Questo esegue `npm publish` dalla directory `.twenty/output/`. - Per pubblicare con un dist-tag specifico (ad es. `beta` o `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### Come funziona l'individuazione nel marketplace -Il server Twenty sincronizza il proprio catalogo del marketplace dal registro npm **ogni ora**: +Il server Twenty sincronizza il proprio catalogo del marketplace dal registro npm **ogni ora**. -1. Cerca tutti i pacchetti npm con `keywords:twenty-app` -2. Per ogni pacchetto, recupera il `manifest.json` dalla CDN di npm -3. I metadati dell'app (nome, descrizione, autore, logo, screenshot, categoria) vengono estratti dal manifest e visualizzati nel marketplace - -Dopo la pubblicazione, la tua app può impiegare fino a un'ora per apparire nel marketplace. Per attivare subito la sincronizzazione invece di attendere la prossima esecuzione oraria: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -Per puntare a un remote specifico: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -I metadati mostrati nel marketplace provengono dalla chiamata a `defineApplication()` nel codice sorgente della tua app — campi come `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` e `termsUrl`. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. Se la tua app non definisce un `aboutDescription` in `defineApplication()`, il marketplace userà automaticamente il `README.md` del tuo pacchetto su npm come contenuto della pagina Informazioni. Questo significa che puoi mantenere un unico README sia per npm sia per il marketplace di Twenty. Se desideri una descrizione diversa nel marketplace, imposta esplicitamente `aboutDescription`. @@ -108,7 +143,7 @@ Se la tua app non definisce un `aboutDescription` in `defineApplication()`, il m ### Pubblicazione con CI -Il progetto generato include un workflow di GitHub Actions che pubblica a ogni release: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `yarn twenty build`, quindi `npm publish` da `.twenty/output`. - + **npm provenance** è opzionale ma consigliata. La pubblicazione con `--provenance` aggiunge un badge di attendibilità alla tua scheda npm, consentendo agli utenti di verificare che il pacchetto sia stato creato a partire da uno specifico commit in una pipeline CI pubblica. Consulta la [documentazione su npm provenance](https://docs.npmjs.com/generating-provenance-statements) per le istruzioni di configurazione. - - -## Distribuzione su un server (tarball) - -Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo aziendali o build sperimentali — puoi distribuire un tarball direttamente su un server Twenty. - -### Prerequisiti - -Prima della distribuzione, ti serve un remote configurato che punti al server di destinazione. I remote memorizzano localmente l'URL del server e le credenziali di autenticazione in `~/.twenty/config.json`. - -Aggiungi un remote: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -Per un server di sviluppo locale: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -Puoi anche autenticarti con una chiave API per ambienti non interattivi: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -Gestisci i tuoi remote: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### Distribuzione - -Compila e carica la tua app sul server in un solo passaggio: - -```bash filename="Terminal" -yarn twenty deploy -``` - -Questo compila l'app con `--tarball`, quindi carica il tarball sul remote predefinito tramite un upload multipart GraphQL. - -Per distribuire su un remote specifico: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### Condivisione di un'app distribuita - -Le app in formato tarball non sono elencate nel marketplace pubblico, quindi altri spazi di lavoro sullo stesso server non le troveranno navigando. Per condividere un'app distribuita: - -1. Vai su **Impostazioni > Applicazioni > Registrazioni** e apri la tua app -2. Nella scheda **Distribuzione**, fai clic su **Copia link di condivisione** -3. Condividi questo link con utenti su altri spazi di lavoro — li porterà direttamente alla pagina di installazione dell'app - -Il link di condivisione utilizza l'URL di base del server (senza alcun sottodominio dello spazio di lavoro) così funziona per qualsiasi spazio di lavoro sul server. - -### Gestione delle versioni - -Per rilasciare un aggiornamento: - -1. Incrementa il campo `version` nel tuo `package.json` -2. Esegui `yarn twenty deploy` (oppure `yarn twenty deploy -r production`) -3. Gli spazi di lavoro che hanno l'app installata vedranno l'aggiornamento disponibile nelle proprie impostazioni + ## Installazione delle app -Una volta che un'app è stata pubblicata (npm) o distribuita (tarball), gli spazi di lavoro la installano tramite l'interfaccia utente: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -Oppure dalla pagina **Impostazioni > Applicazioni** nell'interfaccia di Twenty, dove è possibile sfogliare e installare sia le app del marketplace sia quelle distribuite tramite tarball. - -## Categorie di distribuzione delle app - -Twenty organizza le app in tre categorie in base a come vengono distribuite: - -| Categoria | Come funziona | Visibile nel marketplace? | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | -| **Sviluppo** | App in modalità di sviluppo locale eseguite tramite `yarn twenty dev`. Usate per la compilazione e i test. | No | -| **Pubblicate (npm)** | App pubblicate su npm con la parola chiave `twenty-app`. Elencate nel marketplace per l'installazione da parte di qualsiasi spazio di lavoro. | Sì | -| **Interne (tarball)** | App distribuite tramite tarball su un server specifico. Disponibili solo per gli spazi di lavoro su quel server tramite un link di condivisione. | No | - - -Inizia in modalità **Sviluppo** mentre crei la tua app. Quando è pronta, scegli **Pubblicata** (npm) per un'ampia distribuzione oppure **Interna** (tarball) per una distribuzione privata. - - -## Riferimento CLI - -| Comando | Descrizione | Flag principali | -| --------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------- | -| `yarn twenty build` | Compila l'app e genera il manifest | `--tarball` — crea anche un pacchetto `.tgz` | -| `yarn twenty publish` | Compila e pubblica su npm | `--tag ` — dist-tag npm (ad es. `beta`, `next`) | -| `yarn twenty deploy` | Compila e carica un tarball su un server | `-r, --remote ` — remote di destinazione | -| `yarn twenty catalog-sync` | Attiva la sincronizzazione del catalogo del marketplace sul server | `-r, --remote ` — remote di destinazione | -| `yarn twenty install` | Installa un'app distribuita su uno spazio di lavoro | `-r, --remote ` — remote di destinazione | -| `yarn twenty dev` | Osserva e sincronizza le modifiche locali | Usa il remote predefinito | -| `yarn twenty remote add` | Aggiungi una connessione al server | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | Elenca i remote configurati | — | -| `yarn twenty remote switch` | Imposta il remote predefinito | — | -| `yarn twenty remote status` | Mostra lo stato della connessione | — | -| `yarn twenty remote remove` | Rimuovi un remote | — | diff --git a/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx index 28473e964b..f8b1b2242b 100644 --- a/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: Crea e gestisci le personalizzazioni di Twenty come codice. --- -Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione. +Apps are currently in alpha. The feature works but is still evolving. -## Cosa sono le app? +## What are apps? -Le app ti consentono di creare e gestire le personalizzazioni di Twenty **come codice**. Invece di configurare tutto tramite l'interfaccia utente, definisci in codice il modello dati e le funzioni logiche — rendendo più veloce creare, mantenere e distribuire su più spazi di lavoro. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**Cosa puoi fare oggi:** +**Cosa puoi creare:** -* Definisci oggetti e campi personalizzati come codice (modello dati gestito) -* Crea funzioni logiche con trigger personalizzati -* Definire skill e agenti per l'IA -* Distribuisci la stessa app su più spazi di lavoro +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Prerequisiti - -* Node.js 24+ e Yarn 4 -* Docker (per il server di sviluppo locale di Twenty) - -## Per iniziare - -Crea una nuova app utilizzando lo scaffolder ufficiale. Può avviare automaticamente un'istanza locale di Twenty per te: +## Quick start ```bash filename="Terminal" -# Crea lo scaffold di una nuova app — la CLI offrirà di avviare un server locale di Twenty npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Avvia la modalità di sviluppo: sincronizza automaticamente le modifiche locali con il tuo workspace yarn twenty dev ``` -### Gestione del server locale +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/it/developers/extend/apps/getting-started) guide for the full walkthrough. -L'SDK include comandi per gestire un server di sviluppo locale di Twenty (immagine Docker all-in-one con PostgreSQL, Redis, server e worker sulla porta 2020). Questi comandi si applicano solo al server di sviluppo basato su Docker — non gestiscono un'istanza di Twenty avviata dal sorgente (ad es. `npx nx start twenty-server` sulla porta 3000): +## Detailed guides -```bash filename="Terminal" -# Avvia il server locale (scarica l'immagine se necessario) -yarn twenty server start +| Guide | Descrizione | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/it/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/it/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/it/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Verifica lo stato del server -yarn twenty server status - -# Segui i log del server -yarn twenty server logs - -# Arresta il server -yarn twenty server stop - -# Reimposta tutti i dati e riparti da zero -yarn twenty server reset -``` - -Il server locale è preconfigurato con uno spazio di lavoro e un utente (`tim@apple.dev` / `tim@apple.dev`), così puoi iniziare a sviluppare immediatamente senza alcuna configurazione manuale. - -### Autenticazione - -Collega la tua app al server locale tramite OAuth: - -```bash filename="Terminal" -# Autenticati tramite OAuth (apre il browser) -yarn twenty remote add --local -``` - -Lo strumento di scaffolding supporta due modalità per controllare quali file di esempio vengono inclusi: - -```bash filename="Terminal" -# Predefinita (esaustiva): tutti gli esempi (oggetto, campo, funzione logica, componente front-end, vista, voce del menu di navigazione, skill, agente) -npx create-twenty-app@latest my-app - -# Minimale: solo i file principali (application-config.ts e default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### Come utilizzare un'istanza locale di Twenty - -Se stai già eseguendo un'istanza locale di Twenty, puoi connetterti ad essa invece di usare Docker. Indica la porta su cui il tuo server locale è in ascolto (predefinita: `3000`): - -```bash filename="Terminal" -# Durante lo scaffolding -npx create-twenty-app@latest my-app --port 3000 - -# Oppure dopo lo scaffolding -yarn twenty remote add --local --port 3000 -``` - -Da qui puoi: - -```bash filename="Terminal" -# Aggiungi una nuova entità alla tua applicazione (guidata) -yarn twenty entity:add - -# Monitora i log delle funzioni della tua applicazione -yarn twenty function:logs - -# Esegui una funzione per nome -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Esegui la funzione di pre-installazione -yarn twenty function:execute --preInstall - -# Esegui la funzione post-installazione -yarn twenty function:execute --postInstall - -# Compila l'app per la distribuzione -yarn twenty build - -# Pubblica l'app su npm o su un server Twenty -yarn twenty publish - -# Disinstalla l'applicazione dallo spazio di lavoro corrente -yarn twenty uninstall - -# Mostra l'aiuto dei comandi -yarn twenty help -``` - -Vedi anche: le pagine di riferimento della CLI per [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## Struttura del progetto (generata dallo scaffolder) - -Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder: - -* Copia un'applicazione base minimale in `my-twenty-app/` -* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4 -* Crea file di configurazione e script collegati alla CLI `twenty` -* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding - -Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Cartella delle risorse pubbliche (immagini, font, ecc.) - src/ - ├── application-config.ts # Obbligatorio - configurazione principale dell'applicazione - ├── roles/ - │ └── default-role.ts # Ruolo predefinito per le funzioni logiche - ├── objects/ - │ └── example-object.ts # Definizione di oggetto personalizzato di esempio - ├── fields/ - │ └── example-field.ts # Definizione di campo autonomo di esempio - ├── logic-functions/ - │ ├── hello-world.ts # Funzione logica di esempio - │ ├── pre-install.ts # Funzione logica di pre-installazione - │ └── post-install.ts # Funzione logica di post-installazione - ├── front-components/ - │ └── hello-world.tsx # Componente front-end di esempio - ├── views/ - │ └── example-view.ts # Definizione di vista salvata di esempio - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Link di navigazione della barra laterale di esempio - ├── skills/ - │ └── example-skill.ts # Definizione di skill per agente IA di esempio - └── agents/ - └── example-agent.ts # Definizione di agente IA di esempio -``` - -Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`). - -A livello generale: - -* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili. -* **.gitignore**: Ignora i file generati comuni come `node_modules`, `.yarn`, `generated/` (client tipizzato), `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto. -* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto. -* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources. -* **README.md**: Un breve README nella radice dell'app con istruzioni di base. -* **public/**: Una cartella per archiviare risorse pubbliche (immagini, font, file statici) che saranno servite con la tua applicazione. I file collocati qui vengono caricati durante la sincronizzazione e sono accessibili in fase di esecuzione. -* **src/**: Il luogo principale in cui definisci la tua applicazione come codice +## Key concepts ### Rilevamento delle entità -L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Funzione helper | Tipo di entità | -| ---------------------------------- | ------------------------------------------------------------------------------ | -| `defineObject()` | Definizioni di oggetti personalizzati | -| `defineLogicFunction()` | Definizioni di funzioni logiche | -| `definePreInstallLogicFunction()` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) | -| `definePostInstallLogicFunction()` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) | -| `defineFrontComponent()` | Definizioni dei componenti front-end | -| `defineRole()` | Definizioni di ruoli | -| `defineField()` | Estensioni di campo per oggetti esistenti | -| `defineView()` | Definizioni di viste salvate | -| `defineNavigationMenuItem()` | Definizioni delle voci del menu di navigazione | -| `defineSkill()` | AI agent skill definitions | -| `defineAgent()` | Definizioni di agenti IA | +### Available entity types - -**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito. - +| Funzione | Scopo | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Esempio di entità rilevata: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Comandi successivi aggiungeranno altri file e cartelle: - -* `yarn twenty dev` genererà automaticamente due client API tipizzati in `node_modules/twenty-sdk/clients`: `CoreApiClient` (per i dati dell'area di lavoro tramite `/graphql`) e `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file tramite `/metadata`). -* `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more. - -## Autenticazione - -La prima volta che esegui `yarn twenty auth:login`, ti verranno richiesti: - -* URL dell'API (predefinito a http://localhost:3000 o al profilo dello spazio di lavoro corrente) -* Chiave API - -Le tue credenziali sono archiviate per utente in `~/.twenty/config.json`. Puoi mantenere più profili e passare da uno all'altro. - -### Gestione delle aree di lavoro +### Riferimento CLI ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login - -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace - -# List all configured workspaces -yarn twenty auth:list - -# Switch the default workspace (interactive) -yarn twenty auth:switch - -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -Una volta che hai cambiato area di lavoro con `yarn twenty auth:switch`, tutti i comandi successivi utilizzeranno quell'area di lavoro per impostazione predefinita. Puoi comunque sovrascriverla temporaneamente con `--workspace `. - -## Usa le risorse dell'SDK (tipi e configurazione) - -Il pacchetto twenty-sdk fornisce blocchi tipizzati e funzioni helper da usare nella tua app. Di seguito gli elementi principali con cui interagirai più spesso. - -### Funzioni helper - -L'SDK fornisce funzioni helper per definire le entità della tua app. Come descritto in [Rilevamento delle entità](#entity-detection), devi usare `export default define({...})` affinché le tue entità vengano rilevate: - -| Funzione | Scopo | -| ---------------------------------- | -------------------------------------------------------------------------- | -| `defineApplication()` | Configura i metadati dell'applicazione (obbligatorio, uno per app) | -| `defineObject()` | Definisci oggetti personalizzati con campi | -| `defineLogicFunction()` | Definisci funzioni logiche con handler | -| `definePreInstallLogicFunction()` | Definisce una funzione logica di pre-installazione (una per applicazione) | -| `definePostInstallLogicFunction()` | Definisce una funzione logica di post-installazione (una per applicazione) | -| `defineFrontComponent()` | Definisci componenti front-end per un'interfaccia utente personalizzata | -| `defineRole()` | Configura i permessi dei ruoli e l'accesso agli oggetti | -| `defineField()` | Estendi gli oggetti esistenti con campi aggiuntivi | -| `defineView()` | Definisce viste salvate per gli oggetti | -| `defineNavigationMenuItem()` | Definisce i link di navigazione della barra laterale | -| `defineSkill()` | Define AI agent skills | -| `defineAgent()` | Definisci agenti IA con prompt di sistema | - -Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi. - -### Definizione degli oggetti - -Gli oggetti personalizzati descrivono sia lo schema sia il comportamento per i record nel tuo spazio di lavoro. Usa `defineObject()` per definire oggetti con convalida integrata: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Punti chiave: - -* Usa `defineObject()` per una convalida integrata e un migliore supporto IDE. -* Il `universalIdentifier` deve essere univoco e stabile tra i deployment. -* Ogni campo richiede un `name`, `type`, `label` e il proprio `universalIdentifier` stabile. -* L'array `fields` è facoltativo: puoi definire oggetti senza campi personalizzati. -* Puoi generare nuovi oggetti con `yarn twenty entity:add`, che ti guida nella denominazione, nei campi e nelle relazioni. - - -**I campi base vengono creati automaticamente.** Quando definisci un oggetto personalizzato, Twenty aggiunge automaticamente i campi standard -come `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. -Non è necessario definirli nel tuo array `fields` — aggiungi solo i tuoi campi personalizzati. -Puoi sovrascrivere i campi predefiniti definendo un campo con lo stesso nome nel tuo array `fields`, -ma non è consigliato. - - -### Definire campi sugli oggetti esistenti - -Usa `defineField()` per aggiungere campi personalizzati agli oggetti esistenti — sia agli oggetti standard (come `company`, `person`, `opportunity`) sia agli oggetti personalizzati definiti da altre app. Ogni campo risiede nel proprio file e fa riferimento all'oggetto di destinazione tramite il suo `universalIdentifier`. - -Per fare riferimento agli oggetti standard, importa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` da `twenty-sdk`. Questa costante fornisce identificatori stabili per tutti gli oggetti integrati e per i relativi campi: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Punti chiave: - -* `objectUniversalIdentifier` indica a Twenty a quale oggetto associare il campo. Usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` per gli oggetti standard. -* Ogni campo richiede un proprio `universalIdentifier` stabile, un `name`, `type`, `label` e l'`objectUniversalIdentifier` di destinazione. -* Puoi generare nuovi campi usando `yarn twenty entity:add` e scegliendo l'opzione campo. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` è anche esportato come `STANDARD_OBJECT` per comodità — entrambi si riferiscono alla stessa costante. - -Gli oggetti standard disponibili includono: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` e `workspaceMember`. - -Ogni oggetto standard espone anche gli identificatori dei propri campi. Ad esempio, per fare riferimento a un campo specifico su un oggetto standard nelle autorizzazioni dei ruoli: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### Campi di relazione su oggetti esistenti - -Puoi anche definire campi di relazione che collegano oggetti esistenti ai tuoi oggetti personalizzati: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### Configurazione dell'applicazione (application-config.ts) - -Ogni app ha un singolo file `application-config.ts` che descrive: - -* **Identità dell'app**: identificatori, nome visualizzato e descrizione. -* **Come vengono eseguite le sue funzioni**: quale ruolo usano per i permessi. -* **Variabili (opzionali)**: coppie chiave–valore esposte alle funzioni come variabili d'ambiente. -* **(Opzionale) funzione di pre-installazione**: una funzione logica che viene eseguita prima che l'app venga installata. -* **(Opzionale) funzione post-installazione**: una funzione logica che viene eseguita dopo l'installazione dell'app. - -Usa `defineApplication()` per definire la configurazione della tua applicazione: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Note: - -* I campi `universalIdentifier` sono ID deterministici sotto il tuo controllo; generali una volta e mantienili stabili tra le sincronizzazioni. -* `applicationVariables` diventano variabili d'ambiente per le tue funzioni (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` deve corrispondere al file del ruolo (vedi sotto). -* Le funzioni di pre-installazione e post-installazione vengono rilevate automaticamente durante la build del manifesto. Vedi [Funzioni di pre-installazione](#pre-install-functions) e [Funzioni di post-installazione](#post-install-functions). - -#### Ruoli e permessi - -Le applicazioni possono definire ruoli che incapsulano i permessi sugli oggetti e sulle azioni del tuo spazio di lavoro. Il campo `defaultRoleUniversalIdentifier` in `application-config.ts` indica il ruolo predefinito utilizzato dalle funzioni logiche della tua app. - -* La chiave API di runtime iniettata come `TWENTY_API_KEY` è derivata da questo ruolo funzione predefinito. -* Il client tipizzato sarà limitato ai permessi concessi a quel ruolo. -* Segui il principio del privilegio minimo: crea un ruolo dedicato con solo i permessi necessari alle tue funzioni, quindi fai riferimento al suo identificatore universale. - -##### Ruolo funzione predefinito (\*.role.ts) - -Quando generi una nuova app con lo scaffolder, la CLI crea anche un file di ruolo predefinito. Usa `defineRole()` per definire ruoli con convalida integrata: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -L'`universalIdentifier` di questo ruolo viene quindi referenziato in `application-config.ts` come `defaultRoleUniversalIdentifier`. In altre parole: - -* **\*.role.ts** definisce ciò che il ruolo funzione predefinito può fare. -* **application-config.ts** punta a quel ruolo in modo che le tue funzioni ne ereditino i permessi. - -Note: - -* Parti dal ruolo generato dallo scaffolder, quindi restringilo progressivamente seguendo il principio del privilegio minimo. -* Sostituisci `objectPermissions` e `fieldPermissions` con gli oggetti/campi di cui le tue funzioni hanno bisogno. -* `permissionFlags` controllano l'accesso alle funzionalità a livello di piattaforma. Mantienili al minimo; aggiungi solo ciò che ti serve. -* Vedi un esempio funzionante nell'app Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Configurazione e punto di ingresso della funzione logica - -Ogni file di funzione usa `defineLogicFunction()` per esportare una configurazione con un handler e trigger opzionali. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Trigger di route HTTP pubblica '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Trigger Cron (pattern CRON) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Trigger di evento del database - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -}); -``` - -Tipi di trigger comuni: - -* **route**: Espone la funzione su un percorso e metodo HTTP **sotto l'endpoint `/s/`**: - -> es. `path: '/post-card/create',` -> chiamata su `/s/post-card/create` - -* **cron**: Esegue la tua funzione secondo una pianificazione utilizzando un'espressione CRON. -* **databaseEvent**: Viene eseguito sugli eventi del ciclo di vita degli oggetti dello spazio di lavoro. Quando l'operazione dell'evento è `updated`, è possibile specificare campi specifici da monitorare nell'array `updatedFields`. Se lasciato non definito o vuoto, qualsiasi aggiornamento attiverà la funzione. - -> es. `person.updated` - -Note: - -* L'array `triggers` è facoltativo. Le funzioni senza trigger possono essere utilizzate come funzioni di utilità richiamate da altre funzioni. -* Puoi combinare più tipi di trigger in un'unica funzione. - -### Funzioni di pre-installazione - -Una funzione di pre-installazione è una funzione logica che viene eseguita automaticamente prima che la tua app venga installata in uno spazio di lavoro. È utile per attività di convalida, controlli dei prerequisiti o per preparare lo stato dello spazio di lavoro prima che proceda l'installazione principale. - -Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata una funzione di pre-installazione in `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Punti chiave: - -* Le funzioni di pre-installazione utilizzano `definePreInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni). -* È consentita una sola funzione di pre-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. -* L'`universalIdentifier` della funzione viene impostato automaticamente come `preInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`. -* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di preparazione più lunghe. -* Le funzioni di pre-installazione non necessitano di trigger — vengono invocate dalla piattaforma prima dell'installazione o manualmente tramite `function:execute --preInstall`. - -### Funzioni post-installazione - -Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Questo è utile per attività di configurazione una tantum come il popolamento di dati predefiniti, la creazione di record iniziali o la configurazione delle impostazioni dello spazio di lavoro. - -Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata automaticamente una funzione di post-installazione in `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Punti chiave: - -* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni). -* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. -* L'`universalIdentifier` della funzione viene impostato automaticamente come `postInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`. -* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati. -* Le funzioni di post-installazione non necessitano di trigger — vengono invocate dalla piattaforma durante l'installazione o manualmente tramite `function:execute --postInstall`. - -### Payload del trigger di route - - -**Modifica non retrocompatibile (v1.16, gennaio 2026):** Il formato del payload del trigger di route è cambiato. Prima della v1.16, i parametri di query, i parametri di percorso e il corpo venivano inviati direttamente come payload. A partire dalla v1.16, sono annidati all'interno di un oggetto `RoutePayload` strutturato. - -**Prima della v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**Dopo la v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**Per migrare le funzioni esistenti:** Aggiorna l'handler per estrarre i dati da `event.body`, `event.queryStringParameters` o `event.pathParameters` invece che direttamente dall'oggetto `params`. - - -Quando un trigger di route invoca la tua funzione logica, questa riceve un oggetto `RoutePayload` che segue il formato AWS HTTP API v2. Importa il tipo da `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Il tipo `RoutePayload` ha la seguente struttura: - -| Proprietà | Tipo | Descrizione | -| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `headers` | `Record` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parametri della query string (valori multipli uniti da virgole) | -| `pathParameters` | `Record` | Parametri di percorso estratti dal pattern della route (ad es., `/users/:id` → `{ id: '123' }`) | -| `corpo` | `object \| null` | Corpo della richiesta analizzato (JSON) | -| `isBase64Encoded` | `booleano` | Indica se il corpo è codificato in base64 | -| `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | - -### Inoltro delle intestazioni HTTP - -Per impostazione predefinita, le intestazioni HTTP delle richieste in ingresso **non** vengono passate alla tua funzione logica per motivi di sicurezza. Per accedere a intestazioni specifiche, elencale esplicitamente nell'array `forwardedRequestHeaders`: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -Nel tuo handler, puoi quindi accedere a queste intestazioni: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - I nomi delle intestazioni vengono normalizzati in minuscolo. Accedile usando chiavi in minuscolo (ad esempio, `event.headers['content-type']`). - - -Puoi creare nuove funzioni in due modi: - -* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere una nuova funzione logica. Questo genera un file iniziale con un handler e una configurazione. -* **Manuale**: Crea un nuovo file `*.logic-function.ts` e usa `defineLogicFunction()`, seguendo lo stesso schema. - -### Contrassegnare una funzione logica come strumento - -Le funzioni logiche possono essere esposte come **strumenti** per gli agenti di IA e i flussi di lavoro. Quando una funzione è contrassegnata come strumento, diventa individuabile dalle funzionalità di IA di Twenty e può essere selezionata come passaggio nelle automazioni dei flussi di lavoro. - -Per contrassegnare una funzione logica come strumento, imposta `isTool: true` e fornisci un `toolInputSchema` che descriva i parametri di input attesi utilizzando [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Arricchisci i dati per ${params.companyName}`, - body: `Dominio: ${params.domain ?? 'sconosciuto'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Arricchisci un record aziendale con dati esterni', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'Il nome dell\'azienda da arricchire', - }, - domain: { - type: 'string', - description: 'Il dominio del sito web dell\'azienda (facoltativo)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Punti chiave: - -* **`isTool`** (`boolean`, predefinito: `false`): Quando impostato su `true`, la funzione viene registrata come strumento e diventa disponibile per gli agenti IA e le automazioni dei flussi di lavoro. -* **`toolInputSchema`** (`object`, opzionale): Un oggetto JSON Schema che descrive i parametri accettati dalla funzione. Gli agenti IA utilizzano questo schema per capire quali input si aspetta lo strumento e per convalidare le chiamate. Se omesso, lo schema assume il valore predefinito `{ type: 'object', properties: {} }` (nessun parametro). -* Le funzioni con `isTool: false` (o non impostato) **non** vengono esposte come strumenti. Possono comunque essere eseguite direttamente o chiamate da altre funzioni, ma non compariranno nell'individuazione degli strumenti. -* **Denominazione dello strumento**: Quando esposta come strumento, il nome della funzione viene normalizzato automaticamente in `logic_function_` (in minuscolo, i caratteri non alfanumerici vengono sostituiti da trattini bassi). Ad esempio, `enrich-company` diventa `logic_function_enrich_company`. -* È possibile combinare `isTool` con i trigger — una funzione può essere sia uno strumento (invocabile dagli agenti IA) sia attivata da eventi (cron, eventi del database, routes) contemporaneamente. - - -**Scrivi una buona `description`.** Gli agenti IA fanno affidamento sul campo `description` della funzione per decidere quando usare lo strumento. Sii specifico su cosa fa lo strumento e quando dovrebbe essere invocato. - - -### Componenti front-end - -I componenti front-end ti consentono di creare componenti React personalizzati che vengono renderizzati all'interno dell'interfaccia di Twenty. Usa `defineFrontComponent()` per definire componenti con convalida integrata: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -Punti chiave: - -* I componenti front-end sono componenti React che eseguono il rendering in contesti isolati all'interno di Twenty. -* Il campo `component` fa riferimento al tuo componente React. -* I componenti vengono compilati e sincronizzati automaticamente durante `yarn twenty dev`. - -Puoi creare nuovi componenti front-end in due modi: - -* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere un nuovo componente front-end. -* **Manuale**: Crea un nuovo file `.tsx` e usa `defineFrontComponent()`, seguendo lo stesso schema. - -#### Dove possono essere utilizzati i componenti front. - -I componenti front possono essere renderizzati in due posizioni all'interno di Twenty: - -* **Pannello laterale** — I componenti front non headless si aprono nel pannello laterale destro. Questo è il comportamento predefinito quando un componente front viene avviato dal menu comandi. -* **Widget (dashboard e pagine dei record)** — I componenti front possono essere incorporati come widget all'interno dei layout di pagina. Quando si configura una dashboard o il layout di una pagina record, gli utenti possono aggiungere un widget del componente front. - -#### Headless vs non headless - -I componenti front prevedono due modalità di rendering controllate dall'opzione `isHeadless`: - -**Non headless (predefinito)** — Il componente renderizza un'interfaccia utente visibile. Quando viene avviato dal menu comandi, si apre nel pannello laterale. Questo è il comportamento predefinito quando `isHeadless` è `false` o omesso. - -**Headless** — Il componente viene montato in modo invisibile in background. Non apre il pannello laterale. I componenti headless sono pensati per azioni che eseguono una logica e poi si smontano — ad esempio, eseguire un'attività asincrona, navigare a una pagina o mostrare una finestra modale di conferma. Si abbinano naturalmente ai componenti Command dell'SDK descritti di seguito. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Aggiungere voci al menu comandi - -Per far comparire un componente front come voce nel menu comandi di Twenty, aggiungi la proprietà `command` a `defineFrontComponent()`. Quando gli utenti aprono il menu comandi (Cmd+K / Ctrl+K), la voce viene mostrata e al clic attiva il componente front. - -L'oggetto `command` accetta i seguenti campi: - -| Campo | Tipo | Descrizione | -| --------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------- | -| `universalIdentifier` | `string` (obbligatorio) | ID univoco per la voce del menu comandi | -| `etichetta` | `string` (obbligatorio) | Etichetta visualizzata nel menu comandi | -| `icona` | `string` (facoltativo) | Nome dell'icona (ad es., `'IconSparkles'`) | -| `isPinned` | `boolean` (facoltativo) | Indica se il comando è fissato in alto nel menu | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (facoltativo) | `GLOBAL` mostra il comando ovunque; `RECORD_SELECTION` lo mostra solo nei contesti dei record | -| `availabilityObjectUniversalIdentifier` | `string` (facoltativo) | Limita il comando a uno specifico tipo di oggetto (ad es., Person) | - -Ecco un esempio dall'app di registrazione delle chiamate che aggiunge un comando limitato ai record Person: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -Quando il comando viene sincronizzato, appare nel menu comandi. Se il componente front è non headless, si apre il pannello laterale con il componente renderizzato al suo interno. Se è headless, il componente viene montato in background ed esegue la propria logica. - -#### Componenti Command dell'SDK - -Il pacchetto `twenty-sdk` fornisce quattro componenti di supporto Command progettati per i componenti front headless. Ogni componente esegue un'azione al montaggio, gestisce gli errori mostrando una notifica snackbar e smonta automaticamente il componente front al termine. - -Importali da `twenty-sdk/command`: - -* **`Command`** — Esegue una callback asincrona tramite la prop `execute`. -* **`CommandLink`** — Naviga verso un percorso dell'app. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Apre una finestra modale di conferma. Se l'utente conferma, esegue la callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Apre una specifica pagina del pannello laterale. Props: `page`, `pageTitle`, `pageIcon`. - -Ecco un esempio completo di componente front headless che usa `Command` per eseguire un'azione dal menu comandi: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -E un esempio che usa `CommandModal` per chiedere conferma prima di eseguire: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Contesto di esecuzione - -Ogni componente front riceve un contesto di esecuzione che fornisce informazioni su dove e come sta funzionando. Accedi ai valori del contesto usando gli hook di `twenty-sdk`: - -| Hook | Tipo restituito | Descrizione | -| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | L'ID univoco dell'istanza corrente del componente front | -| `useRecordId()` | `string \| null` | L'ID del record corrente, quando il componente viene eseguito in un contesto di record (ad es., un widget di pagina record o un comando con ambito a un record). In caso contrario, restituisce `null`. | -| `useUserId()` | `string \| null` | L'ID dell'utente corrente | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -Il contesto è reattivo — se il record circostante cambia, gli hook restituiscono automaticamente i valori aggiornati. - -#### Funzioni dell'API host - -I componenti front vengono eseguiti in una sandbox isolata ma possono interagire con l'interfaccia di Twenty tramite un insieme di funzioni fornite dall'host. Importale direttamente da `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Funzione | Firma | Descrizione | -| ------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `naviga` | `(to, params?, queryParams?, options?) => Promise` | Naviga verso un percorso tipizzato dell'app all'interno di Twenty | -| `closeSidePanel` | `() => Promise` | Chiudi il pannello laterale | -| `enqueueSnackbar` | `(params) => Promise` | Mostra una notifica snackbar. Parametri: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), `duration` opzionale, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Smonta il componente front corrente (usato dai componenti headless per pulire dopo l'esecuzione) | -| `openSidePanelPage` | `(params) => Promise` | Apri una pagina nel pannello laterale. Parametri: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Mostra una finestra modale di conferma e attende la risposta dell'utente. Parametri: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Ecco un esempio che usa l'API host per mostrare una snackbar e chiudere il pannello laterale dopo il completamento di un'azione: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Abilità - -Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Punti chiave: - -* `name` is a unique identifier string for the skill (kebab-case recommended). -* `label` is the human-readable display name shown in the UI. -* `content` contains the skill instructions — this is the text the AI agent uses. -* `icon` (optional) sets the icon displayed in the UI. -* `description` (optional) provides additional context about the skill's purpose. - -You can create new skills in two ways: - -* **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill. -* **Manual**: Create a new file and use `defineSkill()`, following the same pattern. - -### Agenti - -Gli Agenti definiscono agenti IA con prompt di sistema che possono operare all'interno del tuo spazio di lavoro. Usa `defineAgent()` per definire agenti con convalida integrata: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Punti chiave: - -* `name` è una stringa identificativa univoca per l'agente (kebab-case consigliato). -* `label` è il nome di visualizzazione leggibile mostrato nell'UI. -* `prompt` contiene il prompt di sistema — è il testo di istruzioni che definisce il comportamento dell'agente. -* `icon` (opzionale) imposta l'icona visualizzata nell'UI. -* `description` (opzionale) fornisce contesto aggiuntivo sullo scopo dell'agente. - -Puoi creare nuovi agenti in due modi: - -* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere un nuovo agente. -* **Manuale**: Crea un nuovo file e usa `defineAgent()`, seguendo lo stesso schema. - -### Client tipizzati generati - -Due client tipizzati sono generati automaticamente da `yarn twenty dev` e salvati in `node_modules/twenty-sdk/clients` in base allo schema della tua area di lavoro: - -* **`CoreApiClient`** — interroga l'endpoint `/graphql` per i dati dell'area di lavoro -* **`MetadataApiClient`** — interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro e il caricamento dei file - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });},{ -``` - -`CoreApiClient` viene rigenerato automaticamente da `yarn twenty dev` ogni volta che i tuoi oggetti o campi cambiano. `MetadataApiClient` è fornito pronto all'uso con l'SDK. - -#### Credenziali di runtime nelle funzioni logiche - -Quando la tua funzione viene eseguita su Twenty, la piattaforma inietta le credenziali come variabili d'ambiente prima dell'esecuzione del tuo codice: - -* `TWENTY_API_URL`: URL di base dell'API Twenty a cui punta la tua app. -* `TWENTY_API_KEY`: Chiave a breve durata con ambito al ruolo funzione predefinito della tua applicazione. - -Note: - -* Non è necessario passare URL o chiave API al client generato. Legge `TWENTY_API_URL` e `TWENTY_API_KEY` da process.env in fase di esecuzione. -* I permessi della chiave API sono determinati dal ruolo referenziato nel tuo `application-config.ts` tramite `defaultRoleUniversalIdentifier`. Questo è il ruolo predefinito utilizzato dalle funzioni logiche della tua applicazione. -* Le applicazioni possono definire ruoli per seguire il principio del privilegio minimo. Concedi solo i permessi necessari alle tue funzioni, quindi punta `defaultRoleUniversalIdentifier` all'identificatore universale di quel ruolo. - -#### Caricamento dei file - -Il `MetadataApiClient` include un metodo `uploadFile` per allegare file ai campi di tipo file sugli oggetti del tuo spazio di lavoro. Poiché i client GraphQL standard non supportano nativamente il caricamento di file multipart, il client fornisce questo metodo dedicato che implementa la [specifica della richiesta GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec) dietro le quinte. - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // contenuto del file come Buffer - 'invoice.pdf', // nome del file - 'application/pdf', // Tipo MIME (predefinito: 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // identificatore universale del campo -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -La firma del metodo: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parametro | Tipo | Descrizione | -| ---------------------------------- | -------- | ------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Il contenuto grezzo del file | -| `filename` | `string` | Il nome del file (utilizzato per l'archiviazione e la visualizzazione) | -| `contentType` | `string` | Tipo MIME del file (predefinito su `application/octet-stream` se omesso) | -| `fieldMetadataUniversalIdentifier` | `string` | L'`universalIdentifier` del campo di tipo file nel tuo oggetto | - -Punti chiave: - -* Il metodo `uploadFile` è disponibile su `MetadataApiClient` perché la mutazione di upload viene risolta dall'endpoint `/metadata`. -* Usa l'`universalIdentifier` del campo (non il suo ID specifico dello spazio di lavoro), quindi il tuo codice di upload funziona in qualsiasi spazio di lavoro in cui la tua app è installata — coerentemente con il modo in cui le app fanno riferimento ai campi altrove. -* L'`url` restituito è un URL firmato che puoi usare per accedere al file caricato. - -### Esempio Hello World - -Esplora un esempio minimale end-to-end che dimostra oggetti, funzioni logiche, componenti front-end e trigger multipli [qui](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## Compilazione della tua app - -Una volta che hai sviluppato la tua app con `app:dev`, usa `app:build` per compilarla in un pacchetto distribuibile. - -```bash filename="Terminal" -# Compila l'app (l'output va in .twenty/output/) -yarn twenty build - -# Compila e crea un tarball (.tgz) per la distribuzione -yarn twenty build --tarball -``` - -Il processo di compilazione: - -1. **Analizza e convalida il manifest** — legge tutte le entità `defineX()` dai tuoi file sorgente e convalida la struttura del manifest. -2. **Compila le funzioni di logica e i componenti front-end** — raggruppa i sorgenti TypeScript in file ESM `.mjs` usando esbuild. -3. **Genera i checksum** — calcola gli hash MD5 per ogni file compilato, memorizzati nel manifest come `builtHandlerChecksum` / `builtComponentChecksum`. -4. **Genera il client API tipizzato** — esegue l'analisi dello schema GraphQL e genera i client tipizzati `CoreApiClient` e `MetadataApiClient`. -5. **Esegue un controllo dei tipi di TypeScript** — esegue `tsc --noEmit` per intercettare gli errori di tipo prima della pubblicazione. -6. **Ricompila con il client generato** — esegue una seconda passata di compilazione in modo da includere i tipi del client generato. -7. **Crea facoltativamente un tarball** — se viene passato `--tarball`, esegue `npm pack` per creare un file `.tgz` pronto per la distribuzione. - -L'output della build in `.twenty/output/` contiene: - -```text -.twenty/output/ -├── manifest.json # Manifest con checksum per tutti i file compilati -├── package.json # Copiato dalla radice dell'app -├── yarn.lock # Copiato dalla radice dell'app -├── src/ -│ ├── logic-functions/ # File .mjs compilati delle funzioni logiche -│ └── front-components/ # File .mjs compilati dei componenti front-end -├── public/ # Asset statici (se presenti) -└── my-app-1.0.0.tgz # Solo con il flag --tarball -``` - -| Opzione | Descrizione | -| ----------- | ------------------------------------------------------------------- | -| `[appPath]` | Percorso della directory dell'app (predefinito: directory corrente) | -| `--tarball` | Imballa anche l'output in un tarball `.tgz` | - -## Pubblicazione della tua app - -Usa `app:publish` per distribuire la tua app — al registro npm oppure direttamente a un server Twenty. - -### Pubblica su npm (predefinito) - -```bash filename="Terminal" -# Pubblica su npm (richiede l'accesso a npm) -yarn twenty publish - -# Pubblica con un dist-tag (ad es. beta, next) -yarn twenty publish --tag beta -``` - -Questo compila l'app ed esegue `npm publish` dalla directory `.twenty/output/`. Il pacchetto pubblicato può quindi essere installato dal marketplace di Twenty da qualsiasi area di lavoro. - -### Pubblica su un server Twenty - -```bash filename="Terminal" -# Pubblica direttamente su un server Twenty -yarn twenty publish --server https://app.twenty.com -``` - -Questo compila l'app con un tarball, lo carica sul server tramite la mutation GraphQL `uploadAppTarball` e avvia l'installazione in un unico passaggio. Questo è utile per distribuzioni private o per effettuare test su un server specifico. - -| Opzione | Descrizione | -| ----------------- | -------------------------------------------------------------------------- | -| `[appPath]` | Percorso della directory dell'app (predefinito: directory corrente) | -| `--server ` | Pubblica su un server Twenty invece di npm | -| `--token ` | Token di autenticazione per il server di destinazione | -| `--tag ` | dist-tag di npm (ad es. `beta`, `next`) — solo per la pubblicazione su npm | - -## Registrazione dell'applicazione - -Prima che un'app possa essere installata in un'area di lavoro, deve essere **registrata**. Una registrazione è un record di metadati che descrive l'origine dell'app e come autenticarla. Nella maggior parte dei casi questo è gestito automaticamente dalla CLI. - -### Tipi di origine - -Ogni registrazione ha un **tipo di origine** che determina come vengono risolti i file dell'app durante l'installazione: - -| Tipo di origine | Come vengono risolti i file | Caso d'uso tipico | -| --------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------- | -| `LOCAL` | I file sono sincronizzati in tempo reale dal watcher della CLI — l'installazione viene saltata | Sviluppo con `app:dev` | -| `NPM` | Recuperati dal registro npm tramite il campo `sourcePackage` | App pubblicate su npm | -| `TARBALL` | Estratti da un file `.tgz` caricato e archiviato sul server | App private pubblicate con `--server` | - -### Come avviene la registrazione - -* **`app:dev`** — crea automaticamente una registrazione `LOCAL` la prima volta che esegui la modalità di sviluppo su un'area di lavoro. -* **`app:publish --server`** — carica un tarball e crea (o aggiorna) una registrazione `TARBALL`, quindi installa l'app. -* **Marketplace npm** — le registrazioni `NPM` vengono create quando le app vengono sincronizzate dal registro npm nel catalogo del marketplace di Twenty. -* **GraphQL API** — puoi anche creare registrazioni in modo programmatico tramite la mutation `createApplicationRegistration`. - -### Registrazione vs installazione - -**Registrazione** e **installazione** sono concetti distinti: - -* Una **registrazione** (`ApplicationRegistration`) è un record di metadati globale che descrive l'app: il suo nome, il tipo di origine, le credenziali OAuth e lo stato di pubblicazione nel marketplace. Esiste indipendentemente da qualsiasi area di lavoro. -* Un'**installazione** (`Application`) è un'istanza per area di lavoro. Quando un utente installa un'app, Twenty risolve il pacchetto dalla sorgente della registrazione, scrive i file compilati nell'archiviazione e sincronizza il manifest (creando oggetti, campi, funzioni logiche, ecc.) in quell'area di lavoro. - -Una registrazione può essere installata in molte aree di lavoro. Ogni area di lavoro ottiene la propria copia dei file dell'app e del modello di dati. - -### Credenziali OAuth - -Ogni registrazione include credenziali OAuth (`oAuthClientId` e `oAuthClientSecret`) generate al momento della creazione. Queste vengono utilizzate dall'app per autenticare le richieste API per conto degli utenti. Il client secret viene restituito **una sola volta** alla creazione — conservalo in modo sicuro. Puoi ruotarlo in seguito tramite la mutation `rotateApplicationRegistrationClientSecret`. - -## Configurazione manuale (senza lo scaffolder) - -Sebbene consigliamo di utilizzare `create-twenty-app` per la migliore esperienza iniziale, puoi anche configurare un progetto manualmente. Non installare la CLI globalmente. Invece, aggiungi `twenty-sdk` come dipendenza locale e collega un unico script nel tuo package.json: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Quindi aggiungi uno script `twenty`: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Ora puoi eseguire tutti i comandi tramite `yarn twenty `, ad es. `yarn twenty dev`, `yarn twenty help`, ecc. - -## Risoluzione dei problemi - -* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti. -* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile. -* Tipi o client mancanti/obsoleti: riavvia `yarn twenty dev` — genera automaticamente il client tipizzato. -* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente. - -Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/it/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx index 2d88eb4bbb..3002bb08cd 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx @@ -4,39 +4,174 @@ description: Defina objetos, funções de lógica, componentes de front-end e mu --- -Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. + Apps are currently in alpha. The feature works but is still evolving. -## Use os recursos do SDK (tipos e configuração) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -O twenty-sdk fornece blocos de construção tipados e funções utilitárias que você usa dentro do seu aplicativo. A seguir estão as partes principais que você usará com mais frequência. +## DefineEntity functions -### Funções utilitárias +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos. -O SDK fornece funções utilitárias para definir as entidades do seu app. Conforme descrito em [Detecção de entidades](/l/pt/developers/extend/apps/getting-started#entity-detection), você deve usar `export default define({...})` para que suas entidades sejam detectadas: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| Função | Finalidade | -| -------------------------------- | ------------------------------------------------------------------ | -| `defineApplication` | Configurar metadados do aplicativo (obrigatório, um por app) | -| `defineObject` | Define objetos personalizados com campos | -| `defineLogicFunction` | Defina funções de lógica com handlers | -| `definePreInstallLogicFunction` | Defina uma função de lógica de pré-instalação (uma por aplicativo) | -| `definePostInstallLogicFunction` | Defina uma função de lógica de pós-instalação (uma por aplicativo) | -| `defineFrontComponent` | Definir componentes de front-end para UI personalizada | -| `defineRole` | Configura permissões de papéis e acesso a objetos | -| `defineField` | Estender objetos existentes com campos adicionais | -| `defineView` | Define visualizações salvas para objetos | -| `defineNavigationMenuItem` | Define links de navegação da barra lateral | -| `defineSkill` | Define habilidades de agente de IA | + + -Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos. +Roles encapsulate permissions on your workspace's objects and actions. -### Definindo objetos +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notas: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### Metadados do Marketplace + +If you plan to [publish your app](/l/pt/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| Campo | Descrição | +| ------------------ | ----------------------------------------------------------------------------------------------------------------- | +| `autor` | Nome do autor ou da empresa | +| `categoria` | Categoria do app para filtragem no marketplace | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | Descrição em markdown mais longa para a aba "Sobre". Se omitido, o marketplace usa o `README.md` do pacote no npm | +| `websiteUrl` | Link para seu site | +| `termsUrl` | Link para os Termos de Serviço | +| `emailSupport` | Endereço de e-mail de suporte | +| `issueReportUrl` | Link para o rastreador de problemas | + +#### Papéis e permissões + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts** aponta para esse papel para que suas funções herdem suas permissões. + +Notas: +* Comece pelo papel gerado pelo scaffold e depois restrinja-o progressivamente seguindo o princípio do menor privilégio. +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags` controlam o acesso a recursos em nível de plataforma. Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Objetos personalizados descrevem tanto o esquema quanto o comportamento de registros no seu espaço de trabalho. Use `defineObject()` para definir objetos com validação integrada: -```typescript -// src/app/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -110,7 +245,7 @@ Pontos-chave: * O `universalIdentifier` deve ser exclusivo e estável entre implantações. * Cada campo requer `name`, `type`, `label` e seu próprio `universalIdentifier` estável. * O array `fields` é opcional — você pode definir objetos sem campos personalizados. -* Você pode criar novos objetos usando `yarn twenty entity:add`, que orienta você sobre nomeação, campos e relacionamentos. +* Você pode criar novos objetos usando `yarn twenty add`, que orienta você sobre nomeação, campos e relacionamentos. **Os campos base são criados automaticamente.** Quando você define um objeto personalizado, o Twenty adiciona automaticamente campos padrão @@ -120,120 +255,202 @@ Você pode substituir os campos padrão definindo um campo com o mesmo nome no s mas isso não é recomendado. -### Configuração do aplicativo (application-config.ts) + + -Todo aplicativo tem um único arquivo `application-config.ts` que descreve: +Use `defineField()` para adicionar campos a objetos que não são seus — como objetos padrão do Twenty (Person, Company, etc.). ou a objetos de outros apps. Ao contrário dos campos inline em `defineObject()`, os campos independentes exigem um `objectUniversalIdentifier` para especificar qual objeto eles estendem: -* **O que é o aplicativo**: identificadores, nome de exibição e descrição. -* **Como suas funções são executadas**: qual papel usam para permissões. -* **Variáveis (opcional)**: pares chave–valor expostos às suas funções como variáveis de ambiente. -* **(Opcional) função de pré-instalação**: uma função de lógica que é executada antes da instalação do aplicativo. -* **(Opcional) função de pós-instalação**: uma função de lógica que é executada após a instalação do aplicativo. +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk'; -Use `defineApplication()` para definir a configuração do seu aplicativo: +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; +Pontos-chave: +* `objectUniversalIdentifier` identifica o objeto de destino. Para objetos padrão, use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportado de `twenty-sdk`. +* Ao definir campos inline em `defineObject()`, você não precisa de `objectUniversalIdentifier` — ele é herdado do objeto pai. +* `defineField()` é a única forma de adicionar campos a objetos que você não criou com `defineObject()`. -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, + + + +As relações conectam objetos entre si. No Twenty, as relações são sempre **bidirecionais** — você define ambos os lados, e cada lado faz referência ao outro. + +Existem dois tipos de relação: + +| Tipo de relação | Descrição | Tem chave estrangeira? | +| --------------- | ----------------------------------------------------------------- | ---------------------- | +| `MANY_TO_ONE` | Muitos registros deste objeto apontam para um registro do destino | Sim (`joinColumnName`) | +| `ONE_TO_MANY` | Um registro deste objeto possui muitos registros do destino | Não (lado inverso) | + +#### Como as relações funcionam + +Toda relação requer **dois campos** que façam referência um ao outro: + +1. O lado **MANY_TO_ONE** — fica no objeto que contém a chave estrangeira +2. O lado **ONE_TO_MANY** — fica no objeto que possui a coleção + +Ambos os campos usam `FieldType.RELATION` e fazem referência cruzada um ao outro via `relationTargetFieldMetadataUniversalIdentifier`. + +#### Exemplo: Um cartão postal tem muitos destinatários + +Suponha que um `PostCard` possa ser enviado para muitos registros `PostCardRecipient`. Cada destinatário pertence a exatamente um cartão postal. + +**Etapa 1: Defina o lado ONE_TO_MANY em PostCard** (o lado "um"): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, }); ``` -Notas: +**Etapa 2: Defina o lado MANY_TO_ONE em PostCardRecipient** (o lado "muitos" — contém a chave estrangeira): -* `universalIdentifier` são IDs determinísticos que você controla; gere-os uma vez e mantenha-os estáveis entre sincronizações. -* `applicationVariables` tornam-se variáveis de ambiente para suas funções (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` deve corresponder ao arquivo do papel (veja abaixo). -* As funções de pré-instalação e pós-instalação são detectadas automaticamente durante a geração do manifesto. Consulte [Funções de pré-instalação](#pre-install-functions) e [Funções de pós-instalação](#post-install-functions). +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; -#### Papéis e permissões +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; -Os aplicativos podem definir papéis que encapsulam permissões sobre os objetos e ações do seu espaço de trabalho. O campo `defaultRoleUniversalIdentifier` em `application-config.ts` designa o papel padrão usado pelas funções de lógica do seu app. - -* A chave de API em tempo de execução, injetada como `TWENTY_API_KEY`, é derivada desse papel padrão de função. -* O cliente tipado ficará restrito às permissões concedidas a esse papel. -* Siga o princípio do menor privilégio: crie um papel dedicado com apenas as permissões de que suas funções precisam e, em seguida, faça referência ao seu identificador universal. - -##### Papel de função padrão (*.role.ts) - -Ao criar um novo aplicativo com o scaffold, a CLI também cria um arquivo de papel padrão. Use `defineRole()` para definir papéis com validação integrada: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, }); ``` -O `universalIdentifier` desse papel é então referenciado em `application-config.ts` como `defaultRoleUniversalIdentifier`. Em outras palavras: + +**Importações circulares:** Ambos os campos de relação referenciam o `universalIdentifier` um do outro. Para evitar problemas de importação circular, exporte os IDs dos seus campos como constantes nomeadas de cada arquivo e importe-os no outro arquivo. O sistema de build resolve isso em tempo de compilação. + -* **\*.role.ts** define o que o papel de função padrão pode fazer. -* **application-config.ts** aponta para esse papel para que suas funções herdem suas permissões. +#### Relacionando a objetos padrão -Notas: +Para criar uma relação com um objeto integrado do Twenty (Person, Company, etc.), use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -* Comece pelo papel gerado pelo scaffold e depois restrinja-o progressivamente seguindo o princípio do menor privilégio. -* Substitua `objectPermissions` e `fieldPermissions` pelos objetos/campos de que suas funções precisam. -* `permissionFlags` controlam o acesso a recursos em nível de plataforma. Mantenha-os mínimos; adicione apenas o que for necessário. -* Veja um exemplo funcional no app Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; -### Configuração de função de lógica e ponto de entrada +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### Propriedades de campos de relação + +| Propriedade | Obrigatório | Descrição | +| ------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------- | +| `tipo` | Sim | Deve ser `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do objeto de destino | +| `relationTargetFieldMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do campo correspondente no objeto de destino | +| `universalSettings.relationType` | Sim | `RelationType.MANY_TO_ONE` ou `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Apenas para MANY_TO_ONE | O que acontece quando o registro referenciado é excluído: `CASCADE`, `SET_NULL`, `RESTRICT` ou `NO_ACTION` | +| `universalSettings.joinColumnName` | Apenas para MANY_TO_ONE | Nome da coluna no banco de dados para a chave estrangeira (por exemplo, `postCardId`) | + +#### Campos de relação inline em defineObject + +Você também pode definir campos de relação diretamente dentro de `defineObject()`. Nesse caso, omita `objectUniversalIdentifier` — ele é herdado do objeto pai: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + Cada arquivo de função usa `defineLogicFunction()` para exportar uma configuração com um handler e gatilhos opcionais. -```typescript -// src/app/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -256,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Tipos de gatilho comuns: - -* **route**: Expõe sua função em um caminho e método HTTP **no endpoint `/s/`**: - -> por exemplo, `path: '/post-card/create',` -> chamar em `/s/post-card/create` - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**: Executa sua função em um agendamento usando uma expressão CRON. * **databaseEvent**: Executa em eventos do ciclo de vida de objetos do espaço de trabalho. Quando a operação do evento é `updated`, campos específicos a serem observados podem ser especificados no array `updatedFields`. Se deixar indefinido ou vazio, qualquer atualização acionará a função. +> e.g. `person.updated`, `*.created`, `company.*` -> por exemplo, `person.updated` - -Notas: - -* O array `triggers` é opcional. Funções sem gatilhos podem ser usadas como funções utilitárias chamadas por outras funções. -* Você pode misturar vários tipos de gatilho em uma única função. - -### Funções de pré-instalação - -Uma função de pré-instalação é uma função de lógica que é executada automaticamente antes de o seu aplicativo ser instalado em um espaço de trabalho. Isso é útil para tarefas de validação, verificações de pré-requisitos ou para preparar o estado do espaço de trabalho antes que a instalação principal prossiga. - -Ao criar a estrutura de um novo app com `create-twenty-app`, uma função de pré-instalação é gerada para você em `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Você também pode executar manualmente a função de pré-instalação a qualquer momento usando a CLI: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty function:execute --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Pontos-chave: - -* As funções de pré-instalação usam `definePreInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* O manipulador recebe um `InstallLogicFunctionPayload` com `{ previousVersion: string }` — a versão do app que foi instalada anteriormente (ou uma string vazia para instalações novas). -* É permitida apenas uma função de pré-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada. -* O `universalIdentifier` da função é definido automaticamente como `preInstallLogicFunctionUniversalIdentifier` no manifesto do aplicativo durante a geração — você não precisa referenciá-lo em `defineApplication()`. -* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de preparação mais longas. -* As funções de pré-instalação não precisam de gatilhos — elas são invocadas pela plataforma antes da instalação ou manualmente via `function:execute --preInstall`. - -### Funções de pós-instalação - -Uma função de pós-instalação é uma função de lógica que é executada automaticamente após o seu aplicativo ser instalado em um espaço de trabalho. Isso é útil para tarefas de configuração únicas, como preencher dados padrão, criar registros iniciais ou configurar as configurações do espaço de trabalho. - -Ao criar a estrutura de um novo app com `create-twenty-app`, uma função de pós-instalação é gerada para você em `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI: - ```bash filename="Terminal" -yarn twenty function:execute --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Pontos-chave: +You can watch logs with: -* As funções de pós-instalação usam `definePostInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* O manipulador recebe um `InstallLogicFunctionPayload` com `{ previousVersion: string }` — a versão do app que foi instalada anteriormente (ou uma string vazia para instalações novas). -* É permitida apenas uma função de pós-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada. -* O `universalIdentifier` da função é definido automaticamente como `postInstallLogicFunctionUniversalIdentifier` no manifesto do aplicativo durante a geração — você não precisa referenciá-lo em `defineApplication()`. -* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados. -* As funções de pós-instalação não precisam de gatilhos — elas são invocadas pela plataforma durante a instalação ou manualmente via `function:execute --postInstall`. - -### Payload de gatilho de rota - - -**Alteração incompatível (v1.16, janeiro de 2026):** O formato do payload de gatilho de rota mudou. Antes da v1.16, os parâmetros de consulta, parâmetros de caminho e corpo eram enviados diretamente como o payload. A partir da v1.16, eles ficam aninhados dentro de um objeto estruturado `RoutePayload`. - -**Antes da v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**Depois da v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Payload de gatilho de rota -**Para migrar funções existentes:** Atualize seu handler para desestruturar de `event.body`, `event.queryStringParameters` ou `event.pathParameters` em vez de diretamente do objeto de parâmetros. - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -Quando um gatilho de rota invoca sua função de lógica, ela recebe um objeto `RoutePayload` que segue o formato do AWS HTTP API v2. Importe o tipo de `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -416,41 +531,39 @@ const handler = async (event: RoutePayload) => { O tipo `RoutePayload` tem a seguinte estrutura: -| Propriedade | Tipo | Descrição | -| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `headers` | `Record` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | -| `pathParameters` | `Record` | Parâmetros de caminho extraídos do padrão de rota (por exemplo, `/users/:id` → `{ id: '123' }`) | -| `body` | `object \| null` | Corpo da requisição analisado (JSON) | -| `isBase64Encoded` | `boolean` | Se o corpo está codificado em base64 | -| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Caminho bruto da requisição | + | Propriedade | Tipo | Descrição | Exemplo | + | ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | see section below | + | `queryStringParameters` | `Record` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Corpo da requisição analisado (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Se o corpo está codificado em base64 | | + | `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Caminho bruto da requisição | | -### Encaminhamento de cabeçalhos HTTP -Por padrão, os cabeçalhos HTTP das requisições recebidas **não** são repassados para sua função de lógica por motivos de segurança. Para acessar cabeçalhos específicos, liste-os explicitamente no array `forwardedRequestHeaders`: +#### forwardedRequestHeaders -```typescript +Por padrão, os cabeçalhos HTTP das requisições recebidas **não** são repassados para sua função de lógica por motivos de segurança. +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -No seu handler, você pode então acessar esses cabeçalhos: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -461,24 +574,18 @@ const handler = async (event: RoutePayload) => { ``` - Os nomes dos cabeçalhos são normalizados para minúsculas. Acesse-os usando chaves em minúsculas (por exemplo, `event.headers['content-type']`). +Os nomes dos cabeçalhos são normalizados para minúsculas. Access them using lowercase keys (e.g., `event.headers['content-type']`). -Você pode criar novas funções de duas formas: +#### Exposing a function as a tool -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova função de lógica. Isso gera um arquivo inicial com um handler e configuração. -* **Manual**: Crie um novo arquivo `*.logic-function.ts` e use `defineLogicFunction()`, seguindo o mesmo padrão. +Funções lógicas podem ser expostas como **ferramentas** para agentes de IA e fluxos de trabalho. When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### Marcar uma função lógica como ferramenta +To mark a logic function as a tool, set `isTool: true`: -Funções lógicas podem ser expostas como **ferramentas** para agentes de IA e fluxos de trabalho. Quando uma função é marcada como ferramenta, ela fica disponível para os recursos de IA do Twenty e pode ser selecionada como uma etapa em automações de fluxos de trabalho. - -Para marcar uma função lógica como ferramenta, defina `isTool: true` e forneça um `toolInputSchema` descrevendo os parâmetros de entrada esperados usando [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/generated'; +import { CoreApiClient } from 'twenty-client-sdk/core'; const handler = async (params: { companyName: string; domain?: string }) => { const client = new CoreApiClient(); @@ -505,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Pontos-chave: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -522,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Pontos-chave: - -* **`isTool`** (`boolean`, padrão: `false`): Quando definido como `true`, a função é registrada como uma ferramenta e fica disponível para agentes de IA e automações de fluxos de trabalho. -* **`toolInputSchema`** (`object`, opcional): Um objeto JSON Schema que descreve os parâmetros que sua função aceita. Os agentes de IA usam esse esquema para entender quais entradas a ferramenta espera e para validar as chamadas. Se omitido, o esquema tem como padrão `{ type: 'object', properties: {} }` (sem parâmetros). -* Funções com `isTool: false` (ou não definido) **não** são expostas como ferramentas. Elas ainda podem ser executadas diretamente ou chamadas por outras funções, mas não aparecerão na descoberta de ferramentas. -* **Nomenclatura de ferramentas**: Quando exposta como uma ferramenta, o nome da função é automaticamente normalizado para `logic_function_` (em minúsculas, caracteres não alfanuméricos substituídos por sublinhados). Por exemplo, `enrich-company` torna-se `logic_function_enrich_company`. -* Você pode combinar `isTool` com gatilhos — uma função pode ser ao mesmo tempo uma ferramenta (chamável por agentes de IA) e acionada por eventos (cron, eventos de banco de dados, rotas) simultaneamente. - **Escreva uma boa `description`.** Os agentes de IA dependem do campo `description` da função para decidir quando usar a ferramenta. Seja específico sobre o que a ferramenta faz e quando ela deve ser chamada. -### Componentes de front-end + + -Componentes de front-end permitem criar componentes React personalizados que são renderizados na UI do Twenty. Use `defineFrontComponent()` para definir componentes com validação integrada: +Uma função de pré-instalação é uma função de lógica que é executada automaticamente antes de o seu aplicativo ser instalado em um espaço de trabalho. Isso é útil para tarefas de validação, verificações de pré-requisitos ou para preparar o estado do espaço de trabalho antes que a instalação principal prossiga. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Você também pode executar manualmente a função de pré-instalação a qualquer momento usando a CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Pontos-chave: +* As funções de pré-instalação usam `definePreInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* O manipulador recebe um `InstallLogicFunctionPayload` com `{ previousVersion: string }` — a versão do app que foi instalada anteriormente (ou uma string vazia para instalações novas). +* É permitida apenas uma função de pré-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada. +* O `universalIdentifier` da função é definido automaticamente como `preInstallLogicFunctionUniversalIdentifier` no manifesto do aplicativo durante a geração — você não precisa referenciá-lo em `defineApplication()`. +* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de preparação mais longas. + + + + +Uma função de pós-instalação é uma função de lógica que é executada automaticamente após o seu aplicativo ser instalado em um espaço de trabalho. Isso é útil para tarefas de configuração únicas, como preencher dados padrão, criar registros iniciais ou configurar as configurações do espaço de trabalho. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Pontos-chave: +* As funções de pós-instalação usam `definePostInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* O manipulador recebe um `InstallLogicFunctionPayload` com `{ previousVersion: string }` — a versão do app que foi instalada anteriormente (ou uma string vazia para instalações novas). +* É permitida apenas uma função de pós-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada. +* O `universalIdentifier` da função é definido automaticamente como `postInstallLogicFunctionUniversalIdentifier` no manifesto do aplicativo durante a geração — você não precisa referenciá-lo em `defineApplication()`. +* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados. + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Pontos-chave: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* Componentes de front-end são componentes React que renderizam em contextos isolados dentro do Twenty. -* O campo `component` faz referência ao seu componente React. -* Os componentes são compilados e sincronizados automaticamente durante `yarn twenty dev`. +
+ Quick action button in the top-right corner +
-Você pode criar novos componentes de front-end de duas formas: +Click it to render the component inline. -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo componente de front-end. -* **Manual**: Crie um novo arquivo `.tsx` e use `defineFrontComponent()`, seguindo o mesmo padrão. +{/* TODO: add screenshot of the rendered front component */} -### Habilidades +#### Configuration fields + +| Campo | Obrigatório | Descrição | +| --------------------- | ----------- | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | Sim | Stable unique ID for this component | +| `component` | Sim | A React component function | +| `name` | Não | Display name | +| `description` | Não | Description of what the component does | +| `isHeadless` | Não | Set to `true` if the component has no visible UI (see below) | +| `command` | Não | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| Hook | Returns | Descrição | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | varia | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| Função | Descrição | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | Fecha o painel lateral | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| Campo | Obrigatório | Descrição | +| --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Sim | Stable unique ID for the command | +| `label` | Sim | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | Não | Shorter label displayed on the pinned quick-action button | +| `icon` | Não | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Não | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | Não | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | Não | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | Não | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| Variável | Tipo | Descrição | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `number` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `object` | Permissions for the current object type | +| `targetObjectReadPermissions` | `object` | Read permissions for the target object | +| `targetObjectWritePermissions` | `object` | Write permissions for the target object | +| `featureFlags` | `object` | Active feature flags | +| `objectMetadataItem` | `object` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | Descrição | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### Estilização + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + As habilidades definem instruções e capacidades reutilizáveis que os agentes de IA podem usar no seu espaço de trabalho. Use `defineSkill()` para definir habilidades com validação integrada: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -593,56 +1015,432 @@ export default defineSkill({ ``` Pontos-chave: - * `name` é uma string de identificador exclusivo para a habilidade (recomenda-se kebab-case). * `label` é o nome de exibição legível por humanos mostrado na UI. * `content` contém as instruções da habilidade — este é o texto que o agente de IA usa. * `icon` (opcional) define o ícone exibido na UI. * `description` (opcional) fornece contexto adicional sobre a finalidade da habilidade. -Você pode criar novas habilidades de duas formas: + + -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova habilidade. -* **Manual**: Crie um novo arquivo e use `defineSkill()`, seguindo o mesmo padrão. +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### Clientes tipados gerados +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -Dois clientes tipados são gerados automaticamente pelo `yarn twenty dev` e armazenados em `node_modules/twenty-sdk/generated` com base no esquema do seu espaço de trabalho: - -* **`CoreApiClient`** — consulta o endpoint `/graphql` para dados do espaço de trabalho -* **`MetadataApiClient`** — consulta o endpoint `/metadata` para obter a configuração do espaço de trabalho e o carregamento de arquivos - -```typescript -import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); ``` -Ambos os clientes são regenerados automaticamente pelo `yarn twenty dev` sempre que seus objetos ou campos forem alterados. +Pontos-chave: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon` (opcional) define o ícone exibido na UI. +* `modelId` (optional) overrides the default AI model used by the agent. -#### Credenciais em tempo de execução em funções de lógica + + -Quando sua função é executada no Twenty, a plataforma injeta credenciais como variáveis de ambiente antes da execução do seu código: +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: -* `TWENTY_API_URL`: URL base da API do Twenty que seu aplicativo usa como alvo. -* `TWENTY_API_KEY`: Chave de curta duração com escopo para o papel de função padrão do seu aplicativo. +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -Notas: +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` -* Você não precisa passar a URL ou a chave de API para o cliente gerado. Ele lê `TWENTY_API_URL` e `TWENTY_API_KEY` de process.env em tempo de execução. -* As permissões da chave de API são determinadas pelo papel referenciado no seu `application-config.ts` via `defaultRoleUniversalIdentifier`. Este é o papel padrão usado pelas funções de lógica do seu app. -* Os aplicativos podem definir papéis para seguir o princípio do menor privilégio. Conceda apenas as permissões de que suas funções precisam e, em seguida, aponte `defaultRoleUniversalIdentifier` para o identificador universal desse papel. +Pontos-chave: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Pontos-chave: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Pontos-chave: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| Tipo de entidade | Comando | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| Objeto | `yarn twenty add object` | `src/objects/.ts` | +| Campo | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Função | `yarn twenty add role` | `src/roles/.ts` | +| Habilidade | `yarn twenty add skill` | `src/skills/.ts` | +| Agente | `yarn twenty add agent` | `src/agents/.ts` | +| Vista | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +O pacote `twenty-client-sdk` fornece dois clientes GraphQL tipados para interagir com a API do Twenty a partir das suas funções de lógica e componentes de front-end. + +| Cliente | Importar | Endpoint | Gerado? | +| ------------------- | ---------------------------- | -------------------------------------------------------------------- | -------------------------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — dados do espaço de trabalho (registros, objetos) | Sim, em tempo de dev/build | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configuração do espaço de trabalho, upload de arquivos | Não, vem pré-compilado | + + + + +`CoreApiClient` é o cliente principal para consultar e mutar dados do espaço de trabalho. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +O cliente usa uma sintaxe de selection-set: passe `true` para incluir um campo, use `__args` para argumentos e aninhe objetos para relações. Você tem preenchimento automático e verificação de tipos completos com base no schema do seu espaço de trabalho. + + +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. + + +#### Usando CoreSchema para anotações de tipo + +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` é fornecido pré-compilado com o SDK (não é necessário gerar). Ele consulta o endpoint `/metadata` para configuração do espaço de trabalho, aplicativos e upload de arquivos. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` #### Carregamento de arquivos -O `MetadataApiClient` gerado inclui um método `uploadFile` para anexar arquivos a campos do tipo arquivo nos objetos do seu espaço de trabalho. Como os clientes GraphQL padrão não suportam nativamente o carregamento de arquivos multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de solicitações multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec). +`MetadataApiClient` inclui um método `uploadFile` para anexar arquivos a campos do tipo arquivo: -```typescript -import { MetadataApiClient } from 'twenty-sdk/generated'; +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; const metadataClient = new MetadataApiClient(); @@ -652,38 +1450,266 @@ const fileBuffer = fs.readFileSync('./invoice.pdf'); const uploadedFile = await metadataClient.uploadFile( fileBuffer, // file contents as a Buffer 'invoice.pdf', // filename - 'application/pdf', // MIME type (defaults to 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier ); console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -A assinatura do método: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parâmetro | Tipo | Descrição | -| ---------------------------------- | -------- | ------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | O conteúdo bruto do arquivo | -| `filename` | `string` | O nome do arquivo (usado para armazenamento e exibição) | -| `contentType` | `string` | Tipo MIME do arquivo (padrão para `application/octet-stream` se omitido) | -| `fieldMetadataUniversalIdentifier` | `string` | O `universalIdentifier` do campo do tipo arquivo no seu objeto | +| Parâmetro | Tipo | Descrição | +| ---------------------------------- | -------- | -------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | O conteúdo bruto do arquivo | +| `filename` | `string` | O nome do arquivo (usado para armazenamento e exibição) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | O `universalIdentifier` do campo do tipo arquivo no seu objeto | Pontos-chave: - -* O método `uploadFile` está disponível no `MetadataApiClient` porque a mutação de upload é resolvida pelo endpoint `/metadata`. -* Ele usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado — consistente com a forma como os apps referenciam campos em qualquer outro lugar. +* Usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado. * A `url` retornada é um URL assinado que você pode usar para acessar o arquivo enviado. -### Exemplo Hello World + + -Explore um exemplo mínimo de ponta a ponta que demonstra objetos, funções de lógica, componentes de front-end e vários gatilhos [aqui](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + Quando seu código é executado no Twenty (funções de lógica ou componentes de front-end), a plataforma injeta credenciais como variáveis de ambiente: + + * `TWENTY_API_URL` — URL base da API do Twenty + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + Você **não** precisa passá-las para os clientes — eles leem de `process.env` automaticamente. As permissões da chave de API são determinadas pelo papel referenciado em `defaultRoleUniversalIdentifier` no seu `application-config.ts`. + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### Configuração + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| Função | Descrição | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## Referência da CLI + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx index f2f35b93dd..43ce8d26ea 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx @@ -4,84 +4,142 @@ description: Crie seu primeiro app do Twenty em minutos. --- -Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. +Apps are currently in alpha. The feature works but is still evolving. Os apps permitem que você estenda o Twenty com objetos, campos, funções de lógica, habilidades de IA e componentes de UI personalizados — tudo gerenciado como código. -**O que você pode fazer hoje:** - -* Defina objetos e campos personalizados como código (modelo de dados gerenciado) -* Crie funções de lógica com gatilhos personalizados (rotas HTTP, cron, eventos de banco de dados) -* Defina habilidades para agentes de IA -* Crie componentes de front-end que renderizam dentro da UI do Twenty -* Implemente o mesmo aplicativo em vários espaços de trabalho - ## Pré-requisitos -* Node.js 24+ e Yarn 4 -* Docker (para o servidor de desenvolvimento local do Twenty) +Before you begin, make sure the following is installed on your machine: -## Primeiros passos +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Crie um novo aplicativo usando o gerador oficial, depois autentique-se e comece a desenvolver: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app -cd my-twenty-app +``` -# Start dev mode: automatically syncs local changes to your workspace +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. + +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: + +```bash filename="Terminal" +cd my-twenty-app yarn twenty dev ``` -O gerador de estrutura oferece suporte a dois modos para controlar quais arquivos de exemplo são incluídos: +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. + +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: ```bash filename="Terminal" -# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent) -npx create-twenty-app@latest my-app - -# Minimal: only core files (application-config.ts and default-role.ts) -npx create-twenty-app@latest my-app --minimal +yarn twenty dev --verbose ``` -A partir daqui você pode: + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/pt/developers/extend/apps/publishing) for details. + -```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty entity:add +
+ Dev mode terminal output +
-# Watch your application's function logs -yarn twenty function:logs +## Step 6: See your app in Twenty -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: -# Execute the pre-install function -yarn twenty function:execute --preInstall +
+ Your Apps list showing My twenty app +
-# Execute the post-install function -yarn twenty function:execute --postInstall +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. -# Uninstall the application from the current workspace -yarn twenty uninstall +
+ Application registration details +
-# Display commands' help -yarn twenty help -``` +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: -Veja também: as páginas de referência da CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +
+ Installed app — About tab +
-## Estrutura do projeto (com scaffold) +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: -Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador: +
+ Installed app — Content tab +
-* Copia um aplicativo base mínimo para `my-twenty-app/` -* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4 -* Cria arquivos de configuração e scripts conectados à CLI `twenty` -* Gera arquivos principais (configuração da aplicação, papel padrão para funções de lógica, funções de pré-instalação e pós-instalação) além de arquivos de exemplo com base no modo de geração de estrutura +You are all set! Edit any file in `src/` and the changes will be picked up automatically. -Um app recém-criado com o modo padrão `--exhaustive` fica assim: +Head over to [Building Apps](/l/pt/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -94,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -Com `--minimal`, apenas os arquivos principais são criados (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -Em alto nível: +### Key files -* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis. -* **.gitignore**: Ignora artefatos comuns como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, pastas de cobertura, arquivos de log e arquivos `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloqueiam e configuram a ferramenta Yarn 4 usada pelo projeto. -* **.nvmrc**: Fixa a versão do Node.js esperada pelo projeto. -* **.oxlintrc.json** e **tsconfig.json**: Fornecem lint e configuração do TypeScript para os fontes TypeScript do seu aplicativo. -* **README.md**: Um README curto na raiz do aplicativo com instruções básicas. -* **public/**: Uma pasta para armazenar recursos públicos (imagens, fontes, arquivos estáticos) que serão servidos com sua aplicação. Os arquivos colocados aqui são enviados durante a sincronização e ficam acessíveis em tempo de execução. -* **src/**: O local principal onde você define seu aplicativo como código +| File / Folder | Finalidade | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Habilidades que estendem os agentes de IA do Twenty. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Detecção de entidades +## Managing remotes -O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`: - -| Função utilitária | Tipo de entidade | -| -------------------------------- | -------------------------------------------------------------------- | -| `defineObject` | Definições de objetos personalizados | -| `defineLogicFunction` | Definições de funções de lógica | -| `definePreInstallLogicFunction` | Função de lógica de pré-instalação (é executada antes da instalação) | -| `definePostInstallLogicFunction` | Função de lógica de pós-instalação (é executada após a instalação) | -| `defineFrontComponent` | Definições de componentes de front-end | -| `defineRole` | Definições de papéis | -| `defineField` | Extensões de campos para objetos existentes | -| `defineView` | Definições de visualizações salvas | -| `defineNavigationMenuItem` | Definições de itens do menu de navegação | -| `defineSkill` | Definições de habilidades de agente de IA | - - -**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito. - - -Exemplo de uma entidade detectada: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Comandos posteriores adicionarão mais arquivos e pastas: - -* `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`). -* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros. - -## Autenticação - -Na primeira vez que você executar `yarn twenty auth:login`, será solicitado o seguinte: - -* URL da API (padrão: http://localhost:3000 ou o perfil do seu espaço de trabalho atual) -* Chave de API - -Suas credenciais são armazenadas por usuário em `~/.twenty/config.json`. Você pode manter vários perfis e alternar entre eles. - -### Gerenciando espaços de trabalho +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -Depois que você alternar os espaços de trabalho com `yarn twenty auth:switch`, todos os comandos subsequentes usarão esse espaço de trabalho por padrão. Você ainda pode substituí-lo temporariamente com `--workspace `. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Iniciando o servidor + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Comando | Descrição | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Configuração manual (sem o gerador) -Embora recomendemos usar `create-twenty-app` para a melhor experiência inicial, você também pode configurar um projeto manualmente. Não instale a CLI globalmente. Em vez disso, adicione `twenty-sdk` como uma dependência local e configure um único script no seu package.json: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Em seguida, adicione um script `twenty`: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -221,25 +393,19 @@ Em seguida, adicione um script `twenty`: } ``` -Now you can run all commands via `yarn twenty `, e.g. `yarn twenty dev`, `yarn twenty help`, etc. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## Como usar uma instância local do Twenty - -Se você já estiver executando uma instância do Twenty localmente (por exemplo, via `npx nx start twenty-server`), você pode conectar-se a ela em vez de usar o Docker: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Resolução de Problemas -* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias. -* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível. -* Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client. -* Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment. +If you run into issues: -Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx index 3df603cf14..524c5fcc7a 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx @@ -4,15 +4,75 @@ description: Distribua seu aplicativo Twenty no Marketplace ou implante-o intern --- -Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. + Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. ## Visão Geral Depois que seu aplicativo estiver [compilado e testado localmente](/l/pt/developers/extend/apps/building), você tem dois caminhos para distribuí-lo: +* **Implantar um tarball** — envie seu aplicativo diretamente para um servidor Twenty específico para uso interno ou privado. * **Publicar no npm** — liste seu aplicativo no Marketplace da Twenty para que qualquer espaço de trabalho possa descobrir e instalar. -* **Enviar um tarball** — implante seu aplicativo em um servidor Twenty específico para uso interno sem torná-lo público. + +Ambos os caminhos começam na mesma etapa de **build**. + +## Compilando seu app + +Run the build command to compile your app and generate a distribution-ready `manifest.json`: + +```bash filename="Terminal" +yarn twenty build +``` + +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. + +## Implantando em um servidor (tarball) + +Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode implantar um tarball diretamente em um servidor Twenty. + +### Pré-requisitos + +Antes de implantar, você precisa de um remote configurado apontando para o servidor de destino. Os remotes armazenam a URL do servidor e as credenciais de autenticação localmente em `~/.twenty/config.json`. + +Adicionar um remote: + +```bash filename="Terminal" +yarn twenty remote add --api-url https://your-twenty-server.com --as production +``` + +### Implantando + +Compile e envie seu aplicativo para o servidor em uma única etapa: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Compartilhando um aplicativo implantado + +Aplicativos em tarball não são listados no marketplace público, então outros espaços de trabalho no mesmo servidor não os descobrirão ao navegar. Para compartilhar um aplicativo implantado: + +1. Vá para **Configurações > Aplicações > Registros** e abra seu aplicativo +2. Na guia **Distribuição**, clique em **Copiar link de compartilhamento** +3. Compartilhe esse link com usuários de outros espaços de trabalho — ele os leva diretamente para a página de instalação do aplicativo + +O link de compartilhamento usa a URL base do servidor (sem qualquer subdomínio de espaço de trabalho), para funcionar em qualquer espaço de trabalho no servidor. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Gerenciamento de versões + +Para lançar uma atualização: + +1. Atualize o campo `version` no seu `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Os espaços de trabalho que têm o aplicativo instalado verão a atualização disponível em suas configurações + +{/* TODO: add screenshot of the Upgrade button */} ## Publicação no npm @@ -21,29 +81,69 @@ Publicar no npm torna seu aplicativo descobrível no Marketplace da Twenty. Qual ### Requisitos * Uma conta no [npm](https://www.npmjs.com) -* O nome do seu pacote **deve** usar o prefixo `twenty-app-` (por exemplo, `twenty-app-postcard-sender`) +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) -### Etapas - -1. **Compile seu aplicativo** — a CLI compila seus códigos-fonte TypeScript e gera o manifesto do aplicativo: - -```bash filename="Terminal" -yarn twenty build +```json filename="package.json" +{ + "name": "twenty-app-postcard-sender", + "version": "1.0.0", + "keywords": ["twenty-app"] +} ``` -2. **Publicar no npm** — envie o pacote compilado para o registro do npm: +### Metadados do Marketplace -```bash filename="Terminal" -npx twenty publish +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: + +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -### Descoberta automática +See the [defineApplication accordion](/l/pt/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). -Pacotes com o prefixo `twenty-app-` são detectados automaticamente pelo catálogo do Marketplace da Twenty. Depois de publicado, seu aplicativo aparece no Marketplace em poucos minutos — sem necessidade de registro manual ou aprovação. +### Publish + +```bash filename="Terminal" +yarn twenty publish +``` + +Para publicar sob uma dist-tag específica (por exemplo, `beta` ou `next`): + +```bash filename="Terminal" +yarn twenty publish --tag beta +``` + +### Como funciona a descoberta no marketplace + +O servidor Twenty sincroniza seu catálogo do marketplace a partir do registro do npm **a cada hora**. + +You can trigger the sync immediately instead of waiting: + +```bash filename="Terminal" +yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production +``` + +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. + + +Se o seu aplicativo não definir um `aboutDescription` em `defineApplication()`, o marketplace usará automaticamente o `README.md` do seu pacote no npm como conteúdo da página Sobre. Isso significa que você pode manter um único README tanto para o npm quanto para o marketplace da Twenty. Se quiser uma descrição diferente no marketplace, defina explicitamente `aboutDescription`. + ### Publicação via CI -O projeto gerado inclui um workflow do GitHub Actions que publica a cada lançamento. Ele executa `app:build` e depois `npm publish --provenance` a partir da saída do build: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -68,52 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` -Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `npx twenty build` e depois `npm publish` a partir de `.twenty/output`. - - -**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração. - - -## Distribuição interna - -Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode enviar um tarball diretamente para um servidor Twenty. - -### Enviar um tarball - -Compile seu aplicativo e implante-o em um servidor específico em uma única etapa: - -```bash filename="Terminal" -npx twenty publish --server -``` - -Qualquer espaço de trabalho nesse servidor pode então instalar e atualizar o aplicativo na página de configurações de **Aplicativos**. - -### Gerenciamento de versões - -Para lançar uma atualização: - -1. Atualize o campo `version` no seu `package.json` -2. Envie um novo tarball com `npx twenty publish --server ` -3. Os espaços de trabalho nesse servidor verão a atualização disponível nas suas configurações +Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `yarn twenty build` e, em seguida, `npm publish` a partir de `.twenty/output`. -Aplicativos internos ficam restritos ao servidor para o qual são enviados. Eles não aparecem no Marketplace público e não podem ser instalados por espaços de trabalho em outros servidores. +**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração. -## Categorias de aplicativos +## Instalando aplicativos -A Twenty organiza os aplicativos em três categorias com base em como são distribuídos: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. -| Categoria | Como Funciona | Visível no Marketplace? | -| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| **Desenvolvimento** | Aplicativos em modo de desenvolvimento local executados via `yarn twenty dev`. Usados para compilação e testes. | Não | -| **Publicado** | Aplicativos publicados no npm com o prefixo `twenty-app-`. Listados no Marketplace para que qualquer espaço de trabalho possa instalar. | Sim | -| **Interno** | Aplicativos implantados via tarball em um servidor específico. Disponível apenas para espaços de trabalho nesse servidor. | Não | +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. - -Comece no modo de **Desenvolvimento** enquanto cria seu aplicativo. Quando estiver pronto, escolha **Publicado** (npm) para ampla distribuição ou **Interno** (tarball) para implantação privada. - +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: + +```bash filename="Terminal" +yarn twenty install +``` diff --git a/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx index 3cdcb00a79..281965dbd5 100644 --- a/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx @@ -4,1425 +4,78 @@ description: Crie e gerencie personalizações do Twenty como código. --- -Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. +Apps are currently in alpha. The feature works but is still evolving. -## O que são aplicativos? +## What are apps? -Os aplicativos permitem criar e gerenciar personalizações do Twenty **como código**. Em vez de configurar tudo pela UI, você define seu modelo de dados e funções de lógica em código — tornando mais rápido criar, manter e distribuir para vários workspaces. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**O que você pode fazer hoje:** +**O que você pode criar:** -* Defina objetos e campos personalizados como código (modelo de dados gerenciado) -* Crie funções de lógica com gatilhos personalizados -* Defina habilidades e agentes de IA -* Implemente o mesmo aplicativo em vários espaços de trabalho +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Pré-requisitos - -* Node.js 24+ e Yarn 4 -* Docker (para o servidor de desenvolvimento local do Twenty) - -## Primeiros passos - -Crie um novo app usando o gerador oficial de estrutura. Ele pode iniciar automaticamente uma instância local do Twenty para você: +## Quick start ```bash filename="Terminal" -# Criar a estrutura de um novo app — a CLI oferecerá iniciar um servidor local do Twenty npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Iniciar modo de desenvolvimento: sincroniza automaticamente as alterações locais com seu workspace yarn twenty dev ``` -### Gerenciamento do Servidor Local +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/pt/developers/extend/apps/getting-started) guide for the full walkthrough. -O SDK inclui comandos para gerenciar um servidor de desenvolvimento local do Twenty (imagem Docker all-in-one com PostgreSQL, Redis, servidor e worker): +## Detailed guides -```bash filename="Terminal" -# Iniciar o servidor local (faz pull da imagem se necessário) -yarn twenty server start +| Guia | Descrição | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/pt/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/pt/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/pt/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Verificar o status do servidor -yarn twenty server status - -# Transmitir os logs do servidor -yarn twenty server logs - -# Parar o servidor -yarn twenty server stop - -# Redefinir todos os dados e começar do zero -yarn twenty server reset -``` - -O servidor local já vem pré-configurado com um espaço de trabalho e um usuário (`tim@apple.dev` / `tim@apple.dev`), para que você possa começar a desenvolver imediatamente, sem qualquer configuração manual. - -### Autenticação - -Conecte seu aplicativo ao servidor local usando OAuth: - -```bash filename="Terminal" -# Authenticate via OAuth (opens browser) -yarn twenty remote add --local -``` - -O gerador de estrutura oferece suporte a dois modos para controlar quais arquivos de exemplo são incluídos: - -```bash filename="Terminal" -# Padrão (exhaustivo): todos os exemplos (objeto, campo, função de lógica, componente de front-end, visualização, item do menu de navegação, habilidade, agente) -npx create-twenty-app@latest my-app - -# Mínimo: apenas arquivos principais (application-config.ts e default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### Como usar uma instância local do Twenty - -Se você já estiver executando uma instância local do Twenty, você pode conectar-se a ela em vez de usar o Docker. Informe a porta em que seu servidor local está escutando (padrão: `3000`): - -```bash filename="Terminal" -# Durante a criação do projeto -npx create-twenty-app@latest my-app --port 3000 - -# Ou após a criação do projeto -yarn twenty remote add --local --port 3000 -``` - -A partir daqui você pode: - -```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty entity:add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Build the app for distribution -yarn twenty build - -# Publish the app to npm or a Twenty server -yarn twenty publish - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help -``` - -Veja também: as páginas de referência da CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## Estrutura do projeto (com scaffold) - -Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador: - -* Copia um aplicativo base mínimo para `my-twenty-app/` -* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4 -* Cria arquivos de configuração e scripts conectados à CLI `twenty` -* Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode - -Um app recém-criado com o modo padrão `--exhaustive` fica assim: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Public assets folder (images, fonts, etc.) - src/ - ├── application-config.ts # Required - main application configuration - ├── roles/ - │ └── default-role.ts # Default role for logic functions - ├── objects/ - │ └── example-object.ts # Example custom object definition - ├── fields/ - │ └── example-field.ts # Example standalone field definition - ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function - ├── front-components/ - │ └── hello-world.tsx # Example front component - ├── views/ - │ └── example-view.ts # Example saved view definition - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Example sidebar navigation link - ├── skills/ - │ └── example-skill.ts # Example AI agent skill definition - └── agents/ - └── example-agent.ts # Example AI agent definition -``` - -With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). - -Em alto nível: - -* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis. -* **.gitignore**: Ignora artefatos comuns como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, pastas de cobertura, arquivos de log e arquivos `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloqueiam e configuram a ferramenta Yarn 4 usada pelo projeto. -* **.nvmrc**: Fixa a versão do Node.js esperada pelo projeto. -* **.oxlintrc.json** e **tsconfig.json**: Fornecem lint e configuração do TypeScript para os fontes TypeScript do seu aplicativo. -* **README.md**: Um README curto na raiz do aplicativo com instruções básicas. -* **public/**: Uma pasta para armazenar recursos públicos (imagens, fontes, arquivos estáticos) que serão servidos com sua aplicação. Os arquivos colocados aqui são enviados durante a sincronização e ficam acessíveis em tempo de execução. -* **src/**: O local principal onde você define seu aplicativo como código +## Key concepts ### Detecção de entidades -O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Função utilitária | Tipo de entidade | -| ---------------------------------- | ----------------------------------------------------- | -| `defineObject()` | Definições de objetos personalizados | -| `defineLogicFunction()` | Definições de funções de lógica | -| `definePreInstallLogicFunction()` | Pre-install logic function (runs before installation) | -| `definePostInstallLogicFunction()` | Post-install logic function (runs after installation) | -| `defineFrontComponent()` | Definições de componentes de front-end | -| `defineRole()` | Definições de papéis | -| `defineField()` | Extensões de campos para objetos existentes | -| `defineView()` | Definições de visualizações salvas | -| `defineNavigationMenuItem()` | Definições de itens do menu de navegação | -| `defineSkill()` | Definições de habilidades de agente de IA | -| `defineAgent()` | Definições de agentes de IA | +### Available entity types - -**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito. - +| Função | Finalidade | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Exemplo de uma entidade detectada: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Comandos posteriores adicionarão mais arquivos e pastas: - -* `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/clients`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`). -* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros. - -## Autenticação - -Na primeira vez que você executar `yarn twenty auth:login`, será solicitado o seguinte: - -* URL da API (padrão: http://localhost:3000 ou o perfil do seu espaço de trabalho atual) -* Chave de API - -Suas credenciais são armazenadas por usuário em `~/.twenty/config.json`. Você pode manter vários perfis e alternar entre eles. - -### Gerenciando espaços de trabalho +### Referência da CLI ```bash filename="Terminal" -# Fazer login interativamente (recomendado) -yarn twenty auth:login - -# Fazer login em um perfil de espaço de trabalho específico -yarn twenty auth:login --workspace my-custom-workspace - -# Listar todos os espaços de trabalho configurados -yarn twenty auth:list - -# Alterar o espaço de trabalho padrão (interativo) -yarn twenty auth:switch - -# Alternar para um espaço de trabalho específico -yarn twenty auth:switch production - -# Verificar o status atual da autenticação -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -Depois que você alternar os espaços de trabalho com `yarn twenty auth:switch`, todos os comandos subsequentes usarão esse espaço de trabalho por padrão. Você ainda pode substituí-lo temporariamente com `--workspace `. - -## Use os recursos do SDK (tipos e configuração) - -O twenty-sdk fornece blocos de construção tipados e funções utilitárias que você usa dentro do seu aplicativo. A seguir estão as partes principais que você usará com mais frequência. - -### Funções utilitárias - -O SDK fornece funções utilitárias para definir as entidades do seu app. Conforme descrito em [Detecção de entidades](#entity-detection), você deve usar `export default define({...})` para que suas entidades sejam detectadas: - -| Função | Finalidade | -| ---------------------------------- | ------------------------------------------------------------ | -| `defineApplication()` | Configurar metadados do aplicativo (obrigatório, um por app) | -| `defineObject()` | Define objetos personalizados com campos | -| `defineLogicFunction()` | Defina funções de lógica com handlers | -| `definePreInstallLogicFunction()` | Define a pre-install logic function (one per app) | -| `definePostInstallLogicFunction()` | Define a post-install logic function (one per app) | -| `defineFrontComponent()` | Definir componentes de front-end para UI personalizada | -| `defineRole()` | Configura permissões de papéis e acesso a objetos | -| `defineField()` | Estender objetos existentes com campos adicionais | -| `defineView()` | Define visualizações salvas para objetos | -| `defineNavigationMenuItem()` | Define links de navegação da barra lateral | -| `defineSkill()` | Define habilidades de agente de IA | -| `defineAgent()` | Defina agentes de IA com prompts do sistema | - -Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos. - -### Definindo objetos - -Objetos personalizados descrevem tanto o esquema quanto o comportamento de registros no seu espaço de trabalho. Use `defineObject()` para definir objetos com validação integrada: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Pontos-chave: - -* Use `defineObject()` para validação integrada e melhor suporte na IDE. -* O `universalIdentifier` deve ser exclusivo e estável entre implantações. -* Cada campo requer `name`, `type`, `label` e seu próprio `universalIdentifier` estável. -* O array `fields` é opcional — você pode definir objetos sem campos personalizados. -* Você pode criar novos objetos usando `yarn twenty entity:add`, que orienta você sobre nomeação, campos e relacionamentos. - - -**Os campos base são criados automaticamente.** Quando você define um objeto personalizado, o Twenty adiciona automaticamente campos padrão -como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. -Você não precisa definir esses no seu array `fields` — adicione apenas seus campos personalizados. -Você pode substituir os campos padrão definindo um campo com o mesmo nome no seu array `fields`, -mas isso não é recomendado. - - -### Definindo campos em objetos existentes - -Use `defineField()` para adicionar campos personalizados a objetos existentes — tanto objetos padrão (como `company`, `person`, `opportunity`) quanto objetos personalizados definidos por outros aplicativos. Cada campo fica em seu próprio arquivo e faz referência ao objeto de destino por seu `universalIdentifier`. - -Para fazer referência a objetos padrão, importe `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` de `twenty-sdk`. Essa constante fornece identificadores estáveis para todos os objetos integrados e seus campos: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Pontos-chave: - -* `objectUniversalIdentifier` indica ao Twenty a qual objeto anexar o campo. Use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS. { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -}); -``` - -Tipos de gatilho comuns: - -* **route**: Expõe sua função em um caminho e método HTTP **no endpoint `/s/`**: - -> por exemplo, `path: '/post-card/create',` -> chamar em `/s/post-card/create` - -* **cron**: Executa sua função em um agendamento usando uma expressão CRON. -* **databaseEvent**: Executa em eventos do ciclo de vida de objetos do espaço de trabalho. Quando a operação do evento é `updated`, campos específicos a serem observados podem ser especificados no array `updatedFields`. Se deixar indefinido ou vazio, qualquer atualização acionará a função. - -> por exemplo, `person.updated` - -Notas: - -* O array `triggers` é opcional. Funções sem gatilhos podem ser usadas como funções utilitárias chamadas por outras funções. -* Você pode misturar vários tipos de gatilho em uma única função. - -### Pre-install functions - -A pre-install function is a logic function that runs automatically before your app is installed on a workspace. This is useful for validation tasks, prerequisite checks, or preparing workspace state before the main installation proceeds. - -When you scaffold a new app with `create-twenty-app`, a pre-install function is generated for you at `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -You can also manually execute the pre-install function at any time using the CLI: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Pontos-chave: - -* Pre-install functions use `definePreInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs). -* Only one pre-install function is allowed per application. The manifest build will error if more than one is detected. -* The function's `universalIdentifier` is automatically set as `preInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`. -* The default timeout is set to 300 seconds (5 minutes) to allow for longer preparation tasks. -* Pre-install functions do not need triggers — they are invoked by the platform before installation or manually via `function:execute --preInstall`. - -### Funções de pós-instalação - -Uma função de pós-instalação é uma função de lógica que é executada automaticamente após a sua aplicação ser instalada em um espaço de trabalho. Isso é útil para tarefas de configuração únicas, como preencher dados padrão, criar registros iniciais ou configurar as configurações do espaço de trabalho. - -Ao criar a estrutura de um novo app com `create-twenty-app`, uma função de pós-instalação é gerada para você em `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Pontos-chave: - -* Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs). -* Only one post-install function is allowed per application. The manifest build will error if more than one is detected. -* The function's `universalIdentifier` is automatically set as `postInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`. -* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados. -* As funções de pós-instalação não precisam de gatilhos — elas são invocadas pela plataforma durante a instalação ou manualmente via `function:execute --postInstall`. - -### Payload de gatilho de rota - - -**Alteração incompatível (v1.16, janeiro de 2026):** O formato do payload de gatilho de rota mudou. Antes da v1.16, os parâmetros de consulta, parâmetros de caminho e corpo eram enviados diretamente como o payload. A partir da v1.16, eles ficam aninhados dentro de um objeto estruturado `RoutePayload`. - -**Antes da v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**Depois da v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**Para migrar funções existentes:** Atualize seu handler para desestruturar de `event.body`, `event.queryStringParameters` ou `event.pathParameters` em vez de diretamente do objeto de parâmetros. - - -Quando um gatilho de rota invoca sua função de lógica, ela recebe um objeto `RoutePayload` que segue o formato do AWS HTTP API v2. Importe o tipo de `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -O tipo `RoutePayload` tem a seguinte estrutura: - -| Propriedade | Tipo | Descrição | -| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `headers` | `Record` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | -| `pathParameters` | `Record` | Parâmetros de caminho extraídos do padrão de rota (por exemplo, `/users/:id` → `{ id: '123' }`) | -| `corpo` | `object \| null` | Corpo da requisição analisado (JSON) | -| `isBase64Encoded` | `booleano` | Se o corpo está codificado em base64 | -| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Caminho bruto da requisição | - -### Encaminhamento de cabeçalhos HTTP - -Por padrão, os cabeçalhos HTTP das requisições recebidas **não** são repassados para sua função de lógica por motivos de segurança. Para acessar cabeçalhos específicos, liste-os explicitamente no array `forwardedRequestHeaders`: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -No seu handler, você pode então acessar esses cabeçalhos: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - Os nomes dos cabeçalhos são normalizados para minúsculas. Acesse-os usando chaves em minúsculas (por exemplo, `event.headers['content-type']`). - - -Você pode criar novas funções de duas formas: - -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova função de lógica. Isso gera um arquivo inicial com um handler e configuração. -* **Manual**: Crie um novo arquivo `*.logic-function.ts` e use `defineLogicFunction()`, seguindo o mesmo padrão. - -### Marcar uma função lógica como ferramenta - -Funções lógicas podem ser expostas como **ferramentas** para agentes de IA e fluxos de trabalho. Quando uma função é marcada como ferramenta, ela fica disponível para os recursos de IA do Twenty e pode ser selecionada como uma etapa em automações de fluxos de trabalho. - -Para marcar uma função lógica como ferramenta, defina `isTool: true` e forneça um `toolInputSchema` descrevendo os parâmetros de entrada esperados usando [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Pontos-chave: - -* **`isTool`** (`boolean`, padrão: `false`): Quando definido como `true`, a função é registrada como uma ferramenta e fica disponível para agentes de IA e automações de fluxos de trabalho. -* **`toolInputSchema`** (`object`, opcional): Um objeto JSON Schema que descreve os parâmetros que sua função aceita. Os agentes de IA usam esse esquema para entender quais entradas a ferramenta espera e para validar as chamadas. Se omitido, o esquema tem como padrão `{ type: 'object', properties: {} }` (sem parâmetros). -* Funções com `isTool: false` (ou não definido) **não** são expostas como ferramentas. Elas ainda podem ser executadas diretamente ou chamadas por outras funções, mas não aparecerão na descoberta de ferramentas. -* **Nomenclatura de ferramentas**: Quando exposta como uma ferramenta, o nome da função é automaticamente normalizado para `logic_function_` (em minúsculas, caracteres não alfanuméricos substituídos por sublinhados). Por exemplo, `enrich-company` torna-se `logic_function_enrich_company`. -* Você pode combinar `isTool` com gatilhos — uma função pode ser ao mesmo tempo uma ferramenta (chamável por agentes de IA) e acionada por eventos (cron, eventos de banco de dados, rotas) simultaneamente. - - -**Escreva uma boa `description`.** Os agentes de IA dependem do campo `description` da função para decidir quando usar a ferramenta. Seja específico sobre o que a ferramenta faz e quando ela deve ser chamada. - - -### Componentes de front-end - -Componentes de front-end permitem criar componentes React personalizados que são renderizados na UI do Twenty. Use `defineFrontComponent()` para definir componentes com validação integrada: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); - -``` - -Pontos-chave: - -* Componentes de front-end são componentes React que renderizam em contextos isolados dentro do Twenty. -* O campo `component` faz referência ao seu componente React. -* Components are built and synced automatically during `yarn twenty dev`. - -Você pode criar novos componentes de front-end de duas formas: - -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo componente de front-end. -* **Manual**: Crie um novo ficheiro `.tsx` e use `defineFrontComponent()`, seguindo o mesmo padrão. - -#### Onde os componentes de front-end podem ser usados - -Os componentes de front-end podem ser renderizados em dois locais dentro do Twenty: - -* **Painel lateral** — Componentes de front-end não headless abrem no painel lateral direito. Este é o comportamento padrão quando um componente de front-end é acionado pelo menu de comandos. -* **Widgets (painéis e páginas de registro)** — Componentes de front-end podem ser incorporados como widgets nos layouts de página. Ao configurar um painel ou o layout de uma página de registro, os usuários podem adicionar um widget de componente de front-end. - -#### Headless vs não headless - -Os componentes de front-end têm dois modos de renderização controlados pela opção `isHeadless`: - -**Não headless (padrão)** — O componente renderiza uma interface visível. Quando acionado pelo menu de comandos, ele é aberto no painel lateral. Este é o comportamento padrão quando `isHeadless` é `false` ou omitido. - -**Headless** — O componente é montado de forma invisível em segundo plano. Ele não abre o painel lateral. Componentes headless são projetados para ações que executam lógica e, em seguida, se desmontam — por exemplo, executar uma tarefa assíncrona, navegar para uma página ou exibir um modal de confirmação. Eles se combinam naturalmente com os componentes Command do SDK descritos abaixo. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Adicionando itens ao menu de comandos - -Para que um componente de front-end apareça como um item no menu de comandos do Twenty, adicione a propriedade `command` a `defineFrontComponent()`. Quando os usuários abrem o menu de comandos (Cmd+K / Ctrl+K), o item aparece e aciona o componente de front-end ao clicar. - -O objeto `command` aceita os seguintes campos: - -| Campo | Tipo | Descrição | -| --------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | `string` (obrigatório) | ID exclusivo para o item do menu de comandos | -| `etiqueta` | `string` (obrigatório) | Rótulo exibido no menu de comandos | -| `ícone` | `string` (opcional) | Nome do ícone (por exemplo, `'IconSparkles'`) | -| `isPinned` | `boolean` (opcional) | Se o comando fica fixado no topo do menu | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (opcional) | `GLOBAL` mostra o comando em todos os lugares; `RECORD_SELECTION` o mostra apenas em contextos de registro | -| `availabilityObjectUniversalIdentifier` | `string` (opcional) | Restringe o comando a um tipo específico de objeto (por exemplo, Person) | - -Aqui está um exemplo do app de gravação de chamadas que adiciona um comando com escopo para registros de Person: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -Quando o comando é sincronizado, ele aparece no menu de comandos. Se o componente de front-end não for headless, o painel lateral é aberto com o componente renderizado dentro. Se for headless, o componente é montado em segundo plano e executa sua lógica. - -#### Componentes Command do SDK - -O pacote `twenty-sdk` fornece quatro componentes auxiliares Command projetados para componentes de front-end headless. Cada componente executa uma ação ao montar, trata erros exibindo uma notificação de snackbar e desmonta automaticamente o componente de front-end ao concluir. - -Importe-os de `twenty-sdk/command`: - -* **`Command`** — Executa um callback assíncrono via a prop `execute`. -* **`CommandLink`** — Navega para um caminho do app. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Abre um modal de confirmação. Se o usuário confirmar, executa o callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Abre uma página específica do painel lateral. Props: `page`, `pageTitle`, `pageIcon`. - -Aqui está um exemplo completo de um componente de front-end headless usando `Command` para executar uma ação a partir do menu de comandos: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -E um exemplo usando `CommandModal` para solicitar confirmação antes de executar: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Contexto de execução - -Todo componente de front-end recebe um contexto de execução que fornece informações sobre onde e como está sendo executado. Acesse os valores do contexto usando hooks do `twenty-sdk`: - -| Hook | Tipo de retorno | Descrição | -| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | O ID exclusivo da instância atual do componente de front-end | -| `useRecordId()` | `string \| null` | O ID do registro atual, quando o componente é executado em um contexto de registro (por exemplo, um widget de página de registro ou um comando com escopo para um registro). Retorna `null` caso contrário. | -| `useUserId()` | `string \| null` | O ID do usuário atual | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -O contexto é reativo — se o registro de contexto mudar, os hooks retornam automaticamente os valores atualizados. - -#### Funções da API do host - -Os componentes de front-end são executados em um sandbox isolado, mas podem interagir com a UI do Twenty por meio de um conjunto de funções fornecidas pelo host. Importe-as diretamente de `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Função | Assinatura | Descrição | -| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `navegar` | `(to, params?, queryParams?, options?) => Promise` | Navega para um caminho tipado do app dentro do Twenty | -| `closeSidePanel` | `() => Promise` | Fecha o painel lateral | -| `enqueueSnackbar` | `(params) => Promise` | Exibe uma notificação de snackbar. Parâmetros: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), `duration` opcional, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Desmonta o componente de front-end atual (usado por componentes headless para limpar após a execução) | -| `openSidePanelPage` | `(params) => Promise` | Abre uma página no painel lateral. Parâmetros: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Exibe um modal de confirmação e aguarda a resposta do usuário. Parâmetros: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Aqui está um exemplo que usa a API do host para exibir um snackbar e fechar o painel lateral após a conclusão de uma ação: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Habilidades - -As habilidades definem instruções e capacidades reutilizáveis que os agentes de IA podem usar no seu espaço de trabalho. Use `defineSkill()` para definir habilidades com validação integrada: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Pontos-chave: - -* `name` é uma string de identificador exclusivo para a habilidade (recomenda-se kebab-case). -* `label` é o nome de exibição legível por humanos mostrado na UI. -* `content` contém as instruções da habilidade — este é o texto que o agente de IA usa. -* `icon` (opcional) define o ícone exibido na UI. -* `description` (opcional) fornece contexto adicional sobre a finalidade da habilidade. - -Você pode criar novas habilidades de duas formas: - -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova habilidade. -* **Manual**: Crie um novo arquivo e use `defineSkill()`, seguindo o mesmo padrão. - -### Agentes - -Agentes definem agentes de IA com prompts do sistema que podem operar no seu espaço de trabalho. Use `defineAgent()` para definir agentes com validação integrada: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Pontos-chave: - -* `name` é uma string de identificador exclusivo para o agente (recomenda-se kebab-case). -* `label` é o nome de exibição legível por humanos mostrado na UI. -* `prompt` contém o prompt do sistema — este é o texto de instruções que define o comportamento do agente. -* `icon` (opcional) define o ícone exibido na UI. -* `description` (opcional) fornece contexto adicional sobre a finalidade do agente. - -Você pode criar novos agentes de duas formas: - -* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo agente. -* **Manual**: Crie um novo arquivo e use `defineAgent()`, seguindo o mesmo padrão. - -### Clientes tipados gerados - -Two typed clients are auto-generated by `yarn twenty dev` and stored in `node_modules/twenty-sdk/clients` based on your workspace schema: - -* **`CoreApiClient`** — consulta o endpoint `/graphql` para dados do espaço de trabalho -* **`MetadataApiClient`** — consulta o endpoint `/metadata` para obter a configuração do espaço de trabalho e o carregamento de ficheiros - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); -``` - -`CoreApiClient` is re-generated automatically by `yarn twenty dev` whenever your objects or fields change. `MetadataApiClient` é fornecido pré-compilado com o SDK. - -#### Credenciais em tempo de execução em funções de lógica - -Quando sua função é executada no Twenty, a plataforma injeta credenciais como variáveis de ambiente antes da execução do seu código: - -* `TWENTY_API_URL`: URL base da API do Twenty que seu aplicativo usa como alvo. -* `TWENTY_API_KEY`: Chave de curta duração com escopo para o papel de função padrão do seu aplicativo. - -Notas: - -* Você não precisa passar a URL ou a chave de API para o cliente gerado. Ele lê `TWENTY_API_URL` e `TWENTY_API_KEY` de process.env em tempo de execução. -* As permissões da chave de API são determinadas pelo papel referenciado no seu `application-config.ts` via `defaultRoleUniversalIdentifier`. Este é o papel padrão usado pelas funções de lógica do seu app. -* Os aplicativos podem definir papéis para seguir o princípio do menor privilégio. Conceda apenas as permissões de que suas funções precisam e, em seguida, aponte `defaultRoleUniversalIdentifier` para o identificador universal desse papel. - -#### Carregamento de ficheiros - -`MetadataApiClient` inclui um método `uploadFile` para anexar ficheiros a campos do tipo ficheiro nos objetos do seu espaço de trabalho. Como os clientes GraphQL padrão não suportam nativamente o carregamento de ficheiros multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de pedidos multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec). - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type (defaults to 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -A assinatura do método: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parâmetro | Tipo | Descrição | -| ---------------------------------- | -------- | ------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | O conteúdo bruto do arquivo | -| `filename` | `string` | O nome do arquivo (usado para armazenamento e exibição) | -| `contentType` | `string` | Tipo MIME do arquivo (padrão para `application/octet-stream` se omitido) | -| `fieldMetadataUniversalIdentifier` | `string` | O `universalIdentifier` do campo do tipo arquivo no seu objeto | - -Pontos-chave: - -* O método `uploadFile` está disponível no `MetadataApiClient` porque a mutação de upload é resolvida pelo endpoint `/metadata`. -* Ele usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado — consistente com a forma como os apps referenciam campos em qualquer outro lugar. -* A `url` retornada é um URL assinado que você pode usar para acessar o arquivo enviado. - -### Exemplo Hello World - -Explore um exemplo mínimo de ponta a ponta que demonstra objetos, funções de lógica, componentes de front-end e vários gatilhos [aqui](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## Compilando seu app - -Depois de desenvolver seu app com `app:dev`, use `app:build` para compilá-lo em um pacote distribuível. - -```bash filename="Terminal" -# Build the app (output goes to .twenty/output/) -yarn twenty build - -# Build and create a tarball (.tgz) for distribution -yarn twenty build --tarball -``` - -O processo de build: - -1. **Analisa e valida o manifesto** — lê todas as entidades `defineX()` dos seus arquivos de código-fonte e valida a estrutura do manifesto. -2. **Compila funções de lógica e componentes de front-end** — empacota o código-fonte TypeScript em arquivos ESM `.mjs` usando o esbuild. -3. **Gera checksums** — calcula hashes MD5 para cada arquivo gerado, armazenados no manifesto como `builtHandlerChecksum` / `builtComponentChecksum`. -4. **Gera o cliente de API tipado** — inspeciona o esquema GraphQL e gera clientes tipados `CoreApiClient` e `MetadataApiClient`. -5. **Executa uma verificação de tipos do TypeScript** — executa `tsc --noEmit` para detectar erros de tipo antes da publicação. -6. **Reconstrói com o cliente gerado** — realiza uma segunda passagem de compilação para que os tipos do cliente gerado sejam incluídos. -7. **Opcionalmente cria um tarball** — se `--tarball` for passado, executa `npm pack` para criar um arquivo `.tgz` pronto para distribuição. - -A saída da compilação em `.twenty/output/` contém: - -```text -.twenty/output/ -├── manifest.json # Manifesto com somas de verificação para todos os arquivos compilados -├── package.json # Copiado da raiz do aplicativo -├── yarn.lock # Copiado da raiz do aplicativo -├── src/ -│ ├── logic-functions/ # Arquivos .mjs compilados de funções de lógica -│ └── front-components/ # Arquivos .mjs compilados de componentes de front-end -├── public/ # Recursos estáticos (se houver) -└── my-app-1.0.0.tgz # Apenas com a opção --tarball -``` - -| Opção | Descrição | -| ----------- | --------------------------------------------------------- | -| `[appPath]` | Caminho para o diretório do app (padrão: diretório atual) | -| `--tarball` | Também empacota a saída em um tarball `.tgz` | - -## Publicando seu app - -Use `app:publish` para distribuir seu app — ou para o registro do npm ou diretamente para um servidor Twenty. - -### Publicar no npm (padrão) - -```bash filename="Terminal" -# Publish to npm (requires npm login) -yarn twenty publish - -# Publish with a dist-tag (e.g. beta, next) -yarn twenty publish --tag beta -``` - -Isso compila o app e executa `npm publish` a partir do diretório `.twenty/output/`. O pacote publicado pode então ser instalado no marketplace da Twenty por qualquer espaço de trabalho. - -### Publicar em um servidor Twenty - -```bash filename="Terminal" -# Publish directly to a Twenty server -yarn twenty publish --server https://app.twenty.com -``` - -Isso compila o app com um tarball, faz o upload para o servidor via a mutação GraphQL `uploadAppTarball` e aciona a instalação em uma única etapa. Isso é útil para implantações privadas ou para testar em um servidor específico. - -| Opção | Descrição | -| ----------------- | --------------------------------------------------------------------- | -| `[appPath]` | Caminho para o diretório do app (padrão: diretório atual) | -| `--server ` | Publicar em um servidor Twenty em vez de no npm | -| `--token ` | Token de autenticação para o servidor de destino | -| `--tag ` | dist-tag do npm (ex.: `beta`, `next`) — apenas para publicação no npm | - -## Registro de aplicação - -Antes que um app possa ser instalado em um espaço de trabalho, ele precisa ser **registrado**. Um registro é um registro de metadados que descreve de onde o app vem e como autenticá-lo. Isso é tratado automaticamente pela CLI na maioria dos casos. - -### Tipos de origem - -Cada registro tem um **tipo de origem** que determina como os arquivos do app são resolvidos durante a instalação: - -| Tipo de origem | Como os arquivos são resolvidos | Caso de uso típico | -| -------------- | ------------------------------------------------------------------------------------------- | --------------------------------------- | -| `LOCAL` | Os arquivos são sincronizados em tempo real pelo observador da CLI — a instalação é omitida | Desenvolvimento com `app:dev` | -| `NPM` | Obtidos do registro npm por meio do campo `sourcePackage` | Apps publicados no npm | -| `TARBALL` | Extraídos de um arquivo `.tgz` enviado e armazenado no servidor | Apps privados publicados com `--server` | - -### Como o registro acontece - -* **`app:dev`** — cria automaticamente um registro `LOCAL` na primeira vez que você executa o modo de desenvolvimento em um espaço de trabalho. -* **`app:publish --server`** — faz o upload de um tarball e cria (ou atualiza) um registro `TARBALL`, e em seguida instala o app. -* **marketplace do npm** — registros `NPM` são criados quando apps são sincronizados do registro npm para o catálogo do marketplace da Twenty. -* **API GraphQL** — você também pode criar registros programaticamente por meio da mutação `createApplicationRegistration`. - -### Registro vs instalação - -**Registro** e **instalação** são conceitos distintos: - -* Um **registro** (`ApplicationRegistration`) é um registro global de metadados que descreve o app: seu nome, tipo de origem, credenciais OAuth e status de listagem no marketplace. Ele existe independentemente de qualquer espaço de trabalho. -* Uma **instalação** (`Application`) é uma instância por espaço de trabalho. Quando um usuário instala um app, a Twenty resolve o pacote a partir da origem do registro, grava os arquivos compilados no armazenamento e sincroniza o manifesto (criando objetos, campos, funções de lógica etc.). naquele espaço de trabalho. - -Um registro pode ser instalado em muitos espaços de trabalho. Cada espaço de trabalho recebe sua própria cópia dos arquivos e do modelo de dados do app. - -### Credenciais OAuth - -Cada registro inclui credenciais OAuth (`oAuthClientId` e `oAuthClientSecret`) geradas no momento da criação. Elas são usadas pelo app para autenticar requisições de API em nome dos usuários. O segredo do cliente é retornado **uma única vez** na criação — armazene-o com segurança. Você pode rotacioná-lo posteriormente por meio da mutação `rotateApplicationRegistrationClientSecret`. - -## Configuração manual (sem o gerador) - -Embora recomendemos usar `create-twenty-app` para a melhor experiência inicial, você também pode configurar um projeto manualmente. Não instale a CLI globalmente. Em vez disso, adicione `twenty-sdk` como uma dependência local e configure um único script no seu package.json: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Em seguida, adicione um script `twenty`: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Now you can run all commands via `yarn twenty `, e.g. `yarn twenty dev`, `yarn twenty help`, etc. - -## Resolução de Problemas - -* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias. -* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível. -* Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client. -* Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment. - -Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/pt/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx index 098c6f4e98..b4033d4df4 100644 --- a/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx @@ -297,6 +297,16 @@ yarn command:prod cron:workflow:automated-cron-trigger **Modo somente ambiente:** Se você definir `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, adicione estas variáveis ao seu arquivo `.env`. +## Armazenamento S3 + + +Por padrão, o Twenty armazena os arquivos enviados no sistema de arquivos local. Para implantações em produção, use o S3 ou um serviço compatível com S3 (MinIO, DigitalOcean Spaces, etc.) para garantir que os arquivos persistam entre reinicializações do contêiner e possam escalar em várias instâncias de servidor. + + +Defina `STORAGE_TYPE=S_3` e configure as variáveis `STORAGE_S3_*` pelo painel de administração ou `.env`. Veja a [referência de config-variables.ts](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts) para a lista completa de variáveis do S3. + +Ao usar o S3 com recursos dependentes de CORS (por exemplo, downloads de arquivos no navegador), verifique se o seu bucket permite a origem do seu frontend do Twenty na sua configuração de CORS. + ## Funções lógicas e interpretador de código O Twenty oferece suporte a funções lógicas para fluxos de trabalho e ao interpretador de código para análise de dados com IA. Ambos executam código fornecido pelo usuário e exigem configuração explícita por motivos de segurança. diff --git a/packages/twenty-docs/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty.mdx b/packages/twenty-docs/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty.mdx index 4b22701604..33aca017db 100644 --- a/packages/twenty-docs/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty.mdx +++ b/packages/twenty-docs/l/pt/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty.mdx @@ -55,11 +55,12 @@ export const main = async ( params: { companyId: string }, ) => { const { companyId } = params; - - // Replace with your Twenty GraphQL endpoint + // Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records) // Cloud: https://api.twenty.com/graphql // Self-hosted: https://your-domain.com/graphql - const graphqlEndpoint = 'https://api.twenty.com/graphql'; + + const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata'; + const dataGraphqlEndpoint = 'https://api.twenty.com/graphql'; // Replace with your API key from Settings → APIs const authToken = 'YOUR_API_KEY'; @@ -79,11 +80,40 @@ export const main = async ( const pdfBlob = await pdfResponse.blob(); const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' }); - // Step 2: Upload the file via GraphQL multipart upload + const fieldMetadataIdQuery = ` + query FindUploadFileFieldMetadataId { + objects { + edges { + node { + nameSingular + fieldsList { + id + name + } + } + } + } + } + `; + + // Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API + const response = await fetch(metadataGraphqlEndpoint, { + method: 'POST', + headers: { + Authorization: `Bearer ${authToken}` + }, + body: { + query: fieldMetadataIdQuery, + } + }); + const result = await response.json(); + const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id; + + // Step 3: Upload the file via GraphQL multipart upload const uploadMutation = ` - mutation UploadFile($file: Upload!, $fileFolder: FileFolder) { - uploadFile(file: $file, fileFolder: $fileFolder) { - path + mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) { + uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) { + id } } `; @@ -91,12 +121,12 @@ export const main = async ( const uploadForm = new FormData(); uploadForm.append('operations', JSON.stringify({ query: uploadMutation, - variables: { file: null, fileFolder: 'Attachment' }, + variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId }, })); uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] })); uploadForm.append('0', pdfFile); - const uploadResponse = await fetch(graphqlEndpoint, { + const uploadResponse = await fetch(metadataGraphqlEndpoint, { method: 'POST', headers: { Authorization: `Bearer ${authToken}` }, body: uploadForm, @@ -108,15 +138,15 @@ export const main = async ( throw new Error(`Upload failed: ${uploadResult.errors[0].message}`); } - const filePath = uploadResult.data?.uploadFile?.path; + const fileId = uploadResult.data?.uploadFilesFieldFile?.id; - if (!filePath) { - throw new Error('No file path returned from upload'); + if (!fileId) { + throw new Error('No file id returned from upload'); } - // Step 3: Create the attachment linked to the company + // Step 4: Create the attachment linked to the company const attachmentMutation = ` - mutation CreateAttachment($data: AttachmentCreateInput!) { + mutation CreateOneAttachment($data: AttachmentCreateInput!) { createAttachment(data: $data) { id name @@ -124,7 +154,7 @@ export const main = async ( } `; - const attachmentResponse = await fetch(graphqlEndpoint, { + const attachmentResponse = await fetch(dataGraphqlEndpoint, { method: 'POST', headers: { Authorization: `Bearer ${authToken}`, @@ -135,8 +165,13 @@ export const main = async ( variables: { data: { name: filename, - fullPath: filePath, - companyId, + targetCompanyId: companyId, + file: [ + { + fileId: fileId, + label: filename + } + ] }, }, }), @@ -156,14 +191,14 @@ export const main = async ( #### Para anexar a um objeto diferente -Substitua `companyId` pelo campo apropriado: +Substitua `targetCompanyId` pelo campo apropriado: -| Objeto | Nome do Campo | -| -------------------- | -------------------- | -| Empresa | `companyId` | -| Pessoa | `personId` | -| Oportunidade | `opportunityId` | -| Objeto personalizado | `yourCustomObjectId` | +| Objeto | Nome do Campo | +| -------------------- | -------------------------- | +| Empresa | `targetCompanyId` | +| Pessoa | `targetPersonId` | +| Oportunidade | `targetOpportunityId` | +| Objeto personalizado | `targetYourCustomObjectId` | Atualize tanto o parâmetro da função como o objeto `variables.data` na mutação de anexo. diff --git a/packages/twenty-docs/l/ro/developers/extend/apps/building.mdx b/packages/twenty-docs/l/ro/developers/extend/apps/building.mdx index 020ff28d64..334932bb4a 100644 --- a/packages/twenty-docs/l/ro/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/ro/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: Definiți obiecte, funcții logice, componente front-end și multe --- -Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare. + Apps are currently in alpha. The feature works but is still evolving. -## Utilizați resursele SDK (tipuri și configurare) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -Biblioteca twenty-sdk oferă blocuri de bază tipizate și funcții ajutătoare pe care le utilizați în aplicația dvs. Mai jos sunt elementele cheie cu care veți interacționa cel mai des. +## DefineEntity functions -### Funcții ajutătoare +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. Aceste funcții validează configurația în timpul build-ului și oferă completare automată în IDE și siguranța tipurilor. -SDK-ul oferă funcții ajutătoare pentru definirea entităților aplicației. După cum este descris în [Detectarea entităților](/l/ro/developers/extend/apps/getting-started#entity-detection), trebuie să folosiți `export default define({...})` pentru ca entitățile să fie detectate: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| Funcție | Scop | -| -------------------------------- | -------------------------------------------------------------------------------------------------- | -| `defineApplication` | Configurați metadatele aplicației (obligatoriu, una per aplicație) | -| `defineObject` | Definiți obiecte personalizate cu câmpuri | -| `defineField` | Extindeți obiectele existente cu câmpuri suplimentare sau definiți câmpuri de relație independente | -| `defineLogicFunction` | Definiți funcții de logică cu handleri | -| `definePreInstallLogicFunction` | Definește o funcție logică de pre-instalare (una per aplicație) | -| `definePostInstallLogicFunction` | Definește o funcție logică post-instalare (una per aplicație) | -| `defineFrontComponent` | Definiți componente Front pentru interfața de utilizator personalizată | -| `defineRole` | Configurați permisiunile rolurilor și accesul la obiecte | -| `defineView` | Definește vizualizări salvate pentru obiecte | -| `defineNavigationMenuItem` | Definește linkuri de navigare în bara laterală | -| `defineSkill` | Definiți abilități pentru agentul AI | -| `defineAgent` | Definiți agenți AI | -| `definePageLayout` | Definiți machete de pagină personalizate | + + -Aceste funcții validează configurația în timpul build-ului și oferă completare automată în IDE și siguranța tipurilor. +Roles encapsulate permissions on your workspace's objects and actions. -### Definirea obiectelor +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notițe: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### Metadate pentru marketplace + +If you plan to [publish your app](/l/ro/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| Câmp | Descriere | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| `autor` | Numele autorului sau al companiei | +| `categorie` | Categoria aplicației pentru filtrarea în marketplace | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | Descriere markdown mai lungă pentru fila "About". Dacă este omis, marketplace-ul folosește `README.md` al pachetului de pe npm | +| `websiteUrl` | Link către site-ul dvs. | +| `termsUrl` | Link către termenii de serviciu | +| `emailSupport` | Adresă de e-mail pentru suport | +| `issueReportUrl` | Link către sistemul de urmărire a problemelor | + +#### Roluri și permisiuni + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts** indică acel rol, astfel încât funcțiile moștenesc permisiunile lui. + +Notițe: +* Porniți de la rolul generat, apoi restrângeți-l progresiv urmând principiul celui mai mic privilegiu. +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags` controlează accesul la capabilități la nivelul platformei. Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Obiectele personalizate descriu atât schema, cât și comportamentul înregistrărilor din spațiul dvs. de lucru. Utilizați `defineObject()` pentru a defini obiecte cu validare încorporată: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ Puteți suprascrie câmpurile implicite definind un câmp cu același nume în t dar acest lucru nu este recomandat. -### Definirea câmpurilor pe obiecte existente + + Utilizați `defineField()` pentru a adăuga câmpuri la obiecte pe care nu le dețineți — cum ar fi obiectele standard Twenty (Person, Company etc.). sau obiecte din alte aplicații. Spre deosebire de câmpurile inline din `defineObject()`, câmpurile independente necesită un `objectUniversalIdentifier` pentru a specifica obiectul pe care îl extind: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` Puncte cheie: - * `objectUniversalIdentifier` identifică obiectul țintă. Pentru obiectele standard, utilizați `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportați din `twenty-sdk`. * Atunci când definiți câmpuri inline în `defineObject()`, nu aveți nevoie de `objectUniversalIdentifier` — este moștenit de la obiectul părinte. * `defineField()` este singura modalitate de a adăuga câmpuri la obiecte pe care nu le-ați creat cu `defineObject()`. -### Relații + + Relațiile conectează obiectele între ele. În Twenty, relațiile sunt întotdeauna bidirecționale — definiți ambele părți, iar fiecare parte o referențiază pe cealaltă. @@ -177,8 +310,7 @@ Presupuneți că un `PostCard` poate fi trimis către multe înregistrări `Post **Pasul 1: Definiți partea ONE_TO_MANY pe PostCard** (partea "one"): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **Pasul 2: Definiți partea MANY_TO_ONE pe PostCardRecipient** (partea "many" — deține cheia străină): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ Pentru a crea o relație cu un obiect Twenty încorporat (Person, Company etc.), utilizați `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ Puteți defini, de asemenea, câmpuri de relație direct în `defineObject()`. În acest caz, omiteți `objectUniversalIdentifier` — este moștenit de la obiectul părinte: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### Configurația aplicației (application-config.ts) - -Fiecare aplicație are un singur fișier `application-config.ts` care descrie: - -* **Cine este aplicația**: identificatori, nume de afișare și descriere. -* **Cum rulează funcțiile**: ce rol folosesc pentru permisiuni. -* **(Opțional) variabile**: perechi cheie–valoare expuse funcțiilor ca variabile de mediu. -* **(Opțional) funcție de pre-instalare**: o funcție logică care rulează înainte ca aplicația să fie instalată. -* **(Opțional) funcție post-instalare**: o funcție logică care rulează după instalarea aplicației. - -Folosiți `defineApplication()` pentru a defini configurația aplicației: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notițe: - -* Câmpurile `universalIdentifier` sunt ID-uri deterministe pe care le dețineți; generați-le o singură dată și păstrați-le stabile între sincronizări. -* `applicationVariables` devin variabile de mediu pentru funcțiile dvs. (de exemplu, `DEFAULT_RECIPIENT_NAME` este disponibil ca `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` trebuie să corespundă fișierului de rol (vedeți mai jos). -* Funcțiile de pre-instalare și post-instalare sunt detectate automat în timpul construirii manifestului. Vezi [Funcții de pre-instalare](#pre-install-functions) și [Funcții post-instalare](#post-install-functions). - -#### Metadate pentru marketplace - -Dacă intenționați să [publicați aplicația](/l/ro/developers/extend/apps/publishing), aceste câmpuri opționale controlează modul în care aplicația apare în marketplace: - -| Câmp | Descriere | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -| `autor` | Numele autorului sau al companiei | -| `categorie` | Categoria aplicației pentru filtrarea în marketplace | -| `logoUrl` | Calea către logo-ul aplicației (relativă la `./assets/`) | -| `screenshots` | Listă de căi către capturi de ecran (relative la `./assets/`) | -| `aboutDescription` | Descriere markdown mai lungă pentru fila "About". Dacă este omis, marketplace-ul folosește `README.md` al pachetului de pe npm | -| `websiteUrl` | Link către site-ul dvs. | -| `termsUrl` | Link către termenii de serviciu | -| `emailSupport` | Adresă de e-mail pentru suport | -| `issueReportUrl` | Link către sistemul de urmărire a problemelor | - -#### Roluri și permisiuni - -Aplicațiile pot defini roluri care încapsulează permisiuni asupra obiectelor și acțiunilor din spațiul dvs. de lucru. Câmpul `defaultRoleUniversalIdentifier` din `application-config.ts` desemnează rolul implicit utilizat de funcțiile de logică ale aplicației. - -* Cheia API de runtime injectată ca `TWENTY_API_KEY` este derivată din acest rol implicit pentru funcții. -* Clientul tipizat va fi restricționat la permisiunile acordate acelui rol. -* Respectați principiul celui mai mic privilegiu: creați un rol dedicat doar cu permisiunile de care au nevoie funcțiile, apoi referiți identificatorul său universal. - -##### Rol implicit pentru funcții (*.role.ts) - -Când generați o aplicație nouă, CLI creează și un fișier de rol implicit. Folosiți `defineRole()` pentru a defini roluri cu validare încorporată: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -`universalIdentifier` al acestui rol este apoi referențiat în `application-config.ts` ca `defaultRoleUniversalIdentifier`. Cu alte cuvinte: - -* **\*.role.ts** definește ce poate face rolul implicit pentru funcții. -* **application-config.ts** indică acel rol, astfel încât funcțiile moștenesc permisiunile lui. - -Notițe: - -* Porniți de la rolul generat, apoi restrângeți-l progresiv urmând principiul celui mai mic privilegiu. -* Înlocuiți `objectPermissions` și `fieldPermissions` cu obiectele/câmpurile de care au nevoie funcțiile. -* `permissionFlags` controlează accesul la capabilități la nivelul platformei. Mențineți-le la minimum; adăugați doar ceea ce aveți nevoie. -* Vedeți un exemplu funcțional în aplicația Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Configurația funcției de logică și punctul de intrare + + Fiecare fișier de funcție folosește `defineLogicFunction()` pentru a exporta o configurație cu un handler și declanșatoare opționale. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Tipuri comune de declanșatoare: - -* **route**: Expune funcția pe o rută și metodă HTTP **sub endpoint-ul `/s/`**: - -> de ex. `path: '/post-card/create',` -> apel pe `/s/post-card/create` - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**: Rulează funcția pe un program folosind o expresie CRON. * **databaseEvent**: Rulează la evenimentele ciclului de viață ale obiectelor din spațiul de lucru. Când operațiunea evenimentului este `updated`, câmpurile specifice de urmărit pot fi specificate în array-ul `updatedFields`. Dacă este lăsat nedefinit sau gol, orice actualizare va declanșa funcția. +> e.g. `person.updated`, `*.created`, `company.*` -> de ex. `person.updated` - -Notițe: - -* Matricea `triggers` este opțională. Funcțiile fără declanșatoare pot fi folosite ca funcții utilitare apelate de alte funcții. -* Puteți combina mai multe tipuri de declanșatoare într-o singură funcție. - -### Funcții de pre-instalare - -O funcție de pre-instalare este o funcție logică ce rulează automat înainte ca aplicația ta să fie instalată într-un spațiu de lucru. Aceasta este utilă pentru sarcini de validare, verificări ale condițiilor prealabile sau pregătirea stării spațiului de lucru înainte ca instalarea principală să continue. - -Când creezi scheletul unei aplicații noi cu `create-twenty-app`, ți se generează o funcție de pre-instalare la `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Poți, de asemenea, să execuți manual funcția de pre-instalare oricând folosind CLI: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Puncte cheie: - -* Funcțiile de pre-instalare folosesc `definePreInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi). -* Este permisă o singură funcție de pre-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una. -* Proprietatea `universalIdentifier` a funcției este setată automat ca `preInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`. -* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de pregătire mai lungi. -* Funcțiile de pre-instalare nu au nevoie de declanșatoare — sunt invocate de platformă înainte de instalare sau manual prin `exec --preInstall`. - -### Funcții post-instalare - -O funcție post-instalare este o funcție logică care rulează automat după instalarea aplicației într-un spațiu de lucru. Aceasta este utilă pentru sarcini de configurare unice, cum ar fi popularea cu date implicite, crearea înregistrărilor inițiale sau configurarea setărilor spațiului de lucru. - -Când creezi scheletul unei aplicații noi cu `create-twenty-app`, este generată o funcție post-instalare la `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Poți, de asemenea, să execuți manual funcția post-instalare oricând folosind CLI: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Puncte cheie: +You can watch logs with: -* Funcțiile de post-instalare folosesc `definePostInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi). -* Este permisă o singură funcție de post-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una. -* Proprietatea `universalIdentifier` a funcției este setată automat ca `postInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`. -* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor. -* Funcțiile post-instalare nu au nevoie de declanșatoare — sunt invocate de platformă în timpul instalării sau manual prin `exec --postInstall`. - -### Payload-ul declanșatorului de rută - - -**Modificare incompatibilă (v1.16, ianuarie 2026):** Formatul payload-ului declanșatorului de rută s-a schimbat. Înainte de v1.16, parametrii de interogare (query), parametrii de cale și corpul erau trimiși direct ca payload. Începând cu v1.16, acestea sunt incluse într-un obiect structurat `RoutePayload`. - -**Înainte de v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**După v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Payload-ul declanșatorului de rută -**Pentru a migra funcțiile existente:** Actualizează handler-ul pentru a extrage câmpurile din `event.body`, `event.queryStringParameters` sau `event.pathParameters` în loc să le iei direct din obiectul params. - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -Când un declanșator de rută apelează funcția dvs. de logică, aceasta primește un obiect `RoutePayload` care urmează formatul AWS HTTP API v2. Importă tipul din `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { Tipul `RoutePayload` are următoarea structură: -| Proprietate | Tip | Descriere | -| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------- | -| `headers` | `Record` | Anteturi HTTP (doar cele listate în `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parametri query string (valorile multiple unite cu virgule) | -| `pathParameters` | `Record` | Parametri de cale extrași din modelul rutei (de ex., `/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | Corpul cererii analizat (JSON) | -| `isBase64Encoded` | `boolean` | Indică dacă corpul este codificat în base64 | -| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Calea brută a cererii | + | Proprietate | Tip | Descriere | Exemplu | + | ---------------------------- | ------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | Anteturi HTTP (doar cele listate în `forwardedRequestHeaders`) | see section below | + | `queryStringParameters` | `Record` | Parametri query string (valorile multiple unite cu virgule) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Corpul cererii analizat (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Indică dacă corpul este codificat în base64 | | + | `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Calea brută a cererii | | -### Transmiterea anteturilor HTTP -În mod implicit, anteturile HTTP din cererile de intrare **nu** sunt transmise funcției dvs. de logică din motive de securitate. Pentru a accesa anumite anteturi, listează-le explicit în array-ul `forwardedRequestHeaders`: +#### forwardedRequestHeaders -```typescript +În mod implicit, anteturile HTTP din cererile de intrare **nu** sunt transmise funcției dvs. de logică din motive de securitate. +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -În handler, poți apoi accesa aceste anteturi: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - Numele anteturilor sunt normalizate la litere mici. Accesează-le folosind chei cu litere mici (de exemplu, `event.headers['content-type']`). +Numele anteturilor sunt normalizate la litere mici. Access them using lowercase keys (e.g., `event.headers['content-type']`). -Puteți crea funcții noi în două moduri: +#### Exposing a function as a tool -* **Generat**: Rulați `yarn twenty add` și alegeți opțiunea de a adăuga o funcție de logică nouă. Aceasta generează un fișier inițial cu un handler și o configurație. -* **Manual**: Creați un fișier nou `*.logic-function.ts` și folosiți `defineLogicFunction()`, urmând același model. +Funcțiile logice pot fi expuse ca **instrumente** pentru agenți de IA și fluxuri de lucru. When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### Marcarea unei funcții logice drept instrument +To mark a logic function as a tool, set `isTool: true`: -Funcțiile logice pot fi expuse ca **instrumente** pentru agenți de IA și fluxuri de lucru. Când o funcție este marcată ca instrument, poate fi descoperită de funcționalitățile de IA ale Twenty și poate fi selectată ca pas în automatizări ale fluxurilor de lucru. - -Pentru a marca o funcție logică drept instrument, setați `isTool: true` și furnizați un `toolInputSchema` care descrie parametrii de intrare așteptați folosind [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Puncte cheie: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Puncte cheie: - -* **`isTool`** (`boolean`, implicit: `false`): Când este setat la `true`, funcția este înregistrată ca instrument și devine disponibilă pentru agenții AI și automatizările de fluxuri de lucru. -* **`toolInputSchema`** (`object`, opțional): Un obiect JSON Schema care descrie parametrii pe care îi acceptă funcția dvs. Agenții AI folosesc această schemă pentru a înțelege ce intrări așteaptă instrumentul și pentru a valida apelurile. Dacă este omisă, schema are implicit valoarea `{ type: 'object', properties: {} }` (fără parametri). -* Funcțiile cu `isTool: false` (sau nedefinit) **nu** sunt expuse ca instrumente. Pot totuși fi executate direct sau apelate de alte funcții, dar nu vor apărea în descoperirea instrumentelor. -* **Denumierea instrumentelor**: Când este expusă ca instrument, denumirea funcției este normalizată automat la `logic_function_` (convertită la litere mici, iar caracterele non-alfanumerice sunt înlocuite cu caractere de subliniere). De exemplu, `enrich-company` devine `logic_function_enrich_company`. -* Puteți combina `isTool` cu declanșatoare — o funcție poate fi atât un instrument (apelabilă de agenții AI), cât și declanșată de evenimente (cron, evenimente de bază de date, rute) în același timp. - **Scrieți o `description` bună.** Agenții AI se bazează pe câmpul `description` al funcției pentru a decide când să folosească instrumentul. Fiți specifici cu privire la ceea ce face instrumentul și când ar trebui apelat. -### Componente Front + + -Componentele Front vă permit să construiți componente React personalizate care sunt randate în interfața Twenty. Utilizați `defineFrontComponent()` pentru a defini componente cu validare încorporată: +O funcție de pre-instalare este o funcție logică ce rulează automat înainte ca aplicația ta să fie instalată într-un spațiu de lucru. Aceasta este utilă pentru sarcini de validare, verificări ale condițiilor prealabile sau pregătirea stării spațiului de lucru înainte ca instalarea principală să continue. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Poți, de asemenea, să execuți manual funcția de pre-instalare oricând folosind CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Puncte cheie: +* Funcțiile de pre-instalare folosesc `definePreInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi). +* Este permisă o singură funcție de pre-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una. +* Proprietatea `universalIdentifier` a funcției este setată automat ca `preInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`. +* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de pregătire mai lungi. + + + + +O funcție post-instalare este o funcție logică care rulează automat după instalarea aplicației într-un spațiu de lucru. Aceasta este utilă pentru sarcini de configurare unice, cum ar fi popularea cu date implicite, crearea înregistrărilor inițiale sau configurarea setărilor spațiului de lucru. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Poți, de asemenea, să execuți manual funcția post-instalare oricând folosind CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Puncte cheie: +* Funcțiile de post-instalare folosesc `definePostInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi). +* Este permisă o singură funcție de post-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una. +* Proprietatea `universalIdentifier` a funcției este setată automat ca `postInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`. +* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor. + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Puncte cheie: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* Componentele Front sunt componente React care sunt randate în contexte izolate în cadrul Twenty. -* Câmpul `component` face referire la componenta React. -* Componentele sunt construite și sincronizate automat în timpul `yarn twenty dev`. +
+ Quick action button in the top-right corner +
-Puteți crea componente Front noi în două moduri: +Click it to render the component inline. -* **Generat**: Rulați `yarn twenty add` și alegeți opțiunea de a adăuga o componentă Front nouă. -* **Manual**: Creați un fișier nou `.tsx` și folosiți `defineFrontComponent()`, urmând același model. +{/* TODO: add screenshot of the rendered front component */} -### Abilități +#### Configuration fields + +| Câmp | Obligatoriu | Descriere | +| --------------------- | ----------- | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | Da | Stable unique ID for this component | +| `component` | Da | A React component function | +| `name` | Nu | Display name | +| `description` | Nu | Description of what the component does | +| `isHeadless` | Nu | Set to `true` if the component has no visible UI (see below) | +| `command` | Nu | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| Hook | Returns | Descriere | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | variază | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| Funcție | Descriere | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | Close the side panel | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| Câmp | Obligatoriu | Descriere | +| --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Da | Stable unique ID for the command | +| `label` | Da | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | Nu | Shorter label displayed on the pinned quick-action button | +| `icon` | Nu | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Nu | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | Nu | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | Nu | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | Nu | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| Variabilă | Tip | Descriere | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `number` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `object` | Permissions for the current object type | +| `targetObjectReadPermissions` | `object` | Read permissions for the target object | +| `targetObjectWritePermissions` | `object` | Write permissions for the target object | +| `featureFlags` | `object` | Active feature flags | +| `objectMetadataItem` | `object` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | Descriere | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### Stilizare + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + Abilitățile definesc instrucțiuni și capabilități reutilizabile pe care agenții AI le pot folosi în spațiul dvs. de lucru. Folosiți `defineSkill()` pentru a defini abilități cu validare încorporată: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` Puncte cheie: - * `name` este un șir identificator unic pentru abilitate (se recomandă kebab-case). * `label` este numele lizibil afișat în interfața cu utilizatorul (UI). * `content` conține instrucțiunile abilității — acesta este textul pe care agentul AI îl folosește. * `icon` (opțional) setează pictograma afișată în UI. * `description` (opțional) oferă context suplimentar despre scopul abilității. -Puteți crea abilități noi în două moduri: + + -* **Generat**: Rulați `yarn twenty add` și alegeți opțiunea de a adăuga o abilitate nouă. -* **Manual**: Creați un fișier nou și folosiți `defineSkill()`, urmând același model. +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### Clienți API tipați (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -Pachetul `twenty-client-sdk` oferă doi clienți GraphQL tipați pentru a interacționa cu API-ul Twenty din funcțiile de logică și componentele Front: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Puncte cheie: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon` (opțional) setează pictograma afișată în UI. +* `modelId` (optional) overrides the default AI model used by the agent. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Puncte cheie: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Puncte cheie: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Puncte cheie: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| Tipul entității | Comandă | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| Obiect | `yarn twenty add object` | `src/objects/.ts` | +| Câmp | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Rol | `yarn twenty add role` | `src/roles/.ts` | +| Abilitate | `yarn twenty add skill` | `src/skills/.ts` | +| Agent | `yarn twenty add agent` | `src/agents/.ts` | +| Vizualizare | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +Pachetul `twenty-client-sdk` oferă doi clienți GraphQL tipați pentru a interacționa cu API-ul Twenty din funcțiile de logică și componentele Front. | Client | Importați | Endpoint | Generat? | | ------------------- | ---------------------------- | ------------------------------------------------------------------- | ---------------------------- | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — date ale spațiului de lucru (înregistrări, obiecte) | Da, în timpul dev/build | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configurarea spațiului de lucru, încărcări de fișiere | Nu, este livrat preconstruit | -#### CoreApiClient + + -`CoreApiClient` este clientul principal pentru interogarea și modificarea datelor din spațiul de lucru. Este generat din schema spațiului dvs. de lucru în timpul `yarn twenty dev` sau `yarn twenty build`, astfel încât este complet tipat pentru a corespunde obiectelor și câmpurilor dvs. +`CoreApiClient` este clientul principal pentru interogarea și modificarea datelor din spațiul de lucru. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ Clientul folosește o sintaxă de tip selection-set: transmiteți `true` pentru a include un câmp, folosiți `__args` pentru argumente și imbricați obiecte pentru relații. Obțineți autocompletare și verificare a tipurilor complete, pe baza schemei spațiului dvs. de lucru. -**CoreApiClient este generat în timpul dev/build.** Dacă încercați să îl utilizați fără a rula mai întâi `yarn twenty dev` sau `yarn twenty build`, va arunca o eroare. Generarea are loc automat — CLI-ul introspectează schema GraphQL a spațiului dvs. de lucru, generează un client tipat folosind `@genql/cli`, scrie sursele generate în `node_modules/twenty-client-sdk/dist/core/generated/` și înlocuiește stubs-urile din `node_modules/twenty-client-sdk/dist/core.mjs` și `node_modules/twenty-client-sdk/dist/core.cjs`. +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### Folosirea CoreSchema pentru adnotări de tip -`CoreSchema` oferă tipuri TypeScript care se potrivesc obiectelor din spațiul dvs. de lucru, utile pentru tiparea stării componentelor sau a parametrilor funcțiilor: +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient` este livrat preconstruit împreună cu SDK-ul (nu este necesară generarea). Interoghează endpointul `/metadata` pentru configurarea spațiului de lucru, aplicații și încărcări de fișiere: +`MetadataApiClient` este livrat preconstruit împreună cu SDK-ul (nu este necesară generarea). Interoghează endpointul `/metadata` pentru configurarea spațiului de lucru, aplicații și încărcări de fișiere. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### Acreditări la rulare - -Când codul dvs. rulează pe Twenty (funcții de logică sau componente Front), platforma injectează acreditările ca variabile de mediu: - -* `TWENTY_API_URL` — URL-ul de bază al API-ului Twenty -* `TWENTY_API_KEY` — Cheie cu durată scurtă, limitată la rolul implicit de funcție al aplicației - -Nu trebuie să le transmiteți clienților — aceștia citesc automat din `process.env`. Permisiunile cheii API sunt determinate de rolul referențiat în `defaultRoleUniversalIdentifier` din `application-config.ts`. - #### Încărcarea fișierelor -`MetadataApiClient` include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier. Implementează [specificația pentru cereri GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| Parametru | Tip | Descriere | -| ---------------------------------- | -------- | ------------------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Conținutul brut al fișierului | -| `filename` | `string` | Numele fișierului (folosit pentru stocare și afișare) | -| `contentType` | `string` | Tipul MIME al fișierului (are valoarea implicită `application/octet-stream` dacă este omis) | -| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` al câmpului de tip fișier de pe obiectul tău | +| Parametru | Tip | Descriere | +| ---------------------------------- | -------- | ------------------------------------------------------------------ | +| `fileBuffer` | `Buffer` | Conținutul brut al fișierului | +| `filename` | `string` | Numele fișierului (folosit pentru stocare și afișare) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` al câmpului de tip fișier de pe obiectul tău | Puncte cheie: - * Folosește `universalIdentifier` al câmpului (nu ID-ul specific spațiului de lucru), astfel încât codul dvs. de încărcare funcționează în orice spațiu de lucru în care aplicația dvs. este instalată. * `url` returnat este un URL semnat pe care îl poți folosi pentru a accesa fișierul încărcat. -### Exemplu Hello World + + -Explorați un exemplu minim, cap la cap, care demonstrează obiecte, funcții de logică, componente Front și declanșatoare multiple [aici](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + Când codul dvs. rulează pe Twenty (funcții de logică sau componente Front), platforma injectează acreditările ca variabile de mediu: + + * `TWENTY_API_URL` — URL-ul de bază al API-ului Twenty + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + Nu trebuie să le transmiteți clienților — aceștia citesc automat din `process.env`. Permisiunile cheii API sunt determinate de rolul referențiat în `defaultRoleUniversalIdentifier` din `application-config.ts`. + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### Configurare + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| Funcție | Descriere | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## Referință CLI + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/ro/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/ro/developers/extend/apps/getting-started.mdx index bf4010a979..5197fb3c75 100644 --- a/packages/twenty-docs/l/ro/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/ro/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: Creați prima dvs. aplicație Twenty în câteva minute. --- -Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare. +Apps are currently in alpha. The feature works but is still evolving. Aplicațiile vă permit să extindeți Twenty cu obiecte personalizate, câmpuri, funcții logice, abilități IA și componente UI — toate gestionate ca cod. -**Ce puteți construi:** - -* Obiecte, câmpuri, vizualizări și elemente de navigare personalizate pentru a defini modelul dumneavoastră de date -* Funcții logice declanșate de rute HTTP, programări cron sau evenimente din baza de date -* Componente front-end care se afișează direct în interfața Twenty -* Abilități care extind capabilitățile agenților AI ai Twenty -* Implementați o aplicație în mai multe spații de lucru - ## Cerințe -* Node.js 24+ -* Yarn 4 -* Docker (sau o instanță Twenty locală în execuție) +Before you begin, make sure the following is installed on your machine: -## Începeți +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Creați o aplicație nouă folosind generatorul oficial, apoi autentificați-vă și începeți să dezvoltați: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> Folosiți opțiunea `--minimal` pentru a genera o instalare minimă +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -De aici puteți: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## Structura proiectului (generată) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* Copiază o aplicație de bază minimală în `my-twenty-app/` -* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4 -* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty` -* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului. + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/ro/developers/extend/apps/publishing) for details. + -O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/ro/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -Pe scurt: +### Key files -* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulați `yarn twenty help` pentru a lista toate comenzile disponibile. -* **.gitignore**: Ignoră artefacte comune precum `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect. -* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect. -* **.oxlintrc.json** și **tsconfig.json**: Oferă linting și configurație TypeScript pentru fișierele TypeScript ale aplicației. -* **README.md**: Un README scurt în rădăcina aplicației, cu instrucțiuni de bază. -* **public/**: Un folder pentru stocarea resurselor publice (imagini, fonturi, fișiere statice) care vor fi servite împreună cu aplicația dvs. Fișierele plasate aici sunt încărcate în timpul sincronizării și sunt accesibile la rulare. -* **src/**: Locul principal unde vă definiți aplicația sub formă de cod +| File / Folder | Scop | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Abilități care extind capabilitățile agenților AI ai Twenty. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Detectarea entităților +## Managing remotes -SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`: - -| Funcție ajutătoare | Tipul entității | -| -------------------------------- | -------------------------------------------------------------- | -| `defineObject` | Definiții de obiecte personalizate | -| `defineLogicFunction` | Definiții de funcții de logică | -| `definePreInstallLogicFunction` | Funcție logică de pre-instalare (rulează înainte de instalare) | -| `definePostInstallLogicFunction` | Funcție logică post-instalare (rulează după instalare) | -| `defineFrontComponent` | Definiții ale componentelor de interfață | -| `defineRole` | Definiții de rol | -| `defineField` | Extensii de câmp pentru obiectele existente | -| `defineView` | Definiții pentru vizualizări salvate | -| `defineNavigationMenuItem` | Definiții pentru elemente de meniu de navigare | -| `defineSkill` | Definiții ale abilităților agentului IA | - - -**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință. - - -Exemplu de entitate detectată: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Comenzile ulterioare vor adăuga mai multe fișiere și foldere: - -* `yarn twenty dev` va genera automat `CoreApiClient` tipizat (pentru datele spațiului de lucru prin `/graphql`) în `node_modules/twenty-client-sdk/`. `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`) este livrat preconstruit și este disponibil imediat. Importați-le din `twenty-client-sdk/core` și `twenty-client-sdk/metadata`, respectiv. -* `yarn twenty add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele personalizate, funcțiile, componentele front-end, rolurile, abilitățile și altele. - -## Autentificare - -Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita: - -* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent) -* Cheie API - -Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele. - -### Gestionarea spațiilor de lucru +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -După ce ați schimbat spațiul de lucru cu `yarn twenty auth:switch`, toate comenzile ulterioare vor folosi implicit acel spațiu de lucru. Îl puteți totuși suprascrie temporar cu `--workspace `. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Pornirea serverului + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Comandă | Descriere | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Configurare manuală (fără generator) -Deși recomandăm utilizarea `create-twenty-app` pentru cea mai bună experiență de început, puteți configura și un proiect manual. Nu instalați CLI-ul global. În schimb, adăugați `twenty-sdk` ca dependență locală și conectați un singur script în package.json-ul dvs.: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Apoi adăugați un script `twenty`: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ Apoi adăugați un script `twenty`: } ``` -Acum poți rula toate comenzile prin `yarn twenty `, de ex. `yarn twenty dev`, `yarn twenty help`, etc. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## Cum să folosești o instanță Twenty locală - -Dacă rulezi deja local o instanță Twenty (de exemplu prin `npx nx start twenty-server`), te poți conecta la ea în loc să folosești Docker: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Depanare -* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare. -* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil. -* Tipuri sau client lipsă/învechite: repornește `yarn twenty dev` — acesta generează automat clientul tipizat. -* Modul dev nu sincronizează: asigură-te că `yarn twenty dev` rulează și că modificările nu sunt ignorate de mediul tău. +If you run into issues: -Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/ro/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/ro/developers/extend/apps/publishing.mdx index 5a02a85fd1..dac2ddd384 100644 --- a/packages/twenty-docs/l/ro/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/ro/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: Distribuie aplicația ta Twenty în marketplace sau implementeaz-o --- -Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare. + Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare. ## Prezentare generală După ce aplicația ta este [construită și testată local](/l/ro/developers/extend/apps/building), ai două căi pentru distribuire: -* **Publică pe npm** — listează aplicația ta în marketplace-ul Twenty pentru ca orice spațiu de lucru să o poată descoperi și instala. * **Implementați o arhivă tar** — încărcați aplicația direct pe un server Twenty anume pentru uz intern sau privat. +* **Publică pe npm** — listează aplicația ta în marketplace-ul Twenty pentru ca orice spațiu de lucru să o poată descoperi și instala. Ambele căi pornesc din aceeași etapă de **build**. ## Construirea aplicației -Comanda `build` compilează sursele TypeScript, transpilează funcțiile de logică și componentele de front-end și generează un `manifest.json` care descrie conținutul aplicației: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -Rezultatul este scris în `.twenty/output/`. Acest director conține tot ce este necesar pentru distribuție: cod compilat, resurse, manifestul și o copie a fișierului tău `package.json`. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -Pentru a crea și un pachet `.tgz` (folosit intern de comanda de implementare sau pentru distribuire manuală): +## Implementare pe un server (tarball) + +Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți implementa un tarball direct pe un server Twenty. + +### Cerințe + +Înainte de implementare, ai nevoie de un remote configurat care să indice serverul țintă. Remote-urile stochează local URL-ul serverului și credențialele de autentificare în `~/.twenty/config.json`. + +Adaugă un remote: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### Implementare + +Construiește și încarcă aplicația ta pe server într-un singur pas: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Partajarea unei aplicații implementate + +Aplicațiile tarball nu sunt listate în marketplace-ul public, astfel încât alte spații de lucru de pe același server nu le vor descoperi prin navigare. Pentru a partaja o aplicație implementată: + +1. Mergi la **Setări > Aplicații > Înregistrări** și deschide aplicația ta +2. În fila **Distribuție**, fă clic pe **Copiază linkul de partajare** +3. Partajează acest link cu utilizatori din alte spații de lucru — îi duce direct la pagina de instalare a aplicației + +Linkul de partajare folosește URL-ul de bază al serverului (fără niciun subdomeniu de spațiu de lucru), astfel încât funcționează pentru orice spațiu de lucru de pe server. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Gestionarea versiunilor + +Pentru a lansa o actualizare: + +1. Actualizează câmpul `version` din `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Spațiile de lucru care au aplicația instalată vor vedea actualizarea disponibilă în setările lor + +{/* TODO: add screenshot of the Upgrade button */} + ## Publicarea pe npm Publicarea pe npm face ca aplicația ta să poată fi descoperită în marketplace-ul Twenty. Orice spațiu de lucru Twenty poate răsfoi, instala și actualiza aplicațiile din marketplace direct din interfață. @@ -39,41 +81,42 @@ Publicarea pe npm face ca aplicația ta să poată fi descoperită în marketpla ### Cerințe * Un cont [npm](https://www.npmjs.com) -* Cuvântul cheie `twenty-app` trebuie să fie listat în array-ul `keywords` din `package.json`-ul tău - -### Adăugarea cuvântului cheie necesar - -Marketplace-ul Twenty descoperă aplicații căutând în registrul npm pachete cu cuvântul cheie `twenty-app`. Adaugă-l în `package.json`-ul tău: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -Marketplace-ul caută `keywords:twenty-app` în registrul npm. Fără acest cuvânt cheie, pachetul tău nu va apărea în marketplace chiar dacă are prefixul de nume `twenty-app-`. - +### Metadate pentru marketplace -### Pași +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **Construiește-ți aplicația:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **Publică pe npm:** +See the [defineApplication accordion](/l/ro/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -Aceasta rulează `npm publish` din directorul `.twenty/output/`. - Pentru a publica sub un dist-tag specific (de ex., `beta` sau `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### Cum funcționează descoperirea în marketplace -Serverul Twenty sincronizează catalogul marketplace-ului din registrul npm la fiecare oră: +The Twenty server syncs its marketplace catalog from the npm registry **every hour**. -1. Caută toate pachetele npm cu cuvântul cheie `keywords:twenty-app` -2. Pentru fiecare pachet, preia `manifest.json` din CDN-ul npm -3. Metadatele aplicației (nume, descriere, autor, logo, capturi de ecran, categorie) sunt extrase din manifest și afișate în marketplace - -După publicare, poate dura până la o oră ca aplicația ta să apară în marketplace. Pentru a declanșa sincronizarea imediat, în loc să aștepți următoarea rulare orară: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -Pentru a viza un remote specific: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -Metadatele afișate în marketplace provin din apelul tău `defineApplication()` din codul sursă al aplicației — câmpuri precum `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` și `termsUrl`. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. Dacă aplicația ta nu definește un `aboutDescription` în `defineApplication()`, piața va folosi automat fișierul `README.md` al pachetului tău de pe npm drept conținut pentru pagina Despre. Acest lucru înseamnă că poți menține un singur README atât pentru npm, cât și pentru piața Twenty. Dacă vrei o descriere diferită în piață, setează explicit `aboutDescription`. @@ -108,7 +143,7 @@ Dacă aplicația ta nu definește un `aboutDescription` în `defineApplication() ### Publicare CI -Proiectul generat include un workflow GitHub Actions care publică la fiecare lansare: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` Pentru alte sisteme CI (GitLab CI, CircleCI etc.), se aplică aceleași trei comenzi: `yarn install`, `yarn twenty build`, apoi `npm publish` din `.twenty/output`. - + **npm provenance** este opțională, dar recomandată. Publicarea cu `--provenance` adaugă un badge de încredere la listarea ta în npm, permițând utilizatorilor să verifice că pachetul a fost construit dintr-un commit specific într-un pipeline CI public. Vezi [documentația npm provenance](https://docs.npmjs.com/generating-provenance-statements) pentru instrucțiuni de configurare. - - -## Implementare pe un server (tarball) - -Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți implementa un tarball direct pe un server Twenty. - -### Cerințe - -Înainte de implementare, ai nevoie de un remote configurat care să indice serverul țintă. Remote-urile stochează local URL-ul serverului și credențialele de autentificare în `~/.twenty/config.json`. - -Adaugă un remote: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -Pentru un server de dezvoltare local: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -Te poți autentifica și cu o cheie API pentru medii neinteractive: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -Gestionează-ți remote-urile: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### Implementare - -Construiește și încarcă aplicația ta pe server într-un singur pas: - -```bash filename="Terminal" -yarn twenty deploy -``` - -Aceasta construiește aplicația cu `--tarball`, apoi încarcă tarball-ul către remote-ul implicit printr-o încărcare multipart GraphQL. - -Pentru a implementa către un remote specific: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### Partajarea unei aplicații implementate - -Aplicațiile tarball nu sunt listate în marketplace-ul public, astfel încât alte spații de lucru de pe același server nu le vor descoperi prin navigare. Pentru a partaja o aplicație implementată: - -1. Mergi la **Setări > Aplicații > Înregistrări** și deschide aplicația ta -2. În fila **Distribuție**, fă clic pe **Copiază linkul de partajare** -3. Partajează acest link cu utilizatori din alte spații de lucru — îi duce direct la pagina de instalare a aplicației - -Linkul de partajare folosește URL-ul de bază al serverului (fără niciun subdomeniu de spațiu de lucru), astfel încât funcționează pentru orice spațiu de lucru de pe server. - -### Gestionarea versiunilor - -Pentru a lansa o actualizare: - -1. Actualizează câmpul `version` din `package.json` -2. Rulează `yarn twenty deploy` (sau `yarn twenty deploy -r production`) -3. Spațiile de lucru care au aplicația instalată vor vedea actualizarea disponibilă în setările lor + ## Instalarea aplicațiilor -După ce o aplicație este publicată (npm) sau implementată (tarball), spațiile de lucru o instalează prin interfața utilizatorului (UI): +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -Sau din pagina **Setări > Aplicații** din Twenty UI, unde pot fi navigate și instalate atât aplicațiile din marketplace, cât și cele implementate prin tarball. - -## Categorii de distribuție a aplicațiilor - -Twenty organizează aplicațiile în trei categorii, în funcție de modul în care sunt distribuite: - -| Categorie | Cum funcționează | Vizibilă în marketplace? | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| **Dezvoltare** | Aplicații în modul de dezvoltare local, rulate prin `yarn twenty dev`. Folosite pentru construire și testare. | Nu | -| **Publicat (npm)** | Aplicații publicate pe npm cu cuvântul cheie `twenty-app`. Listate în marketplace pentru ca orice spațiu de lucru să le poată instala. | Da | -| **Intern (tarball)** | Aplicații implementate prin tarball pe un server specific. Disponibile doar pentru spațiile de lucru de pe acel server printr-un link de partajare. | Nu | - - -Pornește în modul **Dezvoltare** în timp ce îți construiești aplicația. Când este gata, alege **Publicat** (npm) pentru distribuire largă sau **Intern** (tarball) pentru implementare privată. - - -## Referință CLI - -| Comandă | Descriere | Opțiuni cheie | -| --------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- | -| `yarn twenty build` | Compilează aplicația și generează manifestul | `--tarball` — creează și un pachet `.tgz` | -| `yarn twenty publish` | Construiește și publică pe npm | `--tag ` — dist-tag npm (de ex., `beta`, `next`) | -| `yarn twenty deploy` | Construiește și încarcă un tarball pe un server | `-r, --remote ` — remote țintă | -| `yarn twenty catalog-sync` | Declanșează sincronizarea catalogului marketplace-ului pe server | `-r, --remote ` — remote țintă | -| `yarn twenty install` | Instalează o aplicație implementată pe un spațiu de lucru | `-r, --remote ` — remote țintă | -| `yarn twenty dev` | Monitorizează și sincronizează modificările locale | Folosește remote-ul implicit | -| `yarn twenty remote add` | Adaugă o conexiune la server | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | Listează remote-urile configurate | — | -| `yarn twenty remote switch` | Setează remote-ul implicit | — | -| `yarn twenty remote status` | Afișează starea conexiunii | — | -| `yarn twenty remote remove` | Elimină un remote | — | diff --git a/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx index 195a427ed9..47628f492a 100644 --- a/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: Construiți și gestionați personalizările Twenty sub formă de c --- -Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare. +Apps are currently in alpha. The feature works but is still evolving. -## Ce sunt aplicațiile? +## What are apps? -Aplicațiile vă permit să construiți și să gestionați personalizările Twenty **sub formă de cod**. În loc să configurați totul prin interfața de utilizator (UI), vă definiți modelul de date și funcțiile de logică în cod — făcând mai rapidă construirea, mentenanța și implementarea în mai multe spații de lucru. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**Ce puteți face astăzi:** +**Ce puteți construi:** -* Definiți obiecte și câmpuri personalizate sub formă de cod (model de date gestionat) -* Creați funcții de logică cu declanșatoare personalizate -* Definiți abilități și agenți pentru AI -* Implementați aceeași aplicație în mai multe spații de lucru +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Cerințe - -* Node.js 24+ și Yarn 4 -* Docker (pentru serverul local de dezvoltare Twenty) - -## Începeți - -Creează o aplicație nouă folosind generatorul oficial. Poate porni automat o instanță Twenty locală pentru tine: +## Quick start ```bash filename="Terminal" -# Creează scheletul unei aplicații noi — CLI-ul îți va oferi opțiunea de a porni un server Twenty local npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Pornește modul de dezvoltare: sincronizează automat modificările locale cu spațiul tău de lucru yarn twenty dev ``` -### Gestionarea serverului local +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/ro/developers/extend/apps/getting-started) guide for the full walkthrough. -SDK-ul include comenzi pentru a gestiona un server local de dezvoltare Twenty (imagine Docker all-in-one cu PostgreSQL, Redis, server și worker pe portul 2020). Aceste comenzi se aplică doar serverului de dezvoltare bazat pe Docker — nu gestionează o instanță Twenty pornită din sursă (de exemplu, `npx nx start twenty-server` pe portul 3000): +## Detailed guides -```bash filename="Terminal" -# Pornește serverul local (descarcă imaginea dacă este necesar) -yarn twenty server start +| Ghid | Descriere | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/ro/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/ro/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/ro/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Verifică starea serverului -yarn twenty server status - -# Afișează în timp real jurnalele serverului -yarn twenty server logs - -# Oprește serverul -yarn twenty server stop - -# Resetează toate datele și pornește de la zero -yarn twenty server reset -``` - -Serverul local vine preconfigurat cu un spațiu de lucru și un utilizator (`tim@apple.dev` / `tim@apple.dev`), astfel încât să poți începe să dezvolți imediat, fără nicio configurare manuală. - -### Autentificare - -Conectează-ți aplicația la serverul local folosind OAuth: - -```bash filename="Terminal" -# Autentifică-te prin OAuth (se deschide browserul) -yarn twenty remote add --local -``` - -Generatorul de schelet acceptă două moduri pentru a controla ce fișiere de exemplu sunt incluse: - -```bash filename="Terminal" -# Implicit (exhaustiv): toate exemplele (obiect, câmp, funcție logică, componentă de interfață, vizualizare, element de meniu de navigare, abilitate, agent) -npx create-twenty-app@latest my-app - -# Minimal: doar fișierele de bază (application-config.ts și default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### Cum să folosești o instanță Twenty locală - -Dacă rulezi deja o instanță Twenty locală, te poți conecta la ea în loc să folosești Docker. Specifică portul pe care ascultă serverul tău local (implicit: `3000`): - -```bash filename="Terminal" -# During scaffolding -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding -yarn twenty remote add --local --port 3000 -``` - -De aici puteți: - -```bash filename="Terminal" -# Adaugă o entitate nouă în aplicația ta (ghidat) -yarn twenty entity:add - -# Urmărește jurnalele funcțiilor aplicației tale -yarn twenty function:logs - -# Execută o funcție după nume -yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}' - -# Execută funcția de pre-instalare -yarn twenty function:execute --preInstall - -# Execută funcția post-instalare -yarn twenty function:execute --postInstall - -# Construiește aplicația pentru distribuire -yarn twenty build - -# Publică aplicația pe npm sau pe un server Twenty -yarn twenty publish - -# Dezinstalează aplicația din spațiul de lucru curent -yarn twenty uninstall - -# Afișează ajutorul pentru comenzi -yarn twenty help -``` - -Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## Structura proiectului (generată) - -Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul: - -* Copiază o aplicație de bază minimală în `my-twenty-app/` -* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4 -* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty` -* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului. - -O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Director pentru resurse publice (imagini, fonturi etc.) - src/ - ├── application-config.ts # Obligatoriu - configurația principală a aplicației - ├── roles/ - │ └── default-role.ts # Rol implicit pentru funcțiile logice - ├── objects/ - │ └── example-object.ts # Exemplu de definiție a unui obiect personalizat - ├── fields/ - │ └── example-field.ts # Exemplu de definiție de câmp independent - ├── logic-functions/ - │ ├── hello-world.ts # Exemplu de funcție logică - │ ├── pre-install.ts # Funcție logică de pre-instalare - │ └── post-install.ts # Funcție logică post-instalare - ├── front-components/ - │ └── hello-world.tsx # Exemplu de componentă de interfață - ├── views/ - │ └── example-view.ts # Exemplu de definiție a unei vizualizări salvate - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Exemplu de link de navigare în bara laterală - ├── skills/ - │ └── example-skill.ts # Exemplu de definiție a unei abilități a agentului AI - └── agents/ - └── example-agent.ts # Exemplu de definiție a unui agent AI -``` - -Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`). - -Pe scurt: - -* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulează `yarn twenty help` pentru a lista toate comenzile disponibile. -* **.gitignore**: Ignoră artefacte comune precum `node_modules`, `.yarn`, `generated/` (client tipizat), `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect. -* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect. -* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources. -* **README.md**: Un README scurt în rădăcina aplicației, cu instrucțiuni de bază. -* **public/**: Un folder pentru stocarea resurselor publice (imagini, fonturi, fișiere statice) care vor fi servite împreună cu aplicația ta. Fișierele plasate aici sunt încărcate în timpul sincronizării și sunt accesibile la rulare. -* **src/**: Locul principal unde vă definiți aplicația sub formă de cod +## Key concepts ### Detectarea entităților -SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Funcție ajutătoare | Tipul entității | -| ---------------------------------- | -------------------------------------------------------------- | -| `defineObject()` | Definiții de obiecte personalizate | -| `defineLogicFunction()` | Definiții de funcții de logică | -| `definePreInstallLogicFunction()` | Funcție logică de pre-instalare (rulează înainte de instalare) | -| `definePostInstallLogicFunction()` | Funcție logică post-instalare (rulează după instalare) | -| `defineFrontComponent()` | Definiții ale componentelor de interfață | -| `defineRole()` | Definiții de rol | -| `defineField()` | Extensii de câmp pentru obiectele existente | -| `defineView()` | Definiții pentru vizualizări salvate | -| `defineNavigationMenuItem()` | Definiții pentru elemente de meniu de navigare | -| `defineSkill()` | Definiții ale abilităților agentului AI | -| `defineAgent()` | Definiții ale agenților AI | +### Available entity types - -**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință. - +| Funcție | Scop | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Exemplu de entitate detectată: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Comenzile ulterioare vor adăuga mai multe fișiere și foldere: - -* `yarn twenty dev` va genera automat doi clienți API tipizați în `node_modules/twenty-sdk/clients`: `CoreApiClient` (pentru datele spațiului de lucru prin `/graphql`) și `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`). -* `yarn twenty entity:add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele, funcțiile, componentele front-end, rolurile, abilitățile și altele. - -## Autentificare - -Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita: - -* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent) -* Cheie API - -Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele. - -### Gestionarea spațiilor de lucru +### Referință CLI ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login - -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace - -# List all configured workspaces -yarn twenty auth:list - -# Switch the default workspace (interactive) -yarn twenty auth:switch - -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -După ce ați schimbat spațiul de lucru cu `yarn twenty auth:switch`, toate comenzile ulterioare vor folosi implicit acel spațiu de lucru. Îl puteți totuși suprascrie temporar cu `--workspace `. - -## Utilizați resursele SDK (tipuri și configurare) - -Biblioteca twenty-sdk oferă blocuri de bază tipizate și funcții ajutătoare pe care le utilizați în aplicația dvs. Mai jos sunt elementele cheie cu care veți interacționa cel mai des. - -### Funcții ajutătoare - -SDK-ul oferă funcții ajutătoare pentru definirea entităților aplicației. După cum este descris în [Detectarea entităților](#entity-detection), trebuie să folosiți `export default define({...})` pentru ca entitățile să fie detectate: - -| Funcție | Scop | -| ---------------------------------- | ---------------------------------------------------------------------- | -| `defineApplication()` | Configurați metadatele aplicației (obligatoriu, una per aplicație) | -| `defineObject()` | Definiți obiecte personalizate cu câmpuri | -| `defineLogicFunction()` | Definiți funcții de logică cu handleri | -| `definePreInstallLogicFunction()` | Definește o funcție logică de pre-instalare (una per aplicație) | -| `definePostInstallLogicFunction()` | Definește o funcție logică post-instalare (una per aplicație) | -| `defineFrontComponent()` | Definiți componente Front pentru interfața de utilizator personalizată | -| `defineRole()` | Configurați permisiunile rolurilor și accesul la obiecte | -| `defineField()` | Extindeți obiectele existente cu câmpuri suplimentare | -| `defineView()` | Definește vizualizări salvate pentru obiecte | -| `defineNavigationMenuItem()` | Definește linkuri de navigare în bara laterală | -| `defineSkill()` | Definiți abilități pentru agentul AI | -| `defineAgent()` | Definiți agenți AI cu prompturi de sistem | - -Aceste funcții validează configurația în timpul build-ului și oferă completare automată în IDE și siguranța tipurilor. - -### Definirea obiectelor - -Obiectele personalizate descriu atât schema, cât și comportamentul înregistrărilor din spațiul dvs. de lucru. Utilizați `defineObject()` pentru a defini obiecte cu validare încorporată: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Puncte cheie: - -* Folosiți `defineObject()` pentru validare încorporată și suport mai bun în IDE. -* `universalIdentifier` trebuie să fie unic și stabil între implementări. -* Fiecare câmp necesită un `name`, un `type`, un `label` și propriul `universalIdentifier` stabil. -* Matricea `fields` este opțională — puteți defini obiecte fără câmpuri personalizate. -* Puteți genera obiecte noi folosind `yarn twenty entity:add`, care vă ghidează prin denumire, câmpuri și relații. - - -**Câmpurile de bază sunt create automat.** Când definiți un obiect personalizat, Twenty adaugă automat câmpuri standard -precum `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` și `deletedAt`. -Nu trebuie să le definiți în tabloul `fields` — adăugați doar câmpurile personalizate proprii. -Puteți suprascrie câmpurile implicite definind un câmp cu același nume în tabloul `fields`, -dar acest lucru nu este recomandat. - - -### Definirea câmpurilor pe obiecte existente - -Folosiți `defineField()` pentru a adăuga câmpuri personalizate la obiectele existente — atât obiecte standard (precum `company`, `person`, `opportunity`), cât și obiecte personalizate definite de alte aplicații. Fiecare câmp se află în propriul fișier și face referire la obiectul țintă prin `universalIdentifier`. - -Pentru a face referire la obiectele standard, importați `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` din `twenty-sdk`. Această constantă furnizează identificatori stabili pentru toate obiectele integrate și câmpurile lor: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Puncte cheie: - -* `objectUniversalIdentifier` îi indică lui Twenty la care obiect să atașeze câmpul. Folosiți `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` pentru obiectele standard. -* Fiecare câmp necesită propriul `universalIdentifier` stabil, un `name`, `type`, `label` și `objectUniversalIdentifier` țintă. -* Puteți genera câmpuri noi folosind `yarn twenty entity:add` și alegând opțiunea pentru câmp. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` este exportată și ca `STANDARD_OBJECT` pentru comoditate — ambele se referă la aceeași constantă. - -Obiectele standard disponibile includ: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` și `workspaceMember`. - -Fiecare obiect standard expune, de asemenea, identificatorii câmpurilor sale. De exemplu, pentru a face referire la un câmp specific pe un obiect standard în permisiunile de rol: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### Câmpuri de relație pe obiecte existente - -Puteți defini, de asemenea, câmpuri de relație care leagă obiectele existente de obiectele dvs. personalizate: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### Configurația aplicației (application-config.ts) - -Fiecare aplicație are un singur fișier `application-config.ts` care descrie: - -* **Cine este aplicația**: identificatori, nume de afișare și descriere. -* **Cum rulează funcțiile**: ce rol folosesc pentru permisiuni. -* **(Opțional) variabile**: perechi cheie–valoare expuse funcțiilor ca variabile de mediu. -* **(Opțional) funcție de pre-instalare**: o funcție logică care rulează înainte ca aplicația să fie instalată. -* **(Opțional) funcție post-instalare**: o funcție logică care rulează după instalarea aplicației. - -Folosiți `defineApplication()` pentru a defini configurația aplicației: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notițe: - -* Câmpurile `universalIdentifier` sunt ID-uri deterministe pe care le dețineți; generați-le o singură dată și păstrați-le stabile între sincronizări. -* `applicationVariables` devin variabile de mediu pentru funcțiile dvs. (de exemplu, `DEFAULT_RECIPIENT_NAME` este disponibil ca `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` trebuie să corespundă fișierului de rol (vedeți mai jos). -* Funcțiile de pre-instalare și post-instalare sunt detectate automat în timpul construirii manifestului. Vezi [Funcții de pre-instalare](#pre-install-functions) și [Funcții post-instalare](#post-install-functions). - -#### Roluri și permisiuni - -Aplicațiile pot defini roluri care încapsulează permisiuni asupra obiectelor și acțiunilor din spațiul dvs. de lucru. Câmpul `defaultRoleUniversalIdentifier` din `application-config.ts` desemnează rolul implicit utilizat de funcțiile de logică ale aplicației. - -* Cheia API de runtime injectată ca `TWENTY_API_KEY` este derivată din acest rol implicit pentru funcții. -* Clientul tipizat va fi restricționat la permisiunile acordate acelui rol. -* Respectați principiul celui mai mic privilegiu: creați un rol dedicat doar cu permisiunile de care au nevoie funcțiile, apoi referiți identificatorul său universal. - -##### Rol implicit pentru funcții (\*.role.ts) - -Când generați o aplicație nouă, CLI creează și un fișier de rol implicit. Folosiți `defineRole()` pentru a defini roluri cu validare încorporată: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -`universalIdentifier` al acestui rol este apoi referențiat în `application-config.ts` ca `defaultRoleUniversalIdentifier`. Cu alte cuvinte: - -* **\*.role.ts** definește ce poate face rolul implicit pentru funcții. -* **application-config.ts** indică acel rol, astfel încât funcțiile moștenesc permisiunile lui. - -Notițe: - -* Porniți de la rolul generat, apoi restrângeți-l progresiv urmând principiul celui mai mic privilegiu. -* Înlocuiți `objectPermissions` și `fieldPermissions` cu obiectele/câmpurile de care au nevoie funcțiile. -* `permissionFlags` controlează accesul la capabilități la nivelul platformei. Mențineți-le la minimum; adăugați doar ceea ce aveți nevoie. -* Vedeți un exemplu funcțional în aplicația Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Configurația funcției de logică și punctul de intrare - -Fiecare fișier de funcție folosește `defineLogicFunction()` pentru a exporta o configurație cu un handler și declanșatoare opționale. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -});},{ -``` - -Tipuri comune de declanșatoare: - -* **route**: Expune funcția pe o rută și metodă HTTP **sub endpoint-ul `/s/`**: - -> de ex. `path: '/post-card/create',` -> apel pe `/s/post-card/create` - -* **cron**: Rulează funcția pe un program folosind o expresie CRON. -* **databaseEvent**: Rulează la evenimentele ciclului de viață ale obiectelor din spațiul de lucru. Când operațiunea evenimentului este `updated`, câmpurile specifice de urmărit pot fi specificate în array-ul `updatedFields`. Dacă este lăsat nedefinit sau gol, orice actualizare va declanșa funcția. - -> de ex. `person.updated` - -Notițe: - -* Matricea `triggers` este opțională. Funcțiile fără declanșatoare pot fi folosite ca funcții utilitare apelate de alte funcții. -* Puteți combina mai multe tipuri de declanșatoare într-o singură funcție. - -### Funcții de pre-instalare - -O funcție de pre-instalare este o funcție logică ce rulează automat înainte ca aplicația ta să fie instalată într-un spațiu de lucru. Aceasta este utilă pentru sarcini de validare, verificări ale condițiilor prealabile sau pregătirea stării spațiului de lucru înainte ca instalarea principală să continue. - -Când creezi scheletul unei aplicații noi cu `create-twenty-app`, ți se generează o funcție de pre-instalare la `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Poți, de asemenea, să execuți manual funcția de pre-instalare oricând folosind CLI: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Puncte cheie: - -* Funcțiile de pre-instalare folosesc `definePreInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi). -* Este permisă o singură funcție de pre-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una. -* Proprietatea `universalIdentifier` a funcției este setată automat ca `preInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`. -* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de pregătire mai lungi. -* Funcțiile de pre-instalare nu au nevoie de declanșatoare — sunt invocate de platformă înainte de instalare sau manual prin `function:execute --preInstall`. - -### Funcții post-instalare - -O funcție post-instalare este o funcție logică care rulează automat după instalarea aplicației într-un spațiu de lucru. Aceasta este utilă pentru sarcini de configurare unice, cum ar fi popularea cu date implicite, crearea înregistrărilor inițiale sau configurarea setărilor spațiului de lucru. - -Când creezi scheletul unei aplicații noi cu `create-twenty-app`, este generată o funcție post-instalare la `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Poți, de asemenea, să execuți manual funcția post-instalare oricând folosind CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Puncte cheie: - -* Funcțiile de post-instalare folosesc `definePostInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi). -* Este permisă o singură funcție de post-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una. -* Proprietatea `universalIdentifier` a funcției este setată automat ca `postInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`. -* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor. -* Funcțiile post-instalare nu au nevoie de declanșatoare — sunt invocate de platformă în timpul instalării sau manual prin `function:execute --postInstall`. - -### Payload-ul declanșatorului de rută - - -**Modificare incompatibilă (v1.16, ianuarie 2026):** Formatul payload-ului declanșatorului de rută s-a schimbat. Înainte de v1.16, parametrii de interogare (query), parametrii de cale și corpul erau trimiși direct ca payload. Începând cu v1.16, acestea sunt incluse într-un obiect structurat `RoutePayload`. - -**Înainte de v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**După v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**Pentru a migra funcțiile existente:** Actualizează handler-ul pentru a extrage câmpurile din `event.body`, `event.queryStringParameters` sau `event.pathParameters` în loc să le iei direct din obiectul params. - - -Când un declanșator de rută apelează funcția dvs. de logică, aceasta primește un obiect `RoutePayload` care urmează formatul AWS HTTP API v2. Importă tipul din `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Tipul `RoutePayload` are următoarea structură: - -| Proprietate | Tip | Descriere | -| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------ | -| `headers` | `Record` | Anteturi HTTP (doar cele listate în `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Parametri query string (valorile multiple unite cu virgule) | -| `pathParameters` | `Record` | Parametri de cale extrași din modelul rutei (de ex., `/users/:id` → `{ id: '123' }`) | -| `corp` | `object \| null` | Corpul cererii analizat (JSON) | -| `isBase64Encoded` | `boolean` | Indică dacă corpul este codificat în base64 | -| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Calea brută a cererii | - -### Transmiterea anteturilor HTTP - -În mod implicit, anteturile HTTP din cererile de intrare **nu** sunt transmise funcției dvs. de logică din motive de securitate. Pentru a accesa anumite anteturi, listează-le explicit în array-ul `forwardedRequestHeaders`: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -În handler, poți apoi accesa aceste anteturi: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - Numele anteturilor sunt normalizate la litere mici. Accesează-le folosind chei cu litere mici (de exemplu, `event.headers['content-type']`). - - -Puteți crea funcții noi în două moduri: - -* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o funcție logică nouă. Aceasta generează un fișier inițial cu un handler și o configurație. -* **Manual**: Creați un fișier nou `*.logic-function.ts` și folosiți `defineLogicFunction()`, urmând același model. - -### Marcarea unei funcții logice drept instrument - -Funcțiile logice pot fi expuse ca **instrumente** pentru agenți de IA și fluxuri de lucru. Când o funcție este marcată ca instrument, poate fi descoperită de funcționalitățile de IA ale Twenty și poate fi selectată ca pas în automatizări ale fluxurilor de lucru. - -Pentru a marca o funcție logică drept instrument, setați `isTool: true` și furnizați un `toolInputSchema` care descrie parametrii de intrare așteptați folosind [JSON Schema](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Puncte cheie: - -* **`isTool`** (`boolean`, implicit: `false`): Când este setat la `true`, funcția este înregistrată ca instrument și devine disponibilă pentru agenții AI și automatizările de fluxuri de lucru. -* **`toolInputSchema`** (`object`, opțional): Un obiect JSON Schema care descrie parametrii pe care îi acceptă funcția dvs. Agenții AI folosesc această schemă pentru a înțelege ce intrări așteaptă instrumentul și pentru a valida apelurile. Dacă este omisă, schema are implicit valoarea `{ type: 'object', properties: {} }` (fără parametri). -* Funcțiile cu `isTool: false` (sau nedefinit) **nu** sunt expuse ca instrumente. Pot totuși fi executate direct sau apelate de alte funcții, dar nu vor apărea în descoperirea instrumentelor. -* **Denumierea instrumentelor**: Când este expusă ca instrument, denumirea funcției este normalizată automat la `logic_function_` (convertită la litere mici, iar caracterele non-alfanumerice sunt înlocuite cu caractere de subliniere). De exemplu, `enrich-company` devine `logic_function_enrich_company`. -* Puteți combina `isTool` cu declanșatoare — o funcție poate fi atât un instrument (apelabilă de agenții AI), cât și declanșată de evenimente (cron, evenimente de bază de date, rute) în același timp. - - -**Scrieți o `description` bună.** Agenții AI se bazează pe câmpul `description` al funcției pentru a decide când să folosească instrumentul. Fiți specifici cu privire la ceea ce face instrumentul și când ar trebui apelat. - - -### Componente Front - -Componentele Front vă permit să construiți componente React personalizate care sunt randate în interfața Twenty. Utilizați `defineFrontComponent()` pentru a defini componente cu validare încorporată: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -Puncte cheie: - -* Componentele Front sunt componente React care sunt randate în contexte izolate în cadrul Twenty. -* Câmpul `component` face referire la componenta React. -* Componentele sunt construite și sincronizate automat în timpul `yarn twenty dev`. - -Puteți crea componente Front noi în două moduri: - -* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o componentă frontend nouă. -* **Manual**: Creați un fișier nou `.tsx` și folosiți `defineFrontComponent()`, urmând același model. - -#### Where front components can be used - -Front components can render in two locations within Twenty: - -* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. -* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. - -#### Headless vs non-headless - -Front components come in two rendering modes controlled by the `isHeadless` option: - -**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. - -**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Adding command menu items - -To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. - -The `command` object accepts the following fields: - -| Câmp | Tip | Descriere | -| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | -| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | -| `etichetă` | `string` (required) | Display label shown in the command menu | -| `pictogramă` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | -| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | -| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | - -Here is an example from the call-recording app that adds a command scoped to Person records: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. - -#### SDK Command components - -The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. - -Import them from `twenty-sdk/command`: - -* **`Command`** — Runs an async callback via the `execute` prop. -* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. - -Here is a full example of a headless front component using `Command` to run an action from the command menu: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -And an example using `CommandModal` to ask for confirmation before executing: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Execution context - -Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: - -| Hook | Return type | Descriere | -| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | -| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | -| `useUserId()` | `string \| null` | The ID of the current user | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -The context is reactive — if the surrounding record changes, hooks automatically return the updated values. - -#### Host API functions - -Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Funcție | Signature | Descriere | -| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `navigate` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | -| `closeSidePanel` | `() => Promise` | Close the side panel | -| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | -| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Abilități - -Abilitățile definesc instrucțiuni și capabilități reutilizabile pe care agenții AI le pot folosi în spațiul dvs. de lucru. Folosiți `defineSkill()` pentru a defini abilități cu validare încorporată: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Abordare de vânzări', - description: 'Ghidează agentul AI printr-un proces structurat de abordare de vânzări', - icon: 'IconBrain', - content: `Ești un asistent pentru abordare de vânzări. Când contactezi un potențial client: -1. Cercetează compania și noutățile recente -2. Identifică rolul potențialului client și probabilele puncte sensibile -3. Redactează un mesaj personalizat care face referire la detalii specifice -4. Păstrează un ton profesionist, dar conversațional`, -}); -``` - -Puncte cheie: - -* `name` este un șir identificator unic pentru abilitate (se recomandă kebab-case). -* `label` este numele lizibil afișat în interfața cu utilizatorul (UI). -* `content` conține instrucțiunile abilității — acesta este textul pe care agentul AI îl folosește. -* `icon` (opțional) setează pictograma afișată în UI. -* `description` (opțional) oferă context suplimentar despre scopul abilității. - -Puteți crea abilități noi în două moduri: - -* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o abilitate nouă. -* **Manual**: Creați un fișier nou și folosiți `defineSkill()`, urmând același model. - -### Agenți - -Agenții sunt agenți AI definiți cu prompturi de sistem, care pot funcționa în spațiul dvs. de lucru. Utilizați `defineAgent()` pentru a defini agenți cu validare încorporată: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Puncte cheie: - -* `name` este un șir identificator unic pentru agent (se recomandă kebab-case). -* `label` este numele lizibil afișat în interfața cu utilizatorul (UI). -* `prompt` conține promptul de sistem — acesta este textul de instrucțiuni care definește comportamentul agentului. -* `icon` (opțional) setează pictograma afișată în UI. -* `description` (opțional) oferă context suplimentar despre scopul agentului. - -Puteți crea agenți noi în două moduri: - -* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga un agent nou. -* **Manual**: Creați un fișier nou și folosiți `defineAgent()`, urmând același model. - -### Generated typed clients - -Doi clienți tipizați sunt generați automat de `yarn twenty dev` și stocați în `node_modules/twenty-sdk/clients`, pe baza schemei spațiului tău de lucru: - -* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data -* **`MetadataApiClient`** — interoghează endpointul `/metadata` pentru configurarea spațiului de lucru și încărcarea fișierelor - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); -``` - -`CoreApiClient` este regenerat automat de `yarn twenty dev` ori de câte ori obiectele sau câmpurile tale se schimbă. `MetadataApiClient` este livrat preconstruit împreună cu SDK-ul. - -#### Acreditări la runtime în funcțiile de logică - -Când funcția rulează pe Twenty, platforma injectează acreditări ca variabile de mediu înainte de execuția codului: - -* `TWENTY_API_URL`: URL-ul de bază al API-ului Twenty către care țintește aplicația. -* `TWENTY_API_KEY`: Cheie cu durată scurtă, limitată la rolul implicit de funcție al aplicației. - -Notițe: - -* Nu trebuie să transmiteți URL-ul sau cheia API către clientul generat. Acesta citește `TWENTY_API_URL` și `TWENTY_API_KEY` din process.env la runtime. -* Permisiunile cheii API sunt determinate de rolul referențiat în `application-config.ts` prin `defaultRoleUniversalIdentifier`. Acesta este rolul implicit folosit de funcțiile de logică ale aplicației. -* Aplicațiile pot defini roluri pentru a urma principiul celui mai mic privilegiu. Acordați doar permisiunile de care au nevoie funcțiile, apoi setați `defaultRoleUniversalIdentifier` la identificatorul universal al acelui rol. - -#### Încărcarea fișierelor - -`MetadataApiClient` include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier ale obiectelor din spațiul tău de lucru. Deoarece clienții GraphQL standard nu acceptă în mod nativ încărcarea fișierelor multipart, clientul oferă această metodă dedicată care implementează, sub capotă, [specificația cererilor GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec). - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type (defaults to 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -Semnătura metodei: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parametru | Tip | Descriere | -| ---------------------------------- | -------- | ------------------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Conținutul brut al fișierului | -| `filename` | `string` | Numele fișierului (folosit pentru stocare și afișare) | -| `contentType` | `string` | Tipul MIME al fișierului (are valoarea implicită `application/octet-stream` dacă este omis) | -| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` al câmpului de tip fișier de pe obiectul tău | - -Puncte cheie: - -* Metoda `uploadFile` este disponibilă pe `MetadataApiClient` deoarece mutația de încărcare este rezolvată de endpointul `/metadata`. -* Folosește `universalIdentifier` al câmpului (nu ID-ul specific spațiului de lucru), astfel încât codul tău de încărcare funcționează în orice spațiu de lucru în care aplicația ta este instalată — în concordanță cu modul în care aplicațiile fac referire la câmpuri în rest. -* `url` returnat este un URL semnat pe care îl poți folosi pentru a accesa fișierul încărcat. - -### Exemplu Hello World - -Explorați un exemplu minim, cap la cap, care demonstrează obiecte, funcții de logică, componente Front și declanșatoare multiple [aici](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## Building your app - -Once you've developed your app with `app:dev`, use `app:build` to compile it into a distributable package. - -```bash filename="Terminal" -# Construiește aplicația (rezultatul este plasat în .twenty/output/) -yarn twenty build - -# Construiește și creează un pachet tar (.tgz) pentru distribuire -yarn twenty build --tarball -``` - -The build process: - -1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure. -2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild. -3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`. -4. **Generează clientul API tipizat** — examinează schema GraphQL și generează clienți tipizați `CoreApiClient` și `MetadataApiClient`. -5. **Rulează o verificare a tipurilor TypeScript** — rulează `tsc --noEmit` pentru a detecta erorile de tip înainte de publicare. -6. **Reconstruiește cu clientul generat** — efectuează o a doua trecere de compilare astfel încât tipurile clientului generat să fie incluse. -7. **Creează opțional un tarball** — dacă se trece `--tarball`, rulează `npm pack` pentru a crea un fișier `.tgz` gata pentru distribuire. - -Rezultatul build-ului din `.twenty/output/` conține: - -```text -.twenty/output/ -├── manifest.json # Manifest cu sume de control pentru toate fișierele generate -├── package.json # Copiat din rădăcina aplicației -├── yarn.lock # Copiat din rădăcina aplicației -├── src/ -│ ├── logic-functions/ # Fișiere .mjs de funcții de logică compilate -│ └── front-components/ # Fișiere .mjs de componente front-end compilate -├── public/ # Resurse statice (dacă există) -└── my-app-1.0.0.tgz # Doar cu opțiunea --tarball -``` - -| Opțiune | Descriere | -| ----------- | -------------------------------------------------------------- | -| `[appPath]` | Calea către directorul aplicației (implicit directorul curent) | -| `--tarball` | De asemenea, împachetează rezultatul într-un tarball `.tgz` | - -## Publicarea aplicației - -Folosește `app:publish` pentru a distribui aplicația — fie în registrul npm, fie direct pe un server Twenty. - -### Publicare pe npm (implicit) - -```bash filename="Terminal" -# Publică pe npm (necesită autentificare npm) -yarn twenty publish - -# Publică cu un dist-tag (de ex. beta, next) -yarn twenty publish --tag beta -``` - -Aceasta construiește aplicația și rulează `npm publish` din directorul `.twenty/output/`. Pachetul publicat poate fi apoi instalat din marketplace-ul Twenty de către orice spațiu de lucru. - -### Publicare pe un server Twenty - -```bash filename="Terminal" -# Publică direct pe un server Twenty -yarn twenty publish --server https://app.twenty.com -``` - -Aceasta construiește aplicația cu un tarball, o încarcă pe server prin mutația GraphQL `uploadAppTarball` și declanșează instalarea într-un singur pas. Acest lucru este util pentru implementări private sau pentru testare pe un server specific. - -| Opțiune | Descriere | -| ----------------- | -------------------------------------------------------------------- | -| `[appPath]` | Calea către directorul aplicației (implicit directorul curent) | -| `--server ` | Publică pe un server Twenty în loc de npm | -| `--token ` | Jeton de autentificare pentru serverul țintă | -| `--tag ` | npm dist-tag (de ex. `beta`, `next`) — doar pentru publicarea pe npm | - -## Înregistrarea aplicației - -Înainte ca o aplicație să poată fi instalată într-un spațiu de lucru, aceasta trebuie să fie **înregistrată**. O înregistrare este o înregistrare de metadate care descrie de unde provine aplicația și cum se autentifică. Acest lucru este gestionat automat de CLI în cele mai multe cazuri. - -### Tipuri de sursă - -Fiecare înregistrare are un **tip de sursă** care determină modul în care fișierele aplicației sunt preluate în timpul instalării: - -| Tip de sursă | Cum sunt preluate fișierele | Caz de utilizare tipic | -| ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------- | -| `LOCAL` | Fișierele sunt sincronizate în timp real de către watcher-ul CLI — instalarea este omisă | Dezvoltare cu `app:dev` | -| `NPM` | Obținute din registrul npm prin câmpul `sourcePackage` | Aplicații publicate pe npm | -| `TARBALL` | Extrase dintr-un fișier `.tgz` încărcat, stocat pe server | Aplicații private publicate cu `--server` | - -### Cum are loc înregistrarea - -* **`app:dev`** — creează automat o înregistrare `LOCAL` prima dată când rulezi modul de dezvoltare pentru un spațiu de lucru. -* **`app:publish --server`** — încarcă un tarball și creează (sau actualizează) o înregistrare `TARBALL`, apoi instalează aplicația. -* **marketplace-ul npm** — înregistrările `NPM` sunt create când aplicațiile sunt sincronizate din registrul npm în catalogul marketplace-ului Twenty. -* **API GraphQL** — poți de asemenea să creezi înregistrări programatic prin mutația `createApplicationRegistration`. - -### Înregistrare vs instalare - -**Înregistrarea** și **instalarea** sunt concepte separate: - -* O **înregistrare** (`ApplicationRegistration`) este o înregistrare globală de metadate care descrie aplicația: numele ei, tipul de sursă, acreditările OAuth și statutul listării în marketplace. Există independent de orice spațiu de lucru. -* O **instalare** (`Application`) este o instanță per spațiu de lucru. Când un utilizator instalează o aplicație, Twenty rezolvă pachetul din sursa înregistrării, scrie fișierele compilate în stocare și sincronizează manifestul (creând obiecte, câmpuri, funcții de logică etc.) în acel spațiu de lucru. - -O singură înregistrare poate fi instalată în multe spații de lucru. Fiecare spațiu de lucru primește propria copie a fișierelor și a modelului de date al aplicației. - -### Acreditări OAuth - -Fiecare înregistrare include acreditări OAuth (`oAuthClientId` și `oAuthClientSecret`) generate la momentul creării. Acestea sunt folosite de aplicație pentru a autentifica cererile API în numele utilizatorilor. Secretul clientului este returnat **o singură dată** la creare — păstrează-l în siguranță. Îl poți roti ulterior prin mutația `rotateApplicationRegistrationClientSecret`. - -## Configurare manuală (fără generator) - -Deși recomandăm utilizarea `create-twenty-app` pentru cea mai bună experiență de început, puteți configura și un proiect manual. Nu instalați CLI-ul global. În schimb, adăugați `twenty-sdk` ca dependență locală și conectați un singur script în package.json-ul dvs.: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Apoi adăugați un script `twenty`: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Acum poți rula toate comenzile prin `yarn twenty `, de ex. `yarn twenty dev`, `yarn twenty help`, etc. - -## Depanare - -* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare. -* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil. -* Tipuri sau client lipsă/învechite: repornește `yarn twenty dev` — acesta generează automat clientul tipizat. -* Modul dev nu sincronizează: asigură-te că `yarn twenty dev` rulează și că modificările nu sunt ignorate de mediul tău. - -Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/ro/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx index 43ad4112a8..cf68f98acc 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: Определяйте объекты, функции логики, --- -Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться. + Apps are currently in alpha. The feature works but is still evolving. -## Используйте ресурсы SDK (типы и конфигурация) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -Пакет twenty-sdk предоставляет типизированные строительные блоки и вспомогательные функции, которые вы используете внутри своего приложения. Ниже — ключевые части, с которыми вы будете работать чаще всего. +## DefineEntity functions -### Вспомогательные функции +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов. -SDK предоставляет вспомогательные функции для определения сущностей вашего приложения. Как описано в [Обнаружение сущностей](/l/ru/developers/extend/apps/getting-started#entity-detection), вы должны использовать `export default define({...})`, чтобы ваши сущности были обнаружены: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| Функция | Назначение | -| -------------------------------- | ----------------------------------------------------------------------------------------------- | -| `defineApplication` | Настройка метаданных приложения (обязательно, по одному на приложение) | -| `defineObject` | Определяет пользовательские объекты с полями | -| `defineField` | Расширяйте существующие объекты дополнительными полями или определяйте отдельные поля отношений | -| `defineLogicFunction` | Определение логических функций с обработчиками | -| `definePreInstallLogicFunction` | Определяет предустановочную логическую функцию (по одной на приложение) | -| `definePostInstallLogicFunction` | Определяет послеустановочную логическую функцию (по одной на приложение) | -| `defineFrontComponent` | Определение фронт-компонентов для настраиваемого интерфейса | -| `defineRole` | Настраивает права роли и доступ к объектам | -| `defineView` | Определяйте сохранённые представления для объектов | -| `defineNavigationMenuItem` | Определяйте ссылки боковой панели навигации | -| `defineSkill` | Определение навыков агента ИИ | -| `defineAgent` | Определяйте агентов ИИ | -| `definePageLayout` | Определяйте пользовательские макеты страниц | + + -Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов. +Roles encapsulate permissions on your workspace's objects and actions. -### Определение объектов +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Заметки: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### Метаданные маркетплейса + +If you plan to [publish your app](/l/ru/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| Поле | Описание | +| ------------------ | ------------------------------------------------------------------------------------------------------------------- | +| `author` | Имя автора или название компании | +| `category` | Категория приложения для фильтрации в маркетплейсе | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | Расширенное описание в Markdown для вкладки "About". Если опущено, маркетплейс использует `README.md` пакета из npm | +| `websiteUrl` | Ссылка на ваш сайт | +| `termsUrl` | Ссылка на условия предоставления услуг | +| `emailSupport` | Адрес электронной почты поддержки | +| `issueReportUrl` | Ссылка на систему отслеживания проблем | + +#### Роли и разрешения + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts** указывает на эту роль, чтобы ваши функции наследовали её права. + +Заметки: +* Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий. +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags` управляют доступом к возможностям на уровне платформы. Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте `defineObject()` для определения объектов со встроенной валидацией: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ export default defineObject({ но это не рекомендуется. -### Определение полей для существующих объектов + + Используйте `defineField()` для добавления полей к объектам, которые вам не принадлежат — например, к стандартным объектам Twenty (Person, Company и т. д.). или к объектам из других приложений. В отличие от встроенных полей в `defineObject()`, отдельные поля требуют `objectUniversalIdentifier`, чтобы указать, какой объект они расширяют: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` Основные моменты: - * `objectUniversalIdentifier` определяет целевой объект. Для стандартных объектов используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, экспортируемые из `twenty-sdk`. * При определении полей непосредственно в `defineObject()` вам не нужен `objectUniversalIdentifier` — он наследуется от родительского объекта. * `defineField()` — единственный способ добавить поля к объектам, которые вы не создавали с помощью `defineObject()`. -### Связи + + Отношения связывают объекты между собой. В Twenty отношения всегда двунаправленные — вы определяете обе стороны, и каждая сторона ссылается на другую. @@ -177,8 +310,7 @@ export default defineField({ **Шаг 1: Определите сторону ONE_TO_MANY на PostCard** (сторона "one"): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **Шаг 2: Определите сторону MANY_TO_ONE на PostCardRecipient** (сторона "many" — содержит внешний ключ): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ Чтобы создать отношение со встроенным объектом Twenty (Person, Company и т. д.), используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ Вы также можете определять поля отношений непосредственно внутри `defineObject()`. В этом случае опустите `objectUniversalIdentifier` — он наследуется от родительского объекта: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### Конфигурация приложения (application-config.ts) - -У каждого приложения есть единственный файл `application-config.ts`, который описывает: - -* **Что это за приложение**: идентификаторы, отображаемое имя и описание. -* **Как запускаются его функции**: какую роль они используют для прав доступа. -* **(Необязательно) переменные**: пары ключ-значение, предоставляемые вашим функциям как переменные окружения. -* **(Необязательно) предустановочная функция**: логическая функция, которая запускается до установки приложения. -* **(Необязательно) послеустановочная функция**: функция логики, которая запускается после установки приложения. - -Используйте `defineApplication()` для определения конфигурации вашего приложения: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Заметки: - -* `universalIdentifier` — это детерминированные идентификаторы, которыми вы управляете; сгенерируйте их один раз и сохраняйте стабильными между синхронизациями. -* `applicationVariables` становятся переменными окружения для ваших функций (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` должен соответствовать файлу роли (см. ниже). -* Предустановочные и послеустановочные функции автоматически обнаруживаются во время сборки манифеста. См. [Предустановочные функции](#pre-install-functions) и [Послеустановочные функции](#post-install-functions). - -#### Метаданные маркетплейса - -Если вы планируете [опубликовать приложение](/l/ru/developers/extend/apps/publishing), эти необязательные поля определяют, как ваше приложение отображается в маркетплейсе: - -| Поле | Описание | -| ------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `author` | Имя автора или название компании | -| `category` | Категория приложения для фильтрации в маркетплейсе | -| `logoUrl` | Путь к логотипу вашего приложения (относительно `./assets/`) | -| `screenshots` | Массив путей к скриншотам (относительно `./assets/`) | -| `aboutDescription` | Расширенное описание в Markdown для вкладки "About". Если опущено, маркетплейс использует `README.md` пакета из npm | -| `websiteUrl` | Ссылка на ваш сайт | -| `termsUrl` | Ссылка на условия предоставления услуг | -| `emailSupport` | Адрес электронной почты поддержки | -| `issueReportUrl` | Ссылка на систему отслеживания проблем | - -#### Роли и разрешения - -Приложения могут определять роли, инкапсулирующие права на объекты и действия в вашем рабочем пространстве. Поле `defaultRoleUniversalIdentifier` в `application-config.ts` обозначает роль по умолчанию, используемую логическими функциями вашего приложения. - -* Ключ API во время выполнения, подставляемый как `TWENTY_API_KEY`, получается из этой роли функции по умолчанию. -* Типизированный клиент будет ограничен правами, предоставленными этой ролью. -* Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям, и укажите её универсальный идентификатор. - -##### Роль функции по умолчанию (*.role.ts) - -Когда вы генерируете новое приложение, CLI также создаёт файл роли по умолчанию. Используйте `defineRole()` для определения ролей со встроенной валидацией: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Значение `universalIdentifier` этой роли затем указывается в `application-config.ts` как `defaultRoleUniversalIdentifier`. Иными словами: - -* **\*.role.ts** определяет, что может делать роль функции по умолчанию. -* **application-config.ts** указывает на эту роль, чтобы ваши функции наследовали её права. - -Заметки: - -* Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий. -* Замените `objectPermissions` и `fieldPermissions` на объекты/поля, которые нужны вашим функциям. -* `permissionFlags` управляют доступом к возможностям на уровне платформы. Держите их минимальными; добавляйте только то, что нужно. -* См. рабочий пример в приложении Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Конфигурация логической функции и точка входа + + Каждый файл функции использует `defineLogicFunction()` для экспорта конфигурации с обработчиком и необязательными триггерами. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Распространённые типы триггеров: - -* **route**: Публикует вашу функцию по HTTP-пути и методу **под конечной точкой `/s/`**: - -> например, `path: '/post-card/create',` -> вызов по адресу `/s/post-card/create` - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**: Запускает вашу функцию по расписанию с использованием выражения CRON. * **databaseEvent**: Запускается при событиях жизненного цикла объектов рабочего пространства. Когда операция события — `updated`, можно указать конкретные поля для отслеживания в массиве `updatedFields`. Если оставить не заданным или пустым, любое обновление будет вызывать функцию. +> e.g. `person.updated`, `*.created`, `company.*` -> например, `person.updated` - -Заметки: - -* Массив `triggers` необязателен. Функции без триггеров можно использовать как вспомогательные, вызываемые другими функциями. -* Вы можете сочетать несколько типов триггеров в одной функции. - -### Предустановочные функции - -Предустановочная функция — это логическая функция, которая автоматически выполняется до установки вашего приложения в рабочем пространстве. Это полезно для задач валидации, проверки предварительных условий или подготовки состояния рабочего пространства перед основной установкой. - -Когда вы создаёте каркас нового приложения с помощью `create-twenty-app`, для вас генерируется предустановочная функция по пути `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Основные моменты: - -* Предустановочные функции используют `definePreInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок). -* Для каждого приложения допускается только одна предустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. -* Параметр `universalIdentifier` функции автоматически устанавливается как `preInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`. -* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы обеспечить выполнение более длительных задач подготовки. -* Предустановочным функциям не нужны триггеры — платформа вызывает их перед установкой или вручную через `exec --preInstall`. - -### Послеустановочные функции - -Послеустановочная функция — это функция логики, которая автоматически выполняется после установки вашего приложения в рабочем пространстве. Это полезно для одноразовых задач настройки, таких как инициализация данных по умолчанию, создание начальных записей или настройка параметров рабочего пространства. - -Когда вы создаёте каркас нового приложения с помощью `create-twenty-app`, для вас генерируется постустановочная функция по пути `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Основные моменты: +You can watch logs with: -* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок). -* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. -* Параметр `universalIdentifier` функции автоматически устанавливается как `postInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`. -* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных. -* Постустановочным функциям не нужны триггеры — платформа вызывает их во время установки или вручную через `exec --postInstall`. - -### Полезная нагрузка триггера маршрута - - -**Нарушающее совместимость изменение (v1.16, январь 2026):** Формат полезной нагрузки триггера маршрута изменился. До v1.16 параметры запроса, параметры пути и тело передавались напрямую в качестве полезной нагрузки. Начиная с v1.16 они вложены в структурированный объект `RoutePayload`. - -**До v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**После v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Полезная нагрузка триггера маршрута -**Чтобы мигрировать существующие функции:** Обновите обработчик, чтобы деструктурировать из `event.body`, `event.queryStringParameters` или `event.pathParameters` вместо прямого доступа к объекту params. - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -Когда триггер маршрута вызывает вашу логическую функцию, она получает объект `RoutePayload`, соответствующий формату AWS HTTP API v2. Импортируйте тип из `twenty-sdk`: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { Тип `RoutePayload` имеет следующую структуру: -| Свойство | Тип | Описание | -| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------- | -| `headers` | `Record` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Параметры строки запроса (несколько значений объединяются запятыми) | -| `pathParameters` | `Record` | Параметры пути, извлечённые из шаблона маршрута (например, `/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | Разобранное тело запроса (JSON) | -| `isBase64Encoded` | `логический тип` | Является ли тело закодированным в base64 | -| `requestContext.http.method` | `строка` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `строка` | Необработанный путь запроса | + | Свойство | Тип | Описание | Пример | + | ---------------------------- | ------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`) | see section below | + | `queryStringParameters` | `Record` | Параметры строки запроса (несколько значений объединяются запятыми) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Разобранное тело запроса (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `логический тип` | Является ли тело закодированным в base64 | | + | `requestContext.http.method` | `строка` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `строка` | Необработанный путь запроса | | -### Проброс HTTP-заголовков -По умолчанию HTTP-заголовки из входящих запросов **не** передаются в вашу логическую функцию по соображениям безопасности. Чтобы получить доступ к определённым заголовкам, явно перечислите их в массиве `forwardedRequestHeaders`: +#### forwardedRequestHeaders -```typescript +По умолчанию HTTP-заголовки из входящих запросов **не** передаются в вашу логическую функцию по соображениям безопасности. +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -В обработчике вы сможете получить доступ к этим заголовкам: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - Имена заголовков приводятся к нижнему регистру. Обращайтесь к ним, используя ключи в нижнем регистре (например, `event.headers['content-type']`). +Имена заголовков приводятся к нижнему регистру. Access them using lowercase keys (e.g., `event.headers['content-type']`). -Вы можете создать новые функции двумя способами: +#### Exposing a function as a tool -* **Сгенерировано**: Запустите `yarn twenty add` и выберите опцию добавления новой логической функции. Это создаёт стартовый файл с обработчиком и конфигурацией. -* **Вручную**: Создайте новый файл `*.logic-function.ts` и используйте `defineLogicFunction()`, следуя тому же шаблону. +Логические функции можно предоставлять как **инструменты** для ИИ-агентов и рабочих процессов. When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### Пометка логической функции как инструмента +To mark a logic function as a tool, set `isTool: true`: -Логические функции можно предоставлять как **инструменты** для ИИ-агентов и рабочих процессов. Когда функция помечена как инструмент, она становится доступной для ИИ Twenty и может быть выбрана в качестве шага в автоматизациях рабочих процессов. - -Чтобы пометить логическую функцию как инструмент, установите `isTool: true` и укажите `toolInputSchema` для описания ожидаемых входных параметров с помощью [схемы JSON](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Основные моменты: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Основные моменты: - -* **`isTool`** (`boolean`, по умолчанию: `false`): Если значение равно `true`, функция регистрируется как инструмент и становится доступной агентам ИИ и автоматизациям рабочих процессов. -* **`toolInputSchema`** (`object`, необязательно): Объект JSON Schema, который описывает параметры, которые принимает ваша функция. Агенты ИИ используют эту схему, чтобы понять, какие входные данные ожидает инструмент, и проверять корректность вызовов. Если опущено, по умолчанию используется схема `{ type: 'object', properties: {} }` (без параметров). -* Функции с `isTool: false` (или без указания) **не** выставляются как инструменты. Их по-прежнему можно выполнять напрямую или вызывать из других функций, но они не будут отображаться при обнаружении инструментов. -* **Именование инструмента**: При публикации как инструмента имя функции автоматически нормализуется до `logic_function_` (в нижнем регистре, небуквенно-цифровые символы заменяются на подчёркивания). Например, `enrich-company` становится `logic_function_enrich_company`. -* Вы можете комбинировать `isTool` с триггерами — функция может одновременно быть инструментом (вызываемым агентами ИИ) и запускаться событиями (cron, события базы данных, маршруты). - **Напишите хорошее описание в поле `description`.** Агенты ИИ опираются на поле `description` функции, чтобы решить, когда использовать инструмент. Чётко опишите, что делает инструмент и когда его следует вызывать. -### Фронт-компоненты + + -Фронт-компоненты позволяют создавать пользовательские компоненты React, которые рендерятся внутри интерфейса Twenty. Используйте `defineFrontComponent()` для определения компонентов со встроенной валидацией: +Предустановочная функция — это логическая функция, которая автоматически выполняется до установки вашего приложения в рабочем пространстве. Это полезно для задач валидации, проверки предварительных условий или подготовки состояния рабочего пространства перед основной установкой. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Основные моменты: +* Предустановочные функции используют `definePreInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок). +* Для каждого приложения допускается только одна предустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. +* Параметр `universalIdentifier` функции автоматически устанавливается как `preInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`. +* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы обеспечить выполнение более длительных задач подготовки. + + + + +Послеустановочная функция — это функция логики, которая автоматически выполняется после установки вашего приложения в рабочем пространстве. Это полезно для одноразовых задач настройки, таких как инициализация данных по умолчанию, создание начальных записей или настройка параметров рабочего пространства. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Основные моменты: +* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок). +* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. +* Параметр `universalIdentifier` функции автоматически устанавливается как `postInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`. +* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных. + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Основные моменты: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* Фронт-компоненты — это компоненты React, которые рендерятся в изолированных контекстах внутри Twenty. -* Поле `component` ссылается на ваш компонент React. -* Компоненты автоматически собираются и синхронизируются во время `yarn twenty dev`. +
+ Quick action button in the top-right corner +
-Вы можете создать новые фронт-компоненты двумя способами: +Click it to render the component inline. -* **Сгенерировано**: Запустите `yarn twenty add` и выберите опцию добавления нового фронт-компонента. -* **Вручную**: Создайте новый файл `.tsx` и используйте `defineFrontComponent()`, следуя тому же шаблону. +{/* TODO: add screenshot of the rendered front component */} -### Навыки +#### Configuration fields + +| Поле | Обязательно | Описание | +| --------------------- | ----------- | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | Да | Stable unique ID for this component | +| `component` | Да | A React component function | +| `name` | Нет | Display name | +| `description` | Нет | Description of what the component does | +| `isHeadless` | Нет | Set to `true` if the component has no visible UI (see below) | +| `command` | Нет | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| Хук | Returns | Описание | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `строка` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | различается | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| Функция | Описание | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | Закрыть боковую панель | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| Поле | Обязательно | Описание | +| --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Да | Stable unique ID for the command | +| `label` | Да | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | Нет | Shorter label displayed on the pinned quick-action button | +| `icon` | Нет | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Нет | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | Нет | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | Нет | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | Нет | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| Переменная | Тип | Описание | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `строка` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `number` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `object` | Permissions for the current object type | +| `targetObjectReadPermissions` | `object` | Read permissions for the target object | +| `targetObjectWritePermissions` | `object` | Write permissions for the target object | +| `featureFlags` | `object` | Active feature flags | +| `objectMetadataItem` | `object` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | Описание | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### Стилизация + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + Навыки определяют многократно используемые инструкции и возможности, которые агенты ИИ могут использовать в вашем рабочем пространстве. Используйте `defineSkill()` для определения навыков со встроенной валидацией: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` Основные моменты: - * `name` — уникальная строка-идентификатор навыка (рекомендуется kebab-case). * `label` — читаемое человеком отображаемое имя, показываемое в UI. * `content` содержит инструкции навыка — это текст, который использует агент ИИ. * `icon` (необязательно) задаёт значок, отображаемый в UI. * `description` (необязательно) предоставляет дополнительный контекст о назначении навыка. -Вы можете создать новые навыки двумя способами: + + -* **Сгенерировано**: Запустите `yarn twenty add` и выберите опцию добавления нового навыка. -* **Вручную**: Создайте новый файл и используйте `defineSkill()`, следуя тому же шаблону. +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### Типизированные клиенты API (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -Пакет `twenty-client-sdk` предоставляет два типизированных клиента GraphQL для взаимодействия с API Twenty из ваших логических функций и фронт-компонентов: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Основные моменты: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon` (необязательно) задаёт значок, отображаемый в UI. +* `modelId` (optional) overrides the default AI model used by the agent. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Основные моменты: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Основные моменты: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Основные моменты: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| Тип сущности | Команда | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| Объект | `yarn twenty add object` | `src/objects/.ts` | +| Поле | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Роль | `yarn twenty add role` | `src/roles/.ts` | +| Навык | `yarn twenty add skill` | `src/skills/.ts` | +| Агент | `yarn twenty add agent` | `src/agents/.ts` | +| Представление | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +Пакет `twenty-client-sdk` предоставляет два типизированных клиента GraphQL для взаимодействия с API Twenty из ваших логических функций и фронт-компонентов. | Клиент | Импорт | Конечная точка | Генерируется? | | ------------------- | ---------------------------- | ----------------------------------------------------------------- | -------------------------------- | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — данные рабочего пространства (записи, объекты) | Да, на этапе dev/build | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — конфигурация рабочего пространства, загрузка файлов | Нет, поставляется в готовом виде | -#### CoreApiClient + + -`CoreApiClient` — основной клиент для запросов и изменений данных рабочего пространства. Он генерируется из схемы вашего рабочего пространства во время `yarn twenty dev` или `yarn twenty build`, поэтому он полностью типизирован в соответствии с вашими объектами и полями. +`CoreApiClient` — основной клиент для запросов и изменений данных рабочего пространства. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ Клиент использует синтаксис selection-set: передайте `true`, чтобы включить поле, используйте `__args` для аргументов и вкладывайте объекты для отношений. Вы получаете полное автодополнение и проверку типов на основе схемы вашего рабочего пространства. -**CoreApiClient генерируется на этапе dev/build.** Если вы попытаетесь использовать его, не запустив сначала `yarn twenty dev` или `yarn twenty build`, он выбросит ошибку. Генерация происходит автоматически — CLI анализирует GraphQL-схему вашего рабочего пространства, генерирует типизированный клиент с помощью `@genql/cli`, записывает сгенерированные исходники в `node_modules/twenty-client-sdk/dist/core/generated/` и заменяет заглушки в `node_modules/twenty-client-sdk/dist/core.mjs` и `node_modules/twenty-client-sdk/dist/core.cjs`. +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### Использование CoreSchema для аннотаций типов -`CoreSchema` предоставляет типы TypeScript, соответствующие объектам вашего рабочего пространства, что полезно для типизации состояния компонентов или параметров функций: +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient` поставляется в готовом виде вместе с SDK (генерация не требуется). Он выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства, приложений и загрузки файлов: +`MetadataApiClient` поставляется в готовом виде вместе с SDK (генерация не требуется). Он выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства, приложений и загрузки файлов. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### Учётные данные времени выполнения - -Когда ваш код выполняется на Twenty (логические функции или фронт-компоненты), платформа предоставляет учётные данные в виде переменных окружения: - -* `TWENTY_API_URL` — базовый URL API Twenty -* `TWENTY_API_KEY` — краткоживущий ключ, ограниченный ролью функции по умолчанию вашего приложения - -Вам не нужно передавать их клиентам — они автоматически читаются из `process.env`. Права ключа API определяются ролью, указанной в `defaultRoleUniversalIdentifier` в вашем `application-config.ts`. - #### Загрузка файлов -`MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа файла. Он реализует [спецификацию многочастных запросов GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа файла: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| Параметр | Тип | Описание | -| ---------------------------------- | -------- | ------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Необработанное содержимое файла | -| `filename` | `строка` | Имя файла (используется для хранения и отображения) | -| `contentType` | `строка` | Тип MIME файла (по умолчанию `application/octet-stream`, если не указан) | -| `fieldMetadataUniversalIdentifier` | `строка` | Значение `universalIdentifier` для поля типа файла в вашем объекте | +| Параметр | Тип | Описание | +| ---------------------------------- | -------- | ------------------------------------------------------------------ | +| `fileBuffer` | `Buffer` | Необработанное содержимое файла | +| `filename` | `строка` | Имя файла (используется для хранения и отображения) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | Значение `universalIdentifier` для поля типа файла в вашем объекте | Основные моменты: - * Он использует `universalIdentifier` поля (а не его идентификатор, специфичный для рабочего пространства), поэтому ваш код загрузки будет работать в любом рабочем пространстве, где установлено ваше приложение. * Возвращаемый `url` — это подписанный URL, который можно использовать для доступа к загруженному файлу. -### Пример Hello World + + -Ознакомьтесь с минимальным сквозным примером, демонстрирующим объекты, логические функции, фронт-компоненты и несколько триггеров, [здесь](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world). + + Когда ваш код выполняется на Twenty (логические функции или фронт-компоненты), платформа предоставляет учётные данные в виде переменных окружения: + + * `TWENTY_API_URL` — базовый URL API Twenty + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + Вам не нужно передавать их клиентам — они автоматически читаются из `process.env`. Права ключа API определяются ролью, указанной в `defaultRoleUniversalIdentifier` в вашем `application-config.ts`. + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### Настройка + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| Функция | Описание | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## Справочник по CLI + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx index 9c73fd18ed..fa44d7fedd 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: Создайте своё первое приложение Twenty --- -Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться. +Apps are currently in alpha. The feature works but is still evolving. Приложения позволяют расширять Twenty с помощью пользовательских объектов, полей, логических функций, навыков ИИ и UI-компонентов — всё это управляется как код. -**Что вы можете создать:** - -* Пользовательские объекты, поля, представления и элементы навигации для формирования вашей модели данных -* Логические функции, запускаемые маршрутами HTTP, расписаниями cron или событиями базы данных -* Фронтенд-компоненты, которые непосредственно отображаются внутри интерфейса Twenty -* Навыки, расширяющие возможности ИИ-агентов Twenty -* Разверните приложение в нескольких рабочих пространствах - ## Требования -* Node.js 24+ -* Yarn 4 -* Docker (или запущенный локальный экземпляр Twenty) +Before you begin, make sure the following is installed on your machine: -## Начало работы +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Создайте новое приложение с помощью официального генератора, затем выполните аутентификацию и начните разработку: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> Используйте параметр `--minimal`, чтобы создать минимальную установку +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -Отсюда вы можете: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -Смотрите также: страницы справки CLI для [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) и [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## Структура проекта (сгенерированного) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -Когда вы запускаете `npx create-twenty-app@latest my-twenty-app`, генератор: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* Копирует минимальное базовое приложение в `my-twenty-app/` -* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4 -* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty` -* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, предустановочную и послеустановочную функции), а также примерные файлы в зависимости от выбранного режима создания каркаса + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/ru/developers/extend/apps/publishing) for details. + -Сгенерированное с помощью каркаса приложение с режимом по умолчанию `--exhaustive` выглядит так: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/ru/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -В общих чертах: +### Key files -* **package.json**: Объявляет имя приложения, версию, движки (Node 24+, Yarn 4) и добавляет `twenty-sdk`, а также скрипт `twenty`, который делегирует выполнение локальному CLI `twenty`. Выполните `yarn twenty help`, чтобы вывести список всех доступных команд. -* **.gitignore**: Игнорирует распространённые артефакты, такие как `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, каталоги coverage, файлы журналов и файлы `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Фиксируют и настраивают используемый в проекте инструментарий Yarn 4. -* **.nvmrc**: Фиксирует версию Node.js, ожидаемую проектом. -* **.oxlintrc.json** и **tsconfig.json**: Обеспечивают линтинг и конфигурацию TypeScript для исходников вашего приложения на TypeScript. -* **README.md**: Короткий README в корне приложения с базовыми инструкциями. -* **public/**: Папка для хранения общедоступных ресурсов (изображений, шрифтов, статических файлов), которые будут отдаваться вашим приложением. Файлы, размещённые здесь, загружаются во время синхронизации и доступны во время выполнения. -* **src/**: Основное место, где вы определяете приложение как код +| File / Folder | Назначение | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Навыки, расширяющие возможности ИИ-агентов Twenty. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Обнаружение сущностей +## Managing remotes -SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов **`export default define({...})`**. Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из `twenty-sdk`: - -| Вспомогательная функция | Тип сущности | -| -------------------------------- | ------------------------------------------------------------------ | -| `defineObject` | Определения пользовательских объектов | -| `defineLogicFunction` | Определения логических функций | -| `definePreInstallLogicFunction` | Предустановочная логическая функция (запускается до установки) | -| `definePostInstallLogicFunction` | Послеустановочная логическая функция (запускается после установки) | -| `defineFrontComponent` | Определения компонентов фронтенда | -| `defineRole` | Определения ролей | -| `defineField` | Расширения полей для существующих объектов | -| `defineView` | Определения сохранённых представлений | -| `defineNavigationMenuItem` | Определения пунктов меню навигации | -| `defineSkill` | Определения навыков агента ИИ | - - -**Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование. - - -Пример обнаруженной сущности: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Позднее команды добавят больше файлов и папок: - -* `yarn twenty dev` автоматически сгенерирует типизированный `CoreApiClient` (для данных рабочего пространства через `/graphql`) в `node_modules/twenty-client-sdk/`. `MetadataApiClient` (для конфигурации рабочего пространства и загрузки файлов через `/metadata`) поставляется в предсобранном виде и доступен сразу. Импортируйте их из `twenty-client-sdk/core` и `twenty-client-sdk/metadata` соответственно. -* `yarn twenty add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов, ролей, навыков и многого другого. - -## Аутентификация - -При первом запуске `yarn twenty auth:login` вам будет предложено указать: - -* URL API (по умолчанию http://localhost:3000 или текущий профиль рабочего пространства) -* Ключ API - -Ваши учётные данные хранятся для каждого пользователя в `~/.twenty/config.json`. Вы можете хранить несколько профилей и переключаться между ними. - -### Управление рабочими пространствами +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -После переключения рабочего пространства с помощью `yarn twenty auth:switch` все последующие команды по умолчанию будут использовать это рабочее пространство. Вы по-прежнему можете временно переопределить это с помощью `--workspace `. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Запуск сервера + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Команда | Описание | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Ручная настройка (без генератора) -Хотя мы рекомендуем использовать `create-twenty-app` для наилучшего старта, вы также можете настроить проект вручную. Не устанавливайте CLI глобально. Вместо этого добавьте `twenty-sdk` как локальную зависимость и настройте один скрипт в вашем package.json: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Затем добавьте скрипт `twenty`: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ yarn add -D twenty-sdk } ``` -Теперь вы можете запускать все команды через `yarn twenty `, например, `yarn twenty dev`, `yarn twenty help` и т. д. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## Как использовать локальный экземпляр Twenty - -Если у вас уже запущен локально экземпляр Twenty (например, через `npx nx start twenty-server`), вы можете подключиться к нему вместо использования Docker: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Устранение неполадок -* Ошибки аутентификации: выполните `yarn twenty auth:login` и убедитесь, что у вашего ключа API есть необходимые права. -* Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty. -* Типы или клиент отсутствуют/устарели: перезапустите `yarn twenty dev` — он автоматически генерирует типизированный клиент. -* Режим разработки не синхронизируется: убедитесь, что запущен `yarn twenty dev`, и что ваша среда не игнорирует изменения. +If you run into issues: -Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx index 2aa6a385ac..be720c5daf 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: Распространяйте своё приложение Twenty --- -Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться. + Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться. ## Обзор После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/building), у вас есть два пути для его распространения: -* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить. * **Разверните tar-архив** — загрузите своё приложение напрямую на конкретный сервер Twenty для внутреннего или частного использования. +* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить. Оба пути начинаются с одного и того же шага **build**. ## Сборка вашего приложения -Команда `build` компилирует ваши исходники TypeScript, транспилирует функции логики и фронтенд-компоненты и генерирует `manifest.json`, который описывает содержимое вашего приложения: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -Выходные данные записываются в `.twenty/output/`. Этот каталог содержит всё необходимое для распространения: скомпилированный код, ресурсы, манифест и копию вашего `package.json`. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -Чтобы также создать tarball `.tgz` (который внутренне используется командой deploy или для ручного распространения): +## Развертывание на сервер (tarball) + +Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty. + +### Требования + +Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`. + +Добавьте remote: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### Развертывание + +Соберите и загрузите ваше приложение на сервер в одном шаге: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Общий доступ к развернутому приложению + +Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Чтобы поделиться развернутым приложением: + +1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение +2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа** +3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения + +Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Управление версиями + +Чтобы выпустить обновление: + +1. Обновите значение поля `version` в файле `package.json` +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Рабочие пространства, в которых установлено приложение, увидят доступное обновление в своих настройках + +{/* TODO: add screenshot of the Upgrade button */} + ## Публикация в npm Публикация в npm делает ваше приложение видимым в маркетплейсе Twenty. Любое рабочее пространство Twenty может просматривать, устанавливать и обновлять приложения из маркетплейса непосредственно из интерфейса. @@ -39,41 +81,42 @@ yarn twenty build --tarball ### Требования * Учётная запись [npm](https://www.npmjs.com) -* Ключевое слово `twenty-app` **обязательно** должно быть указано в массиве `keywords` вашего `package.json` - -### Добавление обязательного ключевого слова - -Маркетплейс Twenty находит приложения, ища в реестре npm пакеты с ключевым словом `twenty-app`. Добавьте его в ваш `package.json`: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -Маркетплейс ищет в реестре npm по `keywords:twenty-app`. Без этого ключевого слова ваш пакет не появится в маркетплейсе, даже если в его имени есть префикс `twenty-app-`. - +### Метаданные маркетплейса -### Шаги +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **Сборка вашего приложения:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **Публикация в npm:** +See the [defineApplication accordion](/l/ru/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -Это выполняет `npm publish` из каталога `.twenty/output/`. - Чтобы опубликовать с определённым dist-tag (например, `beta` или `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### Как работает обнаружение приложений в маркетплейсе -Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**: +Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**. -1. Он ищет все пакеты npm с ключевым словом `keywords:twenty-app` -2. Для каждого пакета он извлекает `manifest.json` с CDN npm -3. Метаданные приложения (name, description, author, logo, screenshots, category) извлекаются из манифеста и отображаются в маркетплейсе - -После публикации может пройти до одного часа, прежде чем ваше приложение появится в маркетплейсе. Чтобы запустить синхронизацию немедленно, не дожидаясь следующего почасового запуска: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -Чтобы указать конкретный remote: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -Метаданные, отображаемые в маркетплейсе, берутся из вызова `defineApplication()` в исходном коде вашего приложения — из таких полей, как `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` и `termsUrl`. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. Если ваше приложение не определяет `aboutDescription` в `defineApplication()`, маркетплейс автоматически использует `README.md` вашего пакета из npm в качестве содержимого страницы «О приложении». Это означает, что вы можете поддерживать единый README как для npm, так и для маркетплейса Twenty. Если вы хотите другое описание в маркетплейсе, явно задайте `aboutDescription`. @@ -108,7 +143,7 @@ yarn twenty catalog-sync -r production ### Публикация через CI -Сгенерированный шаблоном проект включает рабочий процесс GitHub Actions, который выполняет публикацию при каждом релизе: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `yarn twenty build`, затем `npm publish` из `.twenty/output`. - + **npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements). - - -## Развертывание на сервер (tarball) - -Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty. - -### Требования - -Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`. - -Добавьте remote: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -Для локального сервера разработки: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -Вы также можете аутентифицироваться с помощью API-ключа в неинтерактивных средах: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -Управляйте remotes: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### Развертывание - -Соберите и загрузите ваше приложение на сервер в одном шаге: - -```bash filename="Terminal" -yarn twenty deploy -``` - -Это собирает приложение с флагом `--tarball`, затем загружает tarball на remote по умолчанию через GraphQL multipart upload. - -Чтобы развернуть на конкретный remote: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### Общий доступ к развернутому приложению - -Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Чтобы поделиться развернутым приложением: - -1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение -2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа** -3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения - -Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере. - -### Управление версиями - -Чтобы выпустить обновление: - -1. Обновите значение поля `version` в файле `package.json` -2. Выполните `yarn twenty deploy` (или `yarn twenty deploy -r production`) -3. Рабочие пространства, в которых установлено приложение, увидят доступное обновление в своих настройках + ## Установка приложений -После публикации приложения (npm) или его развертывания (tarball) рабочие пространства устанавливают его через интерфейс: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -Или со страницы **Настройки > Приложения** в интерфейсе Twenty, где можно просматривать и устанавливать как приложения из маркетплейса, так и развернутые через tarball. - -## Категории распространения приложений - -Twenty группирует приложения в три категории в зависимости от способа их распространения: - -| Категория | Как это работает | Отображается в маркетплейсе? | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| **Разработка** | Локальные приложения в режиме разработки, запущенные через `yarn twenty dev`. Используются для сборки и тестирования. | Нет | -| **Опубликовано (npm)** | Приложения, опубликованные в npm с ключевым словом `twenty-app`. Отображаются в маркетплейсе, доступные для установки любому рабочему пространству. | Да | -| **Внутренние (tarball)** | Приложения, развернутые через tarball на конкретном сервере. Доступны только рабочим пространствам на этом сервере по ссылке общего доступа. | Нет | - - -Начните в режиме **Разработка** во время создания приложения. Когда будет готово, выберите **Опубликовано** (npm) для широкого распространения или **Внутренний** (tarball) для приватного развертывания. - - -## Справочник по CLI - -| Команда | Описание | Основные флаги | -| --------------------------- | -------------------------------------------------------- | ------------------------------------------------------- | -| `yarn twenty build` | Скомпилировать приложение и сгенерировать манифест | `--tarball` — также создать пакет `.tgz` | -| `yarn twenty publish` | Собрать и опубликовать в npm | `--tag ` — dist-tag npm (например, `beta`, `next`) | -| `yarn twenty deploy` | Собрать и загрузить tarball на сервер | `-r, --remote ` — целевой удалённый репозиторий | -| `yarn twenty catalog-sync` | Запустить на сервере синхронизацию каталога маркетплейса | `-r, --remote ` — целевой удалённый репозиторий | -| `yarn twenty install` | Установить развернутое приложение в рабочем пространстве | `-r, --remote ` — целевой remote | -| `yarn twenty dev` | Отслеживать и синхронизировать локальные изменения | Использует remote по умолчанию | -| `yarn twenty remote add` | Добавить подключение к серверу | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | Показать настроенные remotes | — | -| `yarn twenty remote switch` | Установить remote по умолчанию | — | -| `yarn twenty remote status` | Показать статус подключения | — | -| `yarn twenty remote remove` | Удалить remote | — | diff --git a/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx index f09904bbab..a478147368 100644 --- a/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: Создавайте и управляйте настройками --- -Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться. +Apps are currently in alpha. The feature works but is still evolving. -## Что такое приложения? +## What are apps? -Приложения позволяют создавать и управлять настройками Twenty **в виде кода**. Вместо настройки всего через интерфейс вы определяете модель данных и логические функции в коде — так быстрее создавать, поддерживать и развёртывать в нескольких рабочих пространствах. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**Что вы можете делать уже сегодня:** +**Что вы можете создать:** -* Определяйте пользовательские объекты и поля в виде кода (управляемая модель данных) -* Создавайте логические функции с пользовательскими триггерами -* Определите навыки и агентов для ИИ -* Развёртывайте одно и то же приложение в нескольких рабочих пространствах +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Требования - -* Node.js 24+ и Yarn 4 -* Docker (для локального сервера разработки Twenty) - -## Начало работы - -Создайте новое приложение с помощью официального генератора каркаса. Он может автоматически запустить локальный экземпляр Twenty: +## Quick start ```bash filename="Terminal" -# Создать каркас нового приложения — CLI предложит запустить локальный сервер Twenty npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Запустить режим разработки: автоматически синхронизирует локальные изменения с вашим рабочим пространством yarn twenty dev ``` -### Управление локальным сервером +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/ru/developers/extend/apps/getting-started) guide for the full walkthrough. -SDK включает команды для управления локальным сервером разработки Twenty (универсальный образ Docker с PostgreSQL, Redis, сервером и воркером на порту 2020). Эти команды применимы только к серверу разработки на базе Docker — они не управляют экземпляром Twenty, запущенным из исходного кода (например, `npx nx start twenty-server` на порту 3000): +## Detailed guides -```bash filename="Terminal" -# Запустить локальный сервер (при необходимости будет загружен образ) -yarn twenty server start +| Руководство | Описание | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/ru/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/ru/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/ru/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Проверить статус сервера -yarn twenty server status - -# Просмотр логов сервера в реальном времени -yarn twenty server logs - -# Остановить сервер -yarn twenty server stop - -# Сбросить все данные и начать с нуля -yarn twenty server reset -``` - -Локальный сервер уже содержит рабочее пространство и пользователя (`tim@apple.dev` / `tim@apple.dev`), так что вы можете сразу начать разработку без какой-либо ручной настройки. - -### Аутентификация - -Подключите своё приложение к локальному серверу с помощью OAuth: - -```bash filename="Terminal" -# Authenticate via OAuth (opens browser) -yarn twenty remote add --local -``` - -Генератор каркаса поддерживает два режима для управления тем, какие файлы-примеры включаются: - -```bash filename="Terminal" -# По умолчанию (полный набор): все примеры (объект, поле, логическая функция, фронтенд-компонент, представление, пункт меню навигации, навык, агент) -npx create-twenty-app@latest my-app - -# Минимальный: только основные файлы (application-config.ts и default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### Как использовать локальный экземпляр Twenty - -Если у вас уже запущен локальный экземпляр Twenty, вы можете подключиться к нему вместо использования Docker. Укажите порт, который слушает ваш локальный сервер (по умолчанию: `3000`): - -```bash filename="Terminal" -# During scaffolding -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding -yarn twenty remote add --local --port 3000 -``` - -Отсюда вы можете: - -```bash filename="Terminal" -# Добавить новую сущность в ваше приложение (с мастером) -yarn twenty entity:add - -# Просматривать логи функций вашего приложения -yarn twenty function:logs - -# Выполнить функцию по имени -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Выполнить предустановочную функцию -yarn twenty function:execute --preInstall - -# Выполнить послеустановочную функцию -yarn twenty function:execute --postInstall - -# Собрать приложение для распространения -yarn twenty build - -# Опубликовать приложение в npm или на сервер Twenty -yarn twenty publish - -# Удалить приложение из текущего рабочего пространства -yarn twenty uninstall - -# Показать справку по командам -yarn twenty help},{ -``` - -Смотрите также: страницы справки CLI для [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) и [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk). - -## Структура проекта (сгенерированного) - -Когда вы запускаете `npx create-twenty-app@latest my-twenty-app`, генератор: - -* Копирует минимальное базовое приложение в `my-twenty-app/` -* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4 -* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty` -* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, предустановочную и послеустановочную функции), а также примерные файлы в зависимости от выбранного режима создания каркаса - -Сгенерированное с помощью каркаса приложение с режимом по умолчанию `--exhaustive` выглядит так: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Папка публичных ресурсов (изображения, шрифты и т. д.) - src/ - ├── application-config.ts # Обязательный — основная конфигурация приложения - ├── roles/ - │ └── default-role.ts # Роль по умолчанию для логических функций - ├── objects/ - │ └── example-object.ts # Пример определения пользовательского объекта - ├── fields/ - │ └── example-field.ts # Пример определения отдельного поля - ├── logic-functions/ - │ ├── hello-world.ts # Пример логической функции - │ ├── pre-install.ts # Предустановочная логическая функция - │ └── post-install.ts # Послеустановочная логическая функция - ├── front-components/ - │ └── hello-world.tsx # Пример фронтенд-компонента - ├── views/ - │ └── example-view.ts # Пример определения сохранённого представления - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Пример ссылки боковой панели навигации - ├── skills/ - │ └── example-skill.ts # Пример определения навыка агента ИИ - └── agents/ - └── example-agent.ts # Пример определения агента ИИ -``` - -С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`). - -В общих чертах: - -* **package.json**: Объявляет имя приложения, версию, движки (Node 24+, Yarn 4) и добавляет `twenty-sdk`, а также скрипт `twenty`, который делегирует выполнение локальному CLI `twenty`. Выполните `yarn twenty help`, чтобы вывести список всех доступных команд. -* **.gitignore**: Игнорирует распространённые артефакты, такие как `node_modules`, `.yarn`, `generated/` (типизированный клиент), `dist/`, `build/`, каталоги coverage, файлы журналов и файлы `.env*`. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Фиксируют и настраивают используемый в проекте инструментарий Yarn 4. -* **.nvmrc**: Фиксирует версию Node.js, ожидаемую проектом. -* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources. -* **README.md**: Короткий README в корне приложения с базовыми инструкциями. -* **public/**: Папка для хранения общедоступных ресурсов (изображений, шрифтов, статических файлов), которые будут отдаваться вашим приложением. Файлы, размещённые здесь, загружаются во время синхронизации и доступны во время выполнения. -* **src/**: Основное место, где вы определяете приложение как код +## Key concepts ### Обнаружение сущностей -SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов **`export default define({...})`**. Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из `twenty-sdk`: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Вспомогательная функция | Тип сущности | -| ---------------------------------- | ------------------------------------------------------------------ | -| `defineObject()` | Определения пользовательских объектов | -| `defineLogicFunction()` | Определения логических функций | -| `definePreInstallLogicFunction()` | Предустановочная логическая функция (запускается до установки) | -| `definePostInstallLogicFunction()` | Послеустановочная логическая функция (запускается после установки) | -| `defineFrontComponent()` | Определения компонентов фронтенда | -| `defineRole()` | Определения ролей | -| `defineField()` | Расширения полей для существующих объектов | -| `defineView()` | Определения сохранённых представлений | -| `defineNavigationMenuItem()` | Определения пунктов меню навигации | -| `defineSkill()` | Определения навыков агента ИИ | -| `defineAgent()` | Определения агентов ИИ | +### Available entity types - -**Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование. - +| Функция | Назначение | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Пример обнаруженной сущности: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -Позднее команды добавят больше файлов и папок: - -* `yarn twenty dev` автоматически сгенерирует два типизированных клиента API в `node_modules/twenty-sdk/clients`: `CoreApiClient` (для данных рабочего пространства через `/graphql`) и `MetadataApiClient` (для конфигурации рабочего пространства и загрузки файлов через `/metadata`). -* `yarn twenty entity:add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов, ролей, навыков и многого другого. - -## Аутентификация - -При первом запуске `yarn twenty auth:login` вам будет предложено указать: - -* URL API (по умолчанию http://localhost:3000 или текущий профиль рабочего пространства) -* Ключ API - -Ваши учётные данные хранятся для каждого пользователя в `~/.twenty/config.json`. Вы можете хранить несколько профилей и переключаться между ними. - -### Управление рабочими пространствами +### Справочник по CLI ```bash filename="Terminal" -# Войти в интерактивном режиме (рекомендуется) -yarn twenty auth:login - -# Войти в профиль конкретного рабочего пространства -yarn twenty auth:login --workspace my-custom-workspace - -# Показать список всех настроенных рабочих пространств -yarn twenty auth:list - -# Переключить рабочее пространство по умолчанию (в интерактивном режиме) -yarn twenty auth:switch - -# Переключиться на определённое рабочее пространство -yarn twenty auth:switch production - -# Проверить текущий статус аутентификации -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -После переключения рабочего пространства с помощью `yarn twenty auth:switch` все последующие команды по умолчанию будут использовать это рабочее пространство. Вы по-прежнему можете временно переопределить это с помощью `--workspace `. - -## Используйте ресурсы SDK (типы и конфигурация) - -Пакет twenty-sdk предоставляет типизированные строительные блоки и вспомогательные функции, которые вы используете внутри своего приложения. Ниже — ключевые части, с которыми вы будете работать чаще всего. - -### Вспомогательные функции - -SDK предоставляет вспомогательные функции для определения сущностей вашего приложения. Как описано в [Обнаружение сущностей](#entity-detection), вы должны использовать `export default define({...})`, чтобы ваши сущности были обнаружены: - -| Функция | Назначение | -| ---------------------------------- | ------------------------------------------------------------------------ | -| `defineApplication()` | Настройка метаданных приложения (обязательно, по одному на приложение) | -| `defineObject()` | Определяет пользовательские объекты с полями | -| `defineLogicFunction()` | Определение логических функций с обработчиками | -| `definePreInstallLogicFunction()` | Определяет предустановочную логическую функцию (по одной на приложение) | -| `definePostInstallLogicFunction()` | Определяет послеустановочную логическую функцию (по одной на приложение) | -| `defineFrontComponent()` | Определение фронт-компонентов для настраиваемого интерфейса | -| `defineRole()` | Настраивает права роли и доступ к объектам | -| `defineField()` | Расширение существующих объектов дополнительными полями | -| `defineView()` | Определяйте сохранённые представления для объектов | -| `defineNavigationMenuItem()` | Определяйте ссылки боковой панели навигации | -| `defineSkill()` | Определение навыков агента ИИ | -| `defineAgent()` | Определяйте ИИ-агентов с системными промптами | - -Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов. - -### Определение объектов - -Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте `defineObject()` для определения объектов со встроенной валидацией: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Основные моменты: - -* Используйте `defineObject()` для встроенной валидации и лучшей поддержки в IDE. -* `universalIdentifier` должен быть уникальным и стабильным между развёртываниями. -* Каждому полю требуются `name`, `type`, `label` и собственный стабильный `universalIdentifier`. -* Массив `fields` необязателен — вы можете определять объекты без пользовательских полей. -* Вы можете сгенерировать новые объекты с помощью `yarn twenty entity:add`, который проведёт вас через настройку имени, полей и связей. - - -**Базовые поля создаются автоматически.** Когда вы определяете пользовательский объект, Twenty автоматически добавляет стандартные поля, -такие как `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` и `deletedAt`. -Вам не нужно определять их в массиве `fields` — добавляйте только свои пользовательские поля. -Вы можете переопределить поля по умолчанию, определив поле с тем же именем в массиве `fields`, -но это не рекомендуется. - - -### Определение полей для существующих объектов - -Используйте `defineField()` для добавления настраиваемых полей к существующим объектам — как стандартным объектам (например, `company`, `person`, `opportunity`), так и пользовательским объектам, определённым другими приложениями. Каждое поле находится в собственном файле и ссылается на целевой объект по его `universalIdentifier`. - -Чтобы ссылаться на стандартные объекты, импортируйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` из `twenty-sdk`. Эта константа предоставляет стабильные идентификаторы для всех встроенных объектов и их полей: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Основные моменты: - -* `objectUniversalIdentifier` сообщает Twenty, к какому объекту прикрепить поле. Используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` для стандартных объектов. -* Каждому полю требуется собственный стабильный `universalIdentifier`, а также `name`, `type`, `label` и целевой `objectUniversalIdentifier`. -* Вы можете сгенерировать новые поля с помощью `yarn twenty entity:add`, выбрав опцию поля. -* Для удобства `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` также экспортируется как `STANDARD_OBJECT` — обе ссылки указывают на одну и ту же константу. - -Доступные стандартные объекты включают: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` и `workspaceMember`. - -Каждый стандартный объект также предоставляет идентификаторы своих полей. Например, чтобы сослаться на конкретное поле стандартного объекта в разрешениях роли: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### Поля связей в существующих объектах - -Вы также можете определить поля связей, которые связывают существующие объекты с вашими пользовательскими объектами: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### Конфигурация приложения (application-config.ts) - -У каждого приложения есть единственный файл `application-config.ts`, который описывает: - -* **Что это за приложение**: идентификаторы, отображаемое имя и описание. -* **Как запускаются его функции**: какую роль они используют для прав доступа. -* **(Необязательно) переменные**: пары ключ-значение, предоставляемые вашим функциям как переменные окружения. -* **(Необязательно) предустановочная функция**: логическая функция, которая запускается до установки приложения. -* **(Необязательно) послеустановочная функция**: функция логики, которая запускается после установки приложения. - -Используйте `defineApplication()` для определения конфигурации вашего приложения: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Заметки: - -* `universalIdentifier` — это детерминированные идентификаторы, которыми вы управляете; сгенерируйте их один раз и сохраняйте стабильными между синхронизациями. -* `applicationVariables` становятся переменными окружения для ваших функций (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` должен соответствовать файлу роли (см. ниже). -* Предустановочные и послеустановочные функции автоматически обнаруживаются во время сборки манифеста. См. [Предустановочные функции](#pre-install-functions) и [Послеустановочные функции](#post-install-functions). - -#### Роли и разрешения - -Приложения могут определять роли, инкапсулирующие права на объекты и действия в вашем рабочем пространстве. Поле `defaultRoleUniversalIdentifier` в `application-config.ts` обозначает роль по умолчанию, используемую логическими функциями вашего приложения. - -* Ключ API во время выполнения, подставляемый как `TWENTY_API_KEY`, получается из этой роли функции по умолчанию. -* Типизированный клиент будет ограничен правами, предоставленными этой ролью. -* Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям, и укажите её универсальный идентификатор. - -##### Роль функции по умолчанию (\*.role.ts) - -Когда вы генерируете новое приложение, CLI также создаёт файл роли по умолчанию. Используйте `defineRole()` для определения ролей со встроенной валидацией: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Значение `universalIdentifier` этой роли затем указывается в `application-config.ts` как `defaultRoleUniversalIdentifier`. Иными словами: - -* **\*.role.ts** определяет, что может делать роль функции по умолчанию. -* **application-config.ts** указывает на эту роль, чтобы ваши функции наследовали её права. - -Заметки: - -* Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий. -* Замените `objectPermissions` и `fieldPermissions` на объекты/поля, которые нужны вашим функциям. -* `permissionFlags` управляют доступом к возможностям на уровне платформы. Держите их минимальными; добавляйте только то, что нужно. -* См. рабочий пример в приложении Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Конфигурация логической функции и точка входа - -Каждый файл функции использует `defineLogicFunction()` для экспорта конфигурации с обработчиком и необязательными триггерами. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -});},{ -``` - -Распространённые типы триггеров: - -* **route**: Публикует вашу функцию по HTTP-пути и методу **под конечной точкой `/s/`**: - -> например, `path: '/post-card/create',` -> вызов по адресу `/s/post-card/create` - -* **cron**: Запускает вашу функцию по расписанию с использованием выражения CRON. -* **databaseEvent**: Запускается при событиях жизненного цикла объектов рабочего пространства. Когда операция события — `updated`, можно указать конкретные поля для отслеживания в массиве `updatedFields`. Если оставить не заданным или пустым, любое обновление будет вызывать функцию. - -> например, `person.updated` - -Заметки: - -* Массив `triggers` необязателен. Функции без триггеров можно использовать как вспомогательные, вызываемые другими функциями. -* Вы можете сочетать несколько типов триггеров в одной функции. - -### Предустановочные функции - -Предустановочная функция — это логическая функция, которая автоматически выполняется до установки вашего приложения в рабочем пространстве. Это полезно для задач валидации, проверки предварительных условий или подготовки состояния рабочего пространства перед основной установкой. - -Когда вы создаёте каркас нового приложения с помощью `create-twenty-app`, для вас генерируется предустановочная функция по пути `src/logic-functions/pre-install.ts`: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Основные моменты: - -* Предустановочные функции используют `definePreInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок). -* Для каждого приложения допускается только одна предустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. -* Параметр `universalIdentifier` функции автоматически устанавливается как `preInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`. -* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы обеспечить выполнение более длительных задач подготовки. -* Предустановочным функциям не нужны триггеры — платформа вызывает их перед установкой или вручную через `function:execute --preInstall`. - -### Послеустановочные функции - -Послеустановочная функция — это функция логики, которая автоматически выполняется после установки вашего приложения в рабочем пространстве. Это полезно для одноразовых задач настройки, таких как инициализация данных по умолчанию, создание начальных записей или настройка параметров рабочего пространства. - -Когда вы создаёте каркас нового приложения с помощью `create-twenty-app`, для вас генерируется постустановочная функция по пути `src/logic-functions/post-install.ts`: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Основные моменты: - -* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок). -* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. -* Параметр `universalIdentifier` функции автоматически устанавливается как `postInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`. -* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных. -* Постустановочным функциям не нужны триггеры — платформа вызывает их во время установки или вручную через `function:execute --postInstall`. - -### Полезная нагрузка триггера маршрута - - -**Нарушающее совместимость изменение (v1.16, январь 2026):** Формат полезной нагрузки триггера маршрута изменился. До v1.16 параметры запроса, параметры пути и тело передавались напрямую в качестве полезной нагрузки. Начиная с v1.16 они вложены в структурированный объект `RoutePayload`. - -**До v1.16:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**После v1.16:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**Чтобы мигрировать существующие функции:** Обновите обработчик, чтобы деструктурировать из `event.body`, `event.queryStringParameters` или `event.pathParameters` вместо прямого доступа к объекту params. - - -Когда триггер маршрута вызывает вашу логическую функцию, она получает объект `RoutePayload`, соответствующий формату AWS HTTP API v2. Импортируйте тип из `twenty-sdk`: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Тип `RoutePayload` имеет следующую структуру: - -| Свойство | Тип | Описание | -| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------ | -| `headers` | `Record` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`) | -| `queryStringParameters` | `Record` | Параметры строки запроса (несколько значений объединяются запятыми) | -| `pathParameters` | `Record` | Параметры пути, извлечённые из шаблона маршрута (например, `/users/:id` → `{ id: '123' }`) | -| `текст` | `object \| null` | Разобранное тело запроса (JSON) | -| `isBase64Encoded` | `логический тип` | Является ли тело закодированным в base64 | -| `requestContext.http.method` | `строка` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `строка` | Необработанный путь запроса | - -### Проброс HTTP-заголовков - -По умолчанию HTTP-заголовки из входящих запросов **не** передаются в вашу логическую функцию по соображениям безопасности. Чтобы получить доступ к определённым заголовкам, явно перечислите их в массиве `forwardedRequestHeaders`: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -В обработчике вы сможете получить доступ к этим заголовкам: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - Имена заголовков приводятся к нижнему регистру. Обращайтесь к ним, используя ключи в нижнем регистре (например, `event.headers['content-type']`). - - -Вы можете создать новые функции двумя способами: - -* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления новой функции логики. Это создаёт стартовый файл с обработчиком и конфигурацией. -* **Вручную**: Создайте новый файл `*.logic-function.ts` и используйте `defineLogicFunction()`, следуя тому же шаблону. - -### Пометка логической функции как инструмента - -Логические функции можно предоставлять как **инструменты** для ИИ-агентов и рабочих процессов. Когда функция помечена как инструмент, она становится доступной для ИИ Twenty и может быть выбрана в качестве шага в автоматизациях рабочих процессов. - -Чтобы пометить логическую функцию как инструмент, установите `isTool: true` и укажите `toolInputSchema` для описания ожидаемых входных параметров с помощью [схемы JSON](https://json-schema.org/): - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Основные моменты: - -* **`isTool`** (`boolean`, по умолчанию: `false`): Если значение равно `true`, функция регистрируется как инструмент и становится доступной агентам ИИ и автоматизациям рабочих процессов. -* **`toolInputSchema`** (`object`, необязательно): Объект JSON Schema, который описывает параметры, которые принимает ваша функция. Агенты ИИ используют эту схему, чтобы понять, какие входные данные ожидает инструмент, и проверять корректность вызовов. Если опущено, по умолчанию используется схема `{ type: 'object', properties: {} }` (без параметров). -* Функции с `isTool: false` (или без указания) **не** выставляются как инструменты. Их по-прежнему можно выполнять напрямую или вызывать из других функций, но они не будут отображаться при обнаружении инструментов. -* **Именование инструмента**: При публикации как инструмента имя функции автоматически нормализуется до `logic_function_` (в нижнем регистре, небуквенно-цифровые символы заменяются на подчёркивания). Например, `enrich-company` становится `logic_function_enrich_company`. -* Вы можете комбинировать `isTool` с триггерами — функция может одновременно быть инструментом (вызываемым агентами ИИ) и запускаться событиями (cron, события базы данных, маршруты). - - -**Напишите хорошее описание в поле `description`.** Агенты ИИ опираются на поле `description` функции, чтобы решить, когда использовать инструмент. Чётко опишите, что делает инструмент и когда его следует вызывать. - - -### Фронт-компоненты - -Фронт-компоненты позволяют создавать пользовательские компоненты React, которые рендерятся внутри интерфейса Twenty. Используйте `defineFrontComponent()` для определения компонентов со встроенной валидацией: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -Основные моменты: - -* Фронт-компоненты — это компоненты React, которые рендерятся в изолированных контекстах внутри Twenty. -* Поле `component` ссылается на ваш компонент React. -* Компоненты автоматически собираются и синхронизируются во время `yarn twenty dev`. - -Вы можете создать новые фронт-компоненты двумя способами: - -* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления нового фронтенд-компонента. -* **Вручную**: Создайте новый файл `.tsx` и используйте `defineFrontComponent()`, следуя тому же шаблону. - -#### Где можно использовать фронт-компоненты - -Фронт-компоненты могут отображаться в двух местах внутри Twenty: - -* **Боковая панель** — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд. -* **Виджеты (дашборды и страницы записей)** — фронт-компоненты можно встраивать как виджеты в макеты страниц. При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента. - -#### Headless и non-headless - -Фронт-компоненты поддерживают два режима отображения, управляемых опцией `isHeadless`: - -**Non-headless (по умолчанию)** — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда `isHeadless` имеет значение `false` или опущен. - -**Headless** — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Добавление элементов меню команд - -Чтобы фронт-компонент отображался как элемент в меню команд Twenty, добавьте свойство `command` в `defineFrontComponent()`. Когда пользователи открывают меню команд (Cmd+K / Ctrl+K), элемент появляется и при клике запускает фронт-компонент. - -Объект `command` принимает следующие поля: - -| Поле | Тип | Описание | -| --------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------- | -| `universalIdentifier` | `string` (обязательно) | Уникальный ID для элемента меню команд | -| `метка` | `string` (обязательно) | Отображаемая метка в меню команд | -| `иконка` | `string` (необязательно) | Имя иконки (например, `'IconSparkles'`) | -| `isPinned` | `boolean` (необязательно) | Закреплена ли команда в верхней части меню | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (необязательно) | `GLOBAL` показывает команду везде; `RECORD_SELECTION` показывает её только в контексте записи | -| `availabilityObjectUniversalIdentifier` | `string` (необязательно) | Ограничивает команду конкретным типом объекта (например, Person) | - -Вот пример из приложения записи звонков, которое добавляет команду, ограниченную записями Person: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -Когда команда синхронизируется, она появляется в меню команд. Если фронт-компонент с интерфейсом, открывается боковая панель с отображаемым внутри компонентом. Если он headless, компонент монтируется в фоновом режиме и выполняет свою логику. - -#### Компоненты SDK Command - -Пакет `twenty-sdk` предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении. - -Импортируйте их из `twenty-sdk/command`: - -* **`Command`** — запускает асинхронный колбэк через проп `execute`. -* **`CommandLink`** — переходит по пути внутри приложения. Пропы: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэк `execute`. Пропы: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — открывает конкретную страницу боковой панели. Пропы: `page`, `pageTitle`, `pageIcon`. - -Полный пример headless фронт-компонента, использующего `Command` для запуска действия из меню команд: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -А также пример с использованием `CommandModal` для запроса подтверждения перед выполнением: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Контекст выполнения - -Каждый фронт-компонент получает контекст выполнения, который предоставляет информацию о том, где и как он запущен. Получайте значения контекста с помощью хуков из `twenty-sdk`: - -| Хук | Тип возвращаемого значения | Описание | -| ----------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `строка` | Уникальный ID текущего экземпляра фронт-компонента | -| `useRecordId()` | `string \| null` | ID текущей записи, когда компонент запускается в контексте записи (например, виджет на странице записи или команда с областью действия записи). В противном случае возвращает `null`. | -| `useUserId()` | `string \| null` | ID текущего пользователя | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -Контекст реактивный: если окружающая запись изменяется, хуки автоматически возвращают обновлённые значения. - -#### Функции API хоста - -Фронт-компоненты выполняются в изолированной песочнице, но могут взаимодействовать с интерфейсом Twenty через набор функций, предоставляемых хостом. Импортируйте их напрямую из `twenty-sdk`: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Функция | Сигнатура | Описание | -| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `навигация` | `(to, params?, queryParams?, options?) => Promise` | Переход по типизированному пути приложения внутри Twenty | -| `closeSidePanel` | `() => Promise` | Закрыть боковую панель | -| `enqueueSnackbar` | `(params) => Promise` | Показать уведомление snackbar. Параметры: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), необязательно `duration`, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Размонтировать текущий фронт-компонент (используется headless-компонентами для очистки после выполнения) | -| `openSidePanelPage` | `(params) => Promise` | Открыть страницу в боковой панели. Параметры: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Показать модальное окно подтверждения и дождаться ответа пользователя. Параметры: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Навыки - -Навыки определяют многократно используемые инструкции и возможности, которые агенты ИИ могут использовать в вашем рабочем пространстве. Используйте `defineSkill()` для определения навыков со встроенной валидацией: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Основные моменты: - -* `name` — уникальная строка-идентификатор навыка (рекомендуется kebab-case). -* `label` — читаемое человеком отображаемое имя, показываемое в UI. -* `content` содержит инструкции навыка — это текст, который использует агент ИИ. -* `icon` (необязательно) задаёт значок, отображаемый в UI. -* `description` (необязательно) предоставляет дополнительный контекст о назначении навыка. - -Вы можете создать новые навыки двумя способами: - -* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления нового навыка. -* **Вручную**: Создайте новый файл и используйте `defineSkill()`, следуя тому же шаблону. - -### Агенты - -Agents позволяют определять ИИ-агентов с системными промптами, которые могут работать в вашем рабочем пространстве. Используйте `defineAgent()` для определения агентов со встроенной валидацией: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Основные моменты: - -* `name` — уникальная строка-идентификатор агента (рекомендуется kebab-case). -* `label` — читаемое человеком отображаемое имя, показываемое в UI. -* `prompt` содержит системный промпт — это текст инструкции, который определяет поведение агента. -* `icon` (необязательно) задаёт значок, отображаемый в UI. -* `description` (необязательно) предоставляет дополнительный контекст о назначении агента. - -Вы можете создать новых агентов двумя способами: - -* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления нового агента. -* **Вручную**: Создайте новый файл и используйте `defineAgent()`, следуя тому же шаблону. - -### Сгенерированные типизированные клиенты - -Два типизированных клиента автоматически генерируются с помощью `yarn twenty dev` и сохраняются в `node_modules/twenty-sdk/clients` на основе схемы вашего рабочего пространства: - -* **`CoreApiClient`** — выполняет запросы к конечной точке `/graphql` для получения данных рабочего пространства -* **`MetadataApiClient`** — выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства и загрузки файлов. - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); -``` - -`CoreApiClient` автоматически перегенерируется с помощью `yarn twenty dev` при изменении ваших объектов или полей. `MetadataApiClient` поставляется готовым в составе SDK. - -#### Учётные данные времени выполнения в логических функциях - -Когда ваша функция запускается на Twenty, платформа подставляет учётные данные как переменные окружения перед выполнением вашего кода: - -* `TWENTY_API_URL`: Базовый URL API Twenty, на который нацелено ваше приложение. -* `TWENTY_API_KEY`: Краткоживущий ключ, ограниченный ролью функции по умолчанию вашего приложения. - -Заметки: - -* Вам не нужно передавать URL или ключ API сгенерированному клиенту. Он читает `TWENTY_API_URL` и `TWENTY_API_KEY` из process.env во время выполнения. -* Права ключа API определяются ролью, на которую ссылается ваш `application-config.ts` через `defaultRoleUniversalIdentifier`. Это роль по умолчанию, используемая логическими функциями вашего приложения. -* Приложения могут определять роли, чтобы следовать принципу наименьших привилегий. Предоставляйте только те права, которые нужны вашим функциям, затем укажите в `defaultRoleUniversalIdentifier` универсальный идентификатор этой роли. - -#### Загрузка файлов - -`MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа «файл» в объектах вашего рабочего пространства. Поскольку стандартные клиенты GraphQL изначально не поддерживают многочастовую загрузку файлов, клиент предоставляет специальный метод, который под капотом реализует [спецификацию многочастных запросов GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec). - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type (defaults to 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -Сигнатура метода: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Параметр | Тип | Описание | -| ---------------------------------- | -------- | ------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Необработанное содержимое файла | -| `filename` | `строка` | Имя файла (используется для хранения и отображения) | -| `contentType` | `строка` | Тип MIME файла (по умолчанию `application/octet-stream`, если не указан) | -| `fieldMetadataUniversalIdentifier` | `строка` | Значение `universalIdentifier` для поля типа файла в вашем объекте | - -Основные моменты: - -* Метод `uploadFile` доступен в `MetadataApiClient`, потому что мутация загрузки обрабатывается эндпоинтом `/metadata`. -* Он использует `universalIdentifier` поля (а не его идентификатор, специфичный для рабочего пространства), поэтому ваш код загрузки будет работать в любом рабочем пространстве, где установлено ваше приложение — в соответствии с тем, как приложения ссылаются на поля повсюду. -* Возвращаемый `url` — это подписанный URL, который можно использовать для доступа к загруженному файлу. - -### Пример Hello World - -Ознакомьтесь с минимальным сквозным примером, демонстрирующим объекты, логические функции, фронт-компоненты и несколько триггеров, [здесь](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world): - -## Сборка вашего приложения - -После того как вы разработали приложение с помощью `app:dev`, используйте `app:build`, чтобы скомпилировать его в распространяемый пакет. - -```bash filename="Terminal" -# Собрать приложение (результат сохраняется в .twenty/output/) -yarn twenty build - -# Собрать и создать tarball (.tgz) для распространения -yarn twenty build --tarball -``` - -Процесс сборки: - -1. **Разбирает и проверяет манифест** — читает все сущности `defineX()` из ваших исходных файлов и проверяет структуру манифеста. -2. **Компилирует логические функции и фронтенд-компоненты** — упаковывает исходники TypeScript в ESM-файлы `.mjs` с помощью esbuild. -3. **Генерирует контрольные суммы** — вычисляет хэши MD5 для каждого собранного файла, сохраняемые в манифесте как `builtHandlerChecksum` / `builtComponentChecksum`. -4. **Генерирует типизированный клиент API** — проводит интроспекцию схемы GraphQL и генерирует типизированные клиенты `CoreApiClient` и `MetadataApiClient`. -5. **Запускает проверку типов TypeScript** — выполняет `tsc --noEmit`, чтобы обнаружить ошибки типов перед публикацией. -6. **Пересобирает со сгенерированным клиентом** — выполняет второй проход компиляции, чтобы включить сгенерированные типы клиента. -7. **Опционально создаёт tar-архив** — если передан `--tarball`, выполняет `npm pack` для создания файла `.tgz`, готового к распространению. - -Результат сборки в `.twenty/output/` содержит: - -```text -.twenty/output/ -├── manifest.json # Manifest with checksums for all built files -├── package.json # Copied from app root -├── yarn.lock # Copied from app root -├── src/ -│ ├── logic-functions/ # Compiled .mjs logic function files -│ └── front-components/ # Compiled .mjs front component files -├── public/ # Static assets (if any) -└── my-app-1.0.0.tgz # Only with --tarball flag -``` - -| Вариант | Описание | -| ----------- | ----------------------------------------------------------- | -| `[appPath]` | Путь к каталогу приложения (по умолчанию — текущий каталог) | -| `--tarball` | Также упаковать результат в tar-архив `.tgz` | - -## Публикация вашего приложения - -Используйте `app:publish` для распространения вашего приложения — либо в реестр npm, либо напрямую на сервер Twenty. - -### Публикация в npm (по умолчанию) - -```bash filename="Terminal" -# Опубликовать в npm (требуется вход в npm) -yarn twenty publish - -# Опубликовать с меткой dist-tag (например, beta, next) -yarn twenty publish --tag beta -``` - -Это собирает приложение и выполняет `npm publish` из каталога `.twenty/output/`. Опубликованный пакет затем может быть установлен из маркетплейса Twenty любым рабочим пространством. - -### Публикация на сервер Twenty - -```bash filename="Terminal" -# Опубликовать напрямую на сервер Twenty -yarn twenty publish --server https://app.twenty.com -``` - -Это собирает приложение с tar-архивом, загружает его на сервер через мутацию GraphQL `uploadAppTarball` и запускает установку в один шаг. Это полезно для приватных развёртываний или тестирования на конкретном сервере. - -| Вариант | Описание | -| ----------------- | --------------------------------------------------------------------- | -| `[appPath]` | Путь к каталогу приложения (по умолчанию — текущий каталог) | -| `--server ` | Публиковать на сервер Twenty вместо npm | -| `--token ` | Токен аутентификации для целевого сервера | -| `--tag ` | dist-тег npm (например, `beta`, `next`) — только для публикации в npm | - -## Регистрация приложения - -Прежде чем приложение можно будет установить в рабочем пространстве, его необходимо **зарегистрировать**. Регистрация — это запись метаданных, описывающая, откуда берётся приложение и как его аутентифицировать. В большинстве случаев это делает CLI автоматически. - -### Типы источников - -У каждой регистрации есть **тип источника**, который определяет, как файлы приложения будут получены при установке: - -| Тип источника | Как получаются файлы | Типичный сценарий использования | -| ------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------- | -| `LOCAL` | Файлы синхронизируются в реальном времени наблюдателем CLI — установка пропускается | Разработка с `app:dev` | -| `NPM` | Получается из реестра npm через поле `sourcePackage` | Опубликованные приложения в npm | -| `TARBALL` | Извлекается из загруженного файла `.tgz`, хранящегося на сервере | Приватные приложения, опубликованные с `--server` | - -### Как происходит регистрация - -* **`app:dev`** — автоматически создаёт регистрацию `LOCAL` при первом запуске режима разработки для рабочего пространства. -* **`app:publish --server`** — загружает tar-архив и создаёт (или обновляет) регистрацию `TARBALL`, затем устанавливает приложение. -* **маркетплейс npm** — регистрации `NPM` создаются, когда приложения синхронизируются из реестра npm в каталог маркетплейса Twenty. -* **GraphQL API** — вы также можете создавать регистрации программно через мутацию `createApplicationRegistration`. - -### Регистрация и установка - -**Регистрация** и **установка** — это разные понятия: - -* **Регистрация** (`ApplicationRegistration`) — это глобальная запись метаданных, описывающая приложение: его имя, тип источника, учётные данные OAuth и статус публикации в маркетплейсе. Она существует независимо от какого-либо рабочего пространства. -* **Установка** (`Application`) — это экземпляр для каждого рабочего пространства. Когда пользователь устанавливает приложение, Twenty получает пакет из источника, указанного в регистрации, записывает собранные файлы в хранилище и синхронизирует манифест (создавая объекты, поля, логические функции и т. д.) в этом рабочем пространстве. - -Одну и ту же регистрацию можно установить во многих рабочих пространствах. Каждое рабочее пространство получает свою собственную копию файлов приложения и модели данных. - -### Учётные данные OAuth - -Каждая регистрация включает учётные данные OAuth (`oAuthClientId` и `oAuthClientSecret`), сгенерированные при создании. Они используются приложением для аутентификации запросов к API от имени пользователей. Секрет клиента возвращается **один раз** при создании — храните его в надёжном месте. Позже вы можете сменить его через мутацию `rotateApplicationRegistrationClientSecret`. - -## Ручная настройка (без генератора) - -Хотя мы рекомендуем использовать `create-twenty-app` для наилучшего старта, вы также можете настроить проект вручную. Не устанавливайте CLI глобально. Вместо этого добавьте `twenty-sdk` как локальную зависимость и настройте один скрипт в вашем package.json: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Затем добавьте скрипт `twenty`: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Теперь вы можете запускать все команды через `yarn twenty `, например, `yarn twenty dev`, `yarn twenty help` и т. д. - -## Устранение неполадок - -* Ошибки аутентификации: выполните `yarn twenty auth:login` и убедитесь, что у вашего ключа API есть необходимые права. -* Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty. -* Типы или клиент отсутствуют/устарели: перезапустите `yarn twenty dev` — он автоматически генерирует типизированный клиент. -* Режим разработки не синхронизируется: убедитесь, что запущен `yarn twenty dev`, и что ваша среда не игнорирует изменения. - -Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/ru/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx index d5a6126368..bc2e67ef75 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: Nesneleri, mantık fonksiyonlarını, ön uç bileşenlerini ve dah --- -Uygulamalar şu anda alfa testinde. Özellik işlevsel ancak hâlâ gelişmekte. + Apps are currently in alpha. The feature works but is still evolving. -## SDK kaynaklarını kullanın (türler ve yapılandırma) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -twenty-sdk, uygulamanız içinde kullandığınız türlendirilmiş yapı taşları ve yardımcı fonksiyonlar sağlar. Aşağıda en sık dokunacağınız başlıca parçalar yer alıyor. +## DefineEntity functions -### Yardımcı fonksiyonlar +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar. -SDK, uygulama varlıklarınızı tanımlamak için yardımcı fonksiyonlar sağlar. [Varlık algılama](/l/tr/developers/extend/apps/getting-started#entity-detection) bölümünde açıklandığı gibi, varlıklarınızın algılanması için `export default define({...})` kullanmalısınız: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| Fonksiyon | Amaç | -| -------------------------------- | ---------------------------------------------------------------------------------- | -| `defineApplication` | Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet) | -| `defineObject` | Alanlara sahip özel nesneler tanımlayın | -| `defineField` | Mevcut nesneleri ek alanlarla genişletin veya bağımsız ilişki alanları tanımlayın. | -| `defineLogicFunction` | İşleyicilerle mantık fonksiyonları tanımlayın | -| `definePreInstallLogicFunction` | Bir kurulum öncesi mantık işlevi tanımlayın (uygulama başına bir adet) | -| `definePostInstallLogicFunction` | Bir kurulum sonrası mantık işlevi tanımlayın (uygulama başına bir adet) | -| `defineFrontComponent` | Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın | -| `defineRole` | Rol izinlerini ve nesne erişimini yapılandırın | -| `defineView` | Nesneler için kaydedilmiş görünümler tanımlayın | -| `defineNavigationMenuItem` | Kenar çubuğu gezinme bağlantılarını tanımlayın | -| `defineSkill` | Yapay zekâ ajanı yeteneklerini tanımlayın | -| `defineAgent` | Yapay zekâ ajanlarını tanımlayın. | -| `definePageLayout` | Özel sayfa düzenlerini tanımlayın. | + + -Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar. +Roles encapsulate permissions on your workspace's objects and actions. -### Nesnelerin tanımlanması +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notlar: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### Pazaryeri meta verileri + +If you plan to [publish your app](/l/tr/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| Alan | Açıklama | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | +| `author` | Yazar veya şirket adı | +| `category` | Pazaryerinde filtreleme için uygulama kategorisi | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | "Hakkında" sekmesi için daha uzun bir markdown açıklaması. Belirtilmezse, pazaryeri npm'deki paketin `README.md` dosyasını kullanır | +| `websiteUrl` | Web sitenize bağlantı | +| `termsUrl` | Hizmet Koşulları'na bağlantı | +| `emailSupport` | Destek e-posta adresi | +| `issueReportUrl` | Sorun izleyicisine bağlantı | + +#### Roller ve izinler + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts**, fonksiyonlarınızın izinlerini devralması için bu role işaret eder. + +Notlar: +* Oluşturulan rolden başlayın ve en az ayrıcalık ilkesini izleyerek bunu aşamalı olarak kısıtlayın. +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags`, platform düzeyindeki yeteneklere erişimi kontrol eder. Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + Özel nesneler, çalışma alanınızdaki kayıtlar için hem şemayı hem de davranışı tanımlar. Yerleşik doğrulamayla nesneler tanımlamak için `defineObject()` kullanın: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ Bunları `fields` dizinizde tanımlamanız gerekmez — yalnızca özel alanlar ancak bu önerilmez. -### Mevcut nesneler üzerinde alanları tanımlama + + Sahibi olmadığınız nesnelere alan eklemek için `defineField()` kullanın — standart Twenty nesneleri (Person, Company, vb.) gibi. veya diğer uygulamalardaki nesneler. `defineObject()` içindeki satır içi alanların aksine, bağımsız alanlar hangi nesneyi genişlettiklerini belirtmek için bir `objectUniversalIdentifier` gerektirir: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` Önemli noktalar: - * `objectUniversalIdentifier` hedef nesneyi tanımlar. Standart nesneler için, `twenty-sdk`'den dışa aktarılan `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`'ı kullanın. * Alanları `defineObject()` içinde satır içi tanımlarken, `objectUniversalIdentifier`'a ihtiyacınız yoktur — üst nesneden devralınır. * `defineField()`, `defineObject()` ile oluşturmadığınız nesnelere alan eklemenin tek yoludur. -### İlişkiler + + İlişkiler nesneleri birbirine bağlar. Twenty'de ilişkiler her zaman **çift yönlüdür** — her iki tarafı da tanımlarsınız ve her taraf diğerine başvurur. @@ -177,8 +310,7 @@ Bir `PostCard`'ın birçok `PostCardRecipient` kaydına gönderilebildiğini var **Adım 1: PostCard üzerinde ONE_TO_MANY tarafını tanımlayın** ("bir" taraf): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **Adım 2: PostCardRecipient üzerinde MANY_TO_ONE tarafını tanımlayın** ("çok" taraf — yabancı anahtarı tutar): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ Yerleşik bir Twenty nesnesiyle (Person, Company, vb.) ilişki oluşturmak için `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` kullanın: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ İlişki alanlarını doğrudan `defineObject()` içinde de tanımlayabilirsiniz. Bu durumda, `objectUniversalIdentifier`'ı atlayın — üst nesneden devralınır: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### Uygulama yapılandırması (application-config.ts) - -Her uygulamanın aşağıdakileri açıklayan tek bir `application-config.ts` dosyası vardır: - -* **Uygulamanın kim olduğu**: tanımlayıcılar, görünen ad ve açıklama. -* **Fonksiyonlarının nasıl çalıştığı**: izinler için hangi rolü kullandıkları. -* **(İsteğe bağlı) değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtar–değer çiftleri. -* **(İsteğe bağlı) kurulum öncesi işlev**: uygulama yüklenmeden önce çalışan bir mantık işlevi. -* **(İsteğe bağlı) kurulum sonrası işlev**: uygulama yüklendikten sonra çalışan bir mantık işlevi. - -Uygulama yapılandırmanızı tanımlamak için `defineApplication()` kullanın: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notlar: - -* `universalIdentifier` alanları size ait belirleyici kimliklerdir; bunları bir kez oluşturun ve eşitlemeler boyunca kararlı tutun. -* `applicationVariables`, fonksiyonlarınız için ortam değişkenlerine dönüşür (örneğin, `DEFAULT_RECIPIENT_NAME` değeri `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir). -* `defaultRoleUniversalIdentifier`, rol dosyasıyla eşleşmelidir (aşağıya bakın). -* Kurulum öncesi ve kurulum sonrası işlevler, manifest oluşturma sırasında otomatik olarak algılanır. Bkz. [Kurulum öncesi işlevler](#pre-install-functions) ve [Kurulum sonrası işlevler](#post-install-functions). - -#### Pazaryeri meta verileri - -Eğer [uygulamanızı yayımlamayı](/l/tr/developers/extend/apps/publishing) planlıyorsanız, bu isteğe bağlı alanlar uygulamanızın pazaryerinde nasıl görüneceğini kontrol eder: - -| Alan | Açıklama | -| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -| `author` | Yazar veya şirket adı | -| `category` | Pazaryerinde filtreleme için uygulama kategorisi | -| `logoUrl` | Uygulama logonuzun yolu (`./assets/` dizinine göre) | -| `screenshots` | Ekran görüntüsü yollarının dizisi (`./assets/` dizinine göre) | -| `aboutDescription` | "Hakkında" sekmesi için daha uzun bir markdown açıklaması. Belirtilmezse, pazaryeri npm'deki paketin `README.md` dosyasını kullanır | -| `websiteUrl` | Web sitenize bağlantı | -| `termsUrl` | Hizmet Koşulları'na bağlantı | -| `emailSupport` | Destek e-posta adresi | -| `issueReportUrl` | Sorun izleyicisine bağlantı | - -#### Roller ve izinler - -Uygulamalar, çalışma alanınızdaki nesneler ve eylemler üzerindeki izinleri kapsülleyen roller tanımlayabilir. `application-config.ts` içindeki `defaultRoleUniversalIdentifier` alanı, uygulamanızın mantık fonksiyonlarının kullandığı varsayılan rolü belirtir. - -* `TWENTY_API_KEY` olarak enjekte edilen çalışma zamanı API anahtarı bu varsayılan fonksiyon rolünden türetilir. -* Türlendirilmiş istemci, o role tanınan izinlerle sınırlandırılır. -* En az ayrıcalık ilkesini izleyin: Yalnızca fonksiyonlarınızın ihtiyaç duyduğu izinlere sahip özel bir rol oluşturun ve ardından evrensel tanımlayıcısına referans verin. - -##### Varsayılan fonksiyon rolü (*.role.ts) - -Yeni bir uygulama oluşturduğunuzda CLI ayrıca varsayılan bir rol dosyası da oluşturur. Yerleşik doğrulamayla roller tanımlamak için `defineRole()` kullanın: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Bu rolün `universalIdentifier` değeri daha sonra `application-config.ts` içinde `defaultRoleUniversalIdentifier` olarak referans verilir. Başka bir deyişle: - -* **\*.role.ts**, varsayılan fonksiyon rolünün neler yapabileceğini tanımlar. -* **application-config.ts**, fonksiyonlarınızın izinlerini devralması için bu role işaret eder. - -Notlar: - -* Oluşturulan rolden başlayın ve en az ayrıcalık ilkesini izleyerek bunu aşamalı olarak kısıtlayın. -* `objectPermissions` ve `fieldPermissions` değerlerini, fonksiyonlarınızın ihtiyaç duyduğu nesneler/alanlarla değiştirin. -* `permissionFlags`, platform düzeyindeki yeteneklere erişimi kontrol eder. Minimumda tutun; yalnızca ihtiyacınız olanları ekleyin. -* Çalışan bir örneği Hello World uygulamasında görün: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Mantık fonksiyon yapılandırması ve giriş noktası + + Her fonksiyon dosyası, bir işleyici ve isteğe bağlı tetikleyiciler içeren bir yapılandırmayı dışa aktarmak için `defineLogicFunction()` kullanır. -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -Yaygın tetikleyici türleri: - -* **route**: Fonksiyonunuzu bir HTTP yolu ve yöntemiyle **`/s/` uç noktası altında** sunar: - -> örn. `path: '/post-card/create',` -> `/s/post-card/create` üzerinden çağırın - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**: Bir CRON ifadesi kullanarak fonksiyonunuzu bir zamanlamayla çalıştırır. * **databaseEvent**: Çalışma alanı nesnesi yaşam döngüsü olaylarında çalışır. Olay işlemi `updated` olduğunda, dinlenecek belirli alanlar `updatedFields` dizisinde belirtilebilir. Tanımsız veya boş bırakılırsa, herhangi bir güncelleme fonksiyonu tetikler. +> e.g. `person.updated`, `*.created`, `company.*` -> örn. `person.updated` - -Notlar: - -* `triggers` dizisi isteğe bağlıdır. Tetikleyicisi olmayan fonksiyonlar, diğer fonksiyonlar tarafından çağrılan yardımcı fonksiyonlar olarak kullanılabilir. -* Tek bir fonksiyonda birden çok tetikleyici türünü birleştirebilirsiniz. - -### Kurulum öncesi işlevler - -Kurulum öncesi işlev, uygulamanız bir çalışma alanına yüklenmeden önce otomatik olarak çalışan bir mantık işlevidir. Bu, doğrulama görevleri, önkoşul kontrolleri veya ana kurulum başlamadan önce çalışma alanı durumunun hazırlanması için yararlıdır. - -`create-twenty-app` ile yeni bir uygulama iskeleti oluşturduğunuzda, `src/logic-functions/pre-install.ts` konumunda sizin için bir kurulum öncesi işlev oluşturulur: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -Önemli noktalar: - -* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. -* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize). -* Uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. -* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `preInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur. -* Varsayılan zaman aşımı, daha uzun hazırlık görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. -* Kurulum öncesi işlevlerin tetikleyicilere ihtiyacı yoktur — kurulumdan önce platform tarafından veya `exec --preInstall` aracılığıyla manuel olarak çağrılırlar. - -### Kurulum sonrası işlevler - -Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Bu, varsayılan verileri tohumlama, ilk kayıtları oluşturma veya çalışma alanı ayarlarını yapılandırma gibi tek seferlik kurulum görevleri için yararlıdır. - -`create-twenty-app` ile yeni bir uygulama iskeleti oluşturduğunuzda, `src/logic-functions/post-install.ts` konumunda sizin için bir kurulum sonrası işlevi oluşturulur: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -Önemli noktalar: +You can watch logs with: -* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. -* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize). -* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. -* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `postInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur. -* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. -* Kurulum sonrası işlevlerin tetikleyicilere ihtiyacı yoktur — kurulum sırasında platform tarafından veya `exec --postInstall` aracılığıyla manuel olarak çağrılırlar. - -### Rota tetikleyicisi yükü - - -**Kırıcı değişiklik (v1.16, Ocak 2026):** Rota tetikleyicisi yük formatı değişti. v1.16'dan önce, sorgu parametreleri, yol parametreleri ve gövde doğrudan payload olarak gönderiliyordu. v1.16 itibarıyla, yapılandırılmış bir `RoutePayload` nesnesinin içine yerleştiriliyorlar. - -**v1.16'dan önce:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**v1.16'dan sonra:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### Rota tetikleyicisi yükü -**Mevcut fonksiyonları taşımak için:** İşleyicinizi, parametreler nesnesinden doğrudan ayırmak yerine `event.body`, `event.queryStringParameters` veya `event.pathParameters` üzerinden ayrıştıracak şekilde güncelleyin. - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -Bir rota tetikleyicisi mantık fonksiyonunuzu çağırdığında, AWS HTTP API v2 formatını izleyen bir `RoutePayload` nesnesi alır. Türü `twenty-sdk` içinden içe aktarın: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { `RoutePayload` türünün yapısı şu şekildedir: -| Özellik | Tür | Açıklama | -| ---------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------- | -| `headers` | `Record` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | -| `queryStringParameters` | `Record` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | -| `pathParameters` | `Record` | Rota deseninden çıkarılan yol parametreleri (örn., `/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | -| `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | -| `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Ham istek yolu | + | Özellik | Tür | Açıklama | Örnek | + | ---------------------------- | ------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | see section below | + | `queryStringParameters` | `Record` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | | + | `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Ham istek yolu | | -### HTTP başlıklarını iletme -Varsayılan olarak, güvenlik nedenleriyle gelen isteklerden HTTP başlıkları mantık fonksiyonunuza **aktarılmaz**. Belirli başlıklara erişmek için bunları açıkça `forwardedRequestHeaders` dizisinde listeleyin: +#### forwardedRequestHeaders -```typescript +Varsayılan olarak, güvenlik nedenleriyle gelen isteklerden HTTP başlıkları mantık fonksiyonunuza **aktarılmaz**. +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -Daha sonra işleyicinizde bu başlıklara erişebilirsiniz: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - Başlık adları küçük harfe normalize edilir. Onlara küçük harfli anahtarlarla erişin (örneğin, `event.headers['content-type']`). +Başlık adları küçük harfe normalize edilir. Access them using lowercase keys (e.g., `event.headers['content-type']`). -Yeni fonksiyonları iki şekilde oluşturabilirsiniz: +#### Exposing a function as a tool -* **Şablondan**: `yarn twenty add` çalıştırın ve yeni bir mantık fonksiyonu ekleme seçeneğini seçin. Bu, bir işleyici ve yapılandırma içeren bir başlangıç dosyası oluşturur. -* **Manuel**: Yeni bir `*.logic-function.ts` dosyası oluşturun ve aynı deseni izleyerek `defineLogicFunction()` kullanın. +Mantık işlevleri, yapay zeka ajanları ve iş akışları için **araçlar** olarak sunulabilir. When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### Bir mantık işlevini araç olarak işaretleme +To mark a logic function as a tool, set `isTool: true`: -Mantık işlevleri, yapay zeka ajanları ve iş akışları için **araçlar** olarak sunulabilir. Bir işlev bir araç olarak işaretlendiğinde, Twenty'nin yapay zeka özellikleri tarafından keşfedilebilir hâle gelir ve iş akışı otomasyonlarında bir adım olarak seçilebilir. - -Bir mantık işlevini bir araç olarak işaretlemek için `isTool: true` olarak ayarlayın ve beklenen giriş parametrelerini açıklayan bir `toolInputSchema`yı [JSON Şeması](https://json-schema.org/) kullanarak sağlayın: - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +Önemli noktalar: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -Önemli noktalar: - -* **`isTool`** (`boolean`, varsayılan: `false`): `true` olarak ayarlandığında, işlev bir araç olarak kaydedilir ve AI ajanları ile iş akışı otomasyonları tarafından kullanılabilir hale gelir. -* **`toolInputSchema`** (`object`, isteğe bağlı): İşlevinizin kabul ettiği parametreleri tanımlayan bir JSON Schema nesnesi. AI ajanları, aracın hangi girdileri beklediğini anlamak ve çağrıları doğrulamak için bu şemayı kullanır. Atlanırsa, şema varsayılan olarak `{ type: 'object', properties: {} }` olur (parametre yok). -* `isTool: false` (veya ayarlanmamış) olan işlevler araç olarak **sunulmaz**. Yine de doğrudan yürütülebilir veya diğer işlevler tarafından çağrılabilirler, ancak araç keşfinde görünmezler. -* **Araç adlandırma**: Bir araç olarak sunulduğunda, işlev adı otomatik olarak `logic_function_` biçimine dönüştürülür (küçük harfe çevrilir, alfasayısal olmayan karakterler alt çizgi ile değiştirilir). Örneğin, `enrich-company` `logic_function_enrich_company` haline gelir. -* `isTool` özelliğini tetikleyicilerle birleştirebilirsiniz — bir işlev aynı anda hem bir araç (AI ajanları tarafından çağrılabilir) olabilir hem de olaylar tarafından tetiklenebilir (cron, veritabanı olayları, routes). - **İyi bir `description` yazın.** AI ajanları, aracı ne zaman kullanacaklarına karar vermek için işlevin `description` alanına güvenir. Aracın ne yaptığını ve ne zaman çağrılması gerektiğini açıkça belirtin. -### Ön uç bileşenleri + + -Ön uç bileşenleri, Twenty'nin kullanıcı arayüzünde görüntülenen özel React bileşenleri oluşturmanıza olanak tanır. Yerleşik doğrulamayla bileşenleri tanımlamak için `defineFrontComponent()` kullanın: +Kurulum öncesi işlev, uygulamanız bir çalışma alanına yüklenmeden önce otomatik olarak çalışan bir mantık işlevidir. Bu, doğrulama görevleri, önkoşul kontrolleri veya ana kurulum başlamadan önce çalışma alanı durumunun hazırlanması için yararlıdır. -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Önemli noktalar: +* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. +* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize). +* Uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. +* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `preInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur. +* Varsayılan zaman aşımı, daha uzun hazırlık görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. + + + + +Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Bu, varsayılan verileri tohumlama, ilk kayıtları oluşturma veya çalışma alanı ayarlarını yapılandırma gibi tek seferlik kurulum görevleri için yararlıdır. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Önemli noktalar: +* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. +* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize). +* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. +* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `postInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur. +* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -Önemli noktalar: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* Ön uç bileşenleri, Twenty içinde yalıtılmış bağlamlarda görüntülenen React bileşenleridir. -* `component` alanı, React bileşeninize referans verir. -* Bileşenler, `yarn twenty dev` sırasında otomatik olarak oluşturulur ve senkronize edilir. +
+ Quick action button in the top-right corner +
-Yeni ön uç bileşenlerini iki şekilde oluşturabilirsiniz: +Click it to render the component inline. -* **Şablondan**: `yarn twenty add` çalıştırın ve yeni bir ön uç bileşeni ekleme seçeneğini seçin. -* **Manuel**: Aynı deseni izleyerek yeni bir `.tsx` dosyası oluşturun ve `defineFrontComponent()` kullanın. +{/* TODO: add screenshot of the rendered front component */} -### Beceriler +#### Configuration fields + +| Alan | Zorunlu | Açıklama | +| --------------------- | ------- | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | Evet | Stable unique ID for this component | +| `component` | Evet | A React component function | +| `name` | Hayır | Display name | +| `description` | Hayır | Description of what the component does | +| `isHeadless` | Hayır | Set to `true` if the component has no visible UI (see below) | +| `command` | Hayır | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| Hook | Returns | Açıklama | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | değişir | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| Fonksiyon | Açıklama | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | Yan paneli kapat | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| Alan | Zorunlu | Açıklama | +| --------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Evet | Stable unique ID for the command | +| `label` | Evet | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | Hayır | Shorter label displayed on the pinned quick-action button | +| `icon` | Hayır | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Hayır | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | Hayır | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | Hayır | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | Hayır | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| Değişken | Tür | Açıklama | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `number` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `object` | Permissions for the current object type | +| `targetObjectReadPermissions` | `object` | Read permissions for the target object | +| `targetObjectWritePermissions` | `object` | Write permissions for the target object | +| `featureFlags` | `object` | Active feature flags | +| `objectMetadataItem` | `object` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | Açıklama | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### Stil + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + Yetenekler, yapay zekâ ajanlarının çalışma alanınızda kullanabileceği yeniden kullanılabilir yönergeleri ve kabiliyetleri tanımlar. Yerleşik doğrulamayla yetenekleri tanımlamak için `defineSkill()` kullanın: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` Önemli noktalar: - * `name`, yetenek için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). * `label`, UI'de gösterilen, insan tarafından okunabilir addır. * `content`, yetenek yönergelerini içerir — bu, yapay zekâ ajanının kullandığı metindir. * `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. * `description` (isteğe bağlı), yeteneğin amacı hakkında ek bağlam sağlar. -Yeni yetenekleri iki şekilde oluşturabilirsiniz: + + -* **Şablondan**: `yarn twenty add` çalıştırın ve yeni bir yetenek ekleme seçeneğini seçin. -* **Manuel**: Yeni bir dosya oluşturun ve aynı deseni izleyerek `defineSkill()` kullanın. +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### Tipli API istemcileri (`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -`twenty-client-sdk` paketi, mantık fonksiyonlarınızdan ve ön uç bileşenlerinizden Twenty API ile etkileşim kurmak için iki tipli GraphQL istemcisi sağlar: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Önemli noktalar: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. +* `modelId` (optional) overrides the default AI model used by the agent. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Önemli noktalar: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Önemli noktalar: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Önemli noktalar: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| Varlık türü | Komut | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| Nesne | `yarn twenty add object` | `src/objects/.ts` | +| Alan | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| Rol | `yarn twenty add role` | `src/roles/.ts` | +| Beceri | `yarn twenty add skill` | `src/skills/.ts` | +| Temsilci | `yarn twenty add agent` | `src/agents/.ts` | +| Görünüm | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +The `twenty-client-sdk` package provides two typed GraphQL clients for interacting with the Twenty API from your logic functions and front components. | İstemci | İçe Aktar | Uç nokta | Oluşturuldu mu? | | ------------------- | ---------------------------- | ------------------------------------------------------------- | --------------------------------------- | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — çalışma alanı verileri (kayıtlar, nesneler) | Evet, geliştirme/derleme zamanında | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — çalışma alanı yapılandırması, dosya yüklemeleri | Hayır, önceden hazırlanmış olarak gelir | -#### CoreApiClient + + -`CoreApiClient`, çalışma alanı verilerini sorgulamak ve değiştirmek için ana istemcidir. `yarn twenty dev` veya `yarn twenty build` sırasında çalışma alanı şemanızdan oluşturulur; bu nedenle nesnelerinize ve alanlarınıza uyacak şekilde tamamen tiplendirilmiştir. +`CoreApiClient`, çalışma alanı verilerini sorgulamak ve değiştirmek için ana istemcidir. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ İstemci bir seçim kümesi sözdizimi kullanır: Bir alanı dahil etmek için `true` geçin, bağımsız değişkenler için `__args` kullanın ve ilişkiler için nesneleri iç içe yerleştirin. Çalışma alanı şemanıza göre tam otomatik tamamlama ve tip denetimi elde edersiniz. -**CoreApiClient geliştirme/derleme zamanında oluşturulur.** Bunu önce `yarn twenty dev` veya `yarn twenty build` çalıştırmadan kullanmaya çalışırsanız, hata verir. Oluşturma otomatik olarak gerçekleşir — CLI, çalışma alanınızın GraphQL şemasını inceler, `@genql/cli` kullanarak tipli bir istemci üretir, üretilen kaynakları `node_modules/twenty-client-sdk/dist/core/generated/` dizinine yazar ve `node_modules/twenty-client-sdk/dist/core.mjs` ile `node_modules/twenty-client-sdk/dist/core.cjs` içindeki taslakları değiştirir. +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### Tür açıklamaları için CoreSchema'yı kullanma -`CoreSchema`, çalışma alanı nesnelerinize uyan TypeScript türleri sağlar; bileşen durumunu veya işlev parametrelerini tiplemek için kullanışlıdır: +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient`, SDK ile birlikte önceden hazırlanmış olarak gelir (oluşturma gerektirmez). Çalışma alanı yapılandırması, uygulamalar ve dosya yüklemeleri için `/metadata` uç noktasını sorgular: +`MetadataApiClient`, SDK ile birlikte önceden hazırlanmış olarak gelir (oluşturma gerektirmez). Çalışma alanı yapılandırması, uygulamalar ve dosya yüklemeleri için `/metadata` uç noktasını sorgular. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### Çalışma zamanı kimlik bilgileri - -Kodunuz Twenty üzerinde çalıştığında (mantık işlevleri veya ön uç bileşenleri), platform kimlik bilgilerini ortam değişkenleri olarak enjekte eder: - -* `TWENTY_API_URL` — Twenty API'nin temel URL'si -* `TWENTY_API_KEY` — Uygulamanızın varsayılan fonksiyon rolü kapsamına sahip kısa ömürlü anahtar - -Bunları istemcilere iletmeniz gerekmez — otomatik olarak `process.env`'den okurlar. API anahtarının izinleri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` ile referans verilen role göre belirlenir. - #### Dosya yükleme -`MetadataApiClient`, dosya türü alanlara dosya eklemek için bir `uploadFile` yöntemi içerir. [GraphQL çok parçalı istek spesifikasyonunu](https://github.com/jaydenseric/graphql-multipart-request-spec) uygular: +`MetadataApiClient`, dosya türü alanlara dosya eklemek için bir `uploadFile` yöntemi içerir: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| Parametre | Tür | Açıklama | -| ---------------------------------- | -------- | ------------------------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Dosyanın ham içeriği | -| `filename` | `string` | Dosyanın adı (depolama ve görüntüleme için kullanılır) | -| `contentType` | `string` | Dosyanın MIME türü (belirtilmezse varsayılan olarak `application/octet-stream` kullanılır) | -| `fieldMetadataUniversalIdentifier` | `string` | Nesnenizdeki dosya türü alanının `universalIdentifier` değeri | +| Parametre | Tür | Açıklama | +| ---------------------------------- | -------- | ------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Dosyanın ham içeriği | +| `filename` | `string` | Dosyanın adı (depolama ve görüntüleme için kullanılır) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | Nesnenizdeki dosya türü alanının `universalIdentifier` değeri | Önemli noktalar: - * Alan için `universalIdentifier` kullanır (çalışma alanına özgü kimliği değil), böylece yükleme kodunuz uygulamanızın yüklü olduğu herhangi bir çalışma alanında çalışır. * Döndürülen `url`, yüklenen dosyaya erişmek için kullanabileceğiniz imzalı bir URL'dir. -### Hello World örneği + + -Nesneleri, mantık fonksiyonlarını, ön uç bileşenlerini ve birden çok tetikleyiciyi gösteren minimal, uçtan uca bir örneği [buradan](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world) inceleyin. + + Kodunuz Twenty üzerinde çalıştığında (mantık işlevleri veya ön uç bileşenleri), platform kimlik bilgilerini ortam değişkenleri olarak enjekte eder: + + * `TWENTY_API_URL` — Twenty API'nin temel URL'si + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + Bunları istemcilere iletmeniz gerekmez — otomatik olarak `process.env`'den okurlar. API anahtarının izinleri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` ile referans verilen role göre belirlenir. + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### Kurulum + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| Fonksiyon | Açıklama | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## CLI başvurusu + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx index f30501de03..9236ca6a65 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: İlk Twenty uygulamanızı dakikalar içinde oluşturun. --- -Uygulamalar şu anda alfa testinde. Özellik işlevsel ancak hâlâ gelişmekte. +Apps are currently in alpha. The feature works but is still evolving. Uygulamalar, Twenty'yi özel nesneler, alanlar, mantık işlevleri, Yapay Zeka yetenekleri ve UI bileşenleriyle genişletmenizi sağlar — tümü kod olarak yönetilir. -**Oluşturabilecekleriniz:** - -* Veri modelinizi şekillendirmek için özel nesneler, alanlar, görünümler ve gezinti öğeleri -* HTTP rotaları, cron zamanlamaları veya veritabanı olayları tarafından tetiklenen mantık işlevleri -* Twenty'nin UI'si içinde doğrudan görüntülenen ön uç bileşenleri -* Twenty'nin yapay zeka ajanlarını genişleten beceriler -* Bir uygulamayı birden çok çalışma alanına dağıtın - ## Ön Gereksinimler -* Node.js 24+ -* Yarn 4 -* Docker (veya çalışan yerel bir Twenty örneği) +Before you begin, make sure the following is installed on your machine: -## Başlarken +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -Resmi iskelet oluşturucu aracını kullanarak yeni bir uygulama oluşturun, ardından kimlik doğrulaması yapıp geliştirmeye başlayın: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> Minimal bir kurulum iskeleti oluşturmak için `--minimal` seçeneğini kullanın +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -Buradan şunları yapabilirsiniz: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -Ayrıca bkz.: [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) ve [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) için CLI başvuru sayfaları. +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## Proje yapısı (şablondan oluşturulmuş) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -`npx create-twenty-app@latest my-twenty-app` komutunu çalıştırdığınızda iskelet oluşturucu şunları yapar: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* Minimal bir temel uygulamayı `my-twenty-app/` içine kopyalar -* Yerel bir `twenty-sdk` bağımlılığı ve Yarn 4 yapılandırması ekler -* `twenty` CLI ile bağlantılı yapılandırma dosyaları ve betikler oluşturur -* İskelet oluşturma moduna bağlı olarak çekirdek dosyaları (uygulama yapılandırması, varsayılan işlev rolü, kurulum öncesi ve kurulum sonrası işlevler) ile örnek dosyaları üretir + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/tr/developers/extend/apps/publishing) for details. + -Varsayılan `--exhaustive` moduyla yeni oluşturulmuş bir uygulama şu şekilde görünür: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/tr/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`). +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -Genel hatlarıyla: +### Key files -* **package.json**: Uygulama adını, sürümünü, motorları (Node 24+, Yarn 4) bildirir ve `twenty-sdk` ile yerel `twenty` CLI'sine yetki devreden bir `twenty` betiği ekler. Tüm mevcut komutları listelemek için `yarn twenty help` komutunu çalıştırın. -* **.gitignore**: `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, kapsam klasörleri, günlük dosyaları ve `.env*` dosyaları gibi yaygın artifaktları yok sayar. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Proje tarafından kullanılan Yarn 4 araç zincirini kilitler ve yapılandırır. -* **.nvmrc**: Projenin beklediği Node.js sürümünü sabitler. -* **.oxlintrc.json** ve **tsconfig.json**: Uygulamanızın TypeScript kaynakları için linting ve TypeScript yapılandırması sağlar. -* **README.md**: Uygulama kökünde temel talimatların yer aldığı kısa bir README. -* **public/**: Uygulamanızla birlikte sunulacak genel varlıkları (görseller, yazı tipleri, statik dosyalar) depolamak için bir klasör. Buraya yerleştirilen dosyalar senkronizasyon sırasında yüklenir ve çalışma zamanında erişilebilir olur. -* **src/**: Uygulamanızı kod olarak tanımladığınız ana yer +| File / Folder | Amaç | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | Twenty'nin yapay zeka ajanlarının yeteneklerini genişleten beceriler. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### Varlık algılama +## Managing remotes -SDK, TypeScript dosyalarınızı **`export default define({...})`** çağrılarını arayarak ayrıştırıp varlıkları algılar. Her varlık türünün, `twenty-sdk` tarafından dışa aktarılan karşılık gelen bir yardımcı fonksiyonu vardır: - -| Yardımcı fonksiyon | Varlık türü | -| -------------------------------- | -------------------------------------------------------- | -| `defineObject` | Özel nesne tanımları | -| `defineLogicFunction` | Mantık işlevi tanımları | -| `definePreInstallLogicFunction` | Kurulum öncesi mantık işlevi (kurulumdan önce çalışır) | -| `definePostInstallLogicFunction` | Kurulum sonrası mantık işlevi (kurulumdan sonra çalışır) | -| `defineFrontComponent` | Ön bileşen tanımları | -| `defineRole` | Rol tanımları | -| `defineField` | Mevcut nesneler için alan genişletmeleri | -| `defineView` | Kaydedilmiş görünüm tanımları | -| `defineNavigationMenuItem` | Gezinme menüsü öğesi tanımları | -| `defineSkill` | Yapay zekâ ajanı yetenek tanımları | - - -**Dosya adlandırma esnektir.** Varlık algılama AST tabanlıdır — SDK, kaynak dosyalarınızı `export default define({...})` desenini bulmak için tarar. Dosyalarınızı ve klasörlerinizi dilediğiniz gibi düzenleyebilirsiniz. Varlık türüne göre gruplama (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca kod organizasyonu için bir gelenektir. - - -Algılanan bir varlığa örnek: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -İlerideki komutlar daha fazla dosya ve klasör ekleyecektir: - -* `yarn twenty dev`, türlendirilmiş `CoreApiClient`'i (çalışma alanı verileri için `/graphql` aracılığıyla) `node_modules/twenty-client-sdk/` içine otomatik olarak oluşturur. `MetadataApiClient` (çalışma alanı yapılandırması ve dosya yüklemeleri için `/metadata` aracılığıyla) önceden derlenmiş olarak gelir ve hemen kullanılabilir. Bunları sırasıyla `twenty-client-sdk/core` ve `twenty-client-sdk/metadata` içinden içe aktarın. -* `yarn twenty add`, özel nesneleriniz, fonksiyonlarınız, ön bileşenleriniz, rolleriniz, yetenekleriniz ve daha fazlası için `src/` altında varlık tanım dosyaları ekler. - -## Kimlik Doğrulama - -`yarn twenty auth:login` komutunu ilk kez çalıştırdığınızda, sizden şunlar istenir: - -* API URL’si (varsayılan: http://localhost:3000 veya mevcut çalışma alanı profiliniz) -* API anahtarı - -Kimlik bilgileriniz kullanıcı başına `~/.twenty/config.json` içinde saklanır. Birden fazla profili yönetebilir ve aralarında geçiş yapabilirsiniz. - -### Çalışma alanlarını yönetme +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -`yarn twenty auth:switch` ile çalışma alanlarını değiştirdikten sonra, sonraki tüm komutlar varsayılan olarak o çalışma alanını kullanacaktır. Yine de bunu geçici olarak `--workspace ` ile geçersiz kılabilirsiniz. +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### Sunucuyu Başlatma + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| Komut | Açıklama | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## Manuel kurulum (iskelet oluşturucu olmadan) -En iyi başlangıç deneyimi için `create-twenty-app` kullanmanızı önersek de, bir projeyi manuel olarak da kurabilirsiniz. CLI'yi global olarak kurmayın. Bunun yerine `twenty-sdk`'yi yerel bir bağımlılık olarak ekleyin ve package.json içinde tek bir betik tanımlayın: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -Ardından bir `twenty` betiği ekleyin: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ Ardından bir `twenty` betiği ekleyin: } ``` -Artık tüm komutları `yarn twenty ` üzerinden çalıştırabilirsiniz; örn. `yarn twenty dev`, `yarn twenty help` vb. +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## Yerel bir Twenty örneği nasıl kullanılır? - -Zaten yerel olarak bir Twenty örneği çalıştırıyorsanız (örneğin `npx nx start twenty-server` ile), Docker kullanmak yerine ona bağlanabilirsiniz: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## Sorun Giderme -* Kimlik doğrulama hataları: `yarn twenty auth:login` çalıştırın ve API anahtarınızın gerekli izinlere sahip olduğundan emin olun. -* Sunucuya bağlanılamıyor: API URL’sini ve Twenty sunucusunun erişilebilir olduğunu doğrulayın. -* Türler veya istemci eksik/eski: `yarn twenty dev` komutunu yeniden çalıştırın — tiplendirilmiş istemciyi otomatik olarak oluşturur. -* Geliştirme modu eşitlenmiyor: `yarn twenty dev`'in çalıştığından ve değişikliklerin ortamınız tarafından yok sayılmadığından emin olun. +If you run into issues: -Discord Yardım Kanalı: https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx index 5ff65fc996..ce04eef64c 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: Twenty uygulamanızı pazaryerine sunun ya da dahili olarak dağıt --- -Uygulamalar şu anda alfa testinde. Özellik işlevsel ancak hâlâ gelişmekte. + Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte. ## Genel Bakış Uygulamanız [yerelde derlenip test edildikten sonra](/l/tr/developers/extend/apps/building), dağıtım için iki yolunuz vardır: -* **npm’ye yayımlama** — uygulamanızı Twenty pazaryerinde listeleyin; böylece herhangi bir çalışma alanı keşfedip yükleyebilir. * **Bir tar arşivi dağıtın** — uygulamanızı dahili veya özel kullanım için doğrudan belirli bir Twenty sunucusuna yükleyin. +* **npm’ye yayımlama** — uygulamanızı Twenty pazaryerinde listeleyin; böylece herhangi bir çalışma alanı keşfedip yükleyebilir. Her iki yol da aynı **build** adımından başlar. ## Uygulamanızı derleme -`build` komutu TypeScript kaynaklarınızı derler, mantık işlevlerini ve ön uç bileşenlerini transpile eder ve uygulamanızın içeriğini açıklayan bir `manifest.json` üretir: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -Çıktı `.twenty/output/` dizinine yazılır. Bu dizin, dağıtım için gereken her şeyi içerir: derlenmiş kod, varlıklar, manifest ve `package.json` dosyanızın bir kopyası. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -Ayrıca bir `.tgz` tarball oluşturmak için (deploy komutu tarafından dahili olarak kullanılır veya el ile dağıtım için): +## Sunucuya dağıtım (tarball) + +Genel kullanıma açık olmasını istemediğiniz uygulamalar — sahipli araçlar, yalnızca kurumsal entegrasyonlar veya deneysel derlemeler — için bir tarball’ı doğrudan bir Twenty sunucusuna dağıtabilirsiniz. + +### Ön Gereksinimler + +Dağıtmadan önce, hedef sunucuyu işaret eden yapılandırılmış bir remote’a ihtiyacınız vardır. Remote’lar sunucu URL’sini ve kimlik doğrulama bilgilerini yerel olarak `~/.twenty/config.json` içinde saklar. + +Bir remote ekleyin: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### Dağıtım + +Uygulamanızı tek adımda derleyip sunucuya yükleyin: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### Dağıtılmış bir uygulamayı paylaşma + +Tarball uygulamaları genel pazar yerinde listelenmez; bu nedenle aynı sunucudaki diğer çalışma alanları gezinerek onları keşfedemez. Dağıtılmış bir uygulamayı paylaşmak için: + +1. **Ayarlar > Uygulamalar > Kayıtlar** bölümüne gidin ve uygulamanızı açın +2. **Dağıtım** sekmesinde, **Paylaşım bağlantısını kopyala**’ya tıklayın +3. Bu bağlantıyı diğer çalışma alanlarındaki kullanıcılarla paylaşın — onları doğrudan uygulamanın yükleme sayfasına götürür + +Paylaşım bağlantısı, sunucunun temel URL’sini (herhangi bir çalışma alanı alt alan adı olmadan) kullanır; böylece sunucudaki herhangi bir çalışma alanı için çalışır. + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### Sürüm yönetimi + +Bir güncelleme yayımlamak için: + +1. `package.json` içindeki `version` alanını artırın +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. Uygulamayı kurmuş olan çalışma alanları, ayarlarında mevcut güncellemeyi görecektir + +{/* TODO: add screenshot of the Upgrade button */} + ## npm’ye yayımlama npm’ye yayımlamak, uygulamanızın Twenty pazaryerinde keşfedilebilir olmasını sağlar. Herhangi bir Twenty çalışma alanı, pazaryeri uygulamalarına doğrudan arayüzden göz atabilir, yükleyebilir ve güncelleyebilir. @@ -39,41 +81,42 @@ npm’ye yayımlamak, uygulamanızın Twenty pazaryerinde keşfedilebilir olmas ### Gereksinimler * Bir [npm](https://www.npmjs.com) hesabı -* `twenty-app` anahtar kelimesi `package.json` dosyanızdaki `keywords` dizisinde **mutlaka** listelenmelidir - -### Gerekli anahtar kelimeyi ekleme - -Twenty pazar yeri, npm kayıt defterinde `twenty-app` anahtar kelimesine sahip paketleri arayarak uygulamaları keşfeder. Bunu `package.json` dosyanıza ekleyin: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -Pazar yeri, npm kayıt defterinde `keywords:twenty-app` araması yapar. Bu anahtar kelime olmadan, adında `twenty-app-` öneki bulunsa bile paketiniz pazar yerinde görünmez. - +### Pazaryeri meta verileri -### Adımlar +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **Uygulamanızı derleyin:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **npm’ye yayımlayın:** +See the [defineApplication accordion](/l/tr/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -Bu, `.twenty/output/` dizininden `npm publish` komutunu çalıştırır. - Belirli bir dist-tag altında yayımlamak için (ör. `beta` veya `next`): ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### Pazar yerinde keşif nasıl çalışır -Twenty sunucusu pazar yeri kataloğunu npm kayıt defterinden **her saat** eşitler: +The Twenty server syncs its marketplace catalog from the npm registry **every hour**. -1. `keywords:twenty-app` anahtar kelimesine sahip tüm npm paketlerini arar -2. Her paket için `manifest.json` dosyasını npm CDN’inden getirir -3. Uygulamanın meta verileri (ad, açıklama, yazar, logo, ekran görüntüleri, kategori) manifest dosyasından çıkarılır ve pazar yerinde görüntülenir - -Yayımladıktan sonra, uygulamanızın pazar yerinde görünmesi bir saate kadar sürebilir. Bir sonraki saatlik çalışmayı beklemek yerine eşitlemeyi hemen tetiklemek için: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -Belirli bir remote’u hedeflemek için: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -Pazar yerinde gösterilen meta veriler, uygulamanızın kaynak kodundaki `defineApplication()` çağrısından gelir — `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` ve `termsUrl` gibi alanlar. +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. Uygulamanız `defineApplication()` içinde bir `aboutDescription` tanımlamıyorsa, pazaryeri, hakkında sayfasının içeriği olarak paketinizin npm'deki `README.md` dosyasını otomatik olarak kullanır. Bu, hem npm hem de Twenty pazaryeri için tek bir README dosyası kullanabileceğiniz anlamına gelir. Pazaryerinde farklı bir açıklama istiyorsanız, `aboutDescription` değerini açıkça ayarlayın. @@ -108,7 +143,7 @@ Uygulamanız `defineApplication()` içinde bir `aboutDescription` tanımlamıyor ### CI üzerinden yayımlama -İskelet proje, her sürümde yayımlayan bir GitHub Actions iş akışını içerir: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` Diğer CI sistemleri (GitLab CI, CircleCI, vb.) için de aynı üç komut geçerlidir: `yarn install`, `yarn twenty build` ve ardından `.twenty/output` dizininden `npm publish`. - + **npm provenance** isteğe bağlıdır ancak önerilir. `--provenance` ile yayımlamak, npm listenize bir güven rozeti ekler ve kullanıcıların paketin herkese açık bir CI ardışık düzenindeki belirli bir commit’ten oluşturulduğunu doğrulamasını sağlar. Kurulum talimatları için [npm provenance belgelerine](https://docs.npmjs.com/generating-provenance-statements) bakın. - - -## Sunucuya dağıtım (tarball) - -Genel kullanıma açık olmasını istemediğiniz uygulamalar — sahipli araçlar, yalnızca kurumsal entegrasyonlar veya deneysel derlemeler — için bir tarball’ı doğrudan bir Twenty sunucusuna dağıtabilirsiniz. - -### Ön Gereksinimler - -Dağıtmadan önce, hedef sunucuyu işaret eden yapılandırılmış bir remote’a ihtiyacınız vardır. Remote’lar sunucu URL’sini ve kimlik doğrulama bilgilerini yerel olarak `~/.twenty/config.json` içinde saklar. - -Bir remote ekleyin: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -Yerel bir geliştirme sunucusu için: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -Etkileşimli olmayan ortamlar için bir API anahtarıyla da kimlik doğrulayabilirsiniz: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -Remote’larınızı yönetin: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### Dağıtım - -Uygulamanızı tek adımda derleyip sunucuya yükleyin: - -```bash filename="Terminal" -yarn twenty deploy -``` - -Bu, uygulamayı `--tarball` ile derler ve ardından tarball’ı varsayılan remote’a GraphQL çok parçalı yükleme ile yükler. - -Belirli bir remote’a dağıtmak için: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### Dağıtılmış bir uygulamayı paylaşma - -Tarball uygulamaları genel pazar yerinde listelenmez; bu nedenle aynı sunucudaki diğer çalışma alanları gezinerek onları keşfedemez. Dağıtılmış bir uygulamayı paylaşmak için: - -1. **Ayarlar > Uygulamalar > Kayıtlar** bölümüne gidin ve uygulamanızı açın -2. **Dağıtım** sekmesinde, **Paylaşım bağlantısını kopyala**’ya tıklayın -3. Bu bağlantıyı diğer çalışma alanlarındaki kullanıcılarla paylaşın — onları doğrudan uygulamanın yükleme sayfasına götürür - -Paylaşım bağlantısı, sunucunun temel URL’sini (herhangi bir çalışma alanı alt alan adı olmadan) kullanır; böylece sunucudaki herhangi bir çalışma alanı için çalışır. - -### Sürüm yönetimi - -Bir güncelleme yayımlamak için: - -1. `package.json` içindeki `version` alanını artırın -2. `yarn twenty deploy` (veya `yarn twenty deploy -r production`) komutunu çalıştırın -3. Uygulamayı kurmuş olan çalışma alanları, ayarlarında mevcut güncellemeyi görecektir + ## Uygulamaları yükleme -Bir uygulama yayımlandığında (npm) veya dağıtıldığında (tarball), çalışma alanları onu kullanıcı arayüzü (UI) aracılığıyla yükler: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -Veya Twenty kullanıcı arayüzündeki **Ayarlar > Uygulamalar** sayfasından; burada hem pazar yerindeki hem de tarball ile dağıtılmış uygulamalar görüntülenip yüklenebilir. - -## Uygulama dağıtım kategorileri - -Twenty, uygulamaları nasıl dağıtıldıklarına göre üç kategoriye ayırır: - -| Kategori | Nasıl Çalışır | Pazaryerinde görünür mü? | -| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| **Geliştirme** | `yarn twenty dev` ile çalışan yerel geliştirme modu uygulamaları. Derleme ve test için kullanılır. | Hayır | -| **Yayımlanmış (npm)** | `twenty-app` anahtar kelimesiyle npm’ye yayımlanan uygulamalar. Herhangi bir çalışma alanının yükleyebilmesi için pazaryerinde listelenir. | Evet | -| **Dahili (tarball)** | Bir tarball aracılığıyla belirli bir sunucuya dağıtılan uygulamalar. Yalnızca o sunucudaki çalışma alanları için bir paylaşım bağlantısı aracılığıyla kullanılabilir. | Hayır | - - -Uygulamanızı geliştirirken **Geliştirme** modunda başlayın. Hazır olduğunda, geniş dağıtım için **Yayımlanmış** (npm) ya da özel dağıtım için **Dahili** (tarball) seçeneğini tercih edin. - - -## CLI başvurusu - -| Komut | Açıklama | Temel bayraklar | -| --------------------------- | --------------------------------------------------- | -------------------------------------------------- | -| `yarn twenty build` | Uygulamayı derleyin ve manifest oluşturun | `--tarball` — ayrıca bir `.tgz` paket oluşturur | -| `yarn twenty publish` | Derleyin ve npm’ye yayımlayın | `--tag ` — npm dist-tag (örn. `beta`, `next`) | -| `yarn twenty deploy` | Derleyin ve tarball’ı bir sunucuya yükleyin | `-r, --remote ` — hedef remote | -| `yarn twenty catalog-sync` | Sunucuda pazar yeri katalog eşitlemesini tetikleyin | `-r, --remote ` — hedef remote | -| `yarn twenty install` | Dağıtılmış bir uygulamayı bir çalışma alanına kurun | `-r, --remote ` — hedef remote | -| `yarn twenty dev` | Yerel değişiklikleri izleyin ve eşitleyin | Varsayılan remote’u kullanır | -| `yarn twenty remote add` | Bir sunucu bağlantısı ekleyin | `--url`, `--token`, `--as`, `--local`, `--port` | -| `yarn twenty remote list` | Yapılandırılmış remote’ları listeleyin | — | -| `yarn twenty remote switch` | Varsayılan remote’u ayarlayın | — | -| `yarn twenty remote status` | Bağlantı durumunu gösterin | — | -| `yarn twenty remote remove` | Bir remote’u kaldırın | — | diff --git a/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx index 8e457d7264..95c4a45425 100644 --- a/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: Twenty özelleştirmelerini kod olarak oluşturun ve yönetin. --- -Uygulamalar şu anda alfa testinde. Özellik işlevsel ancak hâlâ gelişmekte. +Apps are currently in alpha. The feature works but is still evolving. -## Uygulamalar Nedir? +## What are apps? -Uygulamalar, Twenty özelleştirmelerini **kod olarak** oluşturup yönetmenizi sağlar. Her şeyi UI üzerinden yapılandırmak yerine, veri modelinizi ve mantık fonksiyonlarınızı kodla tanımlarsınız — bu da oluşturmayı, bakımı ve birden çok çalışma alanına dağıtmayı hızlandırır. +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**Bugün Yapabilecekleriniz:** +**Oluşturabilecekleriniz:** -* Özel nesneleri ve alanları kod olarak tanımlayın (yönetilen veri modeli) -* Özel tetikleyicilerle mantık fonksiyonları oluşturun -* Yapay zekâ için yetenekleri ve ajanları tanımlayın -* Aynı uygulamayı birden çok çalışma alanına dağıtın +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## Ön Gereksinimler - -* Node.js 24+ ve Yarn 4 -* Docker (yerel Twenty geliştirme sunucusu için) - -## Başlarken - -Resmi iskelet oluşturucusunu kullanarak yeni bir uygulama oluşturun. Sizin için otomatik olarak yerel bir Twenty örneğini başlatabilir: +## Quick start ```bash filename="Terminal" -# Yeni bir uygulamanın iskeletini oluşturun — CLI yerel bir Twenty sunucusunu başlatmayı önerecektir npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Geliştirme modunu başlatın: yerel değişiklikleri çalışma alanınızla otomatik olarak senkronize eder yarn twenty dev ``` -### Yerel Sunucu Yönetimi +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/tr/developers/extend/apps/getting-started) guide for the full walkthrough. -SDK, yerel bir Twenty geliştirme sunucusunu yönetmek için komutlar içerir (PostgreSQL, Redis, sunucu ve worker içeren hepsi bir arada Docker imajı, 2020 numaralı portta). Bu komutlar yalnızca Docker tabanlı geliştirme sunucusu için geçerlidir — kaynak koddan başlatılan bir Twenty örneğini yönetmezler (örn. 3000 numaralı portta `npx nx start twenty-server`): +## Detailed guides -```bash filename="Terminal" -# Yerel sunucuyu başlatın (gerekirse imajı indirir) -yarn twenty server start +| Guide | Açıklama | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/tr/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/tr/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/tr/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Sunucu durumunu kontrol edin -yarn twenty server status - -# Sunucu günlüklerini akış olarak görüntüleyin -yarn twenty server logs - -# Sunucuyu durdurun -yarn twenty server stop - -# Tüm verileri sıfırlayın ve temiz bir başlangıç yapın -yarn twenty server reset -``` - -Yerel sunucu, bir çalışma alanı ve kullanıcıyla (`tim@apple.dev` / `tim@apple.dev`) önceden yapılandırılmış olarak gelir; böylece herhangi bir manuel kurulum gerektirmeden hemen geliştirmeye başlayabilirsiniz. - -### Kimlik Doğrulama - -Uygulamanızı OAuth kullanarak yerel sunucuya bağlayın: - -```bash filename="Terminal" -# Authenticate via OAuth (opens browser) -yarn twenty remote add --local -``` - -İskelet oluşturucu, hangi örnek dosyaların dahil edileceğini kontrol etmek için iki modu destekler: - -```bash filename="Terminal" -# Varsayılan (kapsamlı): tüm örnekler (nesne, alan, mantık fonksiyonu, ön bileşen, görünüm, gezinme menüsü öğesi, yetenek, ajan) -npx create-twenty-app@latest my-app - -# Minimal: yalnızca çekirdek dosyalar (application-config.ts ve default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### Yerel bir Twenty örneği nasıl kullanılır? - -Zaten yerel olarak bir Twenty örneği çalıştırıyorsanız, Docker kullanmak yerine ona bağlanabilirsiniz. Yerel sunucunuzun dinlediği bağlantı noktasını belirtin (varsayılan: `3000`): - -```bash filename="Terminal" -# During scaffolding -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding -yarn twenty remote add --local --port 3000 -``` - -Buradan şunları yapabilirsiniz: - -```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty entity:add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Build the app for distribution -yarn twenty build - -# Publish the app to npm or a Twenty server -yarn twenty publish - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help},{ -``` - -Ayrıca bkz.: [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) ve [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) için CLI başvuru sayfaları. - -## Proje yapısı (şablondan oluşturulmuş) - -`npx create-twenty-app@latest my-twenty-app` komutunu çalıştırdığınızda scaffolder şunları yapar: - -* Minimal bir temel uygulamayı `my-twenty-app/` içine kopyalar -* Yerel bir `twenty-sdk` bağımlılığı ve Yarn 4 yapılandırması ekler -* `twenty` CLI ile bağlantılı yapılandırma dosyaları ve betikler oluşturur -* İskelet oluşturma moduna bağlı olarak çekirdek dosyaları (uygulama yapılandırması, varsayılan işlev rolü, kurulum öncesi ve kurulum sonrası işlevler) ile örnek dosyaları üretir - -Varsayılan `--exhaustive` moduyla yeni oluşturulmuş bir uygulama şu şekilde görünür: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # Genel varlıklar klasörü (görseller, yazı tipleri vb.) - src/ - ├── application-config.ts # Gerekli - ana uygulama yapılandırması - ├── roles/ - │ └── default-role.ts # Mantık fonksiyonları için varsayılan rol - ├── objects/ - │ └── example-object.ts # Örnek özel nesne tanımı - ├── fields/ - │ └── example-field.ts # Örnek bağımsız alan tanımı - ├── logic-functions/ - │ ├── hello-world.ts # Örnek mantık fonksiyonu - │ ├── pre-install.ts # Kurulum öncesi mantık fonksiyonu - │ └── post-install.ts # Kurulum sonrası mantık fonksiyonu - ├── front-components/ - │ └── hello-world.tsx # Örnek ön bileşen - ├── views/ - │ └── example-view.ts # Örnek kaydedilmiş görünüm tanımı - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Örnek kenar çubuğu gezinme bağlantısı - ├── skills/ - │ └── example-skill.ts # Örnek yapay zekâ ajanı yetenek tanımı - └── agents/ - └── example-agent.ts # Örnek yapay zekâ ajanı tanımı -``` - -`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`). - -Genel hatlarıyla: - -* **package.json**: Uygulama adını, sürümünü, motorları (Node 24+, Yarn 4) bildirir ve `twenty-sdk` ile yerel `twenty` CLI'sine yetki devreden bir `twenty` betiği ekler. Tüm mevcut komutları listelemek için `yarn twenty help` komutunu çalıştırın. -* **.gitignore**: `node_modules`, `.yarn`, `generated/` (türlendirilmiş istemci), `dist/`, `build/`, kapsam klasörleri, günlük dosyaları ve `.env*` dosyaları gibi yaygın artifaktları yok sayar. -* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Proje tarafından kullanılan Yarn 4 araç zincirini kilitler ve yapılandırır. -* **.nvmrc**: Projenin beklediği Node.js sürümünü sabitler. -* **.oxlintrc.json** ve **tsconfig.json**: Uygulamanızın TypeScript kaynakları için linting ve TypeScript yapılandırması sağlar. -* **README.md**: Uygulama kökünde temel talimatların yer aldığı kısa bir README. -* **public/**: Uygulamanızla birlikte sunulacak genel varlıkları (görseller, yazı tipleri, statik dosyalar) depolamak için bir klasör. Buraya yerleştirilen dosyalar senkronizasyon sırasında yüklenir ve çalışma zamanında erişilebilir olur. -* **src/**: Uygulamanızı kod olarak tanımladığınız ana yer +## Key concepts ### Varlık algılama -SDK, TypeScript dosyalarınızı **`export default define({...})`** çağrılarını arayarak ayrıştırıp varlıkları algılar. Her varlık türünün, `twenty-sdk` tarafından dışa aktarılan karşılık gelen bir yardımcı fonksiyonu vardır: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| Yardımcı fonksiyon | Varlık türü | -| ---------------------------------- | -------------------------------------------------------- | -| `defineObject()` | Özel nesne tanımları | -| `defineLogicFunction()` | Mantık fonksiyon tanımları | -| `definePreInstallLogicFunction()` | Kurulum öncesi mantık işlevi (kurulumdan önce çalışır) | -| `definePostInstallLogicFunction()` | Kurulum sonrası mantık işlevi (kurulumdan sonra çalışır) | -| `defineFrontComponent()` | Front component definitions | -| `defineRole()` | Rol tanımları | -| `defineField()` | Mevcut nesneler için alan genişletmeleri | -| `defineView()` | Kaydedilmiş görünüm tanımları | -| `defineNavigationMenuItem()` | Gezinme menüsü öğesi tanımları | -| `defineSkill()` | Yapay zekâ ajanı yetenek tanımları | -| `defineAgent()` | Yapay zekâ ajanı tanımları | +### Available entity types - -**Dosya adlandırma esnektir.** Varlık algılama AST tabanlıdır — SDK, kaynak dosyalarınızı `export default define({...})` desenini bulmak için tarar. Dosyalarınızı ve klasörlerinizi dilediğiniz gibi düzenleyebilirsiniz. Varlık türüne göre gruplama (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca kod organizasyonu için bir gelenektir. - +| Fonksiyon | Amaç | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -Algılanan bir varlığa örnek: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -İlerideki komutlar daha fazla dosya ve klasör ekleyecektir: - -* `yarn twenty dev`, `node_modules/twenty-sdk/clients` içinde iki tiplendirilmiş API istemcisini otomatik olarak oluşturur: `CoreApiClient` (`/graphql` üzerinden çalışma alanı verileri için) ve `MetadataApiClient` (çalışma alanı yapılandırması ve `/metadata` üzerinden dosya yüklemeleri için). -* `yarn twenty entity:add`, özel nesneleriniz, fonksiyonlarınız, ön bileşenleriniz, rolleriniz, yetenekleriniz ve daha fazlası için `src/` altında varlık tanım dosyaları ekler. - -## Kimlik Doğrulama - -`yarn twenty auth:login` komutunu ilk kez çalıştırdığınızda, sizden şunlar istenir: - -* API URL’si (varsayılan: http://localhost:3000 veya mevcut çalışma alanı profiliniz) -* API anahtarı - -Kimlik bilgileriniz kullanıcı başına `~/.twenty/config.json` içinde saklanır. You can maintain multiple profiles and switch between them. - -### Managing workspaces +### CLI başvurusu ```bash filename="Terminal" -# Etkileşimli giriş yapın (önerilir) -yarn twenty auth:login - -# Belirli bir çalışma alanı profiline giriş yapın -yarn twenty auth:login --workspace my-custom-workspace - -# Yapılandırılmış tüm çalışma alanlarını listeleyin -yarn twenty auth:list - -# Varsayılan çalışma alanını değiştirin (etkileşimli) -yarn twenty auth:switch - -# Belirli bir çalışma alanına geçin -yarn twenty auth:switch production - -# Mevcut kimlik doğrulama durumunu kontrol edin -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -`yarn twenty auth:switch` ile çalışma alanlarını değiştirdikten sonra, sonraki tüm komutlar varsayılan olarak o çalışma alanını kullanacaktır. You can still override it temporarily with `--workspace `. - -## SDK kaynaklarını kullanın (türler ve yapılandırma) - -twenty-sdk, uygulamanız içinde kullandığınız türlendirilmiş yapı taşları ve yardımcı fonksiyonlar sağlar. Aşağıda en sık dokunacağınız başlıca parçalar yer alıyor. - -### Yardımcı fonksiyonlar - -SDK, uygulama varlıklarınızı tanımlamak için yardımcı fonksiyonlar sağlar. [Varlık algılama](#entity-detection) bölümünde açıklandığı gibi, varlıklarınızın algılanması için `export default define({...})` kullanmalısınız: - -| Fonksiyon | Amaç | -| ---------------------------------- | ------------------------------------------------------------------------- | -| `defineApplication()` | Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet) | -| `defineObject()` | Alanlara sahip özel nesneler tanımlayın | -| `defineLogicFunction()` | İşleyicilerle mantık fonksiyonları tanımlayın | -| `definePreInstallLogicFunction()` | Bir kurulum öncesi mantık işlevi tanımlayın (uygulama başına bir adet) | -| `definePostInstallLogicFunction()` | Bir kurulum sonrası mantık işlevi tanımlayın (uygulama başına bir adet) | -| `defineFrontComponent()` | Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın | -| `defineRole()` | Rol izinlerini ve nesne erişimini yapılandırın | -| `defineField()` | Mevcut nesneleri ek alanlarla genişletin | -| `defineView()` | Nesneler için kaydedilmiş görünümler tanımlayın | -| `defineNavigationMenuItem()` | Kenar çubuğu gezinme bağlantılarını tanımlayın | -| `defineSkill()` | Yapay zekâ ajanı yeteneklerini tanımlayın | -| `defineAgent()` | Sistem istemleriyle yapay zekâ ajanları tanımlayın | - -Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar. - -### Nesnelerin tanımlanması - -Özel nesneler, çalışma alanınızdaki kayıtlar için hem şemayı hem de davranışı tanımlar. Yerleşik doğrulamayla nesneler tanımlamak için `defineObject()` kullanın: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Önemli noktalar: - -* Yerleşik doğrulama ve daha iyi IDE desteği için `defineObject()` kullanın. -* `universalIdentifier` dağıtımlar arasında benzersiz ve kararlı olmalıdır. -* Her alan bir `name`, `type`, `label` ve kendi kararlı `universalIdentifier` değerini gerektirir. -* `fields` dizisi isteğe bağlıdır — özel alanlar olmadan da nesneler tanımlayabilirsiniz. -* `yarn twenty entity:add` kullanarak, adlandırma, alanlar ve ilişkiler konusunda sizi yönlendirerek yeni nesneler oluşturabilirsiniz. - - -**Temel alanlar otomatik olarak oluşturulur.** Özel bir nesne tanımladığınızda Twenty, standart alanları otomatik olarak ekler -örneğin `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` ve `deletedAt`. -Bunları `fields` dizinizde tanımlamanız gerekmez — yalnızca özel alanlarınızı ekleyin. -`fields` dizinizde aynı ada sahip bir alan tanımlayarak varsayılan alanları geçersiz kılabilirsiniz, -ancak bu önerilmez. - - -### Mevcut nesneler üzerinde alanları tanımlama - -Mevcut nesnelere özel alanlar eklemek için `defineField()` kullanın — hem standart nesnelere (ör. `company`, `person`, `opportunity`) hem de diğer uygulamalar tarafından tanımlanan özel nesnelere. Her alan kendi dosyasında bulunur ve hedef nesneye `universalIdentifier` ile başvurur. - -Standart nesnelere başvurmak için `twenty-sdk` içinden `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` öğesini içe aktarın. Bu sabit, tüm yerleşik nesneler ve onların alanları için kararlı tanımlayıcılar sağlar: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -Önemli noktalar: - -* `objectUniversalIdentifier`, alanın hangi nesneye ekleneceğini Twenty'ye bildirir. `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` standart nesneler için kullanın. -* Her alan için kararlı bir `universalIdentifier`, `name`, `type`, `label` ve hedef `objectUniversalIdentifier` gerekir. -* `yarn twenty entity:add` kullanarak yeni alanlar oluşturabilir ve alan seçeneğini seçebilirsiniz. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, kolaylık olması için `STANDARD_OBJECT` olarak da dışa aktarılır — her ikisi de aynı sabite atıfta bulunur. - -Kullanılabilir standart nesneler şunları içerir: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` ve `workspaceMember`. - -Her standart nesne ayrıca alan tanımlayıcılarını da sunar. Örneğin, rol izinlerinde standart bir nesnedeki belirli bir alana başvurmak için: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### Mevcut nesnelerde ilişki alanları - -Mevcut nesneleri özel nesnelerinize bağlayan ilişki alanlarını da tanımlayabilirsiniz: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### Uygulama yapılandırması (application-config.ts) - -Her uygulamanın aşağıdakileri açıklayan tek bir `application-config.ts` dosyası vardır: - -* **Uygulamanın kim olduğu**: tanımlayıcılar, görünen ad ve açıklama. -* **Fonksiyonlarının nasıl çalıştığı**: izinler için hangi rolü kullandıkları. -* **(İsteğe bağlı) değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtar–değer çiftleri. -* **(İsteğe bağlı) kurulum öncesi işlev**: uygulama yüklenmeden önce çalışan bir mantık işlevi. -* **(İsteğe bağlı) kurulum sonrası işlev**: uygulama yüklendikten sonra çalışan bir mantık işlevi. - -Uygulama yapılandırmanızı tanımlamak için `defineApplication()` kullanın: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notlar: - -* `universalIdentifier` alanları size ait belirleyici kimliklerdir; bunları bir kez oluşturun ve eşitlemeler boyunca kararlı tutun. -* `applicationVariables`, fonksiyonlarınız için ortam değişkenlerine dönüşür (örneğin, `DEFAULT_RECIPIENT_NAME` değeri `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir). -* `defaultRoleUniversalIdentifier`, rol dosyasıyla eşleşmelidir (aşağıya bakın). -* Kurulum öncesi ve kurulum sonrası işlevler, manifest oluşturma sırasında otomatik olarak algılanır. Bkz. [Kurulum öncesi işlevler](#pre-install-functions) ve [Kurulum sonrası işlevler](#post-install-functions). - -#### Roller ve izinler - -Uygulamalar, çalışma alanınızdaki nesneler ve eylemler üzerindeki izinleri kapsülleyen roller tanımlayabilir. `application-config.ts` içindeki `defaultRoleUniversalIdentifier` alanı, uygulamanızın mantık fonksiyonlarının kullandığı varsayılan rolü belirtir. - -* `TWENTY_API_KEY` olarak enjekte edilen çalışma zamanı API anahtarı bu varsayılan fonksiyon rolünden türetilir. -* Türlendirilmiş istemci, o role tanınan izinlerle sınırlandırılır. -* En az ayrıcalık ilkesini izleyin: Yalnızca fonksiyonlarınızın ihtiyaç duyduğu izinlere sahip özel bir rol oluşturun ve ardından evrensel tanımlayıcısına referans verin. - -##### Varsayılan fonksiyon rolü (\*.role.ts) - -Yeni bir uygulama oluşturduğunuzda CLI ayrıca varsayılan bir rol dosyası da oluşturur. Yerleşik doğrulamayla roller tanımlamak için `defineRole()` kullanın: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -Bu rolün `universalIdentifier` değeri daha sonra `application-config.ts` içinde `defaultRoleUniversalIdentifier` olarak referans verilir. Başka bir deyişle: - -* **\*.role.ts**, varsayılan fonksiyon rolünün neler yapabileceğini tanımlar. -* **application-config.ts**, fonksiyonlarınızın izinlerini devralması için bu role işaret eder. - -Notlar: - -* Oluşturulan rol ile başlayın ve en az ayrıcalık ilkesini izleyerek aşamalı olarak kısıtlayın. -* `objectPermissions` ve `fieldPermissions` değerlerini, fonksiyonlarınızın ihtiyaç duyduğu nesneler/alanlarla değiştirin. -* `permissionFlags`, platform düzeyindeki yeteneklere erişimi kontrol eder. Minimumda tutun; yalnızca ihtiyacınız olanları ekleyin. -* Çalışan bir örneği Hello World uygulamasında görün: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - -### Mantık fonksiyon yapılandırması ve giriş noktası - -Her fonksiyon dosyası, bir işleyici ve isteğe bağlı tetikleyiciler içeren bir yapılandırmayı dışa aktarmak için `defineLogicFunction()` kullanır. - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Merhaba dünya' - : 'Merhaba dünya'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Herkese açık HTTP rota tetikleyicisi '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron tetikleyicisi (CRON deseni) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Veritabanı olay tetikleyicisi - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -}); -``` - -Yaygın tetikleyici türleri: - -* **route**: Fonksiyonunuzu bir HTTP yolu ve yöntemiyle **`/s/` uç noktası altında** sunar: - -> örn. `path: '/post-card/create',` -> `/s/post-card/create` üzerinden çağırın - -* **cron**: Bir CRON ifadesi kullanarak fonksiyonunuzu bir zamanlamayla çalıştırır. -* **databaseEvent**: Çalışma alanı nesnesi yaşam döngüsü olaylarında çalışır. Olay işlemi `updated` olduğunda, dinlenecek belirli alanlar `updatedFields` dizisinde belirtilebilir. Tanımsız veya boş bırakılırsa, herhangi bir güncelleme fonksiyonu tetikler. - -> örn. `person.updated` - -Notlar: - -* `triggers` dizisi isteğe bağlıdır. Tetikleyicisi olmayan fonksiyonlar, diğer fonksiyonlar tarafından çağrılan yardımcı fonksiyonlar olarak kullanılabilir. -* Tek bir fonksiyonda birden çok tetikleyici türünü birleştirebilirsiniz. - -### Kurulum öncesi işlevler - -Kurulum öncesi işlev, uygulamanız bir çalışma alanına yüklenmeden önce otomatik olarak çalışan bir mantık işlevidir. Bu, doğrulama görevleri, önkoşul kontrolleri veya ana kurulum başlamadan önce çalışma alanı durumunun hazırlanması için yararlıdır. - -`create-twenty-app` ile yeni bir uygulama iskeleti oluşturduğunuzda, `src/logic-functions/pre-install.ts` konumunda sizin için bir kurulum öncesi işlev oluşturulur: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -Önemli noktalar: - -* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. -* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize). -* Uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. -* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `preInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur. -* Varsayılan zaman aşımı, daha uzun hazırlık görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. -* Kurulum öncesi işlevlerin tetikleyicilere ihtiyacı yoktur — kurulumdan önce platform tarafından veya `function:execute --preInstall` aracılığıyla manuel olarak çağrılırlar. - -### Kurulum sonrası işlevler - -Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Bu, varsayılan verileri tohumlama, ilk kayıtları oluşturma veya çalışma alanı ayarlarını yapılandırma gibi tek seferlik kurulum görevleri için yararlıdır. - -`create-twenty-app` ile yeni bir uygulama iskeleti oluşturduğunuzda, `src/logic-functions/post-install.ts` konumunda sizin için bir kurulum sonrası işlevi oluşturulur: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -Önemli noktalar: - -* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. -* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize). -* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. -* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `postInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur. -* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. -* Kurulum sonrası işlevlerin tetikleyicilere ihtiyacı yoktur — kurulum sırasında platform tarafından veya `function:execute --postInstall` aracılığıyla manuel olarak çağrılırlar. - -### Rota tetikleyicisi yükü - - -**Kırıcı değişiklik (v1.16, Ocak 2026):** Rota tetikleyicisi yük formatı değişti. v1.16'dan önce, sorgu parametreleri, yol parametreleri ve gövde doğrudan payload olarak gönderiliyordu. v1.16 itibarıyla, yapılandırılmış bir `RoutePayload` nesnesinin içine yerleştiriliyorlar. - -**v1.16'dan önce:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**v1.16'dan sonra:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**Mevcut fonksiyonları taşımak için:** İşleyicinizi, parametreler nesnesinden doğrudan ayırmak yerine `event.body`, `event.queryStringParameters` veya `event.pathParameters` üzerinden ayrıştıracak şekilde güncelleyin. - - -Bir rota tetikleyicisi mantık fonksiyonunuzu çağırdığında, AWS HTTP API v2 formatını izleyen bir `RoutePayload` nesnesi alır. Türü `twenty-sdk` içinden içe aktarın: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -`RoutePayload` türünün yapısı şu şekildedir: - -| Özellik | Tür | Açıklama | -| ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `headers` | `Record` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | -| `queryStringParameters` | `Record` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | -| `pathParameters` | `Record` | Rota deseninden çıkarılan yol parametreleri (örn., `/users/:id` → `{ id: '123' }`) | -| `gövde` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | -| `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | -| `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | -| `requestContext.http.path` | `string` | Ham istek yolu | - -### HTTP başlıklarını iletme - -Varsayılan olarak, güvenlik nedenleriyle gelen isteklerden HTTP başlıkları mantık fonksiyonunuza **aktarılmaz**. Belirli başlıklara erişmek için bunları açıkça `forwardedRequestHeaders` dizisinde listeleyin: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -Daha sonra işleyicinizde bu başlıklara erişebilirsiniz: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - Başlık adları küçük harfe normalize edilir. Onlara küçük harfli anahtarlarla erişin (örneğin, `event.headers['content-type']`). - - -Yeni fonksiyonları iki şekilde oluşturabilirsiniz: - -* **Şablondan**: `yarn twenty entity:add` çalıştırın ve yeni bir mantık fonksiyonu ekleme seçeneğini seçin. Bu, bir işleyici ve yapılandırma içeren bir başlangıç dosyası oluşturur. -* **Manuel**: Yeni bir `*.logic-function.ts` dosyası oluşturun ve aynı deseni izleyerek `defineLogicFunction()` kullanın. - -### Bir mantık işlevini araç olarak işaretleme - -Mantık işlevleri, yapay zeka ajanları ve iş akışları için **araçlar** olarak sunulabilir. Bir işlev bir araç olarak işaretlendiğinde, Twenty'nin yapay zeka özellikleri tarafından keşfedilebilir hâle gelir ve iş akışı otomasyonlarında bir adım olarak seçilebilir. - -Bir mantık işlevini bir araç olarak işaretlemek için `isTool: true` olarak ayarlayın ve beklenen giriş parametrelerini açıklayan bir `toolInputSchema`yı [JSON Şeması](https://json-schema.org/) kullanarak sağlayın: - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `${params.companyName} için verileri zenginleştir`, - body: `Alan adı: ${params.domain ?? 'bilinmiyor'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Bir şirket kaydını harici verilerle zenginleştir', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'Zenginleştirilecek şirketin adı', - }, - domain: { - type: 'string', - description: 'Şirket web sitesi alan adı (isteğe bağlı)', - }, - }, - required: ['companyName'], - }, -}); -``` - -Önemli noktalar: - -* **`isTool`** (`boolean`, varsayılan: `false`): `true` olarak ayarlandığında, işlev bir araç olarak kaydedilir ve AI ajanları ile iş akışı otomasyonları tarafından kullanılabilir hale gelir. -* **`toolInputSchema`** (`object`, isteğe bağlı): İşlevinizin kabul ettiği parametreleri tanımlayan bir JSON Schema nesnesi. AI ajanları, aracın hangi girdileri beklediğini anlamak ve çağrıları doğrulamak için bu şemayı kullanır. Atlanırsa, şema varsayılan olarak `{ type: 'object', properties: {} }` olur (parametre yok). -* `isTool: false` (veya ayarlanmamış) olan işlevler araç olarak **sunulmaz**. Yine de doğrudan yürütülebilir veya diğer işlevler tarafından çağrılabilirler, ancak araç keşfinde görünmezler. -* **Araç adlandırma**: Bir araç olarak sunulduğunda, işlev adı otomatik olarak `logic_function_` biçimine dönüştürülür (küçük harfe çevrilir, alfasayısal olmayan karakterler alt çizgi ile değiştirilir). Örneğin, `enrich-company` `logic_function_enrich_company` haline gelir. -* `isTool` özelliğini tetikleyicilerle birleştirebilirsiniz — bir işlev aynı anda hem bir araç (AI ajanları tarafından çağrılabilir) olabilir hem de olaylar tarafından tetiklenebilir (cron, veritabanı olayları, routes). - - -**İyi bir `description` yazın.** AI ajanları, aracı ne zaman kullanacaklarına karar vermek için işlevin `description` alanına güvenir. Aracın ne yaptığını ve ne zaman çağrılması gerektiğini açıkça belirtin. - - -### Ön uç bileşenleri - -Ön uç bileşenleri, Twenty'nin kullanıcı arayüzünde görüntülenen özel React bileşenleri oluşturmanıza olanak tanır. Yerleşik doğrulamayla bileşenleri tanımlamak için `defineFrontComponent()` kullanın: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -Önemli noktalar: - -* Ön uç bileşenleri, Twenty içinde yalıtılmış bağlamlarda görüntülenen React bileşenleridir. -* `component` alanı, React bileşeninize referans verir. -* Bileşenler, `yarn twenty dev` sırasında otomatik olarak oluşturulur ve senkronize edilir. - -Yeni ön uç bileşenlerini iki şekilde oluşturabilirsiniz: - -* **Şablondan**: `yarn twenty entity:add` çalıştırın ve yeni bir ön uç bileşeni ekleme seçeneğini seçin. -* **Manuel**: Aynı deseni izleyerek yeni bir `.tsx` dosyası oluşturun ve `defineFrontComponent()` kullanın. - -#### Ön bileşenlerin kullanılabileceği yerler - -Ön bileşenler, Twenty içinde iki konumda işlenebilir: - -* **Yan panel** — Headless olmayan ön bileşenler, sağ taraftaki yan panelde açılır. Bir ön bileşen komut menüsünden tetiklendiğinde varsayılan davranış budur. -* **Widget'lar (panolar ve kayıt sayfaları)** — Ön bileşenler, sayfa düzenlerine widget olarak gömülebilir. Bir pano veya kayıt sayfası düzeni yapılandırılırken kullanıcılar bir ön bileşen widget'ı ekleyebilir. - -#### Headless ve headless olmayan - -Ön bileşenler, `isHeadless` seçeneğiyle kontrol edilen iki işleme kipiyle gelir: - -**Headless olmayan (varsayılan)** — Bileşen görünür bir kullanıcı arayüzü (UI) oluşturur. Komut menüsünden tetiklendiğinde yan panelde açılır. `isHeadless` `false` olduğunda veya belirtilmediğinde bu varsayılan davranıştır. - -**Headless** — Bileşen arka planda görünmez şekilde bağlanır. Yan paneli açmaz. Headless bileşenler, mantığı çalıştırıp ardından kendilerini kaldıran eylemler için tasarlanmıştır — örneğin, bir async görevi çalıştırma, bir sayfaya gitme veya bir onay modalı gösterme. Aşağıda açıklanan SDK Command bileşenleriyle doğal olarak eşleşirler. - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### Komut menüsüne öğe ekleme - -Bir ön bileşenin Twenty'nin komut menüsünde bir öğe olarak görünmesi için `defineFrontComponent()` içine `command` özelliğini ekleyin. Kullanıcılar komut menüsünü (Cmd+K / Ctrl+K) açtığında, öğe görüntülenir ve tıklandığında ön bileşeni tetikler. - -`command` nesnesi aşağıdaki alanları kabul eder: - -| Alan | Tür | Açıklama | -| --------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| `universalIdentifier` | `string` (zorunlu) | Komut menüsü öğesi için benzersiz kimlik | -| `etiket` | `string` (zorunlu) | Komut menüsünde gösterilen etiket | -| `simge` | `string` (isteğe bağlı) | Simge adı (ör. `'IconSparkles'`) | -| `isPinned` | `boolean` (isteğe bağlı) | Komutun menünün en üstüne sabitlenip sabitlenmediği | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (isteğe bağlı) | `GLOBAL` komutu her yerde gösterir; `RECORD_SELECTION` ise yalnızca kayıt bağlamlarında gösterir | -| `availabilityObjectUniversalIdentifier` | `string` (isteğe bağlı) | Komutu belirli bir nesne türüyle sınırlandırın (ör. Person) | - -Person kayıtlarına özel bir komut ekleyen çağrı kaydı uygulamasından bir örnek: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -Komut senkronize edildiğinde komut menüsünde görünür. Ön bileşen headless değilse, yan panel bileşen içeride işlenmiş halde açılır. Headless ise bileşen arka planda bağlanır ve mantığını yürütür. - -#### SDK Command bileşenleri - -`twenty-sdk` paketi, headless ön bileşenler için tasarlanmış dört Command yardımcı bileşeni sağlar. Her bileşen bağlandığında bir eylem yürütür, hataları bir snackbar bildirimi göstererek ele alır ve tamamlandığında ön bileşeni otomatik olarak kaldırır. - -Bunları `twenty-sdk/command` içinden içe aktarın: - -* **`Command`** — `execute` prop'u aracılığıyla async bir geri çağrıyı çalıştırır. -* **`CommandLink`** — Bir uygulama yoluna gider. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Bir onay modalı açar. Kullanıcı onaylarsa `execute` geri çağrısını yürütür. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Belirli bir yan panel sayfasını açar. Props: `page`, `pageTitle`, `pageIcon`. - -`Command` kullanarak komut menüsünden bir eylem çalıştıran headless bir ön bileşenin tam örneği: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -Ve yürütmeden önce onay istemek için `CommandModal` kullanan bir örnek: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Yürütme bağlamı - -Her ön bileşen, nerede ve nasıl çalıştığına dair bilgi sağlayan bir yürütme bağlamı alır. Bağlam değerlerine `twenty-sdk` içindeki hook'ları kullanarak erişin: - -| Hook | Dönüş türü | Açıklama | -| ----------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | Geçerli ön bileşen örneğinin benzersiz kimliği | -| `useRecordId()` | `string \| null` | Bileşen bir kayıt bağlamında çalıştığında (ör. bir kayıt sayfası widget'ı veya bir kayda özel bir komut) geçerli kaydın kimliği. Aksi halde `null` döner. | -| `useUserId()` | `string \| null` | Geçerli kullanıcının kimliği | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -Bağlam tepkiseldir — çevredeki kayıt değişirse, hook'lar güncellenmiş değerleri otomatik olarak döndürür. - -#### Host API işlevleri - -Ön bileşenler yalıtılmış bir korumalı alanda çalışır ancak host tarafından sağlanan bir dizi işleve aracılığıyla Twenty'nin arayüzüyle etkileşime girebilir. Bunları doğrudan `twenty-sdk` içinden içe aktarın: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| Fonksiyon | İmza | Açıklama | -| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `gezin` | `(to, params?, queryParams?, options?) => Promise` | Twenty içinde tiplendirilmiş bir uygulama yoluna gidin | -| `closeSidePanel` | `() => Promise` | Yan paneli kapat | -| `enqueueSnackbar` | `(params) => Promise` | Bir snackbar bildirimi gösterin. Parametreler: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), isteğe bağlı `duration`, `detailedMessage`, `dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | Geçerli ön bileşeni kaldırın (yürütmeden sonra temizlemek için headless bileşenler tarafından kullanılır) | -| `openSidePanelPage` | `(params) => Promise` | Yan panelde bir sayfa açın. Parametreler: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Bir onay modalı gösterin ve kullanıcının yanıtını bekleyin. Parametreler: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | - -Bir eylem tamamlandıktan sonra bir snackbar göstermek ve yan paneli kapatmak için host API'sini kullanan bir örnek: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### Beceriler - -Yetenekler, yapay zekâ ajanlarının çalışma alanınızda kullanabileceği yeniden kullanılabilir yönergeleri ve kabiliyetleri tanımlar. Yerleşik doğrulamayla yetenekleri tanımlamak için `defineSkill()` kullanın: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Önemli noktalar: - -* `name`, yetenek için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). -* `label`, UI'de gösterilen, insan tarafından okunabilir addır. -* `content`, yetenek yönergelerini içerir — bu, yapay zekâ ajanının kullandığı metindir. -* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. -* `description` (isteğe bağlı), yeteneğin amacı hakkında ek bağlam sağlar. - -Yeni yetenekleri iki şekilde oluşturabilirsiniz: - -* **Şablondan**: `yarn twenty entity:add` komutunu çalıştırın ve yeni bir yetenek ekleme seçeneğini seçin. -* **Manuel**: Yeni bir dosya oluşturun ve aynı deseni izleyerek `defineSkill()` kullanın. - -### Temsilciler - -Ajanlar, çalışma alanınızda çalışabilen sistem istemlerine sahip yapay zekâ ajanlarını tanımlar. Yerleşik doğrulamayla ajanları tanımlamak için `defineAgent()` kullanın: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -Önemli noktalar: - -* `name`, ajan için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). -* `label`, UI'de gösterilen, insan tarafından okunabilir addır. -* `prompt`, sistem istemini içerir — bu, ajanın davranışını tanımlayan talimat metnidir. -* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. -* `description` (isteğe bağlı), ajanın amacı hakkında ek bağlam sağlar. - -Yeni ajanları iki şekilde oluşturabilirsiniz: - -* **Şablondan**: `yarn twenty entity:add` komutunu çalıştırın ve yeni bir ajan ekleme seçeneğini seçin. -* **Manuel**: Yeni bir dosya oluşturun ve aynı deseni izleyerek `defineAgent()` kullanın. - -### Oluşturulan tiplendirilmiş istemciler - -Çalışma alanı şemanıza göre `yarn twenty dev` tarafından iki tiplendirilmiş istemci otomatik olarak oluşturulur ve `node_modules/twenty-sdk/clients` içine kaydedilir: - -* **`CoreApiClient`** — çalışma alanı verileri için `/graphql` uç noktasını sorgular -* **`MetadataApiClient`** — çalışma alanı yapılandırması ve dosya yüklemeleri için `/metadata` uç noktasını sorgular. - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });},{ -``` - -`CoreApiClient`, nesneleriniz veya alanlarınız değiştiğinde `yarn twenty dev` tarafından otomatik olarak yeniden oluşturulur. `MetadataApiClient`, SDK ile birlikte önceden hazırlanmış olarak gelir. - -#### Mantık fonksiyonlarında çalışma zamanı kimlik bilgileri - -Fonksiyonunuz Twenty üzerinde çalıştığında, platform kodunuz yürütülmeden önce kimlik bilgilerini ortam değişkenleri olarak enjekte eder: - -* `TWENTY_API_URL`: Uygulamanızın hedeflediği Twenty API'nin temel URL’si. -* `TWENTY_API_KEY`: Uygulamanızın varsayılan fonksiyon rolü kapsamına sahip kısa ömürlü anahtar. - -Notlar: - -* Oluşturulan istemciye URL veya API anahtarı geçirmeniz gerekmez. Çalışma zamanında `TWENTY_API_URL` ve `TWENTY_API_KEY` değerlerini process.env üzerinden okur. -* API anahtarının izinleri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` aracılığıyla referans verilen role göre belirlenir. Bu, uygulamanızın mantık fonksiyonları tarafından kullanılan varsayılan roldür. -* Uygulamalar, en az ayrıcalık ilkesini izlemek için roller tanımlayabilir. Yalnızca fonksiyonlarınızın ihtiyaç duyduğu izinleri verin ve ardından `defaultRoleUniversalIdentifier` değerini o rolün evrensel tanımlayıcısına yönlendirin. - -#### Dosya yükleme - -`MetadataApiClient`, çalışma alanı nesnelerinizdeki dosya türündeki alanlara dosya eklemek için bir `uploadFile` yöntemi içerir. Standart GraphQL istemcileri çok parçalı dosya yüklemelerini yerel olarak desteklemediğinden, istemci arka planda [GraphQL çok parçalı istek belirtimi](https://github.com/jaydenseric/graphql-multipart-request-spec) uygulayan bu özel yöntemi sağlar. - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // dosya içeriği (Buffer olarak) - 'invoice.pdf', // dosya adı - 'application/pdf', // MIME türü (varsayılan: 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // alanın evrensel tanımlayıcısı -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -Yöntem imzası: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| Parametre | Tür | Açıklama | -| ---------------------------------- | -------- | ------------------------------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Dosyanın ham içeriği | -| `filename` | `string` | Dosyanın adı (depolama ve görüntüleme için kullanılır) | -| `contentType` | `string` | Dosyanın MIME türü (belirtilmezse varsayılan olarak `application/octet-stream` kullanılır) | -| `fieldMetadataUniversalIdentifier` | `string` | Nesnenizdeki dosya türü alanının `universalIdentifier` değeri | - -Önemli noktalar: - -* `uploadFile` yöntemi, yükleme mutasyonu `/metadata` uç noktası tarafından çözümlendiği için `MetadataApiClient` üzerinde mevcuttur. -* Alan için `universalIdentifier` kullanılır (çalışma alanına özgü kimliği değil), böylece yükleme kodunuz uygulamanızın yüklü olduğu herhangi bir çalışma alanında çalışır — uygulamaların başka her yerde alanlara nasıl atıfta bulunduğuyla tutarlıdır. -* Döndürülen `url`, yüklenen dosyaya erişmek için kullanabileceğiniz imzalı bir URL'dir. - -### Hello World örneği - -Nesneleri, mantık fonksiyonlarını, ön uç bileşenlerini ve birden çok tetikleyiciyi gösteren minimal, uçtan uca bir örneği [buradan](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world) inceleyin: - -## Uygulamanızı derleme - -Uygulamanızı `app:dev` ile geliştirdikten sonra, `app:build` kullanarak onu dağıtılabilir bir pakete derleyin. - -```bash filename="Terminal" -# Build the app (output goes to .twenty/output/) -yarn twenty build - -# Build and create a tarball (.tgz) for distribution -yarn twenty build --tarball -``` - -Derleme süreci: - -1. **Manifesti ayrıştırır ve doğrular** — kaynak dosyalarınızdaki tüm `defineX()` varlıklarını okur ve manifest yapısını doğrular. -2. **Mantık işlevlerini ve ön bileşenleri derler** — TypeScript kaynaklarını esbuild kullanarak ESM `.mjs` dosyalarına paketler. -3. **Sağlama toplamları üretir** — her bir oluşturulan dosya için MD5 karmalarını hesaplar ve manifestte `builtHandlerChecksum` / `builtComponentChecksum` olarak saklar. -4. **Tipli API istemcisini oluşturur** — GraphQL şemasını inceleyip tipli `CoreApiClient` ve `MetadataApiClient` istemcilerini üretir. -5. **TypeScript tip denetimi çalıştırır** — yayımlamadan önce tip hatalarını yakalamak için `tsc --noEmit` çalıştırır. -6. **Oluşturulan istemciyle yeniden derler** — oluşturulan istemci tiplerinin dahil edilmesi için ikinci bir derleme geçişi yapar. -7. **İsteğe bağlı olarak bir tarball oluşturur** — `--tarball` iletilirse, dağıtıma hazır bir `.tgz` dosyası oluşturmak için `npm pack` çalıştırır. - -`.twenty/output/` içindeki derleme çıktısı şunları içerir: - -```text -.twenty/output/ -├── manifest.json # Manifest with checksums for all built files -├── package.json # Copied from app root -├── yarn.lock # Copied from app root -├── src/ -│ ├── logic-functions/ # Compiled .mjs logic function files -│ └── front-components/ # Compiled .mjs front component files -├── public/ # Static assets (if any) -└── my-app-1.0.0.tgz # Only with --tarball flag -``` - -| Seçenek | Açıklama | -| ----------- | --------------------------------------------------------- | -| `[appPath]` | Uygulama dizininin yolu (varsayılan olarak geçerli dizin) | -| `--tarball` | Çıktıyı ayrıca bir `.tgz` tarball olarak paketler | - -## Uygulamanızı yayımlama - -Uygulamanızı dağıtmak için `app:publish` komutunu kullanın — npm kayıt defterine ya da doğrudan bir Twenty sunucusuna yayımlayın. - -### npm'ye yayımlama (varsayılan) - -```bash filename="Terminal" -# Publish to npm (requires npm login) -yarn twenty publish - -# Publish with a dist-tag (e.g. beta, next) -yarn twenty publish --tag beta -``` - -Bu, uygulamayı derler ve `.twenty/output/` dizininden `npm publish` çalıştırır. Yayımlanan paket daha sonra Twenty pazar yerinden herhangi bir çalışma alanı tarafından kurulabilir. - -### Bir Twenty sunucusuna yayımlama - -```bash filename="Terminal" -# Publish directly to a Twenty server -yarn twenty publish --server https://app.twenty.com -``` - -Bu, uygulamayı bir tarball ile derler, `uploadAppTarball` GraphQL mutasyonu aracılığıyla sunucuya yükler ve tek adımda kurulumu tetikler. Bu, özel dağıtımlar veya belirli bir sunucuya karşı test yapmak için kullanışlıdır. - -| Seçenek | Açıklama | -| ----------------- | ---------------------------------------------------------------- | -| `[appPath]` | Uygulama dizininin yolu (varsayılan olarak geçerli dizin) | -| `--server ` | npm yerine bir Twenty sunucusuna yayımlar | -| `--token ` | Hedef sunucu için kimlik doğrulama belirteci | -| `--tag ` | npm dist-tag (örn. `beta`, `next`) — yalnızca npm yayımlama için | - -## Uygulama kaydı - -Bir uygulama bir çalışma alanına kurulmadan önce kaydedilmelidir. Kayıt, uygulamanın nereden geldiğini ve nasıl kimlik doğrulanacağını açıklayan bir meta veri kaydıdır. Bu, çoğu durumda CLI tarafından otomatik olarak gerçekleştirilir. - -### Kaynak türleri - -Her kaydın, kurulum sırasında uygulamanın dosyalarının nasıl çözümleneceğini belirleyen bir kaynak türü vardır: - -| Kaynak türü | Dosyaların nasıl çözümlendiği | Tipik kullanım durumu | -| ----------- | ----------------------------------------------------------------------------------- | ------------------------------------------ | -| `LOCAL` | Dosyalar, CLI izleyici tarafından gerçek zamanlı olarak eşitlenir — kurulum atlanır | `app:dev` ile geliştirme | -| `NPM` | `sourcePackage` alanı aracılığıyla npm kayıt defterinden alınır | npm'de yayımlanan uygulamalar | -| `TARBALL` | Sunucuda depolanan, yüklenmiş bir `.tgz` dosyasından çıkarılır | `--server` ile yayımlanan özel uygulamalar | - -### Kayıt nasıl gerçekleşir - -* **`app:dev`** — bir çalışma alanına karşı geliştirme modunu ilk kez çalıştırdığınızda otomatik olarak bir `LOCAL` kaydı oluşturur. -* **`app:publish --server`** — bir tarball yükler ve bir `TARBALL` kaydı oluşturur (veya günceller), ardından uygulamayı kurar. -* **npm pazar yeri** — uygulamalar npm kayıt defterinden Twenty pazar yeri kataloğuna eşitlendiğinde `NPM` kayıtları oluşturulur. -* **GraphQL API** — `createApplicationRegistration` mutasyonu aracılığıyla programatik olarak da kayıtlar oluşturabilirsiniz. - -### Kayıt ve kurulum - -**Kayıt** ve **kurulum** ayrı kavramlardır: - -* Bir kayıt (`ApplicationRegistration`), uygulamayı tanımlayan genel bir meta veri kaydıdır: adı, kaynak türü, OAuth kimlik bilgileri ve pazar yeri listeleme durumu. Herhangi bir çalışma alanından bağımsız olarak var olur. -* Bir kurulum (`Application`), çalışma alanı başına bir örnektir. Bir kullanıcı bir uygulamayı kurduğunda, Twenty paketi kaydın kaynağından çözümler, derlenen dosyaları depolamaya yazar ve manifesti (nesneler, alanlar, mantık işlevleri vb. oluşturarak) eşitler o çalışma alanında. - -Bir kayıt birçok çalışma alanına kurulabilir. Her çalışma alanı, uygulamanın dosyalarının ve veri modelinin kendi kopyasını alır. - -### OAuth kimlik bilgileri - -Her kayıt, oluşturma sırasında üretilen OAuth kimlik bilgilerini (`oAuthClientId` ve `oAuthClientSecret`) içerir. Bunlar, kullanıcılar adına API isteklerini kimlik doğrulamak için uygulama tarafından kullanılır. İstemci gizli anahtarı oluşturma sırasında yalnızca bir kez sağlanır — onu güvenli bir şekilde saklayın. Bunu daha sonra `rotateApplicationRegistrationClientSecret` mutasyonu aracılığıyla yenileyebilirsiniz. - -## Manuel kurulum (scaffolder olmadan) - -En iyi başlangıç deneyimi için `create-twenty-app` kullanmanızı önersek de, bir projeyi manuel olarak da kurabilirsiniz. CLI'yi global olarak kurmayın. Bunun yerine `twenty-sdk`'yi yerel bir bağımlılık olarak ekleyin ve package.json içinde tek bir betik tanımlayın: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -Ardından bir `twenty` betiği ekleyin: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -Artık tüm komutları `yarn twenty ` üzerinden çalıştırabilirsiniz; örn. `yarn twenty dev`, `yarn twenty help` vb. - -## Sorun Giderme - -* Kimlik doğrulama hataları: `yarn twenty auth:login` çalıştırın ve API anahtarınızın gerekli izinlere sahip olduğundan emin olun. -* Sunucuya bağlanılamıyor: API URL’sini ve Twenty sunucusunun erişilebilir olduğunu doğrulayın. -* Türler veya istemci eksik/eski: `yarn twenty dev` komutunu yeniden çalıştırın — tiplendirilmiş istemciyi otomatik olarak oluşturur. -* Geliştirme modu eşitlenmiyor: `yarn twenty dev`'in çalıştığından ve değişikliklerin ortamınız tarafından yok sayılmadığından emin olun. - -Discord Yardım Kanalı: https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/tr/developers/extend/apps/getting-started) guide for the full CLI reference. diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx index ee9f2e0fad..ff8af6ae5a 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx @@ -4,41 +4,174 @@ description: 使用 Twenty SDK 定义对象、逻辑函数、前端组件等。 --- -应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 + Apps are currently in alpha. The feature works but is still evolving. -## 使用 SDK 资源(类型与配置) +The `twenty-sdk` package provides typed building blocks to create your app. This page covers every entity type and API client available in the SDK. -twenty-sdk 提供你在应用中使用的类型化构件和辅助函数。 以下是你最常接触的关键部分。 +## DefineEntity functions -### 辅助函数 +The SDK provides functions to define your app entities. You must use `export default defineEntity({...})` for the SDK to detect your entities. 这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。 -该 SDK 提供辅助函数用于定义你的应用实体。 如 [实体检测](/l/zh/developers/extend/apps/getting-started#entity-detection) 中所述,你必须使用 `export default define({...})` 才能让你的实体被检测到: + + **File organization is up to you.** + Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. Grouping files by type (e.g., `logic-functions/`, `roles/`) is just a convention, not a requirement. + -| 函数 | 目的 | -| -------------------------------- | ------------------------ | -| `defineApplication` | 配置应用元数据(必需,每个应用一个) | -| `defineObject` | 定义带字段的自定义对象 | -| `defineField` | 使用附加字段扩展现有对象,或定义独立的关系字段。 | -| `defineLogicFunction` | 定义带处理程序的逻辑函数 | -| `definePreInstallLogicFunction` | 定义一个安装前逻辑函数(每个应用一个) | -| `definePostInstallLogicFunction` | 定义一个安装后逻辑函数(每个应用一个) | -| `defineFrontComponent` | 为自定义 UI 定义前端组件 | -| `defineRole` | 配置角色权限和对象访问 | -| `defineView` | 为对象定义已保存的视图 | -| `defineNavigationMenuItem` | 定义侧边栏导航链接 | -| `defineSkill` | 定义 AI 智能体技能 | -| `defineAgent` | 定义 AI 智能体 | -| `definePageLayout` | 定义自定义页面布局 | + + -这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。 +Roles encapsulate permissions on your workspace's objects and actions. -### 定义对象 +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk'; + +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, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Every app must have exactly one `defineApplication` call that describes: + +* **Identity**: identifiers, display name, and description. +* **Permissions**: which role its functions and front components use. +* **(Optional) Variables**: key–value pairs exposed to your functions as environment variables. +* **(Optional) Pre-install / post-install functions**: logic functions that run before or after installation. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +备注: +* `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 (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` must reference a role defined with `defineRole()` (see above). +* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`. + +#### 应用市场元数据 + +If you plan to [publish your app](/l/zh/developers/extend/apps/publishing), these optional fields control how it appears in the marketplace: + +| 字段 | 描述 | +| ------------------ | -------------------------------------------------------------- | +| `作者` | 作者或公司名称 | +| `类别` | 用于应用市场筛选的应用类别 | +| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) | +| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) | +| `aboutDescription` | 用于“关于”选项卡的更长的 Markdown 描述。 如果省略,市场将使用该软件包在 npm 上的 `README.md`。 | +| `websiteUrl` | 你的网站链接 | +| `termsUrl` | 服务条款链接 | +| `emailSupport` | 支持电子邮件地址 | +| `issueReportUrl` | 问题跟踪器链接 | + +#### 角色和权限 + +The `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions and front components. See `defineRole` above for details. + +* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role. +* The typed client is restricted to the permissions granted to that role. +* Follow least-privilege: create a dedicated role with only the permissions your functions need. + +##### Default function role + +When you scaffold a new app, the CLI creates a default role file: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + 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: [], + permissionFlags: [], +}); +``` + +This role's `universalIdentifier` is referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** defines what the role can do. +* **application-config.ts** 指向该角色,使你的函数继承其权限。 + +备注: +* 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。 +* Replace `objectPermissions` and `fieldPermissions` with the objects and fields your functions actually need. +* `permissionFlags` 控制对平台级能力的访问。 Keep them minimal. +* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + 自定义对象同时描述工作空间中记录的架构与行为。 使用 `defineObject()` 以内置校验定义对象: -```typescript -// src/objects/postCard.object.ts +```ts postCard.object.ts import { defineObject, FieldType } from 'twenty-sdk'; enum PostCardStatus { @@ -122,12 +255,12 @@ export default defineObject({ 但不建议这样做。 -### 在现有对象上定义字段 + + 使用 `defineField()` 向你不拥有的对象添加字段——例如标准的 Twenty 对象(Person、Company 等)。 或来自其他应用的对象。 与在 `defineObject()` 中的内联字段不同,独立字段需要一个 `objectUniversalIdentifier` 来指定它们要扩展的对象: -```typescript -// src/fields/company-loyalty-tier.field.ts +```ts src/fields/company-loyalty-tier.field.ts import { defineField, FieldType } from 'twenty-sdk'; export default defineField({ @@ -146,12 +279,12 @@ export default defineField({ ``` 关键点: - * `objectUniversalIdentifier` 用于标识目标对象。 对于标准对象,请使用从 `twenty-sdk` 导出的 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`。 * 在 `defineObject()` 中以内联方式定义字段时,你不需要 `objectUniversalIdentifier`——它会从父对象继承。 * `defineField()` 是为非通过 `defineObject()` 创建的对象添加字段的唯一方式。 -### 关系 + + 关系用于将对象彼此连接。 在 Twenty 中,关系始终是双向的——你需要定义两侧,每一侧都引用另一侧。 @@ -177,8 +310,7 @@ export default defineField({ **步骤 1:在 PostCard 上定义 ONE_TO_MANY 侧**(“一”侧): -```typescript -// src/fields/post-card-recipients-on-post-card.field.ts +```ts src/fields/post-card-recipients-on-post-card.field.ts import { defineField, FieldType, RelationType } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -205,8 +337,7 @@ export default defineField({ **步骤 2:在 PostCardRecipient 上定义 MANY_TO_ONE 侧**(“多”侧——持有外键): -```typescript -// src/fields/post-card-on-post-card-recipient.field.ts +```ts src/fields/post-card-on-post-card-recipient.field.ts import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; @@ -241,8 +372,7 @@ export default defineField({ 要与内置的 Twenty 对象(Person、Company 等)建立关系,请使用 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: -```typescript -// src/fields/person-on-self-hosting-user.field.ts +```ts src/fields/person-on-self-hosting-user.field.ts import { defineField, FieldType, @@ -289,7 +419,7 @@ export default defineField({ 你也可以直接在 `defineObject()` 内定义关系字段。 在这种情况下,省略 `objectUniversalIdentifier`——它会从父对象继承: -```typescript +```ts export default defineObject({ universalIdentifier: '...', nameSingular: 'postCardRecipient', @@ -312,137 +442,15 @@ export default defineObject({ ], }); ``` - -### 应用配置(application-config.ts) - -每个应用都有一个 `application-config.ts` 文件,用于描述: - -* **应用的身份**:标识符、显示名称和描述。 -* **函数如何运行**:它们用于权限的角色。 -* **(可选)变量**:以环境变量形式提供给函数的键值对。 -* **(可选)安装前函数**:在应用安装之前运行的逻辑函数。 -* **(可选)安装后函数**:在应用安装后运行的逻辑函数。 - -使用 `defineApplication()` 定义你的应用配置: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -备注: - -* `universalIdentifier` 字段是你拥有的确定性 ID;生成一次并在多次同步中保持稳定。 -* `applicationVariables` 会变成函数可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。 -* `defaultRoleUniversalIdentifier` 必须与角色文件一致(见下文)。 -* 清单构建期间会自动检测安装前和安装后函数。 参见 [安装前函数](#pre-install-functions) 和 [安装后函数](#post-install-functions)。 - -#### 应用市场元数据 - -如果你计划[发布你的应用](/l/zh/developers/extend/apps/publishing),这些可选字段将控制你的应用在应用市场中的展示: - -| 字段 | 描述 | -| ------------------ | -------------------------------------------------------------- | -| `作者` | 作者或公司名称 | -| `类别` | 用于应用市场筛选的应用类别 | -| `logoUrl` | 你的应用徽标的路径(相对于 `./assets/`) | -| `screenshots` | 屏幕截图路径数组(相对于 `./assets/`) | -| `aboutDescription` | 用于“关于”选项卡的更长的 Markdown 描述。 如果省略,市场将使用该软件包在 npm 上的 `README.md`。 | -| `websiteUrl` | 你的网站链接 | -| `termsUrl` | 服务条款链接 | -| `emailSupport` | 支持电子邮件地址 | -| `issueReportUrl` | 问题跟踪器链接 | - -#### 角色和权限 - -应用可以定义角色,以封装对工作空间对象与操作的权限。 `application-config.ts` 中的 `defaultRoleUniversalIdentifier` 字段指定你的应用逻辑函数所使用的默认角色。 - -* 作为 `TWENTY_API_KEY` 注入的运行时 API 密钥源自该默认函数角色。 -* 类型化客户端将受限于该角色授予的权限。 -* 遵循最小权限原则:创建一个仅包含你的函数所需权限的专用角色,然后引用其通用标识符。 - -##### 默认函数角色(*.role.ts) - -当你脚手架生成新应用时,CLI 也会创建一个默认角色文件。 使用 `defineRole()` 定义带内置校验的角色: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -随后,该角色的 `universalIdentifier` 会在 `application-config.ts` 中被引用为 `defaultRoleUniversalIdentifier`。 换句话说: - -* **\*.role.ts** 定义默认函数角色可以执行的操作。 -* **application-config.ts** 指向该角色,使你的函数继承其权限。 - -备注: - -* 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。 -* 将 `objectPermissions` 和 `fieldPermissions` 替换为你的函数所需的对象/字段。 -* `permissionFlags` 控制对平台级能力的访问。 尽量保持最小化;仅添加所需项。 -* 在 Hello World 应用中查看可运行示例:[`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts)。 - -### 逻辑函数的配置与入口点 + + 每个函数文件都使用 `defineLogicFunction()` 导出包含处理程序和可选触发器的配置。 -```typescript -// src/logic-functions/createPostCard.logic-function.ts +```ts src/logic-functions/createPostCard.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-sdk/generated'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; const handler = async (params: RoutePayload) => { const client = new CoreApiClient(); @@ -465,158 +473,56 @@ export default defineLogicFunction({ name: 'create-new-post-card', timeoutSeconds: 2, handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ }); ``` -常见触发器类型: - -* **route**:在\*\*`/s/` 端点\*\*下通过 HTTP 路径与方法公开你的函数: - -> 例如 `path: '/post-card/create',` -> 调用 `/s/post-card/create` - +Available trigger types: +* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: +> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` * **cron**:使用 CRON 表达式按计划运行你的函数。 * **databaseEvent**:在工作空间对象生命周期事件上运行。 当事件操作为 `updated` 时,可以在 `updatedFields` 数组中指定要监听的特定字段。 如果未定义或为空,任何更新都会触发该函数。 +> e.g. `person.updated`, `*.created`, `company.*` -> 例如 `person.updated` - -备注: - -* `triggers` 数组是可选的。 没有触发器的函数可作为实用函数,被其他函数调用。 -* 你可以在单个函数中混用多种触发器类型。 - -### 安装前函数 - -安装前函数是在你的应用安装到工作区之前自动运行的逻辑函数。 这对于执行验证任务、先决条件检查,或在主安装开始前准备工作区状态很有用。 - -当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/pre-install.ts` 为你生成一个安装前函数: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -你也可以随时使用 CLI 手动执行安装前函数: + +You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' ``` -关键点: - -* 安装前函数使用 `definePreInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 -* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 -* 每个应用仅允许一个安装前函数。 如果检测到多个,清单构建将报错。 -* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `preInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 -* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的准备任务。 -* 安装前函数不需要触发器——它们会在安装前由平台调用,或通过 `exec --preInstall` 手动调用。 - -### 安装后函数 - -安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。 - -当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/post-install.ts` 为你生成一个安装后函数: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -你也可以随时使用 CLI 手动执行安装后函数: - ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -关键点: +You can watch logs with: -* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 -* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 -* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。 -* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `postInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 -* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的设置任务,如数据填充。 -* 安装后函数不需要触发器——它们会在安装过程中由平台调用,或通过 `exec --postInstall` 手动调用。 - -### 路由触发器负载 - - -**破坏性变更(v1.16,2026 年 1 月):** 路由触发器的负载格式已更改。 在 v1.16 之前,查询参数、路径参数和请求体会直接作为负载发送。 从 v1.16 开始,它们被嵌套在结构化的 `RoutePayload` 对象中。 - -**v1.16 之前:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; +```bash filename="Terminal" +yarn twenty logs ``` + -**v1.16 之后:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` +#### 路由触发器负载 -**迁移现有函数:** 将处理程序更新为从 `event.body`、`event.queryStringParameters` 或 `event.pathParameters` 解构,而不是直接从参数对象解构。 - +When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the +[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Import the `RoutePayload` type from `twenty-sdk`: -当路由触发器调用你的逻辑函数时,它会接收一个遵循 AWS HTTP API v2 格式的 `RoutePayload` 对象。 从 `twenty-sdk` 导入该类型: - -```typescript +```ts import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; const handler = async (event: RoutePayload) => { - // Access request data const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext const { method, path } = event.requestContext.http; return { message: 'Success' }; @@ -625,41 +531,39 @@ const handler = async (event: RoutePayload) => { `RoutePayload` 类型具有以下结构: -| 属性 | 类型 | 描述 | -| ---------------------------- | ------------------------------------- | ------------------------------------------------- | -| `headers` | `Record` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | -| `queryStringParameters` | `Record` | 查询字符串参数(多个值以逗号连接) | -| `pathParameters` | `Record` | 从路由模式中提取的路径参数(例如,`/users/:id` -> `{ id: '123' }`) | -| `body` | `object \| null` | 已解析的请求体(JSON) | -| `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | -| `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | -| `requestContext.http.path` | `string` | 原始请求路径 | + | 属性 | 类型 | 描述 | 示例 | + | ---------------------------- | ------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------- | + | `headers` | `Record` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | see section below | + | `queryStringParameters` | `Record` | 查询字符串参数(多个值以逗号连接) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | 已解析的请求体(JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | | + | `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | | + | `requestContext.http.path` | `string` | 原始请求路径 | | -### 转发 HTTP 请求头 -出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。 如需访问特定请求头,请在 `forwardedRequestHeaders` 数组中显式列出: +#### forwardedRequestHeaders -```typescript +出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。 +To access specific headers, list them in the `forwardedRequestHeaders` array: + +```ts export default defineLogicFunction({ universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', name: 'webhook-handler', handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, }); ``` -随后你可以在处理程序中访问这些请求头: +In your handler, access the forwarded headers like this: -```typescript +```ts const handler = async (event: RoutePayload) => { const signature = event.headers['x-webhook-signature']; const contentType = event.headers['content-type']; @@ -670,22 +574,16 @@ const handler = async (event: RoutePayload) => { ``` - 请求头名称会被规范化为小写。 请使用小写键访问它们(例如,`event.headers['content-type']`)。 +请求头名称会被规范化为小写。 Access them using lowercase keys (e.g., `event.headers['content-type']`). -你可以通过两种方式创建新函数: +#### Exposing a function as a tool -* **脚手架生成**:运行 `yarn twenty add` 并选择添加新逻辑函数的选项。 这将生成一个包含处理程序和配置的入门文件。 -* **手动**:创建一个新的 `*.logic-function.ts` 文件,并使用 `defineLogicFunction()`,遵循相同的模式。 +逻辑函数可以作为供 AI 智能体和工作流使用的**工具**对外提供。 When marked as a tool, a function becomes discoverable by Twenty's AI features and can be used in workflow automations. -### 将逻辑函数标记为工具 +To mark a logic function as a tool, set `isTool: true`: -逻辑函数可以作为供 AI 智能体和工作流使用的**工具**对外提供。 当函数被标记为工具时,Twenty 的 AI 功能即可发现它,并可在工作流自动化中将其选作一个步骤。 - -要将逻辑函数标记为工具,请设置 `isTool: true`,并提供 `toolInputSchema`,使用 [JSON Schema](https://json-schema.org/) 描述预期的输入参数: - -```typescript -// src/logic-functions/enrich-company.logic-function.ts +```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -714,6 +612,17 @@ export default defineLogicFunction({ timeoutSeconds: 10, handler, isTool: true, +}); +``` + +关键点: + +* You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events at the same time. +* **`toolInputSchema`** (optional): A JSON Schema object describing the parameters your function accepts. The schema is computed automatically from source code static analysis, but you can set it explicitly: + +```ts +export default defineLogicFunction({ + ..., toolInputSchema: { type: 'object', properties: { @@ -731,60 +640,364 @@ export default defineLogicFunction({ }); ``` -关键点: - -* **`isTool`** (`boolean`, 默认: `false`): 当设置为 `true` 时,该函数会被注册为工具,并可供 AI 代理和工作流自动化使用。 -* **`toolInputSchema`** (`object`, 可选): 描述函数可接受参数的 JSON Schema 对象。 AI 代理使用此架构来理解该工具期望的输入并验证调用。 如果省略,架构将默认为 `{ type: 'object', properties: {} }`(无参数)。 -* 设置为 `isTool: false`(或未设置)的函数**不会**被暴露为工具。 它们仍可直接执行或被其他函数调用,但不会出现在工具发现中。 -* **工具命名**: 当作为工具对外暴露时,函数名会被自动规范化为 `logic_function_`(转换为小写,非字母数字字符替换为下划线)。 例如,`enrich-company` 将变为 `logic_function_enrich_company`。 -* 你可以将 `isTool` 与触发器结合使用——一个函数既可以作为工具(由 AI 代理调用),也可以同时由事件(cron、数据库事件、路由)触发。 - **写一个好的 `description`。** AI 代理会依赖该函数的 `description` 字段来决定何时使用该工具。 明确说明该工具的作用以及应在何时调用。 -### 前端组件 + + -前端组件使你可以构建在 Twenty 的 UI 中渲染的自定义 React 组件。 使用 `defineFrontComponent()` 以内置校验定义组件: +安装前函数是在你的应用安装到工作区之前自动运行的逻辑函数。 这对于执行验证任务、先决条件检查,或在主安装开始前准备工作区状态很有用。 -```typescript -// src/front-components/my-widget.tsx +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +你也可以随时使用 CLI 手动执行安装前函数: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +关键点: +* 安装前函数使用 `definePreInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 +* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 +* 每个应用仅允许一个安装前函数。 如果检测到多个,清单构建将报错。 +* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `preInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 +* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的准备任务。 + + + + +安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。 + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +你也可以随时使用 CLI 手动执行安装后函数: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +关键点: +* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 +* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 +* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。 +* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `postInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 +* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的设置任务,如数据填充。 + + + + +Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe. + +#### Basic example + +The quickest way to see a front component in action is to register it as a **command**. Adding a `command` field with `isPinned: true` makes it appear as a quick-action button in the top-right corner of the page — no page layout needed: + +```tsx src/front-components/hello-world.tsx import { defineFrontComponent } from 'twenty-sdk'; -const MyWidget = () => { +const HelloWorld = () => { return (
-

My Custom Widget

-

This is a custom front component for Twenty.

+

Hello from my app!

+

This component renders inside Twenty.

); }; export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, }); ``` -关键点: +After syncing with `yarn twenty dev`, the quick action appears in the top-right corner of the page: -* 前端组件是在 Twenty 中的隔离上下文中渲染的 React 组件。 -* `component` 字段引用你的 React 组件。 -* 组件会在 `yarn twenty dev` 期间自动构建并同步。 +
+ Quick action button in the top-right corner +
-你可以通过两种方式创建新的前端组件: +Click it to render the component inline. -* **脚手架生成**:运行 `yarn twenty add` 并选择添加新前端组件的选项。 -* **手动**:创建一个新的 `.tsx` 文件,并使用 `defineFrontComponent()`,遵循相同的模式。 +{/* TODO: add screenshot of the rendered front component */} -### 技能 +#### Configuration fields + +| 字段 | 必填 | 描述 | +| --------------------- | -- | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | 是 | Stable unique ID for this component | +| `component` | 是 | A React component function | +| `name` | 否 | Display name | +| `描述` | 否 | Description of what the component does | +| `isHeadless` | 否 | Set to `true` if the component has no visible UI (see below) | +| `命令` | 否 | Register the component as a command (see [command options](#command-options) below) | + +#### Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. + +#### Headless components (`isHeadless: true`) + +Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +#### Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { + defineFrontComponent, + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| 钩子 | Returns | 描述 | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | 因情况而异 | Access the full execution context with a selector function | + +#### Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| 函数 | 描述 | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | 关闭侧边栏 | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +#### Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| 字段 | 必填 | 描述 | +| --------------------------------------- | -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | 是 | Stable unique ID for the command | +| `标签` | 是 | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | 否 | Shorter label displayed on the pinned quick-action button | +| `图标` | 否 | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | 否 | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | 否 | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | 否 | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | 否 | A boolean expression to dynamically control whether the command is visible (see below) | + +#### Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { + defineFrontComponent, + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| 变量 | 类型 | 描述 | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `数字` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `对象` | Permissions for the current object type | +| `targetObjectReadPermissions` | `对象` | Read permissions for the target object | +| `targetObjectWritePermissions` | `对象` | Write permissions for the target object | +| `featureFlags` | `对象` | Active feature flags | +| `objectMetadataItem` | `对象` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | 描述 | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +#### Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. + +#### 样式 + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` + +
+ + 技能定义了可复用的指令和能力,AI 智能体可在你的工作区中使用。 使用 `defineSkill()` 定义带内置校验的技能: -```typescript -// src/skills/example-skill.ts +```ts src/skills/example-skill.ts import { defineSkill } from 'twenty-sdk'; export default defineSkill({ @@ -802,32 +1015,333 @@ export default defineSkill({ ``` 关键点: - * `name` 是该技能的唯一标识字符串(推荐使用 kebab-case)。 * `label` 是在 UI 中显示的人类可读名称。 * `content` 包含技能指令——这是 AI 智能体使用的文本。 * `icon`(可选)设置在 UI 中显示的图标。 * `description`(可选)提供有关技能用途的更多上下文。 -你可以通过两种方式创建新技能: + + -* **脚手架生成**:运行 `yarn twenty add` 并选择添加新技能的选项。 -* **手动**:创建一个新文件,并使用 `defineSkill()`,遵循相同的模式。 +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: -### 类型化 API 客户端(`twenty-client-sdk`) +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk'; -`twenty-client-sdk` 包提供了两个类型化的 GraphQL 客户端,供你的逻辑函数和前端组件与 Twenty API 交互: +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +关键点: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon`(可选)设置在 UI 中显示的图标。 +* `modelId` (optional) overrides the default AI model used by the agent. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +关键点: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +关键点: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +关键点: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + +
+ +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; + +const handler = async (): Promise => { + 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, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. + +You can also pass the entity type directly to skip the first prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Available entity types + +| 实体类型 | 命令 | Generated file | +| -------------------- | ------------------------------------ | ------------------------------------- | +| 对象 | `yarn twenty add object` | `src/objects/.ts` | +| 字段 | `yarn twenty add field` | `src/fields/.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | +| 角色 | `yarn twenty add role` | `src/roles/.ts` | +| 技能 | `yarn twenty add skill` | `src/skills/.ts` | +| 代理 | `yarn twenty add agent` | `src/agents/.ts` | +| 视图 | `yarn twenty add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | + +### What the scaffolder generates + +Each entity type has its own template. For example, `yarn twenty add object` asks for: + +1. **Name (singular)** — e.g., `invoice` +2. **Name (plural)** — e.g., `invoices` +3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) +4. **Label (plural)** — auto-populated (e.g., `Invoices`) +5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. + +Other entity types have simpler prompts — most only ask for a name. + +The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. + +### Custom output path + +Use the `--path` flag to place the generated file in a custom location: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` + +## Typed API clients (twenty-client-sdk) + +The `twenty-client-sdk` package provides two typed GraphQL clients for interacting with the Twenty API from your logic functions and front components. | 客户端 | 导入 | 端点 | 是否生成? | | ------------------- | ---------------------------- | ------------------------ | --------- | | `CoreApiClient` | `twenty-client-sdk/core` | `/graphql`——工作区数据(记录、对象) | 是,在开发/构建时 | | `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata`——工作区配置、文件上传 | 否,已预构建提供 | -#### CoreApiClient + + -`CoreApiClient` 是用于查询和变更工作区数据的主要客户端。 它会在执行 `yarn twenty dev` 或 `yarn twenty build` 时根据你的工作区架构生成,因此能完全类型化以匹配你的对象和字段。 +`CoreApiClient` 是用于查询和变更工作区数据的主要客户端。 It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. -```typescript +```ts import { CoreApiClient } from 'twenty-client-sdk/core'; const client = new CoreApiClient(); @@ -839,7 +1353,10 @@ const { companies } = await client.query({ node: { id: true, name: true, - domainName: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, }, }, }, @@ -862,14 +1379,14 @@ const { createCompany } = await client.mutation({ 该客户端使用选择集语法:传入 `true` 以包含某字段,使用 `__args` 传递参数,并通过嵌套对象表示关系。 你将基于工作区架构获得完整的自动补全和类型检查。 -**CoreApiClient 在开发/构建时生成。** 如果在未先运行 `yarn twenty dev` 或 `yarn twenty build` 的情况下尝试使用它,将会抛出错误。 生成过程是自动完成的——CLI 会自省你的工作区 GraphQL 架构,使用 `@genql/cli` 生成类型化客户端,将生成的源码写入 `node_modules/twenty-client-sdk/dist/core/generated/`,并替换 `node_modules/twenty-client-sdk/dist/core.mjs` 和 `node_modules/twenty-client-sdk/dist/core.cjs` 中的存根。 +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### 使用 CoreSchema 进行类型标注 -`CoreSchema` 提供与工作区对象相匹配的 TypeScript 类型,可用于为组件状态或函数参数进行类型标注: +`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: -```typescript +```ts import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; import { useState } from 'react'; @@ -888,44 +1405,41 @@ const result = await client.query({ setCompany(result.company); ``` -#### MetadataApiClient + + -`MetadataApiClient` 随 SDK 一并提供,已预构建(无需生成)。 它会查询 `/metadata` 端点以获取工作区配置、应用以及文件上传: +`MetadataApiClient` 随 SDK 一并提供,已预构建(无需生成)。 It queries the `/metadata` endpoint for workspace configuration, applications, and file uploads. -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; const metadataClient = new MetadataApiClient(); -// Query workspace info -const { currentWorkspace } = await metadataClient.query({ - currentWorkspace: { id: true, displayName: true }, -}); - -// List installed applications -const { findManyApplications } = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - version: true, +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, }, }); ``` -#### 运行时凭据 - -当你的代码在 Twenty 上运行(逻辑函数或前端组件)时,平台会以环境变量的形式注入凭据: - -* `TWENTY_API_URL`——Twenty API 的基础 URL -* `TWENTY_API_KEY`——作用域限定于你的应用默认函数角色的短期密钥 - -你无需将这些值传递给客户端——它们会自动从 `process.env` 读取。 API 密钥的权限由你的 `application-config.ts` 中 `defaultRoleUniversalIdentifier` 引用的角色决定。 - #### 上传文件 -`MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到文件类型字段。 它实现了[GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec): +`MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields: -```typescript +```ts import { MetadataApiClient } from 'twenty-client-sdk/metadata'; import * as fs from 'fs'; @@ -944,18 +1458,258 @@ console.log(uploadedFile); // { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } ``` -| 参数 | 类型 | 描述 | -| ---------------------------------- | -------- | ------------------------------------------------ | -| `fileBuffer` | `Buffer` | 原始文件内容 | -| `filename` | `string` | 文件名称(用于存储和显示) | -| `contentType` | `string` | 文件的 MIME 类型(如果省略,默认为 `application/octet-stream`) | -| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` | +| 参数 | 类型 | 描述 | +| ---------------------------------- | -------- | ------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | 原始文件内容 | +| `filename` | `string` | 文件名称(用于存储和显示) | +| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | +| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` | 关键点: - * 使用字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可在安装了你的应用的任何工作区中运行。 * 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。 -### Hello World 示例 + + -在[此处](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world)查看一个最小的端到端示例,展示对象、逻辑函数、前端组件和多种触发器。 + + 当你的代码在 Twenty 上运行(逻辑函数或前端组件)时,平台会以环境变量的形式注入凭据: + + * `TWENTY_API_URL`——Twenty API 的基础 URL + * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role + + 你无需将这些值传递给客户端——它们会自动从 `process.env` 读取。 API 密钥的权限由你的 `application-config.ts` 中 `defaultRoleUniversalIdentifier` 引用的角色决定。 + + +## Testing your app + +The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. + +### 设置 + +The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Create a `vitest.config.ts` at the root of your app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Create a setup file that verifies the server is reachable before tests run: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmatic SDK APIs + +The `twenty-sdk/cli` subpath exports functions you can call directly from test code: + +| 函数 | 描述 | +| -------------- | ------------------------------------------- | +| `appBuild` | Build the app and optionally pack a tarball | +| `appDeploy` | Upload a tarball to the server | +| `appInstall` | Install the app on the active workspace | +| `appUninstall` | Uninstall the app from the active workspace | + +Each function returns a result object with `success: boolean` and either `data` or `error`. + +### Writing an integration test + +Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Running tests + +Make sure your local Twenty server is running, then: + +```bash filename="Terminal" +yarn test +``` + +Or in watch mode during development: + +```bash filename="Terminal" +yarn test:watch +``` + +### Type checking + +You can also run type checking on your app without running tests: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +This runs `tsc --noEmit` and reports any type errors. + +## CLI 参考 + +Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. + +### Executing functions (`yarn twenty exec`) + +Run a logic function manually without triggering it via HTTP, cron, or database event: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute pre-install or post-install functions +yarn twenty exec --preInstall +yarn twenty exec --postInstall +``` + +### Viewing function logs (`yarn twenty logs`) + +Stream execution logs for your app's logic functions: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. + + +### Uninstalling an app (`yarn twenty uninstall`) + +Remove your app from the active workspace: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx index 2bf470bd04..305677329f 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx @@ -4,73 +4,142 @@ description: 几分钟内创建你的第一个 Twenty 应用。 --- -应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 +Apps are currently in alpha. The feature works but is still evolving. 应用可通过自定义对象、字段、逻辑函数、AI 技能和 UI 组件来扩展 Twenty——全部以代码进行管理。 -**你可以构建的内容:** - -* 自定义对象、字段、视图和导航项,以塑造你的数据模型 -* 由 HTTP 路由、cron 调度或数据库事件触发的逻辑函数 -* 在 Twenty 的 UI 中直接渲染的前端组件 -* 用于扩展 Twenty 的 AI 代理的技能 -* 将同一个应用部署到多个工作空间 - ## 先决条件 -* Node.js 24+ -* Yarn 4 -* Docker (或正在运行的本地 Twenty 实例) +Before you begin, make sure the following is installed on your machine: -## 开始使用 +* **Node.js 24+** — [Download here](https://nodejs.org/) +* **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable` +* **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running. -使用官方脚手架创建一个新应用,然后进行身份验证并开始开发: +## Step 1: Scaffold your app + +Open a terminal and run: ```bash filename="Terminal" -# Scaffold a new app (includes all examples by default) npx create-twenty-app@latest my-twenty-app ``` -> 使用 `--minimal` 选项生成最简安装脚手架 +You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults. -从这里您可以: +This creates a new folder called `my-twenty-app` with everything you need. + + +The scaffolder supports these flags: + +* `--minimal` — scaffold only the essential files, no examples (default) +* `--exhaustive` — scaffold all example entities +* `--name ` — set the app name (skips the prompt) +* `--display-name ` — set the display name (skips the prompt) +* `--description ` — set the description (skips the prompt) +* `--skip-local-instance` — skip the local server setup prompt + + +## Step 2: Set up a local Twenty instance + +The scaffolder will ask: + +> **Would you like to set up a local Twenty instance?** + +* **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue. +* **Type `no`** — Choose this if you already have a Twenty server running locally. + +
+ Should start local instance? +
+ +## Step 3: Sign in to your workspace + +Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account: + +* **Email:** `tim@apple.dev` +* **Password:** `tim@apple.dev` + +
+ Twenty login screen +
+ +## Step 4: Authorize the app + +After you sign in, you will see an authorization screen. This lets your app interact with your workspace. + +Click **Authorize** to continue. + +
+ Twenty CLI authorization screen +
+ +Once authorized, your terminal will confirm that everything is set up. + +
+ App scaffolded successfully +
+ +## Step 5: Start developing + +Go into your new app folder and start the development server: ```bash filename="Terminal" -# Add a new entity to your application (guided) -yarn twenty add - -# Watch your application's function logs -yarn twenty function:logs - -# Execute a function by name -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# Execute the pre-install function -yarn twenty function:execute --preInstall - -# Execute the post-install function -yarn twenty function:execute --postInstall - -# Uninstall the application from the current workspace -yarn twenty uninstall - -# Display commands' help -yarn twenty help +cd my-twenty-app +yarn twenty dev ``` -另请参阅:[create-twenty-app](https://www.npmjs.com/package/create-twenty-app) 和 [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) 的 CLI 参考页面。 +This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal. -## 项目结构(脚手架生成) +For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag: -当你运行 `npx create-twenty-app@latest my-twenty-app` 时,脚手架将: +```bash filename="Terminal" +yarn twenty dev --verbose +``` -* 将一个最小的基础应用复制到 `my-twenty-app/` 中 -* 添加本地 `twenty-sdk` 依赖和 Yarn 4 配置 -* 创建与 `twenty` CLI 关联的配置文件和脚本 -* 生成核心文件(应用配置、默认函数角色、安装前/安装后函数),并基于脚手架模式生成示例文件 + +Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/l/zh/developers/extend/apps/publishing) for details. + -使用默认 `--exhaustive` 模式新搭建的应用如下所示: +
+ Dev mode terminal output +
+ +## Step 6: See your app in Twenty + +Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**: + +
+ Your Apps list showing My twenty app +
+ +Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server. + +
+ Application registration details +
+ +Click **View installed app** to see the installed app. The **About** tab shows the current version and management options: + +
+ Installed app — About tab +
+ +Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents: + +
+ Installed app — Content tab +
+ +You are all set! Edit any file in `src/` and the changes will be picked up automatically. + +Head over to [Building Apps](/l/zh/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more. + +--- + +## Project structure + +The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type): ```text filename="my-twenty-app/" my-twenty-app/ @@ -83,124 +152,238 @@ my-twenty-app/ install-state.gz .oxlintrc.json tsconfig.json + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration + LLMS.md README.md - public/ # Public assets folder (images, fonts, etc.) + .github/ + └── workflows/ + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required - main application configuration + ├── application-config.ts # Required — main application configuration + ├── __tests__/ + │ ├── setup-test.ts # Test setup (server health check, config) + │ └── app-install.integration-test.ts # Example integration test ├── roles/ - │ └── default-role.ts # Default role for logic functions + │ └── default-role.ts # Default role for logic functions ├── objects/ - │ └── example-object.ts # Example custom object definition + │ └── example-object.ts # Example custom object definition ├── fields/ - │ └── example-field.ts # Example standalone field definition + │ └── example-field.ts # Example standalone field definition ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── pre-install.ts # Pre-install logic function - │ └── post-install.ts # Post-install logic function + │ ├── hello-world.ts # Example logic function + │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient + │ ├── pre-install.ts # Runs before installation + │ └── post-install.ts # Runs after installation ├── front-components/ - │ └── hello-world.tsx # Example front component + │ └── hello-world.tsx # Example front component + ├── page-layouts/ + │ └── example-record-page-layout.ts # Example page layout with front component ├── views/ - │ └── example-view.ts # Example saved view definition + │ └── example-view.ts # Example saved view definition ├── navigation-menu-items/ │ └── example-navigation-menu-item.ts # Example sidebar navigation link - └── skills/ - └── example-skill.ts # Example AI agent skill definition + ├── skills/ + │ └── example-skill.ts # Example AI agent skill definition + └── agents/ + └── example-agent.ts # Example AI agent definition ``` -使用 `--minimal` 时,只会创建核心文件(`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`)。 +By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above. -总体来说: +### Key files -* **package.json**:声明应用名称、版本、引擎(Node 24+、Yarn 4),并添加 `twenty-sdk` 以及一个 `twenty` 脚本,该脚本会委托给本地的 `twenty` CLI。 运行 `yarn twenty help` 以列出所有可用命令。 -* **.gitignore**:忽略常见产物,如 `node_modules`、`.yarn`、`.twenty/`、`dist/`、`build/`、覆盖率文件夹、日志文件以及 `.env*` 文件。 -* **yarn.lock**、**.yarnrc.yml**、**.yarn/**:锁定并配置项目使用的 Yarn 4 工具链。 -* **.nvmrc**:固定项目期望的 Node.js 版本。 -* **.oxlintrc.json** 和 **tsconfig.json**:为应用的 TypeScript 源码提供 Lint 与 TypeScript 配置。 -* **README.md**:应用根目录中的简短 README,包含基本说明。 -* **public/**: 一个用于存储公共资源(图像、字体、静态文件)的文件夹,这些资源将随你的应用程序一起提供。 放置在此处的文件会在同步期间上传,并可在运行时访问。 -* **src/**:你以代码形式定义应用的主要位置 +| File / Folder | 目的 | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. | +| `src/application-config.ts` | **Required.** The main configuration file for your app. | +| `src/roles/` | Defines roles that control what your logic functions can access. | +| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. | +| `src/front-components/` | React components that render inside Twenty's UI. | +| `src/objects/` | Custom object definitions to extend your data model. | +| `src/fields/` | Custom fields added to existing objects. | +| `src/views/` | Saved view configurations. | +| `src/navigation-menu-items/` | Custom links in the sidebar navigation. | +| `src/skills/` | 用于扩展 Twenty 的 AI 代理的技能. | +| `src/agents/` | AI agents with custom prompts. | +| `src/page-layouts/` | Custom page layouts for record views. | +| `src/__tests__/` | Integration tests (setup + example test). | +| `public/` | Static assets (images, fonts) served with your app. | -### 实体检测 +## Managing remotes -该 SDK 通过在你的 TypeScript 文件中解析 **`export default define({...})`** 调用来检测实体。 每种实体类型都有一个从 `twenty-sdk` 导出的对应辅助函数: - -| 辅助函数 | 实体类型 | -| -------------------------------- | ---------------- | -| `defineObject` | 自定义对象定义 | -| `defineLogicFunction` | 逻辑函数定义 | -| `definePreInstallLogicFunction` | 安装前逻辑函数(在安装之前运行) | -| `definePostInstallLogicFunction` | 安装后逻辑函数(在安装之后运行) | -| `defineFrontComponent` | 前端组件定义 | -| `defineRole` | 角色定义 | -| `defineField` | 现有对象的字段扩展 | -| `defineView` | 已保存的视图定义 | -| `defineNavigationMenuItem` | 导航菜单项定义 | -| `defineSkill` | AI 代理技能定义 | - - -**文件命名是灵活的。** 实体检测基于 AST — SDK 会扫描你的源文件以查找 `export default define({...})` 模式。 你可以按照自己的喜好组织文件和文件夹。 按实体类型分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。 - - -已检测实体的示例: - -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; - -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -后续命令将添加更多文件和文件夹: - -* `yarn twenty dev` 会自动生成类型化的 `CoreApiClient`(通过 `/graphql` 获取工作区数据),并写入 `node_modules/twenty-client-sdk/`。 `MetadataApiClient`(通过 `/metadata` 处理工作区配置和文件上传)为预构建版本,可立即使用。 分别从 `twenty-client-sdk/core` 和 `twenty-client-sdk/metadata` 导入它们。 -* `yarn twenty add` 会在 `src/` 下为你的自定义对象、函数、前端组件、角色、技能等添加实体定义文件。 - -## 身份验证 - -首次运行 `yarn twenty auth:login` 时,你将被提示输入: - -* API URL(默认为 http://localhost:3000 或你当前的工作空间配置) -* API 密钥 - -你的凭据按用户存储在 `~/.twenty/config.json` 中。 你可以维护多个配置文件并在它们之间切换。 - -### 管理工作空间 +A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time. ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local -# List all configured workspaces -yarn twenty auth:list +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote -# Switch the default workspace (interactive) -yarn twenty auth:switch +# List all configured remotes +yarn twenty remote list -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +# Switch the active remote +yarn twenty remote switch ``` -使用 `yarn twenty auth:switch` 切换工作空间后,后续所有命令将默认使用该工作空间。 你仍可通过 `--workspace ` 临时覆盖。 +Your credentials are stored in `~/.twenty/config.json`. + +## Local development server (`yarn twenty server`) + +The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually. + +### 启动服务器 + +```bash filename="Terminal" +yarn twenty server start +``` + +This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning. + +Two Docker volumes are created to persist data between restarts: + +* `twenty-app-dev-data` — PostgreSQL database +* `twenty-app-dev-storage` — file storage + +If port 2020 is already in use, you can start on a different port: + +```bash filename="Terminal" +yarn twenty server start --port 3030 +``` + +The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly. + +Once started, the server is automatically registered as the `local` remote in your CLI config. + +### Checking server status + +```bash filename="Terminal" +yarn twenty server status +``` + +Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`). + +### Viewing server logs + +```bash filename="Terminal" +yarn twenty server logs +``` + +Streams the container logs. Use `--lines` to control how many recent lines to show: + +```bash filename="Terminal" +yarn twenty server logs --lines 100 +``` + +### Stopping the server + +```bash filename="Terminal" +yarn twenty server stop +``` + +Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off. + +### Resetting the server + +```bash filename="Terminal" +yarn twenty server reset +``` + +Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance. + + + The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started. + + +### Command reference + +| 命令 | 描述 | +| -------------------------------------- | ---------------------------------------------- | +| `yarn twenty server start` | Start the local server (pulls image if needed) | +| `yarn twenty server start --port 3030` | Start on a custom port | +| `yarn twenty server stop` | Stop the server (preserves data) | +| `yarn twenty server status` | Show server status, URL, and credentials | +| `yarn twenty server logs` | Stream server logs | +| `yarn twenty server logs --lines 100` | Show the last 100 log lines | +| `yarn twenty server reset` | Delete all data and start fresh | + +## CI with GitHub Actions + +The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. + +The workflow: + +1. Checks out your code +2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action +3. Installs dependencies with `yarn install --immutable` +4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. + +To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. ## 手动设置(不使用脚手架) -虽然我们建议使用 `create-twenty-app` 以获得最佳的上手体验,但你也可以手动设置项目。 不要全局安装 CLI。 相反,请将 `twenty-sdk` 添加为本地依赖,并在你的 package.json 中配置一个脚本: +If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps. + +**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:** ```bash filename="Terminal" -yarn add -D twenty-sdk +yarn add twenty-sdk twenty-client-sdk ``` -然后添加一个 `twenty` 脚本: +**2. Add a `twenty` script to your `package.json`:** ```json filename="package.json" { @@ -210,25 +393,19 @@ yarn add -D twenty-sdk } ``` -现在你可以通过 `yarn twenty ` 运行所有命令,例如 `yarn twenty dev`、`yarn twenty help` 等。 +You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands. -## 如何使用本地 Twenty 实例 - -如果你已经在本地运行一个 Twenty 实例(例如通过 `npx nx start twenty-server`),你可以连接到它,而不是使用 Docker: - -```bash filename="Terminal" -# During scaffolding — skip Docker, connect to your running instance -npx create-twenty-app@latest my-app --port 3000 - -# Or after scaffolding — add a remote pointing to your instance -yarn twenty remote add --local --port 3000 -``` + +Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version. + ## 故障排除 -* 身份验证错误:运行 `yarn twenty auth:login`,并确保你的 API 密钥具有所需权限。 -* 无法连接到服务器:请验证 API URL,并确保 Twenty 服务器可达。 -* 类型或客户端缺失/过期:重启 `yarn twenty dev` — 它会自动生成类型化客户端。 -* 开发模式未同步:确保 `yarn twenty dev` 正在运行,并且你的环境不会忽略变更。 +If you run into issues: -Discord 帮助频道:https://discord.com/channels/1130383047699738754/1130386664812982322 +* Make sure **Docker is running** before starting the scaffolder with a local instance. +* Make sure you are using **Node.js 24+** (`node -v` to check). +* Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available. +* Try deleting `node_modules` and running `yarn install` again if dependencies seem broken. + +Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx index 6a50b2f845..56f2e7a424 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx @@ -4,34 +4,76 @@ description: 将你的 Twenty 应用分发到应用市场,或进行内部部 --- -应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 + 应用目前处于 Alpha 阶段。 该功能可用,但仍在演进中。 ## 概览 一旦你的应用已[在本地构建并完成测试](/l/zh/developers/extend/apps/building),你可以通过两种方式进行分发: -* **发布到 npm** — 将你的应用在 Twenty 应用市场上架,供任何工作区发现并安装。 * **部署 tar 包** — 直接将你的应用上传到特定的 Twenty 服务器,以供内部或私有使用。 +* **发布到 npm** — 将你的应用在 Twenty 应用市场上架,供任何工作区发现并安装。 两种路径都从同一个**构建**步骤开始。 ## 构建你的应用 -`build` 命令会编译你的 TypeScript 源码,转译逻辑函数和前端组件,并生成一个描述你应用内容的 `manifest.json`: +Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" yarn twenty build ``` -输出将写入 `.twenty/output/`。 此目录包含分发所需的一切:已编译的代码、资源、清单,以及你的 `package.json` 副本。 +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. -要同时创建一个 `.tgz` 压缩包(由部署命令在内部使用,或用于手动分发): +## 部署到服务器(tar 包) + +对于你不希望公开的应用(专有工具、仅供企业使用的集成或实验性构建),你可以将 tar 包直接部署到某台 Twenty 服务器。 + +### 先决条件 + +在部署之前,你需要配置一个指向目标服务器的远程。 远程会将服务器 URL 和身份验证凭据本地存储在 `~/.twenty/config.json` 中。 + +添加远程: ```bash filename="Terminal" -yarn twenty build --tarball +yarn twenty remote add --api-url https://your-twenty-server.com --as production ``` +### 部署 + +一步构建并将你的应用上传到服务器: + +```bash filename="Terminal" +yarn twenty deploy +# To deploy to a specific remote: +# yarn twenty deploy --remote production +``` + +### 共享已部署的应用 + +通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 要共享已部署的应用: + +1. 前往 **Settings > Applications > Registrations** 并打开你的应用 +2. 在 **Distribution** 选项卡中,点击 **Copy share link** +3. 将此链接分享给其他工作区的用户 — 它会将他们直接带到该应用的安装页面 + +该分享链接使用服务器的基础 URL(不包含任何工作区子域),因此适用于该服务器上的任意工作区。 + + +Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. + + +### 版本管理 + +要发布更新: + +1. 更新 `package.json` 中的 `version` 字段 +2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +3. 已安装该应用的工作区会在其设置中看到可用的升级 + +{/* TODO: add screenshot of the Upgrade button */} + ## 发布到 npm 发布到 npm 可让你的应用在 Twenty 应用市场中被发现。 任何 Twenty 工作区都可以直接通过 UI 浏览、安装和升级应用市场中的应用。 @@ -39,41 +81,42 @@ yarn twenty build --tarball ### 要求 * 一个 [npm](https://www.npmjs.com) 账户 -* 在你的 `package.json` 的 `keywords` 数组中**必须**包含 `twenty-app` 关键字 - -### 添加所需关键字 - -Twenty 市场通过在 npm 注册表中搜索带有 `twenty-app` 关键字的包来发现应用。 将其添加到你的 `package.json`: +* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", - "keywords": ["twenty-app"], - ... + "keywords": ["twenty-app"] } ``` - -该市场会在 npm 注册表中搜索 `keywords:twenty-app`。 没有此关键字,即使包名带有 `twenty-app-` 前缀,你的包也不会出现在市场中。 - +### 应用市场元数据 -### 步骤 +The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder: -1. **构建你的应用:** - -```bash filename="Terminal" -yarn twenty build +```ts src/application-config.ts +export default defineApplication({ + universalIdentifier: '...', + displayName: 'My App', + description: 'A great app', + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + logoUrl: 'public/logo.png', + screenshots: [ + 'public/screenshot-1.png', + 'public/screenshot-2.png', + ], +}); ``` -2. **发布到 npm:** +See the [defineApplication accordion](/l/zh/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). + +### Publish ```bash filename="Terminal" yarn twenty publish ``` -这会在 `.twenty/output/` 目录下运行 `npm publish`。 - 要在特定的 dist-tag(例如 `beta` 或 `next`)下发布: ```bash filename="Terminal" @@ -82,25 +125,17 @@ yarn twenty publish --tag beta ### 应用市场的发现机制如何运作 -Twenty 服务器会**每小时**从 npm 注册表同步其市场目录: +The Twenty server syncs its marketplace catalog from the npm registry **every hour**. -1. 它会搜索所有带有 `keywords:twenty-app` 关键字的 npm 包 -2. 对于每个包,它会从 npm CDN 获取 `manifest.json` -3. 应用的元数据(名称、描述、作者、徽标、屏幕截图、类别)将从清单中提取,并显示在市场中 - -发布后,你的应用最多可能需要一小时才会出现在市场中。 要立即触发同步,而无需等待下一次每小时同步: +You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" yarn twenty catalog-sync +# To target a specific remote: +# yarn twenty catalog-sync --remote production ``` -要指定特定的远程: - -```bash filename="Terminal" -yarn twenty catalog-sync -r production -``` - -市场中显示的元数据来自你在应用源代码中调用的 `defineApplication()` —— 诸如 `displayName`、`description`、`author`、`category`、`logoUrl`、`screenshots`、`aboutDescription`、`websiteUrl` 和 `termsUrl` 等字段。 +The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. 如果您的应用未在 `defineApplication()` 中定义 `aboutDescription`,市场将自动使用 npm 上您的软件包的 `README.md` 作为关于页面内容。 这意味着您可以为 npm 和 Twenty 市场维护同一个 README。 如果您希望在市场中使用不同的描述,请显式设置 `aboutDescription`。 @@ -108,7 +143,7 @@ yarn twenty catalog-sync -r production ### CI 发布 -脚手架项目包含一个 GitHub Actions 工作流,会在每次发版时自动发布: +Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)): ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -133,121 +168,24 @@ jobs: - run: npx twenty build - run: npm publish --provenance --access public working-directory: .twenty/output - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` 对于其他 CI 系统(GitLab CI、CircleCI 等),同样适用以下三条命令:`yarn install`、`yarn twenty build`,然后在 `.twenty/output` 目录下执行 `npm publish`。 - + **npm provenance** 可选,但建议启用。 使用 `--provenance` 发布会在你的 npm 列表中添加可信徽章,使用户可以验证该包是由公共 CI 流水线中的特定提交构建的。 有关设置说明,请参见 [npm provenance 文档](https://docs.npmjs.com/generating-provenance-statements)。 - - -## 部署到服务器(tar 包) - -对于你不希望公开的应用(专有工具、仅供企业使用的集成或实验性构建),你可以将 tar 包直接部署到某台 Twenty 服务器。 - -### 先决条件 - -在部署之前,你需要配置一个指向目标服务器的远程。 远程会将服务器 URL 和身份验证凭据本地存储在 `~/.twenty/config.json` 中。 - -添加远程: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --as production -``` - -对于本地开发服务器: - -```bash filename="Terminal" -yarn twenty remote add --local --as local -``` - -对于非交互式环境,你也可以使用 API 密钥进行身份验证: - -```bash filename="Terminal" -yarn twenty remote add --url https://your-twenty-server.com --token --as production -``` - -管理你的远程: - -```bash filename="Terminal" -yarn twenty remote list # List all configured remotes -yarn twenty remote switch prod # Set the default remote -yarn twenty remote status # Show active remote and auth status -yarn twenty remote remove old # Remove a remote -``` - -### 部署 - -一步构建并将你的应用上传到服务器: - -```bash filename="Terminal" -yarn twenty deploy -``` - -这会使用 `--tarball` 构建应用,然后通过 GraphQL 多部分上传将该 tar 包上传到默认远程。 - -部署到特定远程: - -```bash filename="Terminal" -yarn twenty deploy -r production -``` - -### 共享已部署的应用 - -通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 要共享已部署的应用: - -1. 前往 **Settings > Applications > Registrations** 并打开你的应用 -2. 在 **Distribution** 选项卡中,点击 **Copy share link** -3. 将此链接分享给其他工作区的用户 — 它会将他们直接带到该应用的安装页面 - -该分享链接使用服务器的基础 URL(不包含任何工作区子域),因此适用于该服务器上的任意工作区。 - -### 版本管理 - -要发布更新: - -1. 更新 `package.json` 中的 `version` 字段 -2. 运行 `yarn twenty deploy`(或 `yarn twenty deploy -r production`) -3. 已安装该应用的工作区会在其设置中看到可用的升级 + ## 安装应用 -一旦应用已发布(npm)或已部署(tar 包),各工作区即可通过 UI 进行安装: +Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. + +Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. + +{/* TODO: add screenshot of the UI when the app is registered */} + +You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` - -或者在 Twenty UI 的 **Settings > Applications** 页面中浏览并安装来自市场或通过 tar 包部署的应用。 - -## 应用分发类别 - -Twenty 会根据分发方式将应用归为三类: - -| 类别 | 工作原理 | 在应用市场中可见? | -| ------------- | -------------------------------------------------- | --------- | -| **开发** | 通过 `yarn twenty dev` 运行的本地开发模式应用。 用于构建和测试。 | 否 | -| **已发布(npm)** | 发布到 npm 且包含 `twenty-app` 关键字的应用。 在应用市场上架,供任何工作区安装。 | 是 | -| **内部(tar 包)** | 通过 tar 包部署到特定服务器的应用。 仅通过分享链接对该服务器上的工作区可用。 | 否 | - - -在构建你的应用时,从**开发**模式开始。 准备就绪后,选择用于广泛分发的**已发布**(npm),或用于私有部署的**内部**(tar 包)。 - - -## CLI 参考 - -| 命令 | 描述 | 关键选项 | -| --------------------------- | ---------------- | ------------------------------------------- | -| `yarn twenty build` | 编译应用并生成清单 | `--tarball` — 同时创建一个 `.tgz` 包 | -| `yarn twenty publish` | 构建并发布到 npm | `--tag ` — npm 分发标签(例如 `beta`、`next`) | -| `yarn twenty deploy` | 构建并将 tar 包上传到服务器 | `-r, --remote ` — 目标远程 | -| `yarn twenty catalog-sync` | 在服务器上触发市场目录同步 | `-r, --remote ` — 目标远程 | -| `yarn twenty install` | 在某个工作区安装已部署的应用 | `-r, --remote ` — 目标远程 | -| `yarn twenty dev` | 监听并同步本地更改 | 使用默认远程 | -| `yarn twenty remote add` | 添加服务器连接 | `--url`,`--token`,`--as`,`--local`,`--port` | -| `yarn twenty remote list` | 列出已配置的远程 | — | -| `yarn twenty remote switch` | 设置默认远程 | — | -| `yarn twenty remote status` | 显示连接状态 | — | -| `yarn twenty remote remove` | 移除远程 | — | diff --git a/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx index c337b9daa5..46ff11c57e 100644 --- a/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx @@ -4,1424 +4,78 @@ description: 以代码的形式构建并管理 Twenty 自定义项。 --- -应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 +Apps are currently in alpha. The feature works but is still evolving. -## 什么是应用? +## What are apps? -应用使你能够**以代码的形式**构建和管理 Twenty 自定义项。 无需通过 UI 配置所有内容,你可以在代码中定义数据模型和逻辑函数——从而更快地构建、维护,并推广到多个工作空间。 +Apps let you extend Twenty with custom objects, fields, logic functions, front components, AI skills, and more — all managed as code. Instead of configuring everything through the UI, you define your data model and logic in TypeScript and deploy it to one or more workspaces. -**你现在可以做什么:** +**你可以构建的内容:** -* 以代码定义自定义对象和字段(受管理的数据模型) -* 构建带有自定义触发器的逻辑函数 -* 为 AI 定义技能和代理 -* 将同一个应用部署到多个工作空间 +* **Custom objects and fields** — extend your data model with new entities or add fields to existing objects like Company or Person +* **Logic functions** — server-side functions triggered by database events, cron schedules, or HTTP routes +* **Front components** — React components that render inside Twenty's UI (record pages, command menu, side panels) +* **AI skills and agents** — extend Twenty's AI with custom capabilities +* **Views and navigation** — preconfigured saved views and sidebar links -## 先决条件 - -* Node.js 24+ 和 Yarn 4 -* Docker (用于本地 Twenty 开发服务器) - -## 开始使用 - -使用官方脚手架创建一个新应用。 它可以为你自动启动一个本地 Twenty 实例: +## Quick start ```bash filename="Terminal" -# Scaffold a new app — the CLI will offer to start a local Twenty server npx create-twenty-app@latest my-twenty-app cd my-twenty-app - -# Start dev mode: automatically syncs local changes to your workspace yarn twenty dev ``` -### 本地服务器管理 +This scaffolds a new app, optionally starts a local Twenty server, and begins watching your files for changes. See the [Getting Started](/l/zh/developers/extend/apps/getting-started) guide for the full walkthrough. -该 SDK 包含用于管理本地 Twenty 开发服务器的命令(该服务器是一体化 Docker 镜像,内含 PostgreSQL、Redis、服务器和工作进程,监听 2020 端口)。 这些命令仅适用于基于 Docker 的开发服务器——它们不会管理从源代码启动的 Twenty 实例(例如通过 `npx nx start twenty-server` 启动的实例,运行在 3000 端口): +## Detailed guides -```bash filename="Terminal" -# Start the local server (pulls the image if needed) -yarn twenty server start +| Guide | 描述 | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [Getting Started](/l/zh/developers/extend/apps/getting-started) | Scaffold an app, set up a local server, project structure, CI | +| [Building Apps](/l/zh/developers/extend/apps/building) | Entity definitions (`defineObject`, `defineLogicFunction`, `defineFrontComponent`, etc.), API clients, npm packages, public assets, testing | +| [Publishing](/l/zh/developers/extend/apps/publishing) | Deploy to a server, publish to npm, marketplace | -# Check server status -yarn twenty server status - -# Stream server logs -yarn twenty server logs - -# Stop the server -yarn twenty server stop - -# Reset all data and start fresh -yarn twenty server reset -``` - -本地服务器预置了一个工作区和用户 (`tim@apple.dev` / `tim@apple.dev`),因此你可以无需任何手动设置即可立即开始开发。 - -### 身份验证 - -使用 OAuth 将你的应用连接到本地服务器: - -```bash filename="Terminal" -# 通过 OAuth 进行身份验证 (将打开浏览器) -yarn twenty remote add --local -``` - -脚手架工具支持两种模式,用于控制包含哪些示例文件: - -```bash filename="Terminal" -# 默认(完整):所有示例(对象、字段、逻辑函数、前端组件、视图、导航菜单项、技能、代理) -npx create-twenty-app@latest my-app - -# 最小化:仅核心文件(application-config.ts 和 default-role.ts) -npx create-twenty-app@latest my-app --minimal -``` - -### 如何使用本地 Twenty 实例 - -如果你已经在本地运行一个 Twenty 实例,你可以连接到它,而不是使用 Docker。 指定本地服务器正在监听的端口(默认:`3000`): - -```bash filename="Terminal" -# 在脚手架过程中 -npx create-twenty-app@latest my-app --port 3000 - -# 或在脚手架之后 -yarn twenty remote add --local --port 3000 -``` - -从这里您可以: - -```bash filename="Terminal" -# 向你的应用添加一个新实体(引导式) -yarn twenty entity:add - -# 监听你的应用函数日志 -yarn twenty function:logs - -# 按名称执行一个函数 -yarn twenty function:execute -n my-function -p '{"name": "test"}' - -# 执行安装前函数 -yarn twenty function:execute --preInstall - -# 执行安装后函数 -yarn twenty function:execute --postInstall - -# 为分发构建应用 -yarn twenty build - -# 将应用发布到 npm 或 Twenty 服务器 -yarn twenty publish - -# 从当前工作区卸载该应用 -yarn twenty uninstall - -# 显示命令帮助 -yarn twenty help -``` - -另请参阅:[create-twenty-app](https://www.npmjs.com/package/create-twenty-app) 和 [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) 的 CLI 参考页面。 - -## 项目结构(脚手架生成) - -当你运行 `npx create-twenty-app@latest my-twenty-app` 时,脚手架将: - -* 将一个最小的基础应用复制到 `my-twenty-app/` 中 -* 添加本地 `twenty-sdk` 依赖和 Yarn 4 配置 -* 创建与 `twenty` CLI 关联的配置文件和脚本 -* 生成核心文件(应用配置、默认函数角色、安装前/安装后函数),并基于脚手架模式生成示例文件 - -A freshly scaffolded app with the default `--exhaustive` mode looks like this: - -```text filename="my-twenty-app/" -my-twenty-app/ - package.json - yarn.lock - .gitignore - .nvmrc - .yarnrc.yml - .yarn/ - install-state.gz - .oxlintrc.json - tsconfig.json - README.md - public/ # 公共资源文件夹(图片、字体等) - src/ - ├── application-config.ts # 必需 - 主应用配置 - ├── roles/ - │ └── default-role.ts # 逻辑函数的默认角色 - ├── objects/ - │ └── example-object.ts # 示例自定义对象定义 - ├── fields/ - │ └── example-field.ts # 示例独立字段定义 - ├── logic-functions/ - │ ├── hello-world.ts # 示例逻辑函数 - │ ├── pre-install.ts # 安装前逻辑函数 - │ └── post-install.ts # 安装后逻辑函数 - ├── front-components/ - │ └── hello-world.tsx # 示例前端组件 - ├── views/ - │ └── example-view.ts # 示例已保存视图定义 - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # 示例侧边栏导航链接 - ├── skills/ - │ └── example-skill.ts # 示例 AI 代理技能定义 - └── agents/ - └── example-agent.ts # 示例 AI 代理定义 -``` - -使用 `--minimal` 时,只会创建核心文件(`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`)。 - -总体来说: - -* **package.json**:声明应用名称、版本、引擎(Node 24+、Yarn 4),并添加 `twenty-sdk` 以及一个 `twenty` 脚本,该脚本会委托给本地的 `twenty` CLI。 运行 `yarn twenty help` 以列出所有可用命令。 -* **.gitignore**:忽略常见产物,如 `node_modules`、`.yarn`、`generated/`(类型化客户端)、`dist/`、`build/`、覆盖率文件夹、日志文件以及 `.env*` 文件。 -* **yarn.lock**、**.yarnrc.yml**、**.yarn/**:锁定并配置项目使用的 Yarn 4 工具链。 -* **.nvmrc**:固定项目期望的 Node.js 版本。 -* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources. -* **README.md**:应用根目录中的简短 README,包含基本说明。 -* **public/**: 一个用于存储公共资源(图像、字体、静态文件)的文件夹,这些资源将随你的应用程序一起提供。 放置在此处的文件会在同步期间上传,并可在运行时访问。 -* **src/**:你以代码形式定义应用的主要位置 +## Key concepts ### 实体检测 -该 SDK 通过在你的 TypeScript 文件中解析 **`export default define({...})`** 调用来检测实体。 每种实体类型都有一个从 `twenty-sdk` 导出的对应辅助函数: +The SDK detects entities by scanning your TypeScript files for `export default define({...})` calls. File naming and folder structure are flexible — detection is AST-based, not path-based. -| 辅助函数 | 实体类型 | -| ---------------------------------- | -------------------------------- | -| `defineObject()` | 自定义对象定义 | -| `defineLogicFunction()` | 逻辑函数定义 | -| `definePreInstallLogicFunction()` | 安装前逻辑函数(在安装之前运行) | -| `definePostInstallLogicFunction()` | 安装后逻辑函数(在安装之后运行) | -| `defineFrontComponent()` | 前端组件定义 | -| `defineRole()` | 角色定义 | -| `defineField()` | 现有对象的字段扩展 | -| `defineView()` | Saved view definitions | -| `defineNavigationMenuItem()` | Navigation menu item definitions | -| `defineSkill()` | AI agent skill definitions | -| `defineAgent()` | AI 代理定义 | +### Available entity types - -**文件命名是灵活的。** 实体检测基于 AST — SDK 会扫描你的源文件以查找 `export default define({...})` 模式。 你可以按照自己的喜好组织文件和文件夹。 按实体类型分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。 - +| 函数 | 目的 | +| ---------------------------------- | -------------------------------------------- | +| `defineApplication()` | Application metadata (required, one per app) | +| `defineObject()` | Custom objects with fields | +| `defineField()` | Fields on existing objects | +| `defineLogicFunction()` | Server-side logic with triggers | +| `defineFrontComponent()` | React components in Twenty's UI | +| `defineRole()` | Permission roles | +| `defineView()` | Saved view configurations | +| `defineNavigationMenuItem()` | Sidebar navigation links | +| `defineSkill()` | AI agent skills | +| `defineAgent()` | AI agents with prompts | +| `definePageLayout()` | Custom record page layouts | +| `definePreInstallLogicFunction()` | Runs before app installation | +| `definePostInstallLogicFunction()` | Runs after app installation | -已检测实体的示例: +### Development workflow -```typescript -// This file can be named anything and placed anywhere in src/ -import { defineObject, FieldType } from 'twenty-sdk'; +1. **`yarn twenty dev`** — watches source files, rebuilds on change, syncs to the server, generates typed API clients +2. **`yarn twenty build`** — produces a distributable build +3. **`yarn twenty deploy`** — deploys to a remote Twenty server +4. **`yarn twenty add`** — scaffolds a new entity interactively -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCard', - // ... rest of config -}); -``` - -后续命令将添加更多文件和文件夹: - -* `yarn twenty dev` 会在 `node_modules/twenty-sdk/clients` 中自动生成两个类型化 API 客户端:`CoreApiClient`(通过 `/graphql` 获取工作区数据)和 `MetadataApiClient`(通过 `/metadata` 处理工作区配置和文件上传)。 -* `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more. - -## 身份验证 - -首次运行 `yarn twenty auth:login` 时,你将被提示输入: - -* API URL(默认为 http://localhost:3000 或你当前的工作空间配置) -* API 密钥 - -你的凭据按用户存储在 `~/.twenty/config.json` 中。 你可以维护多个配置文件并在它们之间切换。 - -### 管理工作空间 +### CLI 参考 ```bash filename="Terminal" -# Login interactively (recommended) -yarn twenty auth:login - -# Login to a specific workspace profile -yarn twenty auth:login --workspace my-custom-workspace - -# List all configured workspaces -yarn twenty auth:list - -# Switch the default workspace (interactive) -yarn twenty auth:switch - -# Switch to a specific workspace -yarn twenty auth:switch production - -# Check current authentication status -yarn twenty auth:status +yarn twenty help # List all commands +yarn twenty server start # Start local dev server +yarn twenty remote add # Connect to a Twenty server +yarn twenty exec -n fn # Execute a logic function +yarn twenty logs -n fn # Stream function logs ``` -使用 `yarn twenty auth:switch` 切换工作空间后,后续所有命令将默认使用该工作空间。 你仍可通过 `--workspace ` 临时覆盖。 - -## 使用 SDK 资源(类型与配置) - -twenty-sdk 提供你在应用中使用的类型化构件和辅助函数。 以下是你最常接触的关键部分。 - -### 辅助函数 - -该 SDK 提供辅助函数用于定义你的应用实体。 如 [实体检测](#entity-detection) 中所述,你必须使用 `export default define({...})` 才能让你的实体被检测到: - -| 函数 | 目的 | -| ---------------------------------- | ------------------------------- | -| `defineApplication()` | 配置应用元数据(必需,每个应用一个) | -| `defineObject()` | 定义带字段的自定义对象 | -| `defineLogicFunction()` | 定义带处理程序的逻辑函数 | -| `definePreInstallLogicFunction()` | 定义一个安装前逻辑函数(每个应用一个) | -| `definePostInstallLogicFunction()` | 定义一个安装后逻辑函数(每个应用一个) | -| `defineFrontComponent()` | 为自定义 UI 定义前端组件 | -| `defineRole()` | 配置角色权限和对象访问 | -| `defineField()` | 为现有对象扩展额外字段 | -| `defineView()` | Define saved views for objects | -| `defineNavigationMenuItem()` | Define sidebar navigation links | -| `defineSkill()` | Define AI agent skills | -| `defineAgent()` | 使用系统提示词定义 AI 智能体 | - -这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。 - -### 定义对象 - -自定义对象同时描述工作空间中记录的架构与行为。 使用 `defineObject()` 以内置校验定义对象: - -```typescript -// src/app/postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -关键点: - -* 使用 `defineObject()` 以获得内置校验和更好的 IDE 支持。 -* `universalIdentifier` 必须在各次部署间保持唯一且稳定。 -* 每个字段都需要 `name`、`type`、`label` 以及其自身稳定的 `universalIdentifier`。 -* `fields` 数组是可选的——你可以定义没有自定义字段的对象。 -* 你可以使用 `yarn twenty entity:add` 脚手架创建新对象,它会引导你完成命名、字段和关系。 - - -**基础字段会自动创建。** 当你定义自定义对象时,Twenty 会自动添加标准字段 -例如 `id`、`name`、`createdAt`、`updatedAt`、`createdBy`、`updatedBy` 和 `deletedAt`。 -你无需在 `fields` 数组中定义这些字段——只需添加你的自定义字段。 -你可以通过在你的 `fields` 数组中定义一个同名字段来覆盖默认字段, -但不建议这样做。 - - -### 在现有对象上定义字段 - -使用 `defineField()` 向现有对象添加自定义字段——包括标准对象(如 `company`、`person`、`opportunity`)以及由其他应用定义的自定义对象。 每个字段位于其各自的文件中,并通过其 `universalIdentifier` 引用目标对象。 - -要引用标准对象,请从 `twenty-sdk` 导入 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`。 该常量为所有内置对象及其字段提供稳定的标识符: - -```typescript -// src/fields/apollo-total-funding.field.ts -import { - defineField, - FieldType, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e', - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - type: FieldType.CURRENCY, - name: 'apolloTotalFunding', - label: 'Total Funding', - description: 'Total funding raised by the company', - icon: 'IconCash', -}); -``` - -关键点: - -* `objectUniversalIdentifier` 告诉 Twenty 将该字段附加到哪个对象。 使用 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` 用于标准对象。 -* 每个字段都需要其自身稳定的 `universalIdentifier`、`name`、`type`、`label`,以及目标 `objectUniversalIdentifier`。 -* 你可以使用 `yarn twenty entity:add` 脚手架创建新字段,并选择字段选项。 -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` 也以 `STANDARD_OBJECT` 的名称导出以方便使用——二者指向同一常量。 - -可用的标准对象包括:`attachment`、`blocklist`、`calendarChannel`、`calendarEvent`、`calendarEventParticipant`、`company`、`connectedAccount`、`dashboard`、`favorite`、`favoriteFolder`、`message`、`messageChannel`、`messageParticipant`、`messageThread`、`note`、`noteTarget`、`opportunity`、`person`、`task`、`taskTarget`、`timelineActivity`、`workflow`、`workflowAutomatedTrigger`、`workflowRun`、`workflowVersion` 和 `workspaceMember`。 - -每个标准对象还会暴露其字段标识符。 例如,要在角色权限中引用标准对象上的特定字段: - -```typescript -STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier -``` - -#### 现有对象上的关联字段 - -你还可以定义关联字段,将现有对象链接到你的自定义对象: - -```typescript -// src/fields/people-on-call-recording.field.ts -import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk'; -import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording'; -import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field'; - -export default defineField({ - universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', - objectUniversalIdentifier: - CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: - CALL_RECORDING_ON_PERSON_ID, - relationType: RelationType.MANY_TO_ONE, -}); -``` - -### 应用配置(application-config.ts) - -每个应用都有一个 `application-config.ts` 文件,用于描述: - -* **应用的身份**:标识符、显示名称和描述。 -* **函数如何运行**:它们用于权限的角色。 -* **(可选)变量**:以环境变量形式提供给函数的键值对。 -* **(可选)安装前函数**:在应用安装之前运行的逻辑函数。 -* **(可选)安装后函数**:在应用安装后运行的逻辑函数。 - -使用 `defineApplication()` 定义你的应用配置: - -```typescript -// src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -备注: - -* `universalIdentifier` 字段是你拥有的确定性 ID;生成一次并在多次同步中保持稳定。 -* `applicationVariables` 会变成函数可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。 -* `defaultRoleUniversalIdentifier` 必须与角色文件一致(见下文)。 -* 清单构建期间会自动检测安装前和安装后函数。 参见 [安装前函数](#pre-install-functions) 和 [安装后函数](#post-install-functions)。 - -#### 角色和权限 - -应用可以定义角色,以封装对工作空间对象与操作的权限。 `application-config.ts` 中的 `defaultRoleUniversalIdentifier` 字段指定你的应用逻辑函数所使用的默认角色。 - -* 作为 `TWENTY_API_KEY` 注入的运行时 API 密钥源自该默认函数角色。 -* 类型化客户端将受限于该角色授予的权限。 -* 遵循最小权限原则:仅授予函数所需权限来创建一个专用角色,然后引用其通用标识符。 - -##### 默认函数角色(\*.role.ts) - -当你脚手架生成新应用时,CLI 也会创建一个默认角色文件。 使用 `defineRole()` 定义带内置校验的角色: - -```typescript -// src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', - fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); -``` - -随后,该角色的 `universalIdentifier` 会在 `application-config.ts` 中被引用为 `defaultRoleUniversalIdentifier`。 换句话说: - -* **\*.role.ts** 定义默认函数角色可以执行的操作。 -* **application-config.ts** 指向该角色,使你的函数继承其权限。 - -备注: - -* 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。 -* 将 `objectPermissions` 和 `fieldPermissions` 替换为你的函数所需的对象/字段。 -* `permissionFlags` 控制对平台级能力的访问。 尽量保持最小化;仅添加所需项。 -* 在 Hello World 应用中查看可运行示例:[`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts)。 - -### 逻辑函数的配置与入口点 - -每个函数文件都使用 `defineLogicFunction()` 导出包含处理程序和可选触发器的配置。 - -```typescript -// src/app/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - triggers: [ - // Public HTTP route trigger '/s/post-card/create' - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - // Cron trigger (CRON pattern) - // { - // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', - // type: 'cron', - // pattern: '0 0 1 1 *', - // }, - // Database event trigger - // { - // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', - // type: 'databaseEvent', - // eventName: 'person.updated', - // updatedFields: ['name'], - // }, - ], -});},{ -``` - -常见触发器类型: - -* **route**:在\*\*`/s/` 端点\*\*下通过 HTTP 路径与方法公开你的函数: - -> 例如 `path: '/post-card/create',` -> 调用 `/s/post-card/create` - -* **cron**:使用 CRON 表达式按计划运行你的函数。 -* **databaseEvent**:在工作空间对象生命周期事件上运行。 当事件操作为 `updated` 时,可以在 `updatedFields` 数组中指定要监听的特定字段。 如果未定义或为空,任何更新都会触发该函数。 - -> 例如 `person.updated` - -备注: - -* `triggers` 数组是可选的。 没有触发器的函数可作为实用函数,被其他函数调用。 -* 你可以在单个函数中混用多种触发器类型。 - -### 安装前函数 - -安装前函数是在你的应用安装到工作区之前自动运行的逻辑函数。 这对于执行验证任务、先决条件检查,或在主安装开始前准备工作区状态很有用。 - -当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/pre-install.ts` 为你生成一个安装前函数: - -```typescript -// src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: '', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -你也可以随时使用 CLI 手动执行安装前函数: - -```bash filename="Terminal" -yarn twenty function:execute --preInstall -``` - -关键点: - -* 安装前函数使用 `definePreInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 -* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 -* 每个应用仅允许一个安装前函数。 如果检测到多个,清单构建将报错。 -* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `preInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 -* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的准备任务。 -* 安装前函数不需要触发器——它们会在安装前由平台调用,或通过 `function:execute --preInstall` 手动调用。 - -### 安装后函数 - -安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。 - -当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/post-install.ts` 为你生成一个安装后函数: - -```typescript -// src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: '', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -You can also manually execute the post-install function at any time using the CLI: - -```bash filename="Terminal" -yarn twenty function:execute --postInstall -``` - -关键点: - -* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 -* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 -* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。 -* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `postInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 -* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding. -* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`. - -### 路由触发器负载 - - -**破坏性变更(v1.16,2026 年 1 月):** 路由触发器的负载格式已更改。 在 v1.16 之前,查询参数、路径参数和请求体会直接作为负载发送。 从 v1.16 开始,它们被嵌套在结构化的 `RoutePayload` 对象中。 - -**v1.16 之前:** -```typescript -const handler = async (params) => { - const { param1, param2 } = params; // Direct access -}; -``` - -**v1.16 之后:** -```typescript -const handler = async (event: RoutePayload) => { - const { param1, param2 } = event.body; // Access via .body - const { queryParam } = event.queryStringParameters; - const { id } = event.pathParameters; -}; -``` - -**迁移现有函数:** 将处理程序更新为从 `event.body`、`event.queryStringParameters` 或 `event.pathParameters` 解构,而不是直接从参数对象解构。 - - -当路由触发器调用你的逻辑函数时,它会接收一个遵循 AWS HTTP API v2 格式的 `RoutePayload` 对象。 从 `twenty-sdk` 导入该类型: - -```typescript -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - // Access request data - const { headers, queryStringParameters, pathParameters, body } = event; - - // HTTP method and path are available in requestContext - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -`RoutePayload` 类型具有以下结构: - -| 属性 | 类型 | 描述 | -| ---------------------------- | ------------------------------------- | ------------------------------------------------ | -| `headers` | `Record` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | -| `queryStringParameters` | `Record` | 查询字符串参数(多个值以逗号连接) | -| `pathParameters` | `Record` | 从路由模式中提取的路径参数(例如,`/users/:id` → `{ id: '123' }`) | -| `请求体` | `object \| null` | 已解析的请求体(JSON) | -| `isBase64Encoded` | `布尔值` | 请求体是否为 base64 编码 | -| `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | -| `requestContext.http.path` | `string` | 原始请求路径 | - -### 转发 HTTP 请求头 - -出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。 如需访问特定请求头,请在 `forwardedRequestHeaders` 数组中显式列出: - -```typescript -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - triggers: [ - { - universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', - type: 'route', - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, - ], -}); -``` - -随后你可以在处理程序中访问这些请求头: - -```typescript -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - - 请求头名称会被规范化为小写。 请使用小写键访问它们(例如,`event.headers['content-type']`)。 - - -你可以通过两种方式创建新函数: - -* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新逻辑函数的选项。 这将生成一个包含处理程序和配置的入门文件。 -* **手动**:创建一个新的 `*.logic-function.ts` 文件,并使用 `defineLogicFunction()`,遵循相同的模式。 - -### 将逻辑函数标记为工具 - -逻辑函数可以作为供 AI 智能体和工作流使用的**工具**对外提供。 当函数被标记为工具时,Twenty 的 AI 功能即可发现它,并可在工作流自动化中将其选作一个步骤。 - -要将逻辑函数标记为工具,请设置 `isTool: true`,并提供 `toolInputSchema`,使用 [JSON Schema](https://json-schema.org/) 描述预期的输入参数: - -```typescript -// src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - -关键点: - -* **`isTool`** (`boolean`, 默认: `false`): 当设置为 `true` 时,该函数会被注册为工具,并可供 AI 代理和工作流自动化使用。 -* **`toolInputSchema`** (`object`, 可选): 描述函数可接受参数的 JSON Schema 对象。 AI 代理使用此架构来理解该工具期望的输入并验证调用。 如果省略,架构将默认为 `{ type: 'object', properties: {} }`(无参数)。 -* 设置为 `isTool: false`(或未设置)的函数**不会**被暴露为工具。 它们仍可直接执行或被其他函数调用,但不会出现在工具发现中。 -* **工具命名**: 当作为工具对外暴露时,函数名会被自动规范化为 `logic_function_`(转换为小写,非字母数字字符替换为下划线)。 例如,`enrich-company` 将变为 `logic_function_enrich_company`。 -* 你可以将 `isTool` 与触发器结合使用——一个函数既可以作为工具(由 AI 代理调用),也可以同时由事件(cron、数据库事件、路由)触发。 - - -**写一个好的 `description`。** AI 代理会依赖该函数的 `description` 字段来决定何时使用该工具。 明确说明该工具的作用以及应在何时调用。 - - -### 前端组件 - -前端组件使你可以构建在 Twenty 的 UI 中渲染的自定义 React 组件。 使用 `defineFrontComponent()` 以内置校验定义组件: - -```typescript -// src/front-components/my-widget.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const MyWidget = () => { - return ( -
-

My Custom Widget

-

This is a custom front component for Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-widget', - description: 'A custom widget component', - component: MyWidget, -}); -``` - -关键点: - -* 前端组件是在 Twenty 中的隔离上下文中渲染的 React 组件。 -* `component` 字段引用你的 React 组件。 -* 组件会在 `yarn twenty dev` 期间自动构建并同步。 - -你可以通过两种方式创建新的前端组件: - -* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新前端组件的选项。 -* **手动**:创建一个新的 `.tsx` 文件,并使用 `defineFrontComponent()`,遵循相同的模式。 - -#### 前端组件可用位置 - -在 Twenty 中,前端组件可在两个位置进行渲染: - -* **侧边栏** — 非无头的前端组件会在右侧侧边栏中打开。 当前端组件从命令菜单触发时,这是默认行为。 -* **小部件(仪表盘和记录页面)** — 前端组件可以作为小部件嵌入页面布局中。 在配置仪表盘或记录页面布局时,用户可以添加前端组件小部件。 - -#### 无头与非无头 - -前端组件有两种由 `isHeadless` 选项控制的渲染模式: - -**非无头(默认)** — 该组件会渲染可见的 UI。 从命令菜单触发时,它会在侧边栏中打开。 当 `isHeadless` 为 `false` 或被省略时,这是默认行为。 - -**无头** — 该组件会在后台以不可见的方式挂载。 它不会打开侧边栏。 无头组件旨在用于执行逻辑后自行卸载的操作——例如运行异步任务、导航到某个页面或显示确认模态框。 它们与下文介绍的 SDK Command 组件天然契合。 - -```typescript -export default defineFrontComponent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'my-action', - description: 'Runs an action without opening the side panel', - component: MyAction, - isHeadless: true, - command: { - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', - label: 'Run my action', - }, -}); -``` - -#### 添加命令菜单项 - -要让前端组件作为一项出现在 Twenty 的命令菜单中,请在 `defineFrontComponent()` 中添加 `command` 属性。 当用户打开命令菜单(Cmd+K / Ctrl+K)时,该项会显示,并在点击时触发该前端组件。 - -`command` 对象接受以下字段: - -| 字段 | 类型 | 描述 | -| --------------------------------------- | ------------------------------------- | -------------------------------------------------- | -| `universalIdentifier` | `string` (必填) | 命令菜单项的唯一 ID | -| `标签` | `string` (必填) | 在命令菜单中显示的标签 | -| `图标` | `string` (可选) | 图标名称(例如,`'IconSparkles'`) | -| `isPinned` | `boolean` (可选) | 该命令是否固定在菜单顶部 | -| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (可选) | `GLOBAL` 会在任意位置显示该命令;`RECORD_SELECTION` 仅在记录上下文中显示 | -| `availabilityObjectUniversalIdentifier` | `string` (可选) | 将该命令限制为特定对象类型(例如,Person) | - -以下是来自通话录音应用的示例,它添加了一个作用域为 Person 记录的命令: - -```typescript -import { defineFrontComponent } from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', - name: 'Summarize Person Call Recordings', - description: 'Generates a summary of call recordings for a person', - component: SummarizePersonRecordings, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', - label: 'Summarize call recordings', - icon: 'IconSparkles', - isPinned: false, - availabilityType: 'RECORD_SELECTION', - availabilityObjectUniversalIdentifier: - '20202020-e674-48e5-a542-72570eee7213', - }, -}); -``` - -当该命令同步后,它会出现在命令菜单中。 如果前端组件为非无头,侧边栏将打开并在其中渲染该组件。 如果为无头,该组件会在后台挂载并执行其逻辑。 - -#### SDK Command 组件 - -`twenty-sdk` 包提供了四个为无头前端组件设计的 Command 辅助组件。 每个组件都会在挂载时执行一个操作,通过显示 snackbar 通知来处理错误,并在完成后自动卸载该前端组件。 - -从 `twenty-sdk/command` 导入它们: - -* **`Command`** — 通过 `execute` 属性运行异步回调。 -* **`CommandLink`** — 导航到某个应用路径。 属性:`to`、`params`、`queryParams`、`options`。 -* **`CommandModal`** — 打开一个确认模态框。 如果用户确认,则执行 `execute` 回调。 属性:`title`、`subtitle`、`execute`、`confirmButtonText`、`confirmButtonAccent`。 -* **`CommandOpenSidePanelPage`** — 打开特定的侧边栏页面。 属性:`page`、`pageTitle`、`pageIcon`。 - -下面是一个完整示例:无头前端组件使用 `Command` 从命令菜单运行一个操作: - -```typescript -// src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -另一个示例:使用 `CommandModal` 在执行前请求确认: - -```typescript -// src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### 执行上下文 - -每个前端组件都会接收一个执行上下文,提供其运行位置与方式的信息。 使用来自 `twenty-sdk` 的钩子来访问上下文值: - -| 钩子 | 返回类型 | 描述 | -| ----------------------- | ---------------- | ------------------------------------------------------------- | -| `useFrontComponentId()` | `string` | 当前前端组件实例的唯一 ID | -| `useRecordId()` | `string \| null` | 当组件在记录上下文中运行时(例如记录页面小部件或作用域限定于某条记录的命令),当前记录的 ID。 否则返回 `null`。 | -| `useUserId()` | `string \| null` | 当前用户的 ID | - -```typescript -import { useRecordId, useUserId } from 'twenty-sdk'; - -const MyWidget = () => { - const recordId = useRecordId(); - const userId = useUserId(); - - return ( -
-

Record: {recordId ?? 'none'}

-

User: {userId ?? 'anonymous'}

-
- ); -}; -``` - -该上下文是响应式的——如果周围的记录发生变化,钩子会自动返回更新后的值。 - -#### 宿主 API 函数 - -前端组件在隔离的沙箱中运行,但可以通过宿主提供的一组函数与 Twenty 的 UI 进行交互。 直接从 `twenty-sdk` 导入它们: - -```typescript -import { - navigate, - closeSidePanel, - enqueueSnackbar, - unmountFrontComponent, - openSidePanelPage, - openCommandConfirmationModal, -} from 'twenty-sdk'; -``` - -| 函数 | 签名 | 描述 | -| ------------------------------ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `导航` | `(to, params?, queryParams?, options?) => Promise` | 在 Twenty 内导航到一个类型化的应用路径 | -| `closeSidePanel` | `() => Promise` | 关闭侧边栏 | -| `enqueueSnackbar` | `(params) => Promise` | 显示一条 snackbar 通知。 参数:`message`、`variant`(`'error'`、`'success'`、`'info'`、`'warning'`),可选的 `duration`、`detailedMessage`、`dedupeKey` | -| `unmountFrontComponent` | `() => Promise` | 卸载当前前端组件(供无头组件在执行后进行清理) | -| `openSidePanelPage` | `(params) => Promise` | 在侧边栏中打开一个页面。 参数:`page`、`pageTitle`、`pageIcon`、`shouldResetSearchState` | -| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | 显示一个确认模态框并等待用户的响应。 参数:`title`、`subtitle`、`confirmButtonText`、`confirmButtonAccent`(`'default'`、`'blue'`、`'danger'`) | - -下面是一个示例,使用宿主 API 在操作完成后显示一条 snackbar 并关闭侧边栏: - -```typescript -import { defineFrontComponent, useRecordId } from 'twenty-sdk'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -### 技能 - -Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation: - -```typescript -// src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -关键点: - -* `name` is a unique identifier string for the skill (kebab-case recommended). -* `label` is the human-readable display name shown in the UI. -* `content` contains the skill instructions — this is the text the AI agent uses. -* `icon` (optional) sets the icon displayed in the UI. -* `description` (optional) provides additional context about the skill's purpose. - -You can create new skills in two ways: - -* **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill. -* **Manual**: Create a new file and use `defineSkill()`, following the same pattern. - -### 代理 - -Agents 通过系统提示词定义可在你的工作区内运行的 AI 智能体。 使用 `defineAgent()` 以内置校验定义智能体: - -```typescript -// src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'An AI agent that helps with sales tasks', - icon: 'IconRobot', - prompt: `You are a sales assistant. Help users with: -1. Researching prospects and companies -2. Drafting personalized outreach messages -3. Tracking follow-ups and next steps -4. Analyzing deal pipeline and suggesting actions`, -}); -``` - -关键点: - -* `name` 是该智能体的唯一标识字符串(推荐使用 kebab-case)。 -* `label` 是在 UI 中显示的人类可读名称。 -* `prompt` 包含系统提示词—这是定义智能体行为的指令文本。 -* `icon`(可选)设置在 UI 中显示的图标。 -* `description`(可选)提供有关智能体用途的更多上下文。 - -你可以通过两种方式创建新智能体: - -* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新智能体的选项。 -* **手动**:创建一个新文件,并使用 `defineAgent()`,遵循相同的模式。 - -### 生成的类型化客户端 - -两个类型化客户端由 `yarn twenty dev` 自动生成(基于你的工作区架构),并存放在 `node_modules/twenty-sdk/clients`: - -* **`CoreApiClient`** — 查询 `/graphql` 端点以获取工作区数据 -* **`MetadataApiClient`** — 查询 `/metadata` 端点以获取工作区配置并处理文件上传 - -```typescript -import { CoreApiClient } from 'twenty-client-sdk/core'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const client = new CoreApiClient(); -const { me } = await client.query({ me: { id: true, displayName: true } }); - -const metadataClient = new MetadataApiClient(); -const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); -``` - -每当你的对象或字段发生变化时,`yarn twenty dev` 都会自动重新生成 `CoreApiClient`。 `MetadataApiClient` 随 SDK 一并提供,已预构建。 - -#### 逻辑函数中的运行时凭据 - -当你的函数在 Twenty 上运行时,平台会在代码执行前将凭据作为环境变量注入: - -* `TWENTY_API_URL`:你的应用所针对的 Twenty API 的基础 URL。 -* `TWENTY_API_KEY`:作用域限定于你的应用默认函数角色的短期密钥。 - -备注: - -* 你无需向生成的客户端传递 URL 或 API 密钥。 它会在运行时从 process.env 读取 `TWENTY_API_URL` 和 `TWENTY_API_KEY`。 -* API 密钥的权限由 `application-config.ts` 中通过 `defaultRoleUniversalIdentifier` 引用的角色决定。 这是你的应用逻辑函数使用的默认角色。 -* 应用可以定义角色以遵循最小权限原则。 仅授予函数所需的权限,然后将 `defaultRoleUniversalIdentifier` 指向该角色的通用标识符。 - -#### 上传文件 - -`MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到你的工作区对象的文件类型字段。 由于标准 GraphQL 客户端不原生支持多部分文件上传,该客户端提供了一个专用方法,在底层实现了 [GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec)。 - -```typescript -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type (defaults to 'application/octet-stream') - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -方法签名: - -```typescript -uploadFile( - fileBuffer: Buffer, - filename: string, - contentType: string, - fieldMetadataUniversalIdentifier: string, -): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> -``` - -| 参数 | 类型 | 描述 | -| ---------------------------------- | -------- | ------------------------------------------------ | -| `fileBuffer` | `Buffer` | 原始文件内容 | -| `filename` | `string` | 文件名称(用于存储和显示) | -| `contentType` | `string` | 文件的 MIME 类型(如果省略,默认为 `application/octet-stream`) | -| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` | - -关键点: - -* `uploadFile` 方法可在 `MetadataApiClient` 上使用,因为上传 mutation 由 `/metadata` 端点解析。 -* 它使用该字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可以在安装了你的应用的任何工作区中使用——这与应用在其他地方引用字段的方式保持一致。 -* 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。 - -### Hello World 示例 - -在[此处](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world)查看一个最小的端到端示例,展示对象、逻辑函数、前端组件和多种触发器: - -## Building your app - -Once you've developed your app with `app:dev`, use `app:build` to compile it into a distributable package. - -```bash filename="Terminal" -# Build the app (output goes to .twenty/output/) -yarn twenty build - -# Build and create a tarball (.tgz) for distribution -yarn twenty build --tarball -``` - -The build process: - -1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure. -2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild. -3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`. -4. **生成类型化的 API 客户端** — 对 GraphQL 架构进行自省,并生成带类型的 `CoreApiClient` 和 `MetadataApiClient` 客户端。 -5. **运行 TypeScript 类型检查** — 运行 `tsc --noEmit` 以在发布前捕获类型错误。 -6. **使用生成的客户端重新构建** — 执行第二次编译,以便包含生成的客户端类型。 -7. **可选地创建一个 tar 包** — 如果传入 `--tarball`,则运行 `npm pack` 以创建用于分发的 `.tgz` 文件。 - -`.twenty/output/` 中的构建产物包含: - -```text -.twenty/output/ -├── manifest.json # Manifest with checksums for all built files -├── package.json # Copied from app root -├── yarn.lock # Copied from app root -├── src/ -│ ├── logic-functions/ # Compiled .mjs logic function files -│ └── front-components/ # Compiled .mjs front component files -├── public/ # Static assets (if any) -└── my-app-1.0.0.tgz # Only with --tarball flag -``` - -| 选项 | 描述 | -| ----------- | ----------------------- | -| `[appPath]` | 应用目录的路径(默认为当前目录) | -| `--tarball` | 同时将输出打包为一个 `.tgz` tar 包 | - -## 发布你的应用 - -使用 `app:publish` 分发你的应用 — 可以发布到 npm 注册表,或直接发布到 Twenty 服务器。 - -### 发布到 npm(默认) - -```bash filename="Terminal" -# Publish to npm (requires npm login) -yarn twenty publish - -# Publish with a dist-tag (e.g. beta, next) -yarn twenty publish --tag beta -``` - -这会构建应用,并在 `.twenty/output/` 目录下运行 `npm publish`。 发布后的软件包可由任何工作区从 Twenty 市场进行安装。 - -### 发布到 Twenty 服务器 - -```bash filename="Terminal" -# Publish directly to a Twenty server -yarn twenty publish --server https://app.twenty.com -``` - -这会以 tar 包方式构建应用,通过 `uploadAppTarball` GraphQL 变更将其上传到服务器,并在一步中触发安装。 这对于私有部署或针对特定服务器进行测试非常有用。 - -| 选项 | 描述 | -| ----------------- | ------------------------------------------ | -| `[appPath]` | 应用目录的路径(默认为当前目录) | -| `--server ` | 发布到 Twenty 服务器(而非 npm) | -| `--token ` | 目标服务器的身份验证令牌 | -| `--tag ` | npm dist-tag(例如 `beta`、`next`)— 仅用于发布到 npm | - -## 应用注册 - -在应用安装到工作区之前,必须先进行**注册**。 注册是一条元数据记录,用于描述应用的来源以及如何对其进行身份验证。 在大多数情况下,CLI 会自动处理这一流程。 - -### 来源类型 - -每个注册都有一个**来源类型**,用于决定安装期间如何解析应用的文件: - -| 来源类型 | 文件的解析方式 | 典型用例 | -| --------- | -------------------------------- | --------------------- | -| `LOCAL` | 文件由 CLI 监听器实时同步——跳过安装步骤 | 使用 `app:dev` 进行开发 | -| `NPM` | 通过 `sourcePackage` 字段从 npm 注册表获取 | 在 npm 上发布的应用 | -| `TARBALL` | 从存储在服务器上的已上传 `.tgz` 文件中解压获得 | 使用 `--server` 发布的私有应用 | - -### 注册如何进行 - -* **`app:dev`** — 第一次针对某个工作区运行开发模式时,会自动创建一个 `LOCAL` 注册。 -* **`app:publish --server`** — 上传一个 tar 包并创建(或更新)一个 `TARBALL` 注册,然后安装应用。 -* **npm 市场** — 当应用从 npm 注册表同步到 Twenty 市场目录时,会创建 `NPM` 注册。 -* **GraphQL API** — 你也可以通过 `createApplicationRegistration` 变更以编程方式创建注册。 - -### 注册与安装 - -**注册** 与 **安装** 是两个独立的概念: - -* **注册**(`ApplicationRegistration`)是一条全局元数据记录,用于描述应用:其名称、来源类型、OAuth 凭据以及在市场中的上架状态。 它独立于任何工作区存在。 -* **安装**(`Application`)是一个按工作区划分的实例。 当用户安装一个应用时,Twenty 会根据注册的来源解析软件包,将构建生成的文件写入存储,并同步清单(创建对象、字段、逻辑函数等) 到该工作区中。 - -一个注册可以安装到多个工作区。 每个工作区都会获得应用文件和数据模型的独立副本。 - -### OAuth 凭据 - -每个注册都包含在创建时生成的 OAuth 凭据(`oAuthClientId` 和 `oAuthClientSecret`)。 应用使用这些凭据代表用户对 API 请求进行身份验证。 客户端密钥在创建时只会返回**一次**——请妥善保管。 你可以稍后通过 `rotateApplicationRegistrationClientSecret` 变更来轮换它。 - -## 手动设置(不使用脚手架) - -虽然我们建议使用 `create-twenty-app` 以获得最佳的上手体验,但你也可以手动设置项目。 不要全局安装 CLI。 相反,请将 `twenty-sdk` 添加为本地依赖,并在你的 package.json 中配置一个脚本: - -```bash filename="Terminal" -yarn add -D twenty-sdk -``` - -然后添加一个 `twenty` 脚本: - -```json filename="package.json" -{ - "scripts": { - "twenty": "twenty" - } -} -``` - -现在你可以通过 `yarn twenty ` 运行所有命令,例如 `yarn twenty dev`、`yarn twenty help` 等。 - -## 故障排除 - -* 身份验证错误:运行 `yarn twenty auth:login`,并确保你的 API 密钥具有所需权限。 -* 无法连接到服务器:请验证 API URL,并确保 Twenty 服务器可达。 -* 类型或客户端缺失/过期:重启 `yarn twenty dev` — 它会自动生成类型化客户端。 -* 开发模式未同步:确保 `yarn twenty dev` 正在运行,并且你的环境不会忽略变更。 - -Discord 帮助频道:https://discord.com/channels/1130383047699738754/1130386664812982322 +See the [Getting Started](/l/zh/developers/extend/apps/getting-started) guide for the full CLI reference.