i18n - docs translations (#22715)

Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22715?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
github-actions[bot]
2026-07-09 11:51:54 +02:00
committed by GitHub
parent a0cf4cc9e1
commit ebee7d71b9
228 changed files with 4216 additions and 4583 deletions
@@ -4,7 +4,7 @@ description: شغّل منطقًا قبل التثبيت أو بعده — لت
icon: wrench
---
خطافات التثبيت هي دوال منطقية خاصة تعمل أثناء دورة حياة التثبيت أو الترقية. تستخدم نفس وقت تشغيل المعالج مثل [دوال المنطق](/l/ar/developers/extend/apps/logic/logic-functions) العادية وتتلقى `InstallPayload`، ولكن يتم التصريح عنها بدوال تعريف خاصة بها — `definePostInstallLogicFunction()` و`definePreInstallLogicFunction()` — وتعمل خارج نموذج المشغّل المعتاد (HTTP، وcron، وأحداث قاعدة البيانات).
خطافات التثبيت هي دوال منطقية خاصة تعمل أثناء دورة حياة التثبيت أو الترقية. تشارك نفس وقت تشغيل المعالج مثل [دوال المنطق](/l/ar/developers/extend/apps/logic/logic-functions) العادية وتتلقى `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — تكون `previousVersion` بقيمة `undefined` في التثبيت الجديد)، ولكن يتم التصريح عنها بدوال تعريف خاصة بها وتعمل خارج نموذج المشغّل المعتاد (HTTP، وcron، وأحداث قاعدة البيانات).
يمكن لكل تطبيق تعريف دالة واحدة على الأكثر لما قبل التثبيت ودالة واحدة على الأكثر لما بعد التثبيت. سيُنتِج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة من أيٍّ منهما.
@@ -19,111 +19,59 @@ icon: wrench
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="تعمل بعد تطبيق ترحيل البيانات الوصفية لمساحة العمل">
## لمحة سريعة
تعمل دالة ما بعد التثبيت تلقائيًا بمجرد انتهاء تثبيت تطبيقك على مساحة عمل. ينفّذه الخادم **بعد** مزامنة البيانات الوصفية للتطبيق وإنشاء عميل SDK، بحيث تكون مساحة العمل جاهزة تمامًا للاستخدام ويكون المخطط الجديد مطبَّقًا. تشمل حالات الاستخدام النموذجية تهيئة البيانات الافتراضية، وإنشاء السجلات الأولية، وتكوين إعدادات مساحة العمل، أو توفير الموارد على خدمات جهات خارجية.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| عمليات التشغيل | قبل ترحيل البيانات الوصفية — لا يزال المخطط والبيانات **السابقة** سليمين | بعد الترحيل وإنشاء الـ SDK — أصبح المخطط **الجديد** في مكانه |
| التنفيذ | دائمًا متزامن؛ يحجب عملية التثبيت | غير متزامن بشكل افتراضي (يُوضَع في قائمة الانتظار، 3 محاولات إعادة)؛ تفعيل التزامن اختياري عبر `shouldRunSynchronously: true` |
| عند الفشل | يتم **إحباط** التثبيت قبل أي تغيير في المخطط | غير متزامن: تُعاد المحاولة حتى 3 مرات. متزامن: يتلقى المستدعي `POST_INSTALL_ERROR` (لن يتم التراجع عن تغييرات المخطط **not**). |
| الاستخدام النموذجي | نسخ احتياطي للبيانات أو إصلاح بيانات قد يفقدها الترحيل؛ رفض ترقية خطِرة عبر الرمي | بذر بيانات افتراضية، تهيئة مساحة العمل، تسجيل موارد خارجية |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**قاعدة عامة:** اجعل الافتراضي هو post-install. الجأ إلى ما قبل التثبيت فقط عندما يكون الترحيل نفسه هدّامًا وتحتاج إلى التقاط الحالة السابقة قبل أن تزول.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| ترغب في... | استخدام |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------- |
| بذر البيانات، تهيئة مساحة العمل، تسجيل موارد خارجية | `post-install` |
| عمل طويل الأمد لا ينبغي أن يحجب استجابة التثبيت | `post-install` (الوضع غير المتزامن الافتراضي، مع محاولات إعادة من العامل) |
| إعداد سريع يعتمد عليه المستدعي مباشرةً بعد عودة التثبيت | `post-install` مع `shouldRunSynchronously: true` |
| قراءة البيانات أو نسخها احتياطيًا والتي قد يفقدها الترحيل القادم | `pre-install` |
| رفض ترقية قد تُفسد البيانات الحالية | `pre-install` (ارمِ من المعالج) |
| تنفيذ مواءمة في كل ترقية | أي من الخطافين مع `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## السلوك المشترك بين كلا الخطافين
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
* إعداد التهيئة هو إعداد `defineLogicFunction` نفسه مطروحًا منه إعدادات المشغّل، مضافًا إليه `shouldRunOnVersionUpgrade`.
* **موعد تشغيله**: في عمليات التثبيت الجديدة فقط، افتراضيًا. عيِّن `shouldRunOnVersionUpgrade: true` لتشغيله أيضًا عند الترقيات. استخدم `previousVersion` / `newVersion` للتفرع حسب مسار الترقية.
* **أهمية اللاّتغيّر (Idempotency)**: قد يُعاد تشغيل post-install غير المتزامن، وأيٌّ من الخطافين يُعاد تشغيله عند الترقيات عندما يكون `shouldRunOnVersionUpgrade` مفعّلًا.
* يتم حقن بيئة دوال المنطق المعتادة (`APPLICATION_ID`، و`APP_ACCESS_TOKEN`، و`API_URL`)، لذا يمكنك استدعاء Twenty API باستخدام رمز التطبيق الخاص بك.
* يُربَط الخطّاف تلقائيًا بملف بيان التطبيق وقت الإنشاء (`preInstallLogicFunction` / `postInstallLogicFunction`) — لا حاجة للإشارة إليه في [`defineApplication()`](/l/ar/developers/extend/apps/config/application).
* القيمة الافتراضية لـ `timeoutSeconds` هي 300 للسماح بمهام إعداد أطول مثل بذر البيانات.
* **غير منفَّذ في نمط التطوير**: يتخطى `yarn twenty dev` تدفق التثبيت ويزامن الملفات مباشرةً، لذا لا تعمل الخطافات هناك مطلقًا. بدلًا من ذلك، شغّلها يدويًا:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
النقاط الرئيسية:
* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — إصدارًا متخصصًا يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`toolTriggerSettings` و`workflowActionTriggerSettings`).
* يتلقى المعالج `InstallPayload` يحتوي على `{ previousVersion?: string; newVersion: string }` — حيث إن `newVersion` هو الإصدار الجاري تثبيته، و`previousVersion` هو الإصدار الذي كان مُثبّتًا سابقًا (أو `undefined` عند التثبيت الأولي). استخدم هذه القيم للتمييز بين عمليات التثبيت الجديدة والترقيات ولتشغيل منطق الترحيل الخاص بالإصدار.
* **موعد تشغيل الخطاف**: في عمليات التثبيت الجديدة فقط، افتراضيًا. مرّر `shouldRunOnVersionUpgrade: true` إذا كنت تريد تشغيله أيضًا عند ترقية التطبيق من إصدار سابق. عند إغفاله، تكون القيمة الافتراضية للعلم `false`، وتتجاوز الترقيات هذا الخطاف.
* **نموذج التنفيذ — غير متزامن افتراضيًا، والتزامني اختياري**: يتحكّم العلم `shouldRunSynchronously` في كيفية تنفيذ ما بعد التثبيت.
* `shouldRunSynchronously: false` *(الإعداد الافتراضي)* — يتم **إدراج الخطاف في قائمة الرسائل** مع `retryLimit: 3` ويعمل بشكل غير متزامن داخل عامل عمل. يعود ردّ التثبيت بمجرد وضع المهمة في الطابور، لذا فإن معالجًا بطيئًا أو متعطلًا لا يحجب المستدعي. سيُجرِّب العامل إعادة المحاولة حتى ثلاث مرات. **استخدم هذا للمهام طويلة التشغيل** — بَذر مجموعات بيانات كبيرة، استدعاء واجهات برمجة تطبيقات خارجية بطيئة، تهيئة موارد خارجية، أو أي شيء قد يتجاوز نافذة استجابة HTTP المعقولة.
* `shouldRunSynchronously: true` — يُنفّذ الخطاف **ضمن تدفّق التثبيت مباشرةً** (نفس المنفِّذ كما قبل التثبيت). يَحجُب طلب التثبيت حتى ينتهي المعالج، وإذا رمى استثناءً، سيتلقى مستدعي التثبيت `POST_INSTALL_ERROR`. لا توجد محاولات إعادة تلقائية. **استخدم هذا للمهام السريعة التي يجب إكمالها قبل الاستجابة** — مثل إظهار خطأ تحقق للمستخدم، أو إعداد سريع سيعتمد عليه العميل مباشرةً بعد عودة نداء التثبيت. ضع في اعتبارك أن ترحيل البيانات الوصفية يكون قد طُبِّق بالفعل عند تشغيل ما بعد التثبيت، لذلك فإن فشل الوضع المتزامن **لا** يعيد التغييرات على المخطط إلى الوراء — بل يكتفي بإبراز الخطأ.
* تأكّد من أن معالجك قابل للتنفيذ المتكرر دون آثار جانبية. في الوضع غير المتزامن قد تُعيد قائمة الانتظار المحاولة حتى ثلاث مرات؛ وفي أي من الوضعين قد يعمل الخطاف مجددًا أثناء الترقيات عند ضبط `shouldRunOnVersionUpgrade: true`.
* متغيرات البيئة `APPLICATION_ID` و`APP_ACCESS_TOKEN` و`API_URL` متاحة داخل المعالج (كما في أي دالة منطق أخرى)، لذا يمكنك استدعاء واجهة Twenty API باستخدام رمز وصول للتطبيق مقيّد بنطاق تطبيقك.
* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
* تُرفَق خصائص الدالة `universalIdentifier` و`shouldRunOnVersionUpgrade` و`shouldRunSynchronously` تلقائيًا ببيان التطبيق ضمن الحقل `postInstallLogicFunction` أثناء عملية البناء — ولا تحتاج إلى الإشارة إليها في [`defineApplication()`](/l/ar/developers/extend/apps/config/application).
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
* **لا يُنفَّذ في وضع التطوير**: عند تسجيل تطبيق محليًا (عبر `yarn twenty dev`)، يتجاوز الخادم تدفّق التثبيت بالكامل ويُزامن الملفات مباشرةً عبر مراقِب CLI — لذا لن يعمل ما بعد التثبيت في وضع التطوير مطلقًا، بغضّ النظر عن `shouldRunSynchronously`. استخدم `yarn twenty dev:function:exec --postInstall` لتشغيله يدويًا على مساحة عمل قيد التشغيل.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="تعمل قبل تطبيق ترحيل البيانات الوصفية لمساحة العمل">
تعمل دالة ما قبل التثبيت تلقائيًا أثناء التثبيت، **قبل تطبيق ترحيل البيانات الوصفية لمساحة العمل**. تتشارك نفس بنية الحمولة مع ما بعد التثبيت (`InstallPayload`)، لكنها موضوعة أبكر في تدفّق التثبيت كي تجهّز حالة يعتمد عليها الترحيل القادم — ومن الاستخدامات الشائعة: نسخ البيانات احتياطيًا، التحقق من التوافق مع المخطط الجديد، أو أرشفة السجلات التي ستُعاد هيكلتها أو ستُحذف.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
النقاط الرئيسية:
* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — نفس الإعدادات المتخصصة كما في ما بعد التثبيت، لكنها مرتبطة بموضع مختلف ضمن دورة الحياة.
* يتلقّى كلٌّ من معالجي ما قبل التثبيت وما بعد التثبيت النوع نفسه `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. استورده مرة واحدة وأعد استخدامه لكلا الخطافين.
* **موعد تشغيل الخطاف**: موضوع مباشرةً قبل ترحيل البيانات الوصفية لمساحة العمل (`synchronizeFromManifest`). قبل التنفيذ، يُشغِّل الخادم مزامنة "pared-down sync" ذات طابع إضافي فقط تقوم بتسجيل دالة ما قبل التثبيت للإصدار **الجديد** في البيانات الوصفية لمساحة العمل — دون لمس أي شيء آخر — ثم يُنفّذها. لأن هذه المزامنة «إضافية فقط»، تبقى كائنات وحقول وبيانات الإصدار السابق سليمة عند تشغيل معالجك: يمكنك قراءة حالة ما قبل الترحيل ونسخها احتياطيًا بأمان.
* **نموذج التنفيذ**: يُنفَّذ ما قبل التثبيت **بشكل متزامن** و**يحجب عملية التثبيت**. إذا رمى المعالج استثناءً، تُلغى عملية التثبيت قبل تطبيق أي تغييرات على المخطط — وتبقى مساحة العمل على الإصدار السابق بحالة متّسقة. هذا مقصود: ما قبل التثبيت هو فرصتك الأخيرة لرفض ترقية تنطوي على مخاطر.
* كما هو الحال مع ما بعد التثبيت، يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. تُربَط تلقائيًا ببيان التطبيق تحت `preInstallLogicFunction` أثناء عملية البناء.
* **لا يُنفَّذ في وضع التطوير**: كما في ما بعد التثبيت — يتم تجاوز تدفّق التثبيت بالكامل للتطبيقات المسجّلة محليًا، لذا لن يعمل ما قبل التثبيت مطلقًا عند `yarn twenty dev`. استخدم `yarn twenty dev:function:exec --preInstall` لتشغيله يدويًا.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="تعمل بعد تطبيق ترحيل البيانات الوصفية لمساحة العمل">
</Accordion>
<Accordion title="ما قبل التثبيت مقابل ما بعد التثبيت: متى تستخدم أيّهما" description="اختيار خطاف التثبيت المناسب">
كلا الخطافين جزء من تدفّق التثبيت نفسه ويتلقّيان نفس `InstallPayload`. الاختلاف يكمن في **موعد** تشغيلهما نسبةً إلى ترحيل البيانات الوصفية لمساحة العمل، وهذا يغيّر البيانات التي يمكنهما التعامل معها بأمان.
ما قبل التثبيت دائمًا **متزامن** (يحجب التثبيت ويمكنه إحباطه). ما بعد التثبيت **غير متزامن افتراضيًا** — يُدرج على عامل مع محاولات إعادة تلقائية — لكن يمكن التبديل إلى تنفيذ متزامن عبر `shouldRunSynchronously: true`. راجع الأكورديون `definePostInstallLogicFunction` أعلاه لمعرفة متى تستخدم كل وضع.
**استخدم `post-install` لأي شيء يتطلّب وجود المخطط الجديد.** وهذا هو السيناريو الشائع:
* بَذر بيانات افتراضية (إنشاء سجلات أولية وعروض افتراضية ومحتوى تجريبي) للكائنات والحقول المضافة حديثًا.
* تسجيل خطافات الويب مع خدمات أطراف ثالثة بعد أن حصل التطبيق على بيانات الاعتماد الخاصة به.
* استدعاء واجهة برمجة التطبيقات الخاصة بك لإكمال إعداد يعتمد على البيانات الوصفية المتزامنة.
* منطق قابل للتنفيذ المتكرر دون آثار جانبية لتحقيق "تأكّد من وجود هذا" والذي ينبغي مواءمة الحالة في كل ترقية — بالاقتران مع `shouldRunOnVersionUpgrade: true`.
مثال — بَذر سجل `PostCard` افتراضي بعد التثبيت:
يعمل بعد انتهاء تثبيت تطبيقك: تمت مزامنة البيانات الوصفية، وتم إنشاء عميل SDK، وأصبح من الممكن الاستعلام عن المخطط الجديد. مثال — بذر سجل افتراضي في عمليات التثبيت الجديدة:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**استخدم `pre-install` عندما قد يُتلف الترحيل أو يدمّر البيانات الحالية.** لأن ما قبل التثبيت يعمل مقابل المخطط *السابق* وفشله يُرجِع الترقية إلى الوراء، فهو المكان المناسب لأي شيء محفوف بالمخاطر:
تتحكم الشارة `shouldRunSynchronously` في نموذج التنفيذ:
* **نسخ البيانات احتياطيًا قبل حذفها أو إعادة هيكلتها** — مثل إزالة حقل في v2 وتحتاج إلى نسخ قيمه إلى حقل آخر أو تصديرها إلى التخزين قبل تشغيل الترحيل.
* **أرشفة السجلات التي سيبطلها قيد جديد** — مثل أن يصبح حقل ما `NOT NULL` وتحتاج أولًا إلى حذف الصفوف ذات القيم الفارغة أو إصلاحها.
* **التحقق من التوافق ورفض الترقية إذا تعذّر ترحيل البيانات الحالية بسلاسة** — ارمِ من داخل المعالج وسيُلغى التثبيت دون تطبيق أي تغييرات. هذا أكثر أمانًا من اكتشاف عدم التوافق في منتصف الترحيل.
* **إعادة تسمية البيانات أو إعادة تعيين مفاتيحها** قبل تغيير في المخطط قد يؤدي إلى فقدان الارتباط.
* `false` *(الإعداد الافتراضي)* — يُوضَع في قائمة انتظار الرسائل (`retryLimit: 3`) ويُشغِّله عامل. تعود استجابة التثبيت بمجرد وضع المهمة في قائمة الانتظار. **يُستخدم للأعمال طويلة الأمد** — بذر مجموعات بيانات كبيرة، وواجهات برمجة تطبيقات بطيئة لأطراف ثالثة.
* `true` — يُنفَّذ مضمَّنًا أثناء تدفق التثبيت. يحجب طلب التثبيت حتى ينتهي المعالج؛ يظهر الخطأ الذي يتم رميه كـ `POST_INSTALL_ERROR` للمستدعي (بدون محاولات إعادة). **يُستخدم للأعمال السريعة التي يجب إتمامها قبل الاستجابة.** تم تطبيق الترحيل بالفعل في هذه المرحلة، لذا لا يؤدي الفشل إلى التراجع عن تغييرات المخطط — بل يُظهِر الخطأ فقط.
مثال — أرشف السجلات قبل ترحيل هدّام:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="تعمل قبل تطبيق ترحيل البيانات الوصفية لمساحة العمل">
يعمل قبل ترحيل البيانات الوصفية، مقابل المخطط **السابق** — المكان المناسب لنسخ البيانات احتياطيًا التي قد يفقدها الترحيل، أو لرفض ترقية خطِرة. قبل التنفيذ، يُشغِّل الخادم مزامنة ذات طابع إضافي فقط "pared-down sync" تُسجِّل دالة ما قبل التثبيت للإصدار الجديد في إصدارها الجديد؛ كل ما عدا ذلك — كائنات الإصدار السابق وحقوله وبياناته — يبقى دون لمس عندما يعمل المعالج.
ما قبل التثبيت دائمًا **متزامن** ويحجب عملية التثبيت. إذا رمى المعالج استثناءً، تُلغى عملية التثبيت قبل أي تغيير في المخطط — وتبقى مساحة العمل على الإصدار السابق بحالة متّسقة. هذا مقصود: ما قبل التثبيت هو فرصتك الأخيرة لرفض ترقية تنطوي على مخاطر.
مثال — نسخ قيم حقل قديم قبل أن يُسقِطه الترحيل:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**قاعدة عامة:**
| ترغب في... | استخدام |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| بذر بيانات افتراضية، تهيئة مساحة العمل، تسجيل موارد خارجية | `post-install` |
| تشغيل بذر طويل الأمد أو استدعاءات أطراف ثالثة لا ينبغي أن تحجب استجابة التثبيت | `post-install` (الإعداد الافتراضي — `shouldRunSynchronously: false`، مع محاولات إعادة من العامل) |
| تشغيل إعداد سريع سيعتمد عليه المستدعي مباشرةً بعد عودة نداء التثبيت | `post-install` مع `shouldRunSynchronously: true` |
| قراءة البيانات أو نسخها احتياطيًا والتي قد يفقدها الترحيل القادم | `pre-install` |
| رفض ترقية قد تُفسد البيانات الحالية | `pre-install` (ارمِ من المعالج) |
| تنفيذ مواءمة في كل ترقية | `post-install` مع `shouldRunOnVersionUpgrade: true` |
| تنفيذ إعداد لمرة واحدة في التثبيت الأول فقط | `post-install` مع `shouldRunOnVersionUpgrade: false` (الإعداد الافتراضي) |
<Note>
إذا ساورك الشك، فاجعل الافتراضي هو **post-install**. الجأ إلى ما قبل التثبيت فقط عندما يكون الترحيل نفسه هدّامًا وتحتاج إلى التقاط الحالة السابقة قبل أن تزول.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**تُضاف الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، ينشئ Twenty حقولًا قياسية مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt` من أجلك. لا تحتاج إلى تعريفها في مصفوفة `fields` — أضف فقط حقولك المخصصة. يمكنك تجاوز حقلًا افتراضيًا بتعريف حقل يحمل الاسم نفسه، لكن هذا نادرًا ما يكون فكرة جيدة.
</Note>
## أنواع الحقول
مجموعة القيم الكاملة لـ`FieldType`، والمصدَّرة من `twenty-sdk/define`:
| الفئة | الأنواع |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| نص | `TEXT`، `RICH_TEXT`، `ARRAY` (من السلاسل النصية)، `RAW_JSON` |
| رقمية | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`)، `NUMERIC` (بدقة عشوائية)، `RATING`، `POSITION` |
| التواريخ | `DATE`, `DATE_TIME` |
| اختيار | `BOOLEAN`، `SELECT`، `MULTI_SELECT` |
| مركّبة | `FULL_NAME`، `ADDRESS`، `EMAILS`، `PHONES`، `LINKS`، `CURRENCY`، `ACTOR`، `FILES` |
| المعرِّفات والعلاقات | `UUID`، `RELATION`، `MORPH_RELATION` (انظر [العلاقات](/l/ar/developers/extend/apps/data/relations)) |
| النظام | `TS_VECTOR` (متجه بحث نصي كامل، يتم إدارته بواسطة الخادم) |
تُخزِّن الأنواع المركّبة عدّة حقول فرعية (مثلًا `FULL_NAME` = الاسم الأول + اسم العائلة؛ `CURRENCY` = `amountMicros` + `currencyCode`). يتطلّب `SELECT` و`MULTI_SELECT` مصفوفة `options` كما في المثال أعلاه.
## القيم الافتراضية
يجب تضمين القيم النصية الافتراضية بين علامات اقتباس أحادية **داخل** السلسلة — `defaultValue: "'Draft'"`، وليس `defaultValue: "Draft"`. لهذا السبب يستخدم الحقل `status` أعلاه `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## الملفات الرئيسية
| ملف / مجلد | الغرض |
| ---------------------------------------- | ------------------------------------------------------------------- |
| `src/application-config.ts` | **مطلوب.** ملف الإعداد الرئيسي لتطبيقك. |
| `src/default-role.ts` | دور افتراضي يتحكّم بما يمكن لدوال المنطق الوصول إليه. |
| `src/constants/universal-identifiers.ts` | معرّفات UUID وبيانات تعريف يتم توليدها تلقائيًا (اسم العرض، الوصف). |
| `src/__tests__/` | اختبارات تكامل (إعداد + اختبار مثال). |
| `public/` | أصول ثابتة (صور، خطوط) تُقدَّم مع تطبيقك. |
| ملف / مجلد | الغرض |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `src/application-config.ts` | **مطلوب.** ملف الإعداد الرئيسي لتطبيقك. |
| `src/default-role.ts` | دور افتراضي يتحكّم بما يمكن لدوال المنطق الوصول إليه. |
| `src/constants/universal-identifiers.ts` | معرّفات UUID وبيانات تعريف يتم توليدها تلقائيًا (اسم العرض، الوصف). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | صفحة ترحيب مبدئية: مكوّن واجهة أمامية يتم تقديمه بواسطة مخطط صفحة مستقل، يمكن الوصول إليه من الشريط الجانبي. |
| `src/__tests__/` | اختبار وحدة بالإضافة إلى اختبار تكامل (مع إعداده العام) يقوم بمزامنة التطبيق مع خادم حقيقي. |
| `public/` | أصول ثابتة (صور، خطوط) تُقدَّم مع تطبيقك. |
| `AGENTS.md` / `CLAUDE.md` | إرشادات لوكلاء برمجة الذكاء الاصطناعي الذين يعملون على التطبيق. |
<Note>
**تنظيم الملفات متروك لك.** المجلدات المذكورة أعلاه هي أعراف متَّبعة — يكتشف SDK الكيانات عبر تحليل AST على استدعاءات `export default defineEntity(...)` بغض النظر عن مكان وجود الملف.
@@ -47,15 +60,18 @@ my-twenty-app/
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
يقوم أداة إنشاء الهيكل بتثبيت إصداري `twenty-sdk` و `twenty-client-sdk` على الإصدار الخاص بها — حافظ على تزامن الاثنين عند الترقية.
* توفّر **`twenty-sdk`** أداة `twenty` CLI وأدوات البناء/إنشاء الهياكل (scaffolding). يعمل فقط أثناء التطوير ووقت البناء، ولا يتم استيراده أبدًا في وقت تشغيل تطبيقك المنشور.
* يتم استيراد **`twenty-client-sdk`** بواسطة كود تطبيقك (`CoreApiClient`، `MetadataApiClient`، `RestApiClient`)؛ لكن Twenty توفّره في وقت التشغيل — حيث تحصل عليه دوال المنطق من طبقة SDK مُولَّدة، وتحصل عليه مكوّنات الواجهة من وحدات يتم تقديمها من الخادم. يُستخدَم الإصدار المثبّت لديك فقط لفحص الأنواع (typechecking) ولبناء النشر (deploy-time build)، لذا لا يلزم أبدًا أن يتم تضمينه في حزمة النشر.
الاحتفاظ بأي من الحزمتين ضمن `dependencies` يؤدي إلى سحبها داخل حزمة وقت تشغيل التطبيق المثبّت، حيث تكون عبئًا زائدًا بلا فائدة. يُطلق `twenty build` تحذيرًا عندما تكون أيٌّ منهما ما تزال مدرجة ضمن `dependencies`.
الاحتفاظ بأي من الحزمتين ضمن `dependencies` يؤدي إلى سحبها داخل حزمة وقت تشغيل التطبيق المثبّت، حيث تكون عبئًا زائدًا بلا فائدة. يُطلق `twenty dev:build` تحذيرًا عندما تكون أيٌّ منهما ما تزال مدرجة ضمن `dependencies`.
أضِف تبعيات وقت التشغيل الخاصة بتطبيقك (المكتبات التي تستوردها دوال المنطق لديك فعلًا في وقت التشغيل) ضمن `dependencies` كالمعتاد.
@@ -6,17 +6,17 @@ description: أنشئ أول تطبيق Twenty خلال دقائق.
## المتطلبات الأساسية
* **Node.js 24+** — [تنزيل](https://nodejs.org/)
* **Node.js 24.5+** — [تنزيل](https://nodejs.org/)
* **Yarn 4** — يأتي مع Node.js عبر Corepack. قم بتمكينه: `corepack enable`
* **Docker** — [تنزيل](https://www.docker.com/products/docker-desktop/). مطلوب لتشغيل خادم Twenty محليًا. تخطَّ ذلك إذا كان لديك Twenty يعمل في مكان آخر.
يتكوّن إنشاء تطبيق Twenty من ثلاث مراحل. تقوم أداة توليد الهيكل بدمجها في أمر واحد لمسار الاستخدام المثالي، لكن كل مرحلة تمثّل مفهومًا منفصلًا — وعند حدوث فشل، فإن معرفة المرحلة التي أنت فيها تُخبرك بما ينبغي إصلاحه.
| المرحلة | ماذا تفعل | الأداة | النتيجة |
| ------------------- | ---------------------------------- | ----------------------------- | ------------------------------- |
| **1. تهيئة الهيكل** | توليد الشفرة المصدرية للتطبيق | `npx create-twenty-app` | مشروع TypeScript على القرص |
| **2. تشغيل خادم** | بدء تشغيل خادم Twenty للمزامنة معه | Docker + `yarn twenty server` | مثيل Twenty قيد التشغيل |
| **3. مزامنة** | قم بمزامنة شفرتك مباشرةً مع الخادم | `yarn twenty dev` | تظهر تغييراتك في واجهة المستخدم |
| المرحلة | ماذا تفعل | الأداة | النتيجة |
| ------------------- | ---------------------------------- | ----------------------------------- | ------------------------------- |
| **1. تهيئة الهيكل** | توليد الشفرة المصدرية للتطبيق | `npx create-twenty-app` | مشروع TypeScript على القرص |
| **2. تشغيل خادم** | بدء تشغيل خادم Twenty للمزامنة معه | Docker + `yarn twenty docker:start` | مثيل Twenty قيد التشغيل |
| **3. مزامنة** | قم بمزامنة شفرتك مباشرةً مع الخادم | `yarn twenty dev` | تظهر تغييراتك في واجهة المستخدم |
---
@@ -28,7 +28,7 @@ description: أنشئ أول تطبيق Twenty خلال دقائق.
npx create-twenty-app@latest my-twenty-app
```
ستتم مطالبتك باسم ووصف — اضغط **Enter** للقيم الافتراضية. يُنشئ هذا مشروع TypeScript في `my-twenty-app/` يتضمن ملف بداية `application-config.ts`، ودورًا افتراضيًا، وسير عمل CI، واختبار تكامل.
أداة التهيئة غير تفاعلية: يصبح اسم الدليل هو اسم التطبيق. مرِّر `--display-name` و`--description` لتخصيص البيانات الوصفية المُولَّدة (يمكنك أيضًا تعديلها لاحقًا في `src/constants/universal-identifiers.ts`). يُنشئ هذا مشروع TypeScript في `my-twenty-app/` يتضمّن ملف بداية `application-config.ts`، ودورًا افتراضيًا، وسير عمل CI/CD، واختبار تكامل.
**بعد هذه المرحلة:** سيكون لديك الشفرة المصدرية لتطبيق على جهازك. ليس قيد التشغيل بعد — وهذه هي المرحلة 2.
@@ -38,28 +38,14 @@ npx create-twenty-app@latest my-twenty-app
يحتاج تطبيقك إلى خادم Twenty للمزامنة معه. الخادم هو مثيل Twenty كامل — واجهة مستخدم، واجهة برمجة تطبيقات GraphQL، PostgreSQL — يعمل محليًا داخل Docker. ترفع شفرتك المحلية تعريفاتها إلى ذلك الخادم، مما يجعلها تظهر في واجهة المستخدم.
تقترح أداة توليد الهيكل تشغيل خادم لك:
يقوم مُنشئ الهياكل بتشغيل مثيل لك: مع تشغيل Docker، يسحب صورة `twentycrm/twenty-app-dev`، ويبدأها على المنفذ `2020`، ويُجري مصادقة أداة CLI على مساحة العمل التجريبية المهيأة مسبقًا (`tim@apple.dev`) — دون الحاجة إلى تسجيل الدخول.
> **هل ترغب في إعداد مثيل محلي من Twenty؟**
* **نعم (موصى به)** — ستسحب صورة Docker `twentycrm/twenty-app-dev` وتبدأ تشغيلها على المنفذ `2020`. تأكّد أولًا من أن Docker قيد التشغيل.
* **لا** — اختر هذا إذا كان لديك بالفعل خادم Twenty تريد الاتصال به. يمكنك ربطه لاحقًا باستخدام `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="هل يجب بدء المثيل المحلي؟" />
</div>
بمجرد أن يصبح الخادم جاهزًا، سيفتح المتصفح لإجراء تسجيل الدخول. استخدم حساب العرض التوضيحي المُجهَّز مسبقًا:
* **البريد الإلكتروني:** `tim@apple.dev`
* **كلمة المرور:** `tim@apple.dev`
للاتصال بخادم Twenty موجود بدلاً من ذلك، مرِّر الخيار `--url \<your-server-url>`. تُجري الخوادم البعيدة المصادقة باستخدام OAuth: يفتح المتصفح حتى تتمكن من تسجيل الدخول والنقر على **Authorize**، مما يمنح أداة CLI حق الوصول إلى مساحة عملك. (يمكنك أيضًا اختيار استخدام OAuth محليًا باستخدام `--authentication-method oauth` — سجِّل الدخول باستخدام `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="شاشة تسجيل الدخول إلى Twenty" />
</div>
انقر **Authorize** في الشاشة التالية — يمنح هذا واجهة سطر الأوامر CLI حق الوصول إلى مساحة العمل الخاصة بك.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="شاشة تفويض واجهة الأوامر (CLI) الخاصة بـ Twenty" />
</div>
@@ -117,28 +103,32 @@ yarn twenty dev
### مزامنة لمرة واحدة لـ CI والبرامج النصية
مرّر `--once` لتشغيل عملية بناء واحدة + مزامنة واحدة ثم الخروج — نفس خط الأنابيب، من دون مراقِب:
استخدم `plan` و`apply` لتشغيل نفس خط الأنابيب مرة واحدة، بدون أداة مراقبة (watcher):
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| أمر | السلوك | متى يُستخدم |
| ---------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `yarn twenty dev` | يراقب ويعيد المزامنة عند كل تغيير. يستمر في العمل حتى توقفه. | تطوير محلي تفاعلي. |
| `yarn twenty dev --once` | بناء واحد + مزامنة واحدة، يخرج برمز `0` عند النجاح، و`1` عند الفشل. | CI، وخطافات ما قبل الالتزام، ووكلاء الذكاء الاصطناعي، وسير عمل مكتوب بنصوص. |
| `yarn twenty dev --once --dry-run` | يبني تغييرات البيانات الوصفية ويطبعها **من دون تطبيقها**. | فحص ما الذي سيُغيِّره التزامن قبل تطبيقه. |
| أمر | السلوك | متى يُستخدم |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `yarn twenty dev` | يراقب ويعيد المزامنة عند كل تغيير. يستمر في العمل حتى توقفه. | تطوير محلي تفاعلي. |
| `yarn twenty apply` | بناء واحد + مزامنة واحدة، يخرج برمز `0` عند النجاح، و`1` عند الفشل. يطلب تأكيدًا عند إجراء تغييرات مدمِّرة (مرِّر `--force` لتجاوز ذلك). | CI، وخطافات ما قبل الالتزام، ووكلاء الذكاء الاصطناعي، وسير عمل مكتوب بنصوص. |
| `yarn twenty plan` | يبني تغييرات البيانات الوصفية ويطبعها **من دون تطبيقها**. | فحص ما الذي سيُغيِّره التزامن قبل تطبيقه. |
كلا الوضعين يحتاجان إلى جهة بعيدة موثَّقة. راجع قسم [المزامنة والاستعادة](/l/ar/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run) للحصول على المزيد من المعلومات حول `--dry-run`.
جميع الأوضاع تحتاج إلى جهة بعيدة موثَّقة. راجع قسم [المزامنة والاستعادة](/l/ar/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) للحصول على مزيد من المعلومات حول `plan`.
<Note>
الأوامر `yarn twenty dev --once` و`yarn twenty dev --once --dry-run` هي أسماء بديلة مهملة للأمرين `yarn twenty apply` و`yarn twenty plan`.
</Note>
### خيارات وضع التطوير
| خيار | الوصف |
| ------------------------------------- | ------------------------------------------------------------------------------------- |
| `--once` | قم بالإنشاء والمزامنة مرة واحدة، ثم اخرج. |
| `--dry-run` | باستخدام `--once`، يمكنك معاينة تغييرات البيانات الوصفية دون تطبيقها. لا يكتب أي شيء. |
| `--debounceMs \<ms>` | اضبط مهلة إزالة الارتداد لتغييرات الملفات بالميلي ثانية (القيمة الافتراضية: `2000`). |
| `--verbose` / `--debug` | إظهار سجلات إنشاء تفصيلية، وطلبات المزامنة، وتتبع الأخطاء. |
| خيار | الوصف |
| ------------------------------------- | ------------------------------------------------------------------------------------ |
| `--force` | تطبيق التغييرات المدمِّرة (الحذف) بدون تأكيد. |
| `--debounceMs \<ms>` | اضبط مهلة إزالة الارتداد لتغييرات الملفات بالميلي ثانية (القيمة الافتراضية: `1000`). |
| `--verbose` / `--debug` | إظهار سجلات إنشاء تفصيلية، وطلبات المزامنة، وتتبع الأخطاء. |
## ما الذي يمكنك بناؤه
@@ -22,18 +22,22 @@ yarn twenty dev:add frontComponent
## أنواع الكيانات المتاحة
| نوع الكيان | أمر | الملف المُولَّد |
| ------------------ | ---------------------------------------- | ------------------------------------------------------- |
| كائن | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| الحقل | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| دالة منطقية | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| مكوّن أمامي | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| دور | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| مهارة | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| وكيل | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| عرض | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| عنصر قائمة التنقّل | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| تخطيط الصفحة | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| نوع الكيان | أمر | الملف المُولَّد |
| ------------------------ | ---------------------------------------- | ------------------------------------------------------- |
| كائن | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| الحقل | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| دالة منطقية | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| مكوّن أمامي | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| دور | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| مهارة | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| وكيل | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| عرض | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| عنصر قائمة التنقّل | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| تخطيط الصفحة | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| علامة تبويب تخطيط الصفحة | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| عنصر قائمة الأوامر | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| حقل العرض | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| موفر الاتصال | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## ما الذي تُنشئه أداة القوالب
@@ -5,10 +5,10 @@ icon: wrench
---
* **أخطاء Docker** — تأكّد من أن Docker Desktop (أو الـ daemon) قيد التشغيل قبل `yarn twenty docker:start`. ستعرض رسالة الخطأ أمر البدء المناسب لنظام التشغيل لديك.
* **إصدار Node غير صحيح** — نحتاج 24 أو أحدث. تحقّق باستخدام `node -v`.
* **إصدار Node غير صحيح** — تحتاج إلى 24.5+ (`engines.node: ^24.5.0`). تحقّق باستخدام `node -v`.
* **Yarn 4 غير موجود** — شغّل `corepack enable`.
* **تبعيات تالفة** — `rm -rf node_modules && yarn install`.
* **أخطاء `twenty-sdk` بعد الترقية إلى v2.8.0** — تم نقله من `dependencies` إلى `devDependencies` في الإصدار v2.8.0. انظر إلى [بنية المشروع → التبعيات](/l/ar/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` يُصدر تحذيرًا بشأن `twenty-client-sdk` تحت `dependencies`** — يتم توفيره في وقت التشغيل بواسطة Twenty، لذلك يجب نقله إلى `devDependencies` جنبًا إلى جنب مع `twenty-sdk`. انظر إلى [بنية المشروع → التبعيات](/l/ar/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` يُصدر تحذيرًا بشأن `twenty-client-sdk` تحت `dependencies`** — يتم توفيره في وقت التشغيل بواسطة Twenty، لذلك يجب نقله إلى `devDependencies` جنبًا إلى جنب مع `twenty-sdk`. انظر إلى [بنية المشروع → التبعيات](/l/ar/developers/extend/apps/getting-started/project-structure#dependencies).
هل علِقت؟ اطلب المساعدة على [خادم Twenty على Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -13,7 +13,6 @@ export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
icon: 'IconLayoutDashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
@@ -22,51 +21,23 @@ export default defineCommandMenuItem({
## حقول التكوين
| الحقل | مطلوب | الوصف |
| --------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `universalIdentifier` | نعم | معرّف فريد ثابت للأمر |
| `label` | نعم | التسمية الكاملة المعروضة في قائمة الأوامر (Cmd+K) |
| `frontComponentUniversalIdentifier` | نعم | قيمة `universalIdentifier` للمكوّن الأمامي الذي يفتحه هذا الأمر |
| `shortLabel` | لا | تسمية أقصر تُعرَض على زر الإجراء السريع المثبّت |
| `icon` | لا | اسم الأيقونة المعروض بجانب التسمية (مثل `'IconBolt'` و`'IconSend'`) |
| `isPinned` | لا | عند كونها `true`، يعرض الأمر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة |
| `availabilityType` | لا | تتحكّم في مكان ظهور الأمر: `'GLOBAL'` (متاح دائمًا)، و`'RECORD_SELECTION'` (فقط عند تحديد سجلات)، أو `'FALLBACK'` (يُعرَض عند عدم تطابق أي أوامر أخرى) |
| `availabilityObjectUniversalIdentifier` | لا | تقييد الأمر بصفحات نوع كائن معيّن (مثل سجلات Company فقط) |
| `conditionalAvailabilityExpression` | لا | تعبير منطقي يتحكّم ديناميكيًا في الظهور (انظر أدناه) |
| الحقل | مطلوب | الوصف |
| --------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | نعم | معرّف فريد ثابت للأمر |
| `label` | نعم | التسمية الكاملة المعروضة في قائمة الأوامر (Cmd+K) |
| `frontComponentUniversalIdentifier` | نعم | قيمة `universalIdentifier` للمكوّن الأمامي الذي يفتحه هذا الأمر |
| `shortLabel` | لا | تسمية أقصر تُعرَض على زر الإجراء السريع المثبّت |
| `icon` | لا | **مهمل** — يتم تجاهله لصالح أيقونة التطبيق؛ يُصدر البناء تحذيرًا إذا تم تعيينه |
| `isPinned` | لا | عند كونها `true`، يعرض الأمر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة |
| `availabilityType` | لا | يتحكّم في مكان ظهور الأمر: `'GLOBAL'` (متاح دائمًا)، و`'GLOBAL_OBJECT_CONTEXT'` (فقط في الصفحات ذات سياق الكائن — صفحات الفهرس والسجل)، و`'RECORD_SELECTION'` (فقط عند تحديد سجلات)، أو `'FALLBACK'` (يُعرَض عند عدم تطابق أي أوامر أخرى) |
| `availabilityObjectUniversalIdentifier` | لا | تقييد الأمر بصفحات نوع كائن معيّن (مثل سجلات Company فقط) |
| `conditionalAvailabilityExpression` | لا | تعبير منطقي يتحكّم ديناميكيًا في الظهور (انظر أدناه) |
## أوامر بدون واجهة
يُعَدّ عنصر قائمة الأوامر المقترن بـ[مكوّن واجهة أمامية بدون واجهة](/l/ar/developers/extend/apps/layout/front-components#headless-vs-non-headless) الطريقة القياسية لتوفير إجراء بنقرة واحدة — لتشغيل الشفرة أو التنقّل أو التأكيد ثم التنفيذ. تغطي صفحة مكوّنات الواجهة الأمامية [مكوّنات الأوامر في SDK](/l/ar/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) التي تتعامل مع نمط الإجراء-ثم-إلغاء التركيب.
تدفق نموذجي:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
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 <Command execute={execute} />;
};
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 execute={...} />` (اطّلع على [المثال الكامل](/l/ar/developers/extend/apps/layout/front-components#sdk-command-components))، ويشير عنصر قائمة الأوامر إليه:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -74,7 +45,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -49,14 +49,13 @@ export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
بعد المزامنة باستخدام `yarn twenty dev` (أو تشغيل الأمر لمرة واحدة `yarn twenty dev --once`)، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة:
بعد المزامنة باستخدام `yarn twenty dev` (أو تشغيل الأمر لمرة واحدة `yarn twenty apply`)، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="زر إجراء سريع في الزاوية العلوية اليمنى" />
@@ -88,11 +87,11 @@ export default defineCommandMenuItem({
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
@@ -116,7 +115,7 @@ export default defineFrontComponent({
توفر حزمة `twenty-sdk` أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء.
استوردها من `twenty-sdk/command`:
استوردها من `twenty-sdk/front-component`:
* **`Command`** — يشغّل رد نداء غير متزامن عبر الخاصية `execute`.
* **`CommandLink`** — ينتقل إلى مسار في التطبيق. الخصائص: `to`، `params`، `queryParams`، `options`.
@@ -127,8 +126,8 @@ export default defineFrontComponent({
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
@@ -160,7 +159,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -169,7 +167,7 @@ export default defineCommandMenuItem({
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
@@ -202,7 +200,7 @@ export default defineFrontComponent({
يتم الوصول إلى الدالة المنطقية المُعلَنة باستخدام `httpRouteTriggerSettings` عبر HTTP عند مسار التوجيه الخاص بها. يقوم Twenty بحقن عنوان URL الأساسي الذي تُقدَّم منه الدوال الخاصة بك في عامل التشغيل على أنه `TWENTY_FUNCTIONS_URL`، إلى جانب `TWENTY_APP_ACCESS_TOKEN` الذي يُصادِّق الاستدعاء. لا يوجد عميل SDK مخصص لاستدعاء دوالك الخاصة بعد، لذا استدعِها باستخدام `fetch` عادي:
> **على Twenty Cloud، يتم تقديم الدوال المنطقية المُفعَّلة عبر HTTP على نطاق مخصص لكل مساحة عمل** عند `https://\<your-workspace-subdomain>.twenty.com\<path>` — وهذا بالضبط ما تُشير إليه قيمة `TWENTY_FUNCTIONS_URL`. للمتصلين الخارجيين، انسخ عنوان URL الدقيق من إعدادات **HTTP trigger** الخاصة بالدالة أو من علامة تبويب **Settings** في التطبيق.
> **على Twenty Cloud، يتم تقديم الدوال المنطقية المُفعَّلة عبر HTTP على نطاق مخصص لكل مساحة عمل** عند `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — وهذا بالضبط ما تُشير إليه قيمة `TWENTY_FUNCTIONS_URL`. للمتصلين الخارجيين، انسخ عنوان URL الدقيق من إعدادات **HTTP trigger** الخاصة بالدالة أو من علامة تبويب **Settings** في التطبيق.
<Warning>
مسار الدالة القديم `/s/` **مهمَل (deprecated)** وسيتم **إيقاف تفعيله في 2026-07-24**. استخدم بدلًا من ذلك `TWENTY_FUNCTIONS_URL` (أعلاه)، ورحِّل أي عناوين URL ثابتة من نوع `/s/` قبل ذلك التاريخ. يبقى مسار `/s/` متاحًا للاستضافة الذاتية.
@@ -212,7 +210,7 @@ export default defineFrontComponent({
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
@@ -316,13 +314,13 @@ try {
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
@@ -405,12 +403,11 @@ export default defineFrontComponent({
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
@@ -451,10 +448,10 @@ export default defineFrontComponent({
استخدم `useSelectedRecordIds()` لمعالجة عدة سجلات محددة. هذا مفيد للعمليات المجمّعة:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
@@ -492,12 +489,19 @@ export default defineFrontComponent({
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
أبرِزْه باستخدام [عنصر قائمة الأوامر](/l/ar/developers/extend/apps/layout/command-menu-items) المقيّد بتحديدات السجلات:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
@@ -35,6 +35,8 @@ export default defineNavigationMenuItem({
* `position` يتحكّم في الترتيب ضمن الشريط الجانبي.
* يحتوي التعداد أيضًا على `NavigationMenuItemType.RECORD`، ويُستخدم داخليًا للمفضلات الخاصة بالسجلات التي ينشئها المستخدم — ولا يمكن استخدامه من بيان التطبيق (app manifest) لأنه لا يوجد حقل للإشارة إلى سجل).
* `icon` و`color` اختياريان ويخصّصان مظهر الإدخال.
* `folderUniversalIdentifier` متاح أيضًا على أي عنصر لوضعه متداخلًا داخل عنصر أب من النوع `FOLDER`.
@@ -33,17 +33,32 @@ export default defineView({
## النقاط الرئيسية
* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا العرض. يمكن أن يكون كائنًا مخصصًا قمتَ بتعريفه أو كائن Twenty قياسيًا.
* يحدّد `key` نوع العرض — يمثّل `ViewKey.INDEX` عرض القائمة الرئيسي للكائن.
* `key: ViewKey.INDEX` يحدد العرض بوصفه عرض القائمة الرئيسي للكائن (ذلك الذي يفتحه عنصر التنقل `OBJECT`).
* يتحكّم `fields` في الأعمدة التي تظهر وترتيبها. يشير كل حقل إلى `fieldMetadataUniversalIdentifier`.
* يمكنك أيضًا تعريف `filters` و`filterGroups` و`groups` و`fieldGroups` لتكوينات أكثر تقدمًا.
* يمكنك أيضًا تعريف `filters` و`filterGroups` و`sorts` و`groups` و`fieldGroups` لتكوينات أكثر تقدمًا.
* يتحكّم `position` في الترتيب عند وجود عدة عروض لنفس الكائن.
## الخصائص الاختيارية
| الخاصية | القيم | الوصف |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | `ViewType.TABLE` (الوضع الافتراضي)، `ViewType.KANBAN`، `ViewType.CALENDAR` | كيفية ترتيب السجلات في الواجهة. (`FIELDS_WIDGET` / `TABLE_WIDGET` موجودة أيضًا ولكن يتم استخدامها داخليًا بواسطة عناصر واجهة تخطيط الصفحة.) |
| `visibility` | `ViewVisibility.WORKSPACE` (الوضع الافتراضي)، `ViewVisibility.UNLISTED` | ما إذا كان العرض مُدرجًا على مستوى مساحة العمل بالكامل أو مخفيًا من أدوات الاختيار. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (الوضع الافتراضي)، `ViewOpenRecordIn.RECORD_PAGE` | المكان الذي يتم فتح السجل فيه عند النقر عليه. |
| `sorts` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | ترتيب الفرز الافتراضي. |
| `isCompact` | `boolean` | عرض الصفوف بشكل مضغوط. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | تجميع السجلات (مثل أعمدة كانبان) حسب حقل معيّن. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | تجميعات وأحجام أعمدة كانبان. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | عروض التقويم: التخطيط وحقل التاريخ الذي يحدد موضع السجلات. |
يتم تصدير جميع التعدادات أعلاه من `twenty-sdk/define`.
## الفلاتر
يمكن أن تأتي طريقة العرض مع عوامل تصفية مُطبَّقة مسبقًا. لكل عامل تصفية ثلاثة مكونات: **الحقل** الذي تُطبَّق عليه التصفية، و**المعامل** (كيفية المقارنة)، و**القيمة** (ما تتم المقارنة به). يجب أن تتطابق العناصر الثلاثة جميعًا — حيث سيتم رفض استخدام معامل لا ينطبق على نوع الحقل في وقت المزامنة.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
أنواع المشغّلات المتاحة:
* **httpRoute**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**:
> مثال: `path: '/post-card/create'` يمكن استدعاؤه عبر `https://your-twenty-server.com/s/post-card/create`
* **httpRoute**: يعرض الدالة الخاصة بك على مسار وطريقة HTTP في مساحة العمل الخاصة بك **URL الأساسي للوظائف** - القيمة العشرين حقن كـ `TWENTY_FUNCTIONS_URL` (على 20 Cloud, مجال مخصص لكل عمل:
> مثال: `path: '/post-card/create'` يمكن استدعاؤه عبر `https://your-workspace.withtwenty.com/post-card/create`
<Warning>
مسار البادئة القديم `/s/' (https://your-twenty-server.com/s/post-card/create`) **مهمل على 20 Cloud** وسيتم إبطاله على **2026-07-24**. يبقى متاحا للحالات التي تستضيف ذاتيا أو المحلية والتي لا تشكل نطاق وظائف معزولة - استخدم `TWENTY_FUNCTIONS_URL` عند تعيينه. والعودة إلى `\<server-url>/s/\<path>` خلاف ذلك.
</Warning>
<Note>
لاستدعاء دالة منطقية يتم تشغيلها بواسطة مسار من مكون واجهة (بدون واجهة رسومية)، راجع قسم [استدعاء دالة منطقية](/l/ar/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -40,13 +40,13 @@ icon: bolt
دالة المنطق تختار واحدًا أو أكثر من المشغلات — كل إدخال أدناه هو حقل منفصل في `defineLogicFunction()`:
| المشغّل | متى يعمل | الإعداد |
| ---------------------- | -------------------------------------------------------------- | ------------------------------- |
| **مسار HTTP** | طلب يصل إلى نقطة نهاية `/s/\<path>` الخاصة بك | `httpRouteTriggerSettings` |
| **كرون** | عند تطابق تعبير CRON | `cronTriggerSettings` |
| **حدث قاعدة البيانات** | يتم إنشاء سجل في مساحة العمل أو تحديثه أو حذفه | `databaseEventTriggerSettings` |
| **أداة ذكاء اصطناعي** | ميزة ذكاء اصطناعي في Twenty تقرر استدعاء دالتك | `toolTriggerSettings` |
| **إجراء سير العمل** | تستدعي خطوة في سير العمل دالتك | `workflowActionTriggerSettings` |
| المشغّل | متى يعمل | الإعداد |
| ---------------------- | ---------------------------------------------- | ------------------------------- |
| **مسار HTTP** | طلب يضرب عنوان URL العام لوظيفتك | `httpRouteTriggerSettings` |
| **كرون** | عند تطابق تعبير CRON | `cronTriggerSettings` |
| **حدث قاعدة البيانات** | يتم إنشاء سجل في مساحة العمل أو تحديثه أو حذفه | `databaseEventTriggerSettings` |
| **أداة ذكاء اصطناعي** | ميزة ذكاء اصطناعي في Twenty تقرر استدعاء دالتك | `toolTriggerSettings` |
| **إجراء سير العمل** | تستدعي خطوة في سير العمل دالتك | `workflowActionTriggerSettings` |
تعمل الدوال ضمن عمليات Node.js معزولة، وتصل إلى مساحة العمل عبر عميل واجهة برمجة تطبيقات مضبوط الأنواع ومحدّد النطاق بالدور المصرّح عنه في [`defineApplication()`](/l/ar/developers/extend/apps/config/application).
@@ -4,7 +4,25 @@ description: أوامر yarn twenty لتنفيذ الدوال، وبثّ الس
icon: terminal
---
إلى جانب `dev` و`dev:build` و`dev:add` و`dev:typecheck`، يوفّر `yarn twenty` CLI أوامر لتنفيذ الدوال، وعرض السجلات، وإدارة تثبيتات التطبيقات.
تُعد واجهة سطر الأوامر `yarn twenty` وسيلتك للتعامل مع كل ما يتعلق بالتطبيق. القائمة الكاملة للأوامر:
| أمر | ماذا يفعل | موثَّق في |
| ----------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `dev` | يراقب ملفات المصدر ويزامن التغييرات مباشرة | [البدء السريع](/l/ar/developers/extend/apps/getting-started/quick-start) |
| `الخطة` | معاينة تغييرات البيانات الوصفية بدون تطبيقها | [المزامنة والاستعادة](/l/ar/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `تطبيق` | تطبيق تغييرات البيانات الوصفية بعد عرض الخطة | [المزامنة والاستعادة](/l/ar/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | ترجمة التطبيق وإنشاء عميل واجهة برمجة التطبيقات (استخدم `--tarball` لحزم ملف `.tgz`) | [النشر](/l/ar/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | تشغيل فحص الأنواع في TypeScript | [الاختبار](/l/ar/developers/extend/apps/operations/testing) |
| `dev:add` | إنشاء هيكل كيان جديد | [إنشاء الهياكل](/l/ar/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | إعادة إنشاء عميل واجهة برمجة التطبيقات مضبوط الأنواع | هذه الصفحة |
| `dev:function:exec` / `dev:function:logs` | تنفيذ الدوال وبث سجلاتها | هذه الصفحة |
| `dev:translations-extract` | استخراج السلاسل القابلة للترجمة إلى كتالوجات `locales/` | [الترجمات](/l/ar/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | تشغيل مزامنة كتالوج السوق | [النشر](/l/ar/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | دورة حياة الإصدارات | [النشر](/l/ar/developers/extend/apps/operations/publishing) وهذه الصفحة |
| `docker:*` | إدارة حاوية خادم Twenty المحلي | [الخادم المحلي](/l/ar/developers/extend/apps/getting-started/local-server) |
| `remote:*` | إدارة اتصالات الخادم | هذه الصفحة |
كل أمر يقبل الوسيط `-r, --remote \<name>` لاستهداف خادم بعيد معيَّن بدلاً من الخادم الافتراضي.
## تنفيذ الدوال (`yarn twenty dev:function:exec`)
@@ -20,8 +38,9 @@ yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the post-install function
# Execute the install hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
## عرض سجلات الدوال (`yarn twenty dev:function:logs`)
@@ -100,6 +119,12 @@ yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
تأتي بيانات التعريف المعروضة في السوق من إعداد `defineApplication()` — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`.
تأتي البيانات الوصفية المعروضة في سوق التطبيقات من إعدادات `defineApplication()` الخاصة بك — راجع قسم [البيانات الوصفية في سوق التطبيقات](#marketplace-metadata) أعلاه.
<Note>
إذا لم يحدد تطبيقك `aboutDescription` في `defineApplication()`، فسيستخدم السوق تلقائيًا ملف `README.md` الخاص بحزمتك من npm كمحتوى لصفحة حول. هذا يعني أنه يمكنك الاحتفاظ بملف README واحد لكل من npm وسوق Twenty. إذا كنت تريد وصفًا مختلفًا في السوق، فقم بتعيين `aboutDescription` بشكل صريح.
@@ -15,33 +15,44 @@ icon: compass
| ترغب في… | أمر | الملاحظات |
| ------------------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| التكرار محليًا مع المزامنة الحية | `yarn twenty dev` | يراقب ملفاتك ويجري مزامنة عند كل تغيير. |
| مزامنة واحدة ثم إنهاء (CI، السكربتات، الخطّافات) | `yarn twenty dev --once` | عملية إنشاء واحدة + مزامنة، ثم إنهاء. |
| معاينة التغييرات **بدون تطبيقها** | `yarn twenty dev --once --dry-run` | يحتسب الفرق ويطبعه؛ ولا يكتب أي شيء. |
| مزامنة واحدة ثم إنهاء (CI، السكربتات، الخطّافات) | `yarn twenty apply` | عملية إنشاء واحدة + مزامنة، ثم إنهاء. أضف `--force` لتخطي تأكيد التغيير التدميري. |
| معاينة التغييرات **بدون تطبيقها** | `yarn twenty plan` | يحتسب الفرق ويطبعه؛ ولا يكتب أي شيء. |
| إزالة التطبيق من مساحة العمل | `yarn twenty app:uninstall` | أضف `--yes` لتخطي رسالة التأكيد. |
| إرسال ملف tarball إلى خادم | `yarn twenty app:publish --private` | يتطلّب إصدارًا **أعلى بشكل صارم** في `package.json` — راجع قسم [النشر](/l/ar/developers/extend/apps/operations/publishing). |
| النشر في السوق (npm) | `yarn twenty app:publish` | — |
| تثبيت / ترقية إصدار منشور | `yarn twenty app:install` | يُثبّت الإصدار المنشور حاليًا. |
| مسح الخادم المحلي والبدء من جديد | `yarn twenty docker:reset` | يحذف **كل** البيانات المحلية — كملاذ أخير. |
<Note>
الأوامر `yarn twenty dev --once` و`yarn twenty dev --once --dry-run` هي أسماء بديلة مهملة للأمرين `yarn twenty apply` و`yarn twenty plan`.
</Note>
### لا تحتاج المزامنة المحلية إلى زيادة في الإصدار
تنطبق قاعدة `version` المتزايدة بدقة (`VERSION_ALREADY_EXISTS` عند النشر، و`APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` عند التثبيت) على **`app:publish` / `app:install`** — مسار الإصدارات. يقوم `yarn twenty dev` بمزامنة ملف manifest في مكانه ولا يتطلّب تغيير الإصدار أبدًا، لذا لست بحاجة إلى تعديل `package.json` للتكرار. إذا وجدت نفسك تزيد الإصدار لاختبار تغيير محلي، فأنت تستخدم مسار الإصدارات بينما ما تريده هو حلقة التطوير.
## قراءة مخرجات المزامنة
كل عملية مزامنة تطبع التغييرات في البيانات الوصفية التي تم تطبيقها (أو التي سيتم تطبيقها، مع خيار `--dry-run`):
كل عملية مزامنة تطبع تغييرات البيانات الوصفية التي تم تطبيقها (أو التي سيتم تطبيقها عند استخدام `plan`)، على غرار Terraform — كتلة واحدة لكل كيان مع خصائصه، ثم سطر ملخص:
```text filename="Terminal"
Metadata changes: 2 created, 1 updated, 1 deleted
created objectMetadata rocket
created fieldMetadata timelineActivities
updated fieldMetadata launchedAt
deleted pageLayout legacyTab
✓ Synced
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
هذه أداتك الأولى للتشخيص: تُخبرك بدقة ما الكائنات والحقول والتخطيطات التي تغيّرت، بحيث يمكنك التأكد من أن المزامنة أنجزت ما توقّعته قبل التحقّق من واجهة المستخدم.
التغييرات التدميرية (`to destroy`) تُدرج مع ما تقوم بحذفه (على سبيل المثال `objectMetadata "auditNote" — drops the table and all its rows`) وتتطلب تأكيدًا تفاعليًا، أو استخدام `--force` في السكربتات.
عندما تفشل المزامنة على كيان واحد، يذكر الخطأ اسم الكيان المسبب للمشكلة و`universalIdentifier` الخاص به، على سبيل المثال:
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
استخدم ذلك المعرّف للعثور على الكيان في ملف manifest الخاص بك (وإن لزم الأمر، في مساحة العمل) بدلًا من تخمين أيّها يتعارض.
## معاينة التغييرات (تشغيل تجريبي dry run)
## معاينة التغييرات (plan)
يبني `yarn twenty dev --once --dry-run` ملف manifest الخاص بك، ويطلب من الخادم خطة الترحيل، ويطبعها — **بدون تطبيق أي شيء**. إنها الطريقة الآمنة للإجابة عن سؤال "ما الذي ستغيّره هذه المزامنة؟" قبل الالتزام بها.
يقوم `yarn twenty plan` ببناء ملف manifest الخاص بك، ويطلب من الخادم خطة الترحيل، ويطبعها — **بدون تطبيق أي شيء**. إنها الطريقة الآمنة للإجابة عن سؤال "ما الذي ستغيّره هذه المزامنة؟" قبل الالتزام بها.
```bash filename="Terminal"
yarn twenty dev --once --dry-run
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata diff (dry run, nothing will be applied)...
Metadata changes: 1 created, 1 updated
created fieldMetadata timelineActivities
updated objectMetadata rocket
✓ Dry run complete for My App — no changes were applied
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "timelineActivities" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
تشغيل تجريبي:
خطة:
* **لا يكتب أي شيء** — لا ترحيل لبيانات وصفية، ولا تحديث لسجل التطبيق، ولا تغييرات في الأدوار/التبويبات الافتراضية، ولا توليد لعميل API.
* يُرجع **نفس الفرق** الذي ستُطبِّقه مزامنة حقيقية، حتى تتمكن من مراجعة الكيانات التي سيتم إنشاؤها/تحديثها/حذفها مسبقًا.
* يكون مفيدًا قبل إجراء تغيير محفوف بالمخاطر، أو عند مراجعة تغيير تم إنشاؤه بواسطة الذكاء الاصطناعي، أو في سكربت يجب أن يفشل إذا كان تغيير غير متوقَّع على وشك الحدوث.
<Note>
يُعاين التشغيل التجريبي فقط **تغييرات البيانات الوصفية**، ويتطلّب أن يكون التطبيق قد تمت مزامنته مرة واحدة على الأقل (حتى تعرف به مساحة العمل). إذا شغّلته ضد تطبيق لم تتم مزامنته من قبل، سيبلغ الخادم أن التطبيق غير مُثبّت — شغّل `yarn twenty dev` مرة واحدة أولًا.
تقوم الخطة فقط بمعاينة **تغييرات البيانات الوصفية**، ويتطلّب ذلك أن يكون التطبيق قد تمت مزامنته مرة واحدة على الأقل (حتى تعرف به مساحة العمل). إذا شغّلته ضد تطبيق لم تتم مزامنته من قبل، سيبلغ الخادم أن التطبيق غير مُثبّت — شغّل `yarn twenty dev` مرة واحدة أولًا.
</Note>
## سلّم الاستعادة
عندما تبدو البيانات الوصفية المحلية غير صحيحة، صعِّد الإجراءات بهذا الترتيب وتوقّف بمجرد زوال العائق. كل خطوة أكثر إرباكًا من التي قبلها.
1. **أعد المزامنة.** شغّل `yarn twenty dev --once` مرة أخرى. عمليات المزامنة متطابِقة الأثر (idempotent) — إعادة تشغيل ملف manifest النظيف آمنة وغالبًا ما تحل تعثرًا عابرًا.
2. **عاين الخطة.** شغّل `yarn twenty dev --once --dry-run` لرؤية ما الذي تنوي المزامنة التالية تغييره بالضبط، بدون تطبيقه.
1. **أعد المزامنة.** شغّل `yarn twenty apply` مرة أخرى. عمليات المزامنة متطابِقة الأثر (idempotent) — إعادة تشغيل ملف manifest النظيف آمنة وغالبًا ما تحل تعثرًا عابرًا.
2. **عاين الخطة.** شغّل `yarn twenty plan` لرؤية ما الذي تنوي المزامنة التالية تغييره بالضبط، بدون تطبيقه.
3. **اقرأ الخطأ المسمّى.** إذا فشلت المزامنة، لاحظ نوع البيانات الوصفية و`universalIdentifier` في الرسالة (انظر أعلاه) وحدّد ذلك الكيان في ملف manifest الخاص بك. يشير التعارض عادةً إلى معرّف مكرر أو مُعاد استخدامه.
4. **إلغاء التثبيت وإعادة التثبيت.** شغّل `yarn twenty app:uninstall`، ثم أجرِ مزامنة مرة أخرى (`yarn twenty dev`). هذا يعيد بناء بيانات التطبيق الوصفية من نقطة بداية نظيفة مع إبقاء باقي مساحة العمل سليمة.
5. **إعادة تعيين كاملة (الملاذ الأخير).** شغّل `yarn twenty docker:reset`، ثم أعد التهيئة والمزامنة.
@@ -78,6 +78,13 @@ yarn add -D vitest vite-tsconfig-paths
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
@@ -88,66 +95,74 @@ export default defineConfig({
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
setupFiles: ['src/__tests__/setup-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL: 'http://localhost:2020',
TWENTY_API_KEY: 'your-api-key',
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
أنشئ ملف إعداد يتحقّق من إمكانية الوصول إلى الخادم قبل تشغيل الاختبارات:
أنشئ ملف إعداد عالمي يتحقق من إمكانية الوصول إلى الخادم، ويكتب إعداد اختبار لحزمة SDK (`~/.twenty/config.test.json`)، ويُجري مزامنة للتطبيق قبل تشغيل الاختبارات:
```ts src/__tests__/setup-test.ts
```ts src/__tests__/global-setup.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');
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
beforeAll(async () => {
// Verify the server is running
const response = await fetch(`${TWENTY_API_URL}/healthz`);
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
'Start the server before running integration tests.',
);
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write a temporary config for the SDK
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(TEST_CONFIG_DIR, 'config.json'),
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
});
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## واجهات SDK البرمجية
يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار:
| دالة | الوصف |
| -------------- | ----------------------------------------- |
| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball |
| `appDeploy` | رفع ملف tarball إلى الخادم |
| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة |
| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة |
| دالة | الوصف |
| -------------- | ------------------------------------------------------------------- |
| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball |
| `appDeploy` | رفع ملف tarball إلى الخادم |
| `appDevOnce` | بناء التطبيق ومزامنته مرة واحدة (نفس الأمر مثل `yarn twenty apply`) |
| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة |
| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة |
تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`.
@@ -238,64 +253,10 @@ yarn test:watch
yarn twenty dev:typecheck
```
يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع.
يشغِّل هذا الأمر `tsc --noEmit` على ملف `tsconfig.json` الخاص بتطبيقك ويبلغ عن أي أخطاء في الأنواع. التطبيقات المُنشأة بالهيكل تأتي أيضًا مع سكربت `yarn typecheck` الذي يشمل ملفات الاختبار أيضًا (`tsconfig.spec.json`).
## التكامل المستمر (CI) باستخدام GitHub Actions
تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب.
تولّد أداة إنشاء الهيكل سير عمل جاهزًا للاستخدام في `.github/workflows/ci.yml`. عند كل دفع إلى الفرع `main` وكل طلب سحب، تُنشئ الأداة خادم Twenty مؤقتًا في بيئة التشغيل (عبر الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`)، ثم تشغِّل الأوامر `yarn lint` و`yarn typecheck` و`yarn test:unit` و`yarn test` مع ضبط المتغيرين `TWENTY_API_URL` و`TWENTY_API_KEY` للإشارة إلى ذلك الخادم. لا تُطلَب أي أسرار، ويمكنك تثبيت إصدار الخادم عبر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل.
سير العمل:
1. يجلب الشيفرة الخاصة بك
2. يشغّل خادم Twenty مؤقتًا باستخدام الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. يثبّت التبعيات باستخدام `yarn install --immutable`
4. يشغّل `yarn test` مع حقن `TWENTY_API_URL` و`TWENTY_API_KEY` من مخرجات الإجراء
```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 }}
```
لا تحتاج إلى تهيئة أي أسرار — إذ يبدأ إجراء `spawn-twenty-docker-image` خادم Twenty عابرًا مباشرة في المشغّل ويُخرِج تفاصيل الاتصال. يتم توفير السر `GITHUB_TOKEN` تلقائيًا من قِبل GitHub.
لتثبيت إصدار محدّد من Twenty بدلًا من `latest`، غيّر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل.
راجع قسم [النشر → التكامل/التسليم المستمران الآليان](/l/ar/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) للاطلاع على شرح كامل لكلا سيرَي العمل المُنشأين بالهيكل (`ci.yml` وخط أنابيب النشر `cd.yml`).
@@ -88,9 +88,11 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
const apiBaseUrl = process.env.TWENTY_API_URL;
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
@@ -179,7 +181,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,8 +9,14 @@ description: قم بتفعيل الوظيفة على HTTP وتقديم الوث
* نقطة النهاية **POST** مكالمات واجهة المستخدم لإنشاء وثيقة، و
* نقطة نهاية عامة **GET** تجعل الوثيقة صفحة ويب قابلة للطباعة.
وكلاهما يستخدم `httpRouteTriggerSettings`. طرق التطبيق تقدم تحت `/s` على خادم
20 (على سبيل المثال 'http://localhost:2020/s/documents/generate\`).
وكلاهما يستخدم `httpRouteTriggerSettings`. على خادم dev المحلي، طرق التطبيق هي
تقدم تحت بادئة `/s` (على سبيل المثال 'http://localhost:2020/s/documents/generate\`).
<Note>
على عشرين سحابة، يتم خدمة المسارات على نطاق وظائف مساحة العمل المخصصة- عنوان URL 20 حقن بـ 'TWENTY_FUNCTIONS_URL`، بدون بادئة '/s'. البادئة `/s\`
مهملة هناك ولا تبقى إلا للجهات المحلية والمحلية.
انظر [تسمية دالة منطقية](/l/ar/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## مسار POST - توليد حسب الطلب
@@ -77,11 +77,11 @@ export default defineApplication({
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty dev --once --dry-run # preview the metadata diff
yarn twenty plan # preview the metadata diff
```
يعرض التشغيل التجريبي بالضبط ما سيتغيّر على الخادم من دون تطبيقه —
وهو فحص نهائي جيّد للسلامة. انظر
تعرض الخطة بالضبط ما سيتغيّر على الخادم من دون تطبيقه —
وهي فحص نهائي جيّد للسلامة. انظر
[Testing](/l/ar/developers/extend/apps/operations/testing) و
[المزامنة والاسترداد](/l/ar/developers/extend/apps/operations/sync-and-recovery).
@@ -4,9 +4,9 @@ description: Spouštějte logiku před instalací nebo po ní — naplňte data,
icon: wrench
---
Instalační hooky jsou speciální logické funkce, které se spouštějí během životního cyklu instalace nebo upgradu. Sdílí stejný runtime handleru jako běžné [logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) a přijímají `InstallPayload`, ale deklarují se pomocí vlastních definičních funkcí — `definePostInstallLogicFunction()` a `definePreInstallLogicFunction()` — a fungují mimo běžný model triggerů (HTTP, cron, databázové události).
Instalační hooky jsou speciální logické funkce, které se spouštějí během životního cyklu instalace nebo upgradu. Sdílí stejný runtime handleru jako běžné [logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) a přijímají `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` je při čisté instalaci `undefined`), ale deklarují se pomocí vlastních definičních funkcí a fungují mimo běžný model triggerů (HTTP, cron, databázové události).
Každá aplikace může definovat **nanejvýš jednu pre-install** a **nanejvýš jednu post-install** funkci. Sestavení manifestu skončí chybou, pokud je zjištěno více než jedno.
Každá aplikace může definovat **nanejvýš jednu pre-install** a **nanejvýš jednu post-install** funkci. Sestavení manifestu skončí chybou, pokud je zjištěno více než jedno z nich.
```
┌─────────────────────────────────────────────────────────────┐
@@ -19,111 +19,59 @@ Každá aplikace může definovat **nanejvýš jednu pre-install** a **nanejvý
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Spouští se po aplikování migrace metadat pracovního prostoru">
## Na první pohled
Postinstalační funkce se spustí automaticky, jakmile je instalace vaší aplikace v pracovním prostoru dokončena. Server ji provede **poté**, co byla synchronizována metadata aplikace a vygenerován klient SDK, takže je pracovní prostor plně připraven k použití a nové schéma je zavedeno. Mezi typické případy použití patří naplnění výchozími daty, vytvoření počátečních záznamů, konfigurace nastavení pracovního prostoru nebo zřizování prostředků ve službách třetích stran.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| --------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Běhy | Před migrací metadat — **předchozí** schéma a data jsou stále neporušené | Po migraci a vygenerování SDK — **nové** schéma je připravené |
| Spuštění | Vždy synchronní; blokuje instalaci | Ve výchozím nastavení asynchronní (zařazeno do fronty, 3 pokusy); synchronní režim volitelný přes `shouldRunSynchronously: true` |
| Při selhání | Instalace je **zrušena** před jakoukoli změnou schématu | Async: opakovaný pokus až 3krát. Sync: volající obdrží `POST_INSTALL_ERROR` (změny schématu se **ne**vracejí zpět) |
| Typické použití | Zálohovat nebo opravit data, která by migrace ztratila; odmítnout rizikovou aktualizaci vyvoláním výjimky | Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Pravidlo:** výchozí volbou je post-install. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Chcete... | Použít |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Naplňte data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` |
| Dlouho běžící práce, která by neměla blokovat odpověď instalace | `post-install` (výchozí asynchronní režim, s opakovanými pokusy workeru) |
| Rychlé nastavení, na které volající spoléhá ihned po dokončení instalace | `post-install` s `shouldRunSynchronously: true` |
| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` |
| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) |
| Srovnání stavu při každé aktualizaci | Libovolný hook s `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## Chování sdílené oběma hooky
Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
* Konfigurace je konfigurace `defineLogicFunction` bez nastavení triggeru, doplněná o `shouldRunOnVersionUpgrade`.
* **Kdy se spouští**: ve výchozím nastavení pouze při čistých instalacích. Nastavte `shouldRunOnVersionUpgrade: true`, aby se spouštěl i při aktualizacích. Použijte `previousVersion` / `newVersion` k větvení podle cesty aktualizace.
* **Idempotence je důležitá**: asynchronní post-install se může spouštět opakovaně a kterýkoli hook se znovu spouští při aktualizacích, když je `shouldRunOnVersionUpgrade` zapnuté.
* Běžné prostředí logické funkce (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) je injektováno, takže můžete volat Twenty API s tokenem své aplikace.
* Hook je při sestavení automaticky připojen k manifestu aplikace (`preInstallLogicFunction` / `postInstallLogicFunction`) — v [`defineApplication()`](/l/cs/developers/extend/apps/config/application) není potřeba na nic odkazovat.
* Výchozí `timeoutSeconds` je 300, aby umožnil delší úlohy nastavení, jako je naplnění daty.
* **Nespouští se v dev režimu**: `yarn twenty dev` přeskočí instalační flow a soubory synchronizuje přímo, takže se hooky v tomto režimu nikdy nespustí. Místo toho je spouštějte ručně:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Hlavní body:
* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* Obslužná funkce obdrží `InstallPayload` s `{ previousVersion?: string; newVersion: string }` — `newVersion` je verze, která se instaluje, a `previousVersion` je verze, která byla nainstalována dříve (nebo `undefined` při čisté instalaci). Tyto hodnoty použijte k rozlišení čistých instalací od aktualizací a ke spuštění migrační logiky specifické pro verzi.
* **Kdy se hook spouští**: ve výchozím nastavení pouze při čistých instalacích. Předejte `shouldRunOnVersionUpgrade: true`, pokud chcete, aby se spouštěl i při aktualizaci aplikace z předchozí verze. Pokud je vynechán, příznak má výchozí hodnotu `false` a při aktualizacích se hook přeskočí.
* **Model provádění — ve výchozím nastavení asynchronní, synchronní volitelně**: příznak `shouldRunSynchronously` určuje *jak* se spouští post-install.
* `shouldRunSynchronously: false` *(výchozí)* — hook je **zařazen do fronty zpráv** s `retryLimit: 3` a běží asynchronně ve workeru. Odezva instalace se vrátí hned po zařazení úlohy do fronty, takže pomalá nebo chybující obslužná funkce neblokuje volajícího. Worker se pokusí o opakování až třikrát. **Použijte pro dlouho běžící úlohy** — plnění velkých datových sad, volání pomalých externích API, zřizování externích prostředků, cokoli, co by mohlo přesáhnout rozumné časové okno HTTP odezvy.
* `shouldRunSynchronously: true` — hook se provádí **inline během instalačního procesu** (stejný vykonavatel jako pre-install). Instalační požadavek blokuje, dokud obslužná funkce nedokončí, a pokud vyvolá výjimku, volající instalace obdrží `POST_INSTALL_ERROR`. Žádné automatické opakování. **Použijte pro rychlé úlohy, které se musí dokončit před odpovědí** — například vrácení validační chyby uživateli nebo rychlé nastavení, na kterém bude klient záviset ihned po návratu volání instalace. Mějte na paměti, že v době, kdy se spustí post-install, už byla migrace metadat aplikována, takže selhání v synchronním režimu změny schématu **ne**vrací zpět — pouze odhalí chybu.
* Ujistěte se, že vaše obslužná funkce je idempotentní. V asynchronním režimu se může fronta pokusit až třikrát; v obou režimech se může hook znovu spustit při aktualizacích, pokud je `shouldRunOnVersionUpgrade: true`.
* Proměnné prostředí `APPLICATION_ID`, `APP_ACCESS_TOKEN` a `API_URL` jsou dostupné uvnitř obslužné funkce (stejně jako u jakékoli jiné logické funkce), takže můžete volat Twenty API s aplikačním přístupovým tokenem omezeným na vaši aplikaci.
* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna.
* Atributy funkce `universalIdentifier`, `shouldRunOnVersionUpgrade` a `shouldRunSynchronously` jsou během buildu automaticky připojeny k manifestu aplikace do pole `postInstallLogicFunction` — není potřeba je uvádět v [`defineApplication()`](/l/cs/developers/extend/apps/config/application).
* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty.
* **Nespouští se v režimu dev**: když je aplikace registrována lokálně (pomocí `yarn twenty dev`), server zcela přeskočí instalační tok a synchronizuje soubory přímo prostřednictvím sledovače CLI — takže se post-install v režimu dev nikdy nespustí bez ohledu na `shouldRunSynchronously`. Použijte `yarn twenty dev:function:exec --postInstall` k ručnímu spuštění nad běžícím pracovním prostorem.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Spouští se před aplikováním migrace metadat pracovního prostoru">
Předinstalační funkce se během instalace spouští automaticky, **před aplikováním migrace metadat pracovního prostoru**. Má stejný tvar payloadu jako post-install (`InstallPayload`), ale je zařazena dříve v instalačním toku, aby mohla připravit stav, na němž nadcházející migrace závisí — typické použití zahrnuje zálohování dat, ověření kompatibility s novým schématem nebo archivaci záznamů, které se chystají přeuspořádat nebo odstranit.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Hlavní body:
* Funkce pre-install používají `definePreInstallLogicFunction()` — stejné specializované nastavení jako u post-install, pouze připojené k jiné fázi životního cyklu.
* Obě obslužné funkce pre- i post-install přijímají stejný typ `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importujte jej jednou a znovu použijte pro oba hooky.
* **Kdy se hook spouští**: umístěn těsně před migrací metadat pracovního prostoru (`synchronizeFromManifest`). Před spuštěním server provede čistě aditivní "zjednodušenou synchronizaci", která v metadatech pracovního prostoru zaregistruje pre-install funkci **nové** verze — ničeho dalšího se nedotkne — a poté ji spustí. Protože tato synchronizace je pouze aditivní, objekty, pole a data předchozí verze zůstávají při spuštění vaší obslužné funkce zachována: můžete bezpečně číst a zálohovat stav před migrací.
* **Model provádění**: pre-install se provádí **synchronně** a **blokuje instalaci**. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před aplikováním jakýchkoli změn schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci.
* Stejně jako u post-install je na jednu aplikaci povolena pouze jedna funkce pre-install. Během buildu je automaticky připojena k manifestu aplikace pod `preInstallLogicFunction`.
* **Nespouští se v režimu dev**: stejně jako u post-install — u lokálně registrovaných aplikací je instalační tok zcela přeskočen, takže se pre-install pod `yarn twenty dev` nikdy nespustí. Použijte `yarn twenty dev:function:exec --preInstall` k ručnímu spuštění.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Spouští se po aplikování migrace metadat pracovního prostoru">
</Accordion>
<Accordion title="Pre-install vs post-install: kdy použít který" description="Výběr správného instalačního hooku">
Oba hooky jsou součástí téhož instalačního toku a přijímají stejný `InstallPayload`. Rozdíl je v tom, **kdy** se spouštějí vzhledem k migraci metadat pracovního prostoru, a to určuje, jakých dat se mohou bezpečně dotýkat.
Pre-install je vždy **synchronní** (blokuje instalaci a může ji přerušit). Post-install je **ve výchozím nastavení asynchronní** — zařazen do workeru s automatickými pokusy o opakování — ale může přejít na synchronní provádění pomocí `shouldRunSynchronously: true`. Viz accordion `definePostInstallLogicFunction` výše, kdy použít jednotlivé režimy.
**Použijte `post-install` pro cokoli, co vyžaduje existenci nového schématu.** To je běžný případ:
* Plnění výchozími daty (vytváření počátečních záznamů, výchozích pohledů, demo obsahu) vůči nově přidaným objektům a polím.
* Registrace webhooků u služeb třetích stran poté, co má aplikace své přihlašovací údaje.
* Volání vlastního API k dokončení nastavení, které závisí na synchronizovaných metadatech.
* Idempotentní logika "zajisti, že to existuje", která má při každé aktualizaci uvést stav do souladu — kombinujte s `shouldRunOnVersionUpgrade: true`.
Příklad — po instalaci naplňte výchozí záznam `PostCard`:
Spouští se, jakmile vaše aplikace dokončí instalaci: metadata jsou synchronizovaná, klient SDK vygenerovaný a nové schéma je možné dotazovat. Příklad — při čisté instalaci naplňte výchozí záznam:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**Použijte `pre-install`, pokud by migrace jinak zničila nebo poškodila existující data.** Protože pre-install běží proti *předchozímu* schématu a jeho selhání vrací aktualizaci zpět, je to správné místo pro cokoli rizikového:
Příznak `shouldRunSynchronously` řídí model spuštění:
* **Zálohování dat, která se chystají odstranit nebo přeuspořádat** — např. odstraňujete pole ve verzi v2 a potřebujete jeho hodnoty zkopírovat do jiného pole nebo je před spuštěním migrace exportovat do úložiště.
* **Archivace záznamů, které by nové omezení zneplatnilo** — např. pole se stává `NOT NULL` a je třeba nejprve smazat nebo opravit řádky s hodnotami null.
* **Ověření kompatibility a odmítnutí aktualizace, pokud nelze aktuální data čistě migrovat** — vyhoďte výjimku z obslužné funkce a instalace se ukončí bez provedených změn. Je to bezpečnější, než zjistit nekompatibilitu uprostřed migrace.
* **Přejmenování nebo změna klíčů dat** před změnou schématu, která by ztratila vazby.
* `false` *(výchozí)* — zařazeno do fronty zpráv (`retryLimit: 3`) a spuštěno workerem. Odezva instalace se vrátí, jakmile je úloha zařazena do fronty. **Používejte pro dlouho běžící práci** — naplňování velkých datových sad, pomalá API třetích stran.
* `true` — spuštěno inline během instalačního flow. Instalační požadavek blokuje, dokud handler neskončí; vyvolaná chyba se projeví pro volajícího jako `POST_INSTALL_ERROR` (žádné opakované pokusy). **Používejte pro rychlou práci, která musí být dokončena před odpovědí.** Migrace už je v tomto bodě aplikovaná, takže selhání nevrací změny schématu zpět — pouze předá chybu dál.
Příklad — archivujte záznamy před destruktivní migrací:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Spouští se před aplikováním migrace metadat pracovního prostoru">
Spouští se před migrací metadat, nad **předchozím** schématem — vhodné místo pro zálohování dat, která by migrace ztratila, nebo pro odmítnutí rizikové aktualizace. Před spuštěním server provede čistě aditivní „zjednodušenou synchronizaci“, která zaregistruje pouze pre-install funkci nové verze; vše ostatní — objekty, pole a data předchozí verze — zůstává při běhu vašeho handleru nedotčeno.
Pre-install je vždy **synchronní** a blokuje instalaci. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před jakoukoli změnou schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci.
Příklad — zkopírujte hodnoty staršího pole dříve, než ho migrace odstraní:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**Zlaté pravidlo:**
| Chcete... | Použít |
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` |
| Spusťte dlouho běžící plnění nebo volání třetích stran, která by neměla blokovat odezvu instalace | `post-install` (výchozí — `shouldRunSynchronously: false`, s opakovanými pokusy workeru) |
| Spusťte rychlé nastavení, na které bude volající spoléhat ihned po návratu volání instalace | `post-install` s `shouldRunSynchronously: true` |
| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` |
| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) |
| Spouštějte srovnání stavu při každé aktualizaci | `post-install` s `shouldRunOnVersionUpgrade: true` |
| Proveďte jednorázové nastavení pouze při první instalaci | `post-install` s `shouldRunOnVersionUpgrade: false` (výchozí) |
<Note>
Pokud si nejste jisti, výchozí volbou je **post-install**. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**Základní pole jsou přidána automaticky.** Když definujete vlastní objekt, Twenty pro vás vytvoří standardní pole jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`. Nemusíte je uvádět v poli `fields` — pouze svá vlastní pole. Výchozí pole můžete přepsat tak, že deklarujete pole se stejným názvem, ale jen zřídka je to dobrý nápad.
</Note>
## Typy polí
Úplná sada hodnot `FieldType`, exportovaných z `twenty-sdk/define`:
| Kategorie | Typy |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Text | `TEXT`, `RICH_TEXT`, `ARRAY` (řetězců), `RAW_JSON` |
| Číselné | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (libovolná přesnost), `RATING`, `POSITION` |
| Data | `DATE`, `DATE_TIME` |
| Výběr | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Složené | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identifikátory a relace | `UUID`, `RELATION`, `MORPH_RELATION` (viz [Relations](/l/cs/developers/extend/apps/data/relations)) |
| Systém | `TS_VECTOR` (vektor pro fulltextové vyhledávání, spravovaný serverem) |
Složené typy ukládají více podpolí (např. `FULL_NAME` = křestní + příjmení; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` a `MULTI_SELECT` vyžadují pole `options`, jak je ukázáno v příkladu výše.
## Výchozí hodnoty
Výchozí textové hodnoty musí být uzavřené v jednoduchých uvozovkách **uvnitř** řetězce — `defaultValue: "'Draft'"`, ne `defaultValue: "Draft"`. Proto pole `status` výše používá `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## Klíčové soubory
| Soubor / Složka | Účel |
| ---------------------------------------- | ----------------------------------------------------------------------- |
| `src/application-config.ts` | **Povinné.** Hlavní konfigurační soubor vaší aplikace. |
| `src/default-role.ts` | Výchozí role, která řídí, k čemu mohou vaše logické funkce přistupovat. |
| `src/constants/universal-identifiers.ts` | Automaticky generovaná UUID a metadata (zobrazovaný název, popis). |
| `src/__tests__/` | Integrační testy (nastavení + ukázkový test). |
| `public/` | Statické soubory (obrázky, písma) doručované vaší aplikací. |
| Soubor / Složka | Účel |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Povinné.** Hlavní konfigurační soubor vaší aplikace. |
| `src/default-role.ts` | Výchozí role, která řídí, k čemu mohou vaše logické funkce přistupovat. |
| `src/constants/universal-identifiers.ts` | Automaticky generovaná UUID a metadata (zobrazovaný název, popis). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Úvodní uvítací stránka: front komponenta renderovaná samostatným rozložením stránky, dostupná z postranního panelu. |
| `src/__tests__/` | Jednotkový test plus integrační test (s jeho globálním nastavením), který synchronizuje aplikaci proti skutečnému serveru. |
| `public/` | Statické soubory (obrázky, písma) doručované vaší aplikací. |
| `AGENTS.md` / `CLAUDE.md` | Pokyny pro agenty AI pro psaní kódu, kteří pracují na aplikaci. |
<Note>
**Uspořádání souborů je na vás.** Výše uvedené složky jsou konvence — SDK detekuje entity pomocí analýzy AST u volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází.
@@ -47,15 +60,18 @@ Oba balíčky Twenty SDK patří pod `devDependencies`, ne pod `dependencies`:
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
Nástroj pro scaffolding připne `twenty-sdk` a `twenty-client-sdk` ke své vlastní verzi — při aktualizaci udržujte tyto dva balíčky synchronizované.
* **`twenty-sdk`** dodává `twenty` CLI a nástroje pro sestavení/scaffolding. Běží pouze při vývoji a sestavování a nikdy není importován za běhu zveřejněné aplikace.
* **`twenty-client-sdk`** je importován kódem vaší aplikace (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), ale Twenty ho poskytuje za běhu — logické funkce ho získávají z vygenerované SDK vrstvy a front-endové komponenty ho načítají z modulů poskytovaných serverem. Vaše nainstalovaná kopie se používá pouze pro kontrolu typů a build v době nasazení, takže ji nikdy není potřeba přibalit do nasazeného balíčku.
Ponechání kteréhokoli balíčku pod `dependencies` ho vtáhne do runtime balíčku nainstalované aplikace, kde je jen mrtvou vahou. `twenty build` vypíše varování, pokud je kterýkoli z nich stále uveden pod `dependencies`.
Ponechání kteréhokoli balíčku pod `dependencies` ho vtáhne do runtime balíčku nainstalované aplikace, kde je jen mrtvou vahou. `twenty dev:build` vypíše varování, pokud je kterýkoli z nich stále uveden pod `dependencies`.
Vlastní runtime závislosti vaší aplikace (knihovny, které vaše logické funkce skutečně importují za běhu) přidejte jako obvykle pod `dependencies`.
@@ -6,17 +6,17 @@ description: Vytvořte svou první aplikaci Twenty během několika minut.
## Předpoklady
* **Node.js 24+** — [Stáhnout](https://nodejs.org/)
* **Node.js 24.5+** — [Stáhnout](https://nodejs.org/)
* **Yarn 4** — je součástí Node.js prostřednictvím Corepacku. Povolte jej: `corepack enable`
* **Docker** — [Stáhnout](https://www.docker.com/products/docker-desktop/). Nutné pro spuštění lokálního serveru Twenty. Přeskočte, pokud už máte Twenty spuštěné jinde.
Vytváření aplikace Twenty má tři fáze. Generátor kostry je spojuje do jediného příkazu pro ideální scénář, ale každá fáze je samostatný koncept — když se něco nepovede, znalost aktuální fáze napoví, co opravit.
| Fáze | Co děláte | Nástroj | Výsledek |
| ---------------------- | -------------------------------------------------------- | ----------------------------- | -------------------------------------------- |
| **1. Vytvořit kostru** | Vygenerovat zdrojový kód aplikace | `npx create-twenty-app` | Projekt v TypeScriptu na disku |
| **2. Spustit server** | Spustit server Twenty, do kterého se bude synchronizovat | Docker + `yarn twenty server` | Běžící instance Twenty |
| **3. Synchronizovat** | Živě synchronizovat kód na server | `yarn twenty dev` | Vaše změny se objeví v uživatelském rozhraní |
| Fáze | Co děláte | Nástroj | Výsledek |
| ---------------------- | -------------------------------------------------------- | ----------------------------------- | -------------------------------------------- |
| **1. Vytvořit kostru** | Vygenerovat zdrojový kód aplikace | `npx create-twenty-app` | Projekt v TypeScriptu na disku |
| **2. Spustit server** | Spustit server Twenty, do kterého se bude synchronizovat | Docker + `yarn twenty docker:start` | Běžící instance Twenty |
| **3. Synchronizovat** | Živě synchronizovat kód na server | `yarn twenty dev` | Vaše změny se objeví v uživatelském rozhraní |
---
@@ -28,7 +28,7 @@ Vytvořte novou aplikaci ze šablony:
npx create-twenty-app@latest my-twenty-app
```
Budete vyzváni k zadání názvu a popisu — výchozí hodnoty potvrdíte klávesou **Enter**. Tím se v `my-twenty-app/` vytvoří projekt v TypeScriptu se startovacím `application-config.ts`, výchozí rolí, CI workflow a integračním testem.
Generátor kostry je neinteraktivní: název adresáře se stane názvem aplikace. Předejte `--display-name` a `--description` pro přizpůsobení vygenerovaných metadat (později je můžete také upravit v `src/constants/universal-identifiers.ts`). Tím se v `my-twenty-app/` vytvoří projekt v TypeScriptu se startovacím `application-config.ts`, výchozí rolí, pracovními postupy CI/CD a integračním testem.
**Po této fázi:** máte na svém počítači zdrojový kód aplikace. Zatím neběží — to je předmětem Fáze 2.
@@ -38,28 +38,14 @@ Budete vyzváni k zadání názvu a popisu — výchozí hodnoty potvrdíte klá
Vaše aplikace potřebuje server Twenty, do kterého se bude synchronizovat. Server je plnohodnotná instance Twenty — UI, GraphQL API, PostgreSQL — běžící lokálně v Dockeru. Váš lokální kód nahrává své definice na tento server, díky čemuž se objeví v UI.
Generátor kostry nabídne, že vám jej spustí:
Nástroj pro vytváření projektů to spustí za vás: při spuštěném Dockeru stáhne image `twentycrm/twenty-app-dev`, spustí ji na portu `2020` a autentizuje CLI vůči předpřipravenému demo workspace (`tim@apple.dev`) — není vyžadováno žádné přihlášení.
> **Chcete nastavit lokální instanci Twenty?**
* **Ano (doporučeno)** — stáhne Docker image `twentycrm/twenty-app-dev` a spustí jej na portu `2020`. Nejprve se ujistěte, že Docker běží.
* **Ne** — zvolte, pokud už máte server Twenty, ke kterému se chcete připojit. Můžete jej propojit později pomocí `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Spustit lokální instanci?" />
</div>
Jakmile server běží, otevře se prohlížeč pro přihlášení. Použijte předpřipravený demo účet:
* **E-mail:** `tim@apple.dev`
* **Heslo:** `tim@apple.dev`
Chcete-li se místo toho připojit k existujícímu serveru Twenty, předejte `--url \<your-server-url>`. Vzdálené servery se ověřují pomocí OAuth: otevře se prohlížeč, abyste se mohli přihlásit a kliknout na **Authorize**, čímž udělíte nástroji CLI přístup k vašemu pracovnímu prostoru. (Můžete se také rozhodnout pro OAuth lokálně pomocí `--authentication-method oauth` — přihlaste se pomocí `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Přihlašovací obrazovka Twenty" />
</div>
Na další obrazovce klikněte na **Authorize** — tím udělíte nástroji CLI přístup k vašemu pracovnímu prostoru.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Autorizační obrazovka Twenty CLI" />
</div>
@@ -117,27 +103,31 @@ Klikněte na **View installed app**, abyste zobrazili instalaci v pracovním pro
### Jednorázová synchronizace pro CI a skripty
Předejte `--once` pro provedení jednoho sestavení + synchronizace a ukončení — stejný postup, bez sledování změn:
Použijte `plan` a `apply` ke spuštění stejné pipeline jednorázově, bez watcheru:
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Příkaz | Chování | Kdy použít |
| ---------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `yarn twenty dev` | Sleduje změny a při každé změně znovu synchronizuje. Běží, dokud jej nezastavíte. | Interaktivní lokální vývoj. |
| `yarn twenty dev --once` | Jedno sestavení + synchronizace, ukončí se s kódem `0` při úspěchu, `1` při chybě. | CI, pre-commit hooky, AI agenti, skriptované pracovní postupy. |
| `yarn twenty dev --once --dry-run` | Sestaví a vypíše změny metadat **bez jejich použití**. | Kontrola toho, co by synchronizace změnila, ještě před jejím potvrzením. |
| Příkaz | Chování | Kdy použít |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `yarn twenty dev` | Sleduje změny a při každé změně znovu synchronizuje. Běží, dokud jej nezastavíte. | Interaktivní lokální vývoj. |
| `yarn twenty apply` | Jedno sestavení + synchronizace, ukončí se s kódem `0` při úspěchu, `1` při chybě. Vyžádá si potvrzení destruktivních změn (předejte `--force`, abyste to přeskočili). | CI, pre-commit hooky, AI agenti, skriptované pracovní postupy. |
| `yarn twenty plan` | Sestaví a vypíše změny metadat **bez jejich použití**. | Kontrola toho, co by synchronizace změnila, ještě před jejím potvrzením. |
Oba režimy vyžadují autentizovaný vzdálený server. Více informací o `--dry-run` najdete v části [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run).
Všechny režimy vyžadují autentizovaný vzdálený server. Více informací o `plan` najdete v části [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan).
<Note>
`yarn twenty dev --once` a `yarn twenty dev --once --dry-run` jsou zastaralé aliasy pro `yarn twenty apply` a `yarn twenty plan`.
</Note>
### Možnosti vývojového režimu
| Přepínač | Popis |
| ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `--once` | Jednou sestavit a synchronizovat, poté ukončit. |
| `--dry-run` | Pomocí `--once` zobrazíte náhled změn metadat, aniž byste je použili. Nic nezapisuje. |
| `--debounceMs \<ms>` | Nastaví prodlevu pro potlačení zákmitů při změnách souborů v milisekundách (výchozí: `2000`). |
| `--force` | Aplikuje destruktivní změny (mazání) bez potvrzení. |
| `--debounceMs \<ms>` | Nastaví prodlevu pro potlačení zákmitů při změnách souborů v milisekundách (výchozí: `1000`). |
| `--verbose` / `--debug` | Zobrazí podrobné protokoly sestavení, požadavky synchronizace a trasování chyb. |
## Co můžete vytvořit
@@ -34,6 +34,10 @@ yarn twenty dev:add frontComponent
| Zobrazení | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Položka navigační nabídky | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Rozvržení stránky | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Karta Rozložení stránky | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Položka menu příkazů | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Pole zobrazení | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Poskytovatel připojení | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## Co generátor vytváří
@@ -5,10 +5,10 @@ icon: wrench
---
* **Chyby Dockeru** — Před spuštěním `yarn twenty docker:start` se ujistěte, že Docker Desktop (nebo démon) běží. Chybová zpráva ukáže správný příkaz pro spuštění pro váš operační systém.
* **Nesprávná verze Node** — Je potřeba 24+. Ověřte pomocí `node -v`.
* **Špatná verze Node** — Potřebujete verzi 24.5+ (`engines.node: ^24.5.0`). Ověřte pomocí `node -v`.
* **Chybí Yarn 4** — Spusťte `corepack enable`.
* **Rozbité závislosti** — `rm -rf node_modules && yarn install`.
* **Chyby `twenty-sdk` po upgradu na v2.8.0** — V 2.8.0 byl přesunut z `dependencies` do `devDependencies`. Viz [Struktura projektu → Závislosti](/l/cs/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` upozorňuje na `twenty-client-sdk` v sekci `dependencies`** — je poskytován za běhu Twenty, takže by měl být přesunut do `devDependencies` vedle `twenty-sdk`. Viz [Struktura projektu → Závislosti](/l/cs/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` upozorňuje na `twenty-client-sdk` v sekci `dependencies`** — je poskytován za běhu Twenty, takže by měl být přesunut do `devDependencies` vedle `twenty-sdk`. Viz [Struktura projektu → Závislosti](/l/cs/developers/extend/apps/getting-started/project-structure#dependencies).
Zasekli jste se? Zeptejte se na [Discordu Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -13,7 +13,6 @@ export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
icon: 'IconLayoutDashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
@@ -22,51 +21,23 @@ export default defineCommandMenuItem({
## Konfigurační pole
| Pole | Povinné | Popis |
| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz |
| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) |
| `frontComponentUniversalIdentifier` | Ano | `universalIdentifier` frontendové komponenty, kterou tento příkaz otevírá |
| `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, který dynamicky řídí viditelnost (viz níže) |
| Pole | Povinné | Popis |
| --------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz |
| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) |
| `frontComponentUniversalIdentifier` | Ano | `universalIdentifier` frontendové komponenty, kterou tento příkaz otevírá |
| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce |
| `icon` | Ne | **Zastaralé** — ignorováno ve prospěch ikony aplikace; při nastavení sestavení vypíše varování |
| `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é), `'GLOBAL_OBJECT_CONTEXT'` (pouze na stránkách s kontextem objektu — indexové a záznamové stránky), `'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, který dynamicky řídí viditelnost (viz níže) |
## Příkazy bez rozhraní
Položka příkazového menu spárovaná s [front komponentou bez rozhraní](/l/cs/developers/extend/apps/layout/front-components#headless-vs-non-headless) je idiomatický způsob, jak dodat akci na jedno kliknutí — spustit kód, přejít na stránku nebo potvrdit a provést. Stránka Front Components popisuje [SDK Command komponenty](/l/cs/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`), které obsluhují pattern akce-a-unmount.
Typický průběh:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
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 <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
Typický průběh: bezhlavá komponenta vykreslí `<Command execute={...} />` (viz [úplný příklad](/l/cs/developers/extend/apps/layout/front-components#sdk-command-components)) a položka v nabídce příkazů na ni ukazuje:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -74,7 +45,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -49,14 +49,13 @@ export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty dev --once`) se rychlá akce zobrazí v pravém horním rohu stránky:
Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty apply`) se rychlá akce zobrazí v pravém horním rohu stránky:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Tlačítko rychlé akce v pravém horním rohu" />
@@ -88,11 +87,11 @@ Front-endové komponenty existují ve dvou režimech vykreslování řízených
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
@@ -116,7 +115,7 @@ Protože komponenta vrací `null`, Twenty přeskočí vykreslení kontejneru —
Balíček `twenty-sdk` poskytuje čtyři pomocné komponenty Command navržené pro headless front-endové 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-endovou komponentu.
Importujte je z `twenty-sdk/command`:
Importujte je z `twenty-sdk/front-component`:
* **`Command`** — Spustí asynchronní callback přes prop `execute`.
* **`CommandLink`** — Naviguje na cestu v aplikaci. Props: `to`, `params`, `queryParams`, `options`.
@@ -127,8 +126,8 @@ Zde je kompletní příklad headless front-endové komponenty, která pomocí `C
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
@@ -160,7 +159,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -169,7 +167,7 @@ A příklad s použitím `CommandModal` k vyžádání potvrzení před proveden
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
@@ -202,7 +200,7 @@ Front komponenty běží v prohlížeči v izolovaném web workeru, zatímco [lo
Logická funkce deklarovaná pomocí `httpRouteTriggerSettings` je přes HTTP dostupná na své cestě (route path). Twenty vloží do workeru základní URL, ze které jsou vaše funkce poskytovány, jako `TWENTY_FUNCTIONS_URL` spolu s `TWENTY_APP_ACCESS_TOKEN`, který volání autentizuje. Zatím neexistuje žádný specializovaný klient SDK pro volání vlastních funkcí, takže je volejte pomocí prostého `fetch`:
> **V Twenty Cloud jsou logické funkce spouštěné přes HTTP poskytovány na vyhrazené doméně pro každý workspace** na adrese `https://\<your-workspace-subdomain>.twenty.com\<path>` — právě na tuto adresu `TWENTY_FUNCTIONS_URL` směřuje. Pro externí volající zkopírujte přesnou URL z nastavení funkce **HTTP trigger** nebo z karty **Settings** aplikace.
> **V Twenty Cloud jsou logické funkce spouštěné přes HTTP poskytovány na vyhrazené doméně pro každý workspace** na adrese `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — právě na tuto adresu `TWENTY_FUNCTIONS_URL` směřuje. Pro externí volající zkopírujte přesnou URL z nastavení funkce **HTTP trigger** nebo z karty **Settings** aplikace.
<Warning>
Původní funkční trasa `/s/` je **zastaralá** a bude **deaktivována dne 2026-07-24**. Místo toho použijte `TWENTY_FUNCTIONS_URL` (viz výše) a do tohoto data migrujte všechny pevně zakódované adresy URL `/s/`. Trasa `/s/` zůstává k dispozici pro self-hosting.
@@ -212,7 +210,7 @@ Headless front komponenta může volání spustit při mountu přes komponentu `
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
@@ -316,13 +314,13 @@ Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli,
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
@@ -405,12 +403,11 @@ Zde je příklad, který používá hostitelské API k zobrazení snackbaru a za
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
@@ -451,10 +448,10 @@ export default defineFrontComponent({
Použijte `useSelectedRecordIds()` pro zpracování více vybraných záznamů. To je užitečné pro hromadné operace:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
@@ -492,12 +489,19 @@ export default defineFrontComponent({
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
Zobrazte ji pomocí [položky příkazové nabídky](/l/cs/developers/extend/apps/layout/command-menu-items) omezené na výběry záznamů:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
@@ -35,6 +35,8 @@ export default defineNavigationMenuItem({
* `position` určuje pořadí v postranním panelu.
* Výčet také obsahuje `NavigationMenuItemType.RECORD`, používaný interně pro oblíbené záznamy vytvořené uživateli — nelze jej použít z manifestu aplikace (neexistuje žádné pole, které by odkazovalo na záznam).
* `icon` a `color` jsou volitelné a upravují, jak položka vypadá.
* `folderUniversalIdentifier` je k dispozici také na libovolné položce, aby ji bylo možné vložit do nadřazené položky typu `FOLDER`.
@@ -33,17 +33,32 @@ export default defineView({
## Hlavní body
* `objectUniversalIdentifier` určuje, na který objekt se toto zobrazení vztahuje. Může to být vlastní objekt, který jste definovali, nebo standardní objekt Twenty.
* `key` určuje typ zobrazení — `ViewKey.INDEX` je hlavní seznamové zobrazení pro daný objekt.
* `key: ViewKey.INDEX` označuje zobrazení jako hlavní seznamové zobrazení objektu (to, které se otevře po kliknutí na navigační položku `OBJECT`).
* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`.
* Pro pokročilé konfigurace můžete také deklarovat `filters`, `filterGroups`, `groups` a `fieldGroups`.
* Pro pokročilé konfigurace můžete také deklarovat `filters`, `filterGroups`, `sorts`, `groups` a `fieldGroups`.
* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení.
## Volitelné vlastnosti
| Vlastnost | Hodnoty | Popis |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | `ViewType.TABLE` (výchozí), `ViewType.KANBAN`, `ViewType.CALENDAR` | Jak jsou záznamy uspořádány. (`FIELDS_WIDGET` / `TABLE_WIDGET` také existují, ale jsou používány interně widgety rozvržení stránky.) |
| `visibility` | `ViewVisibility.WORKSPACE` (výchozí), `ViewVisibility.UNLISTED` | Zda je zobrazení uvedeno pro celý workspace nebo skryto ve výběrech. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (výchozí), `ViewOpenRecordIn.RECORD_PAGE` | Kde se záznam otevře po kliknutí. |
| `sorts` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Výchozí pořadí řazení. |
| `isCompact` | `boolean` | Kompaktní zobrazení řádků. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Seskupení záznamů (např. sloupce kanbanu) podle pole. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Agregace a velikost sloupců v kanbanu. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Zobrazení kalendáře: rozvržení a datumové pole, které určuje umístění záznamů. |
Všechny výše uvedené výčtové typy jsou exportovány z `twenty-sdk/define`.
## Filtry
Zobrazení může být dodáno s předem aplikovanými filtry. Každý filtr má tři souřadnice: **pole**, které se filtruje, **operand** (jak porovnávat) a **hodnotu** (proti čemu porovnávat). Všechny tři musí být v souladu — použití operandu, který se nehodí k typu pole, bude při synchronizaci odmítnuto.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
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`
* **httpRoute**: Expose your function on the HTTP path and method at a **functions base URL** of your workspace — value Twenty injects as `TWENTY_FUNCTIONS_URL` (na dvaceti Cloudu), vyhrazená doména na pracovní plochu):
> např. `path: '/post-card/create'` je volatelné na `https://your-workspace.withtwenty.com/post-card/create`
<Warning>
Starší trasa `/s/` prefixu (`https://your-twenty-server.com/s/post-card/create`) je **zastaralá na dvacet Cloud** a bude deaktivována na **2026-07-24**. Zůstává k dispozici pro vlastní hostované a místní instance, které nenastavují izolovanou doménu funkcí použijte při nastavení `TWENTY_FUNCTIONS_URL`, a přejděte zpět na `\<server-url>/s/\<path>` v opačném případě.
</Warning>
<Note>
Chcete-li vyvolat logickou funkci spuštěnou trasou z (bezhlavé) front-endové komponenty, podívejte se na [Volání logické funkce](/l/cs/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -42,7 +42,7 @@ Logická funkce volí jeden nebo více spouštěčů — každá z níže uveden
| Spouštěč | Kdy se spouští | Nastavení |
| --------------------------- | ----------------------------------------------------------------- | ------------------------------- |
| **HTTP route** | Požadavek dorazí na váš koncový bod `/s/\<path>` | `httpRouteTriggerSettings` |
| **HTTP route** | Požadavek zasáhne veřejnou adresu URL funkce | `httpRouteTriggerSettings` |
| **Cron** | CRON výraz se shoduje | `cronTriggerSettings` |
| **Událost databáze** | Záznam v pracovním prostoru je vytvořen, aktualizován nebo smazán | `databaseEventTriggerSettings` |
| **Nástroj AI** | Funkce Twenty AI se rozhodne zavolat vaši funkci | `toolTriggerSettings` |
@@ -4,7 +4,25 @@ description: příkazy `yarn twenty` pro spouštění funkcí, streamování log
icon: terminal
---
Kromě `dev`, `dev:build`, `dev:add` a `dev:typecheck` poskytuje `yarn twenty` CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací.
CLI `yarn twenty` je vaše rozhraní ke všemu, co souvisí s aplikací. Úplný seznam příkazů:
| Příkaz | K čemu slouží | Zdokumentováno v |
| ----------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `dev` | Sleduje zdrojové soubory a živě synchronizuje změny | [Rychlý start](/l/cs/developers/extend/apps/getting-started/quick-start) |
| `plán` | Náhled změn metadat bez jejich aplikování | [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `použít` | Aplikovat změny metadat po zobrazení plánu | [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Zkompilovat aplikaci a vygenerovat API klienta (`--tarball` pro zabalení do `.tgz`) | [Publikování](/l/cs/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Spustit kontrolu typů TypeScriptu | [Testování](/l/cs/developers/extend/apps/operations/testing) |
| `dev:add` | Vytvořit základ nového objektu (scaffold) | [Scaffolding](/l/cs/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Znovu vygenerovat typovaného API klienta | tato stránka |
| `dev:function:exec` / `dev:function:logs` | Spustit funkce a streamovat jejich logy | tato stránka |
| `dev:translations-extract` | Extrahovat přeložitelné řetězce do katalogů v `locales/` | [Překlady](/l/cs/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | Spustit synchronizaci katalogu tržiště | [Publikování](/l/cs/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Životní cyklus vydání | [Publikování](/l/cs/developers/extend/apps/operations/publishing) a tato stránka |
| `docker:*` | Spravovat kontejner lokálního serveru Twenty | [Lokální server](/l/cs/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Spravovat připojení k serveru | tato stránka |
Každý příkaz přijímá `-r, --remote \<name>` pro zacílení na konkrétní remote místo výchozího.
## Spouštění funkcí (`yarn twenty dev:function:exec`)
@@ -20,8 +38,9 @@ yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the post-install function
# Execute the install hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
## Zobrazení logů funkcí (`yarn twenty dev:function:logs`)
@@ -100,6 +119,12 @@ yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
Metadata zobrazená v tržišti pocházejí z vaší konfigurace `defineApplication()` — z polí jako `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` a `termsUrl`.
Metadata zobrazená v marketplace pochází z vaší konfigurace `defineApplication()` — viz výše [Metadata marketplace](#marketplace-metadata).
<Note>
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`.
@@ -12,16 +12,20 @@ Lokální vývoj aplikací se točí kolem **synchronizace**: CLI znovu sestaví
Pro každodenní lokální iteraci téměř vždy chcete `yarn twenty dev`. Nasazování a publikování slouží k vydávání verzí, **ne** pro lokální vývojovou smyčku.
</Note>
| Chcete… | Příkaz | Poznámky |
| --------------------------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Iterujte lokálně s živou synchronizací | `yarn twenty dev` | Sleduje vaše soubory a při každé změně spustí synchronizaci. |
| Jednorázová synchronizace a ukončení (CI, skripty, hooky) | `yarn twenty dev --once` | Provede jedno sestavení + synchronizaci a skončí. |
| Náhled změn **bez jejich aplikování** | `yarn twenty dev --once --dry-run` | Spočítá a vypíše rozdíly; nic nezapisuje. |
| Odebrat aplikaci z pracovního prostoru | `yarn twenty app:uninstall` | Přidejte `--yes` pro přeskočení výzvy. |
| Odeslat tarball na server | `yarn twenty app:publish --private` | Vyžaduje **přísně vyšší** verzi v `package.json` — viz [Publikování](/l/cs/developers/extend/apps/operations/publishing). |
| Publikovat na marketplace (npm) | `yarn twenty app:publish` | — |
| Nainstalovat / aktualizovat nasazenou verzi | `yarn twenty app:install` | Nainstaluje aktuálně nasazenou verzi. |
| Vymazat lokální server a začít znovu | `yarn twenty docker:reset` | Smaže **všechna** lokální data — krajní řešení. |
| Chcete… | Příkaz | Poznámky |
| --------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Iterujte lokálně s živou synchronizací | `yarn twenty dev` | Sleduje vaše soubory a při každé změně spustí synchronizaci. |
| Jednorázová synchronizace a ukončení (CI, skripty, hooky) | `yarn twenty apply` | Provede jedno sestavení + synchronizaci a skončí. Přidejte `--force`, abyste přeskočili potvrzení destruktivní změny. |
| Náhled změn **bez jejich aplikování** | `yarn twenty plan` | Spočítá a vypíše rozdíly; nic nezapisuje. |
| Odebrat aplikaci z pracovního prostoru | `yarn twenty app:uninstall` | Přidejte `--yes` pro přeskočení výzvy. |
| Odeslat tarball na server | `yarn twenty app:publish --private` | Vyžaduje **přísně vyšší** verzi v `package.json` — viz [Publikování](/l/cs/developers/extend/apps/operations/publishing). |
| Publikovat na marketplace (npm) | `yarn twenty app:publish` | — |
| Nainstalovat / aktualizovat nasazenou verzi | `yarn twenty app:install` | Nainstaluje aktuálně nasazenou verzi. |
| Vymazat lokální server a začít znovu | `yarn twenty docker:reset` | Smaže **všechna** lokální data — krajní řešení. |
<Note>
`yarn twenty dev --once` a `yarn twenty dev --once --dry-run` stále fungují jako zastaralé aliasy pro `yarn twenty apply` a `yarn twenty plan`.
</Note>
### Lokální synchronizace nevyžaduje zvýšení verze
@@ -29,19 +33,26 @@ Pravidlo striktně rostoucí `version` (`VERSION_ALREADY_EXISTS` při nasazení,
## Čtení výstupu synchronizace
Každá synchronizace vypíše změny metadat, které aplikovala (nebo by aplikovala s `--dry-run`):
Každá synchronizace vypíše změny metadat, které aplikovala (nebo by aplikovala s `plan`), ve stylu Terraformu — jeden blok na entitu s jejími atributy a poté souhrnný řádek:
```text filename="Terminal"
Metadata changes: 2 created, 1 updated, 1 deleted
created objectMetadata rocket
created fieldMetadata timelineActivities
updated fieldMetadata launchedAt
deleted pageLayout legacyTab
✓ Synced
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
To je váš první diagnostický nástroj: přesně ukazuje, které objekty, pole a rozložení se změnily, takže si můžete ověřit, že synchronizace udělala to, co jste očekávali, ještě před kontrolou v UI.
Destruktivní změny (`to destroy`) jsou uvedeny s tím, co odstraňují (např. `objectMetadata "auditNote" — drops the table and all its rows`) a vyžadují interaktivní potvrzení, nebo `--force` ve skriptech.
Když synchronizace selže na jedné entitě, chyba uvede problematickou entitu a její `universalIdentifier`, například:
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
Tento identifikátor použijte k nalezení entity ve vašem manifestu (a případně v pracovním prostoru) místo hádání, která je v konfliktu.
## Náhled změn (dry run)
## Náhled změn (plan)
`yarn twenty dev --once --dry-run` sestaví váš manifest, požádá server o migrační plán a vypíše ho — aniž by cokoli aplikoval. Je to bezpečný způsob, jak si předem zodpovědět otázku „co by tato synchronizace změnila?“ ještě předtím, než se k ní zavážete.
`yarn twenty plan` sestaví váš manifest, požádá server o migrační plán a vypíše ho — **aniž by cokoli aplikoval**. Je to bezpečný způsob, jak si předem zodpovědět otázku „co by tato synchronizace změnila?“ ještě předtím, než se k ní zavážete.
```bash filename="Terminal"
yarn twenty dev --once --dry-run
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata diff (dry run, nothing will be applied)...
Metadata changes: 1 created, 1 updated
created fieldMetadata timelineActivities
updated objectMetadata rocket
✓ Dry run complete for My App — no changes were applied
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "timelineActivities" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Spuštění nanečisto:
Plán:
* **Nic nezapisuje** — žádná migrace metadat, žádná aktualizace záznamu aplikace, žádné změny výchozí role / karty a žádná generace API klienta.
* Vrací **stejný diff**, jaký by aplikovala skutečná synchronizace, takže můžete předem zkontrolovat vytvořené / aktualizované / smazané entity.
* Je užitečný před rizikovou změnou, při kontrole změny vygenerované pomocí AI nebo ve skriptu, který má selhat, pokud se má provést neočekávaná změna.
<Note>
Režim dry run zobrazuje pouze náhled změn **metadat** a vyžaduje, aby byla aplikace alespoň jednou synchronizovaná (aby o ní pracovní prostor věděl). Pokud jej spustíte proti aplikaci, která nikdy nebyla synchronizovaná, server ohlásí, že aplikace není nainstalovaná — nejprve jednou spusťte `yarn twenty dev`.
Plán zobrazuje pouze náhled změn **metadat** a vyžaduje, aby byla aplikace alespoň jednou synchronizovaná (aby o ní pracovní prostor věděl). Pokud jej spustíte proti aplikaci, která nikdy nebyla synchronizovaná, server ohlásí, že aplikace není nainstalovaná — nejprve jednou spusťte `yarn twenty dev`.
</Note>
## Postup obnovy
Když lokální metadata vypadají špatně, postupujte v tomto pořadí a zastavte se, jakmile se problém vyřeší. Každý další krok je rušivější než ten předchozí.
1. **Znovu synchronizujte.** Znovu spusťte `yarn twenty dev --once`. Synchronizace jsou idempotentní — znovu spuštěný čistý manifest je bezpečný a často vyřeší přechodný problém.
2. **Prohlédněte si plán.** Spusťte `yarn twenty dev --once --dry-run`, abyste přesně viděli, co chce další synchronizace změnit, aniž by to aplikovala.
1. **Znovu synchronizujte.** Znovu spusťte `yarn twenty apply`. Synchronizace jsou idempotentní — znovu spuštěný čistý manifest je bezpečný a často vyřeší přechodný problém.
2. **Prohlédněte si plán.** Spusťte `yarn twenty plan`, abyste přesně viděli, co chce další synchronizace změnit, aniž by to aplikovala.
3. **Přečtěte si pojmenovanou chybu.** Pokud synchronizace selže, poznamenejte si typ metadat a `universalIdentifier` ve zprávě (viz výše) a tuto entitu najděte ve svém manifestu. Konflikt obvykle ukazuje na duplicitní nebo znovu použitý identifikátor.
4. **Odinstalujte a znovu nainstalujte.** `yarn twenty app:uninstall`, poté znovu synchronizujte (`yarn twenty dev`). Tím znovu vybudujete metadata aplikace z čistého stavu, zatímco zbytek vašeho pracovního prostoru zůstane nedotčený.
5. **Úplný reset (krajní řešení).** `yarn twenty docker:reset`, poté znovu naplňte data a synchronizujte.
@@ -78,6 +78,13 @@ Vytvořte `vitest.config.ts` v kořeni vaší aplikace:
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
@@ -88,66 +95,74 @@ export default defineConfig({
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
setupFiles: ['src/__tests__/setup-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL: 'http://localhost:2020',
TWENTY_API_KEY: 'your-api-key',
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru:
Vytvořte globální soubor pro nastavení, který ověří, že je server dosažitelný, zapíše testovací konfiguraci pro SDK (`~/.twenty/config.test.json`) a před spuštěním testů provede synchronizaci aplikace:
```ts src/__tests__/setup-test.ts
```ts src/__tests__/global-setup.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');
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
beforeAll(async () => {
// Verify the server is running
const response = await fetch(`${TWENTY_API_URL}/healthz`);
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
'Start the server before running integration tests.',
);
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write a temporary config for the SDK
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(TEST_CONFIG_DIR, 'config.json'),
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
});
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## 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 |
| Funkce | Popis |
| -------------- | ------------------------------------------------------------------------------ |
| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball |
| `appDeploy` | Nahraje tarball na server |
| `appDevOnce` | Jednorázově sestaví a synchronizuje aplikaci (stejně jako `yarn twenty apply`) |
| `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`.
@@ -238,64 +253,10 @@ Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů:
yarn twenty dev:typecheck
```
Spustí se `tsc --noEmit` a nahlásí se případné chyby typů.
Spustí se `tsc --noEmit` proti souboru `tsconfig.json` vaší aplikace a nahlásí se případné chyby typů. Vygenerované aplikace také obsahují skript `yarn typecheck`, který pokrývá i testovací soubory (`tsconfig.spec.json`).
## CI s GitHub Actions
Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů.
Generátor kostry vytvoří připravený k použití workflow v `.github/workflows/ci.yml`. Při každém pushi do `main` a při každém pull requestu spustí efemérní server Twenty v runneru (pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), poté spustí `yarn lint`, `yarn typecheck`, `yarn test:unit` a `yarn test` s `TWENTY_API_URL` / `TWENTY_API_KEY` směřujícími na tento server. Nejsou vyžadována žádná tajemství a verzi serveru můžete připnout pomocí proměnné prostředí `TWENTY_VERSION` na začátku workflow.
Workflow:
1. Načte váš kód (checkout).
2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Nainstaluje závislosti pomocí `yarn install --immutable`
4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce
```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 }}
```
Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí dočasný server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky.
Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow.
Úplný postup pro obě workflow generované kostrou (`ci.yml` a nasazovací pipeline `cd.yml`) najdete v části [Publishing → Automated CI/CD](/l/cs/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows).
@@ -91,9 +91,11 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
const apiBaseUrl = process.env.TWENTY_API_URL;
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
@@ -186,7 +188,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,8 +9,15 @@ Stejný handler může také odpovědět na HTTP požadavky. Přidáme dva trasy
* **POST** koncový bod uživatelského rozhraní volá, aby vytvořilo dokument, a
* veřejný **GET** koncový bod, který vykresluje dokument jako tiskovou webovou stránku.
Oba použijte `httpRouteTriggerSettings`. Trasy aplikací jsou vedeny pod `/s` na vašem
serveru (např. `http://localhost:2020/s/documents/generate`).
Oba použijte `httpRouteTriggerSettings`. Na místním serveru vývojáře jsou trasy
provozovány pod předponou `/s` (např. `http://localhost:2020/s/documents/generate`).
<Note>
Na dvaceti Cloudu jsou trasy vedeny na doméně funkcí vyhrazených v pracovním prostoru
— URL je dvacet vložena jako `TWENTY_FUNCTIONS_URL`, bez předpony `/s`. Prefix `/s`
je tam zastaralý a zůstává pouze pro samostatně hostované a místní instance.
Viz [Volání logické funkce](/l/cs/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## POST trasa generovat na požádání
@@ -77,11 +77,11 @@ Spustit tytéž brány CI:
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty dev --once --dry-run # preview the metadata diff
yarn twenty plan # preview the metadata diff
```
Suchý běh vypíše přesně to, co by se změnilo na serveru bez jeho použití
je dobrá závěrečná kontrola. Viz
Plán vypíše přesně to, co by se na serveru změnilo, aniž by změny provedl —
dobrá závěrečná kontrola. Viz
[Testing](/l/cs/developers/extend/apps/operations/testing) a
[Synchronizace a obnovy](/l/cs/developers/extend/apps/operations/sync-and-recovery).
@@ -4,7 +4,7 @@ description: Führen Sie Logik vor oder nach der Installation aus  befüllen
icon: wrench
---
Installations-Hooks sind spezielle Logikfunktionen, die während des Installations- oder Upgrade-Lebenszyklus ausgeführt werden. Sie verwenden dieselbe Handler-Laufzeit wie reguläre [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) und erhalten ein `InstallPayload`, werden jedoch mit eigenen Define-Funktionen deklariert  `definePostInstallLogicFunction()` und `definePreInstallLogicFunction()`  und sind vom normalen Trigger-Modell (HTTP, Cron, Datenbankereignisse) getrennt.
Installations-Hooks sind spezielle Logikfunktionen, die während des Installations- oder Upgrade-Lebenszyklus ausgeführt werden. Sie verwenden dieselbe Handler-Laufzeit wie reguläre [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) und erhalten ein `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` ist bei einer Neuinstallation `undefined`), werden jedoch mit eigenen Define-Funktionen deklariert und befinden sich außerhalb des normalen Trigger-Modells (HTTP, Cron, Datenbankereignisse).
Jede App darf **höchstens eine Pre-Install-Funktion** und **höchstens eine Post-Install-Funktion** definieren. Der Manifest-Build schlägt fehl, wenn mehr als eine von beiden erkannt wird.
@@ -19,111 +19,59 @@ Jede App darf **höchstens eine Pre-Install-Funktion** und **höchstens eine Pos
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Wird ausgeführt, nachdem die Metadatenmigration des Arbeitsbereichs angewendet wurde">
## Auf einen Blick
Eine Post-Install-Funktion wird automatisch ausgeführt, sobald Ihre App die Installation in einem Arbeitsbereich abgeschlossen hat. Der Server führt sie **nach** der Synchronisierung der Metadaten der App und der Generierung des SDK-Clients aus, sodass der Arbeitsbereich vollständig einsatzbereit ist und das neue Schema bereitsteht. Typische Anwendungsfälle umfassen das Befüllen von Standarddaten, das Erstellen anfänglicher Datensätze, das Konfigurieren von Arbeitsbereichseinstellungen oder das Bereitstellen von Ressourcen bei Diensten von Drittanbietern.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Läufe | Vor der Metadatenmigration — das **bisherige** Schema und die Daten sind noch intakt | Nach der Migration und SDK-Generierung — das **neue** Schema ist aktiv |
| Ausführung | Immer synchron; blockiert die Installation | Standardmäßig asynchron (warteschlangengesteuert, 3 Wiederholungsversuche); synchrones Opt-in über `shouldRunSynchronously: true` |
| Im Fehlerfall | Die Installation wird **abgebrochen**, bevor eine Schemaänderung erfolgt | Asynchron: bis zu 3 Mal erneut ausgeführt. Synchron: Der Aufrufer erhält `POST_INSTALL_ERROR` (Schemaänderungen werden **nicht** zurückgerollt) |
| Typische Verwendung | Daten sichern oder korrigieren, die eine Migration verlieren würde; ein riskantes Upgrade ablehnen, indem ein Fehler geworfen wird | Standarddaten befüllen, den Arbeitsbereich konfigurieren, externe Ressourcen registrieren |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Faustregel:** Standardmäßig Post-Install verwenden. Greifen Sie nur zu Pre-Install, wenn die Migration selbst destruktiv ist und Sie den vorherigen Zustand abfangen müssen, bevor er verloren geht.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Sie möchten ... | Verwenden |
| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Daten befüllen, den Arbeitsbereich konfigurieren, externe Ressourcen registrieren | `post-install` |
| Lange laufende Aufgaben, die die Installationsantwort nicht blockieren sollten | `post-install` (standardmäßig asynchroner Modus, mit Worker-Wiederholungsversuchen) |
| Schnelle Einrichtung, auf die sich der Aufrufer unmittelbar nach der Rückkehr der Installationsantwort verlässt | `post-install` mit `shouldRunSynchronously: true` |
| Daten lesen oder sichern, die bei der bevorstehenden Migration verloren gingen | `pre-install` |
| Ein Upgrade ablehnen, das vorhandene Daten beschädigen würde | `pre-install` (`throw` im Handler) |
| Abgleich bei jedem Upgrade | Einer der Hooks mit `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## Verhalten, das von beiden Hooks geteilt wird
Sie können die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen:
* Die Konfiguration ist eine `defineLogicFunction`-Konfiguration ohne die Trigger-Einstellungen, aber mit `shouldRunOnVersionUpgrade`.
* **Wann sie ausgeführt werden**: standardmäßig nur bei Neuinstallationen. Setze `shouldRunOnVersionUpgrade: true`, um sie auch bei Upgrades auszuführen. Verwende `previousVersion` / `newVersion`, um je nach Upgrade-Pfad unterschiedlich zu verzweigen.
* **Idempotenz ist wichtig**: Asynchrones Post-Install kann erneut ausgeführt werden, und beide Hooks werden bei Upgrades erneut ausgeführt, wenn `shouldRunOnVersionUpgrade` aktiviert ist.
* Die übliche Logikfunktions-Umgebung (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) wird injiziert, sodass du die Twenty-API mit dem Token deiner App aufrufen kannst.
* Der Hook wird zur Build-Zeit automatisch an das Anwendungsmanifest angehängt (`preInstallLogicFunction` / `postInstallLogicFunction`) — in [`defineApplication()`](/l/de/developers/extend/apps/config/application) muss nichts referenziert werden.
* Der Standardwert für `timeoutSeconds` ist 300, um längere Einrichtungsaufgaben wie Daten-Seeding zu ermöglichen.
* **Wird im Dev-Modus nicht ausgeführt**: `yarn twenty dev` überspringt den Installations-Flow und synchronisiert Dateien direkt, sodass Hooks dort nie ausgeführt werden. Führe sie stattdessen manuell aus:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Hauptpunkte:
* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`) weglässt.
* Der Handler erhält ein `InstallPayload` mit `{ previousVersion?: string; newVersion: string }` — `newVersion` ist die zu installierende Version, und `previousVersion` ist die zuvor installierte Version (oder `undefined` bei einer Neuinstallation). Verwenden Sie diese Werte, um Neuinstallationen von Upgrades zu unterscheiden und versionsspezifische Migrationslogik auszuführen.
* **Wann der Hook ausgeführt wird**: standardmäßig nur bei Neuinstallationen. Übergeben Sie `shouldRunOnVersionUpgrade: true`, wenn er auch beim Upgrade der App von einer vorherigen Version ausgeführt werden soll. Wenn weggelassen, ist das Flag standardmäßig `false` und Upgrades überspringen den Hook.
* **Ausführungsmodell — standardmäßig asynchron, synchron optional**: Das Flag `shouldRunSynchronously` steuert, *wie* Post-Install ausgeführt wird.
* `shouldRunSynchronously: false` *(Standard)* — der Hook wird **in die Nachrichtenwarteschlange eingereiht** mit `retryLimit: 3` und läuft asynchron in einem Worker. Die Installationsantwort kommt zurück, sobald der Job eingereiht ist, sodass ein langsamer oder fehlschlagender Handler den Aufrufer nicht blockiert. Der Worker versucht es bis zu dreimal erneut. **Verwenden Sie dies für lang laufende Jobs** — das Befüllen großer Datensätze, Aufrufe langsamer Drittanbieter-APIs, Bereitstellung externer Ressourcen, alles, was ein vernünftiges HTTP-Antwortfenster überschreiten könnte.
* `shouldRunSynchronously: true` — der Hook wird **inline während des Installationsablaufs** ausgeführt (gleicher Executor wie bei Pre-Install). Die Installationsanforderung blockiert, bis der Handler fertig ist, und wenn er einen Fehler wirft, erhält der Installationsaufrufer einen `POST_INSTALL_ERROR`. Keine automatischen Wiederholungen. **Verwenden Sie dies für schnelle Aufgaben, die vor der Antwort abgeschlossen sein müssen** — z. B. um dem Benutzer einen Validierungsfehler auszugeben oder für eine schnelle Einrichtung, auf die der Client unmittelbar nach der Rückkehr des Installationsaufrufs angewiesen ist. Beachten Sie, dass die Metadatenmigration bereits angewendet wurde, wenn Post-Install läuft, sodass ein Fehler im Synchronmodus die Schemaänderungen **nicht** rückgängig macht — er zeigt lediglich den Fehler an.
* Stellen Sie sicher, dass Ihr Handler idempotent ist. Im asynchronen Modus kann die Warteschlange bis zu dreimal erneut versuchen; in beiden Modi kann der Hook bei Upgrades erneut laufen, wenn `shouldRunOnVersionUpgrade: true`.
* Die Umgebungsvariablen `APPLICATION_ID`, `APP_ACCESS_TOKEN` und `API_URL` sind im Handler verfügbar (wie bei jeder anderen Logikfunktion), sodass Sie die Twenty API mit einem auf Ihre App beschränkten Anwendungszugriffstoken aufrufen können.
* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird.
* Die `universalIdentifier`, `shouldRunOnVersionUpgrade` und `shouldRunSynchronously` der Funktion werden während des Builds automatisch dem Anwendungsmanifest unter dem Feld `postInstallLogicFunction` hinzugefügt  Sie müssen sie in [`defineApplication()`](/l/de/developers/extend/apps/config/application) nicht referenzieren.
* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen.
* **Nicht im Dev-Modus ausgeführt**: Wenn eine App lokal registriert ist (über `yarn twenty dev`), überspringt der Server den Installationsablauf vollständig und synchronisiert Dateien direkt über den CLI-Watcher — daher läuft Post-Install im Dev-Modus nie, unabhängig von `shouldRunSynchronously`. Verwenden Sie `yarn twenty dev:function:exec --postInstall`, um es manuell gegen einen laufenden Arbeitsbereich auszulösen.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Wird ausgeführt, bevor die Metadatenmigration des Arbeitsbereichs angewendet wird">
Eine Pre-Install-Funktion wird automatisch während der Installation ausgeführt, **bevor die Metadatenmigration des Arbeitsbereichs angewendet wird**. Sie hat die gleiche Payload-Struktur wie Post-Install (`InstallPayload`), ist aber früher im Installationsablauf positioniert, sodass sie Zustände vorbereiten kann, von denen die bevorstehende Migration abhängt — typische Anwendungsfälle sind das Sichern von Daten, die Validierung der Kompatibilität mit dem neuen Schema oder das Archivieren von Datensätzen, die umstrukturiert oder entfernt werden sollen.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
Sie können die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Hauptpunkte:
* Pre-Install-Funktionen verwenden `definePreInstallLogicFunction()` — dieselbe spezialisierte Konfiguration wie bei Post-Install, nur an einen anderen Lifecycle-Slot gebunden.
* Sowohl Pre- als auch Post-Install-Handler erhalten denselben `InstallPayload`-Typ: `{ previousVersion?: string; newVersion: string }`. Importieren Sie ihn einmal und verwenden Sie ihn für beide Hooks wieder.
* **Wann der Hook ausgeführt wird**: positioniert direkt vor der Metadatenmigration des Arbeitsbereichs (`synchronizeFromManifest`). Vor der Ausführung führt der Server einen rein additiven "pared-down sync" durch, der die Pre-Install-Funktion der **neuen** Version in den Metadaten des Arbeitsbereichs registriert — sonst wird nichts angefasst — und führt sie dann aus. Da dieser Sync nur additiv ist, sind die Objekte, Felder und Daten der vorherigen Version noch intakt, wenn Ihr Handler läuft: Sie können den Zustand vor der Migration gefahrlos lesen und sichern.
* **Ausführungsmodell**: Pre-Install wird **synchron** ausgeführt und **blockiert die Installation**. Wenn der Handler einen Fehler wirft, wird die Installation abgebrochen, bevor Schemaänderungen angewendet werden — der Arbeitsbereich verbleibt in der vorherigen Version in einem konsistenten Zustand. Das ist beabsichtigt: Pre-Install ist Ihre letzte Chance, ein riskantes Upgrade abzulehnen.
* Wie bei Post-Install ist pro Anwendung nur eine Pre-Installationsfunktion zulässig. Sie wird während des Builds automatisch dem Anwendungsmanifest unter `preInstallLogicFunction` hinzugefügt.
* **Nicht im Dev-Modus ausgeführt**: wie bei Post-Install — der Installationsablauf wird für lokal registrierte Apps vollständig übersprungen, daher läuft Pre-Install unter `yarn twenty dev` nie. Verwenden Sie `yarn twenty dev:function:exec --preInstall`, um es manuell auszulösen.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Wird ausgeführt, nachdem die Metadatenmigration des Arbeitsbereichs angewendet wurde">
</Accordion>
<Accordion title="Pre-Install vs. Post-Install: wann was verwenden" description="Den richtigen Installations-Hook wählen">
Beide Hooks sind Teil desselben Installationsablaufs und erhalten dasselbe `InstallPayload`. Der Unterschied besteht darin, **wann** sie relativ zur Metadatenmigration des Workspaces ausgeführt werden, und das ändert, auf welche Daten sie gefahrlos zugreifen können.
Pre-Install ist immer **synchron** (blockiert die Installation und kann sie abbrechen). Post-Install ist **standardmäßig asynchron** — in einen Worker eingereiht mit automatischen Wiederholungen — kann aber per `shouldRunSynchronously: true` in die synchrone Ausführung wechseln. Siehe das Akkordeon zu `definePostInstallLogicFunction` oben, wann welcher Modus zu verwenden ist.
**Verwenden Sie `post-install` für alles, wofür das neue Schema existieren muss.** Dies ist der Regelfall:
* Standarddaten befüllen (Anlegen anfänglicher Datensätze, Standardansichten, Demo-Inhalte) für neu hinzugefügte Objekte und Felder.
* Registrieren von Webhooks bei Drittanbieter-Diensten, jetzt, da die App ihre Anmeldedaten hat.
* Aufrufen Ihrer eigenen API, um eine Einrichtung abzuschließen, die von den synchronisierten Metadaten abhängt.
* Idempotente "Stelle sicher, dass dies existiert"-Logik, die bei jedem Upgrade den Zustand abgleichen soll — kombinieren Sie dies mit `shouldRunOnVersionUpgrade: true`.
Beispiel — nach der Installation einen Standard-`PostCard`-Datensatz anlegen:
Wird ausgeführt, nachdem deine App die Installation abgeschlossen hat: Metadaten synchronisiert, SDK-Client generiert, neues Schema abfragbar. Beispiel — bei Neuinstallationen einen Standarddatensatz anlegen:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**Verwenden Sie `pre-install`, wenn eine Migration ansonsten vorhandene Daten löschen oder beschädigen würde.** Da Pre-Install gegen das vorherige Schema läuft und ein Fehlschlag das Upgrade zurückrollt, ist es der richtige Ort für alles Riskante:
Das Flag `shouldRunSynchronously` steuert das Ausführungsmodell:
* **Sichern von Daten, die gleich gelöscht oder umstrukturiert werden** — z. B. Sie entfernen in v2 ein Feld und müssen dessen Werte vor der Migration in ein anderes Feld kopieren oder in einen Speicher exportieren.
* **Archivieren von Datensätzen, die eine neue Einschränkung ungültig machen würde** — z. B. ein Feld wird `NOT NULL` und Sie müssen zuerst Zeilen mit Null-Werten löschen oder korrigieren.
* **Kompatibilität validieren und das Upgrade ablehnen, wenn die aktuellen Daten nicht sauber migriert werden können** — werfen Sie im Handler einen Fehler, und die Installation wird ohne Änderungen abgebrochen. Das ist sicherer, als die Inkompatibilität mitten in der Migration zu entdecken.
* **Daten umbenennen oder Schlüssel neu zuweisen** vor einer Schemaänderung, bei der sonst die Zuordnung verloren ginge.
* `false` *(Standard)* — in die Nachrichtenwarteschlange eingereiht (`retryLimit: 3`) und von einem Worker ausgeführt. Die Installationsantwort wird zurückgegeben, sobald der Job in die Warteschlange eingereiht wurde. **Für lange laufende Aufgaben verwenden** — das Befüllen großer Datensätze, langsame Drittanbieter-APIs.
* `true` — wird inline während des Installations-Flows ausgeführt. Die Installationsanforderung blockiert, bis der Handler fertig ist; ein geworfener Fehler erscheint als `POST_INSTALL_ERROR` beim Aufrufer (keine Wiederholungsversuche). **Für schnelle Aufgaben verwenden, die unbedingt vor der Antwort abgeschlossen sein müssen.** Die Migration wurde zu diesem Zeitpunkt bereits angewendet, daher werden Schemaänderungen bei einem Fehler nicht zurückgerollt — es wird nur der Fehler nach außen gegeben.
Beispiel — Datensätze vor einer destruktiven Migration archivieren:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Wird ausgeführt, bevor die Metadatenmigration des Arbeitsbereichs angewendet wird">
Wird vor der Metadatenmigration gegen das **bisherige** Schema ausgeführt — die richtige Stelle, um Daten zu sichern, die eine Migration verlieren würde, oder um ein riskantes Upgrade abzulehnen. Vor der Ausführung führt der Server einen rein additiven „pared-down sync“ durch, der nur die Pre-Install-Funktion der neuen Version registriert; alles andere — Objekte, Felder und Daten der vorherigen Version — bleibt unangetastet, wenn dein Handler ausgeführt wird.
Pre-Install ist immer **synchron** und blockiert die Installation. Wenn der Handler einen Fehler wirft, wird die Installation abgebrochen, bevor eine Schemaänderung erfolgt — der Arbeitsbereich verbleibt in der vorherigen Version in einem konsistenten Zustand. Das ist beabsichtigt: Pre-Install ist Ihre letzte Chance, ein riskantes Upgrade abzulehnen.
Beispiel — die Werte eines Legacy-Feldes kopieren, bevor die Migration es entfernt:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**Faustregel:**
| Sie möchten ... | Verwenden |
| ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Standarddaten befüllen, den Arbeitsbereich konfigurieren, externe Ressourcen registrieren | `post-install` |
| Lang laufendes Seeding oder Drittanbieteraufrufe ausführen, die die Installationsantwort nicht blockieren sollten | `post-install` (Standard — `shouldRunSynchronously: false`, mit Worker-Wiederholungen) |
| Schnelle Einrichtung ausführen, auf die sich der Aufrufer unmittelbar nach der Rückkehr des Installationsaufrufs verlassen wird | `post-install` mit `shouldRunSynchronously: true` |
| Daten lesen oder sichern, die bei der bevorstehenden Migration verloren gingen | `pre-install` |
| Ein Upgrade ablehnen, das vorhandene Daten beschädigen würde | `pre-install` (`throw` im Handler) |
| Bei jedem Upgrade einen Abgleich ausführen | `post-install` mit `shouldRunOnVersionUpgrade: true` |
| Einmalige Einrichtung nur bei der ersten Installation durchführen | `post-install` mit `shouldRunOnVersionUpgrade: false` (Standard) |
<Note>
Im Zweifel auf **Post-Install** setzen. Greifen Sie nur zu Pre-Install, wenn die Migration selbst destruktiv ist und Sie den vorherigen Zustand abfangen müssen, bevor er verloren geht.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**Basisfelder werden automatisch hinzugefügt.** Wenn Sie ein benutzerdefiniertes Objekt definieren, erstellt Twenty Standardfelder wie `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` und `deletedAt` für Sie. Sie müssen diese nicht in Ihrem `fields`-Array deklarieren nur Ihre benutzerdefinierten Felder. Sie können ein Standardfeld überschreiben, indem Sie eines mit demselben Namen deklarieren, aber das ist nur selten eine gute Idee.
</Note>
## Feldtypen
Die vollständige Menge von `FieldType`-Werten, exportiert aus `twenty-sdk/define`:
| Kategorie | Typen |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Text | `TEXT`, `RICH_TEXT`, `ARRAY` (von Zeichenketten), `RAW_JSON` |
| Numerisch | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (beliebige Genauigkeit), `RATING`, `POSITION` |
| Daten | `DATE`, `DATE_TIME` |
| Auswahl | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Zusammengesetzt | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Bezeichner & Relationen | `UUID`, `RELATION`, `MORPH_RELATION` (siehe [Relationen](/l/de/developers/extend/apps/data/relations)) |
| System | `TS_VECTOR` (Volltext-Suchvektor, vom Server verwaltet) |
Zusammengesetzte Typen speichern mehrere Unterfelder (z. B. `FULL_NAME` = Vorname + Nachname; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` und `MULTI_SELECT` erfordern ein `options`-Array wie im obigen Beispiel.
## Standardwerte
Wörtliche Zeichenfolgen-Standardwerte müssen in einfache Anführungszeichen **innerhalb** der Zeichenfolge eingeschlossen werden — `defaultValue: "'Draft'"`, nicht `defaultValue: "Draft"`. Deshalb verwendet das `status`-Feld oben `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## Wichtige Dateien
| Datei / Ordner | Zweck |
| ---------------------------------------- | ------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Erforderlich.** Die Hauptkonfigurationsdatei für Ihre App. |
| `src/default-role.ts` | Standardrolle, die steuert, worauf Ihre Logikfunktionen zugreifen können. |
| `src/constants/universal-identifiers.ts` | Automatisch erzeugte UUIDs und Metadaten (Anzeigename, Beschreibung). |
| `src/__tests__/` | Integrationstests (Setup + Beispieltest). |
| `public/` | Statische Assets (Bilder, Schriftarten), die mit Ihrer App ausgeliefert werden. |
| Datei / Ordner | Zweck |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/application-config.ts` | **Erforderlich.** Die Hauptkonfigurationsdatei für Ihre App. |
| `src/default-role.ts` | Standardrolle, die steuert, worauf Ihre Logikfunktionen zugreifen können. |
| `src/constants/universal-identifiers.ts` | Automatisch erzeugte UUIDs und Metadaten (Anzeigename, Beschreibung). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Eine Willkommens-Startseite: eine Front-Komponente, die von einem eigenständigen Seitenlayout gerendert wird und über die Seitenleiste erreichbar ist. |
| `src/__tests__/` | Ein Komponententest plus ein Integrationstest (mit seinem globalen Setup), der die App mit einem echten Server synchronisiert. |
| `public/` | Statische Assets (Bilder, Schriftarten), die mit Ihrer App ausgeliefert werden. |
| `AGENTS.md` / `CLAUDE.md` | Anleitung für KI-Coding-Agents, die an der App arbeiten. |
<Note>
**Die Dateiorganisation liegt bei Ihnen.** Die oben genannten Ordner sind Konventionen das SDK erkennt Entitäten über eine AST-Analyse von `export default defineEntity(...)`-Aufrufen, unabhängig davon, wo sich die Datei befindet.
@@ -47,15 +60,18 @@ Beide Twenty-SDK-Pakete gehören unter `devDependencies`, nicht unter `dependenc
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
Das Scaffolding-Tool fixiert `twenty-sdk` und `twenty-client-sdk` auf seine eigene Version halte beide beim Aktualisieren synchron.
* **`twenty-sdk`** stellt die `twenty`-CLI sowie die Build-/Scaffolding-Tools bereit. Es läuft nur während der Entwicklung und beim Build und wird zur Laufzeit der veröffentlichten App niemals importiert.
* **`twenty-client-sdk`** *wird* hingegen von deinem App-Code importiert (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), aber Twenty stellt es zur Laufzeit bereit Logikfunktionen beziehen es aus einer generierten SDK-Schicht, und Frontend-Komponenten lösen es aus serverseitig ausgelieferten Modulen auf. Deine installierte Kopie wird nur für die Typprüfung und den Build zum Zeitpunkt des Deployments verwendet, daher muss sie niemals im ausgelieferten Bundle enthalten sein.
Wenn eines der Pakete unter `dependencies` bleibt, wird es in das Runtime-Bundle der installierten App gezogen, wo es nur Ballast ist. `twenty build` gibt eine Warnung aus, wenn eines von beiden weiterhin unter `dependencies` aufgeführt ist.
Wenn eines der Pakete unter `dependencies` bleibt, wird es in das Runtime-Bundle der installierten App gezogen, wo es nur Ballast ist. `twenty dev:build` gibt eine Warnung aus, wenn eines von beiden weiterhin unter `dependencies` aufgeführt ist.
Füge die eigenen Runtime-Abhängigkeiten deiner App (Bibliotheken, die deine Logikfunktionen zur Laufzeit tatsächlich importieren) wie gewohnt unter `dependencies` hinzu.
@@ -6,17 +6,17 @@ description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App.
## Voraussetzungen
* **Node.js 24+** — [Hier herunterladen](https://nodejs.org/)
* **Node.js 24.5+** — [Hier herunterladen](https://nodejs.org/)
* **Yarn 4** — Wird mit Node.js über Corepack mitgeliefert. Aktivieren Sie es: `corepack enable`
* **Docker** — [Hier herunterladen](https://www.docker.com/products/docker-desktop/). Erforderlich, um einen lokalen Twenty-Server auszuführen. Überspringen Sie dies, wenn Twenty bereits anderswo läuft.
Das Erstellen einer Twenty-App umfasst drei Phasen. Das Scaffolding-Tool fasst sie zu einem einzigen Happy-Path-Befehl zusammen, aber jede Phase ist ein eigenes Konzept — wenn etwas fehlschlägt, hilft Ihnen das Wissen, in welcher Phase Sie sich befinden, zu erkennen, was zu beheben ist.
| Phase | Was Sie tun | Tool | Ergebnis |
| ----------------------- | ------------------------------------------------------- | ----------------------------- | ---------------------------------------------------- |
| **1. Gerüst erstellen** | Den Quellcode der App erzeugen | `npx create-twenty-app` | Ein TypeScript-Projekt auf der Festplatte |
| **2. Server starten** | Einen Twenty-Server starten, in den synchronisiert wird | Docker + `yarn twenty server` | Eine laufende Twenty-Instanz |
| **3. Synchronisieren** | Ihren Code live mit dem Server synchronisieren | `yarn twenty dev` | Ihre Änderungen erscheinen in der Benutzeroberfläche |
| Phase | Was Sie tun | Tool | Ergebnis |
| ----------------------- | ------------------------------------------------------- | ----------------------------------- | ---------------------------------------------------- |
| **1. Gerüst erstellen** | Den Quellcode der App erzeugen | `npx create-twenty-app` | Ein TypeScript-Projekt auf der Festplatte |
| **2. Server starten** | Einen Twenty-Server starten, in den synchronisiert wird | Docker + `yarn twenty docker:start` | Eine laufende Twenty-Instanz |
| **3. Synchronisieren** | Ihren Code live mit dem Server synchronisieren | `yarn twenty dev` | Ihre Änderungen erscheinen in der Benutzeroberfläche |
---
@@ -28,7 +28,7 @@ Erstellen Sie eine neue App aus der Vorlage:
npx create-twenty-app@latest my-twenty-app
```
Sie werden nach einem Namen und einer Beschreibung gefragt — drücken Sie **Enter** für die Standardwerte. Dadurch wird ein TypeScript-Projekt in `my-twenty-app/` erzeugt, mit einer Startdatei `application-config.ts`, einer Standardrolle, einem CI-Workflow und einem Integrationstest.
Das Scaffolding-Tool ist nicht interaktiv: Der Verzeichnisname wird zum App-Namen. Übergeben Sie `--display-name` und `--description`, um die erzeugten Metadaten anzupassen (Sie können sie später auch in `src/constants/universal-identifiers.ts` bearbeiten). Dadurch wird ein TypeScript-Projekt in `my-twenty-app/` erzeugt, mit einer Startdatei `application-config.ts`, einer Standardrolle, CI/CD-Workflows und einem Integrationstest.
**Nach dieser Phase:** Sie haben den Quellcode einer App auf Ihrem Rechner. Es läuft noch nicht — das ist Phase 2.
@@ -38,28 +38,14 @@ Sie werden nach einem Namen und einer Beschreibung gefragt — drücken Sie **En
Ihre App benötigt einen Twenty-Server, in den sie synchronisieren kann. Der Server ist eine vollständige Twenty-Instanz — UI, GraphQL-API, PostgreSQL — die lokal in Docker läuft. Ihr lokaler Code lädt seine Definitionen auf diesen Server hoch, wodurch sie in der Benutzeroberfläche erscheinen.
Das Scaffolding-Tool bietet an, einen für Sie zu starten:
Der Scaffolder startet eine Instanz für Sie: Bei laufendem Docker zieht er das `twentycrm/twenty-app-dev`-Image, startet es auf Port `2020` und authentifiziert die CLI für den vorbefüllten Demo-Workspace (`tim@apple.dev`) keine Anmeldung erforderlich.
> **Möchten Sie eine lokale Twenty-Instanz einrichten?**
* **Ja (empfohlen)** — lädt das Docker-Image `twentycrm/twenty-app-dev` herunter und startet es auf Port `2020`. Stellen Sie sicher, dass Docker läuft.
* **Nein** — wählen Sie dies, wenn Sie bereits einen Twenty-Server haben, mit dem Sie sich verbinden möchten. Sie können die Verbindung später mit `yarn twenty remote:add` herstellen.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Soll die lokale Instanz gestartet werden?" />
</div>
Sobald der Server läuft, öffnet sich ein Browser zur Anmeldung. Verwenden Sie das vorab eingerichtete Demo-Konto:
* **E-Mail:** `tim@apple.dev`
* **Passwort:** `tim@apple.dev`
Um stattdessen eine Verbindung zu einem bestehenden Twenty-Server herzustellen, übergeben Sie `--url \<your-server-url>`. Remote-Server authentifizieren sich mit OAuth: Ein Browser öffnet sich, damit Sie sich anmelden und auf **Authorize** klicken können, wodurch die CLI Zugriff auf Ihren Workspace erhält. (Sie können lokal auch OAuth aktivieren mit `--authentication-method oauth` melden Sie sich mit `tim@apple.dev` / `tim@apple.dev` an.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty-Anmeldebildschirm" />
</div>
Klicken Sie auf dem nächsten Bildschirm auf **Authorize** — dadurch erhält die CLI Zugriff auf Ihren Arbeitsbereich.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty-CLI-Autorisierungsbildschirm" />
</div>
@@ -117,28 +103,32 @@ Klicken Sie auf **View installed app**, um die Installation im Arbeitsbereich an
### Einmalige Synchronisierung für CI und Skripte
Verwenden Sie `--once`, um einen einzelnen Build + Sync auszuführen und zu beenden — gleiche Pipeline, kein Watcher:
Verwenden Sie `plan` und `apply`, um dieselbe Pipeline einmalig ohne Watcher auszuführen:
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Befehl | Verhalten | Wann verwenden |
| ---------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `yarn twenty dev` | Überwacht und synchronisiert bei jeder Änderung erneut. Läuft, bis Sie es stoppen. | Interaktive lokale Entwicklung. |
| `yarn twenty dev --once` | Einmaliger Build + Sync, beendet sich mit `0` bei Erfolg, mit `1` bei Fehler. | CI, Pre-Commit-Hooks, KI-Agenten, skriptgesteuerte Workflows. |
| `yarn twenty dev --once --dry-run` | Erstellt und gibt die Metadatenänderungen aus **ohne sie anzuwenden**. | Prüfen Sie, was eine Synchronisierung ändern würde, bevor Sie sie ausführen. |
| Befehl | Verhalten | Wann verwenden |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `yarn twenty dev` | Überwacht und synchronisiert bei jeder Änderung erneut. Läuft, bis Sie es stoppen. | Interaktive lokale Entwicklung. |
| `yarn twenty apply` | Einmaliger Build + Sync, beendet sich mit `0` bei Erfolg, mit `1` bei Fehler. Fragt bei destruktiven Änderungen nach einer Bestätigung (übergeben Sie `--force`, um dies zu überspringen). | CI, Pre-Commit-Hooks, KI-Agenten, skriptgesteuerte Workflows. |
| `yarn twenty plan` | Erstellt und gibt die Metadatenänderungen aus **ohne sie anzuwenden**. | Prüfen Sie, was eine Synchronisierung ändern würde, bevor Sie sie ausführen. |
Beide Modi benötigen ein authentifiziertes Remote-Repository. Weitere Informationen zu `--dry-run` finden Sie unter [Synchronisierung & Wiederherstellung](/l/de/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run).
Alle Modi benötigen ein authentifiziertes Remote-Repository. Weitere Informationen zu `plan` finden Sie unter [Synchronisierung & Wiederherstellung](/l/de/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan).
<Note>
`yarn twenty dev --once` und `yarn twenty dev --once --dry-run` sind veraltete Aliasse für `yarn twenty apply` und `yarn twenty plan`.
</Note>
### Dev-Modus-Optionen
| Flag | Beschreibung |
| ------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `--once` | Einmal erstellen und synchronisieren, dann beenden. |
| `--dry-run` | Mit `--once` können Sie die Metadatenänderungen anzeigen, ohne sie anzuwenden. Schreibt nichts. |
| `--debounceMs \<ms>` | Legt die Entprellzeit für Dateiänderungen in Millisekunden fest (Standard: `2000`). |
| `--verbose` / `--debug` | Zeigt ausführliche Build-Protokolle, Sync-Anfragen und Fehler-Traces an. |
| Flag | Beschreibung |
| ------------------------------------- | ----------------------------------------------------------------------------------- |
| `--force` | Wendet destruktive Änderungen (Löschungen) ohne Bestätigung an. |
| `--debounceMs \<ms>` | Legt die Entprellzeit für Dateiänderungen in Millisekunden fest (Standard: `1000`). |
| `--verbose` / `--debug` | Zeigt ausführliche Build-Protokolle, Sync-Anfragen und Fehler-Traces an. |
## Was Sie erstellen können
@@ -22,18 +22,22 @@ yarn twenty dev:add frontComponent
## Verfügbare Entitätstypen
| Entitätstyp | Befehl | Generierte Datei |
| ---------------------- | ---------------------------------------- | ------------------------------------------------------- |
| Objekt | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| Feld | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| Logikfunktion | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| Frontend-Komponente | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| Rolle | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| Skill | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| Agent | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| Ansicht | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Navigationsmenüeintrag | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Seitenlayout | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Entitätstyp | Befehl | Generierte Datei |
| -------------------------- | ---------------------------------------- | ------------------------------------------------------- |
| Objekt | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| Feld | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| Logikfunktion | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| Frontend-Komponente | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| Rolle | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| Skill | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| Agent | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| Ansicht | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Navigationsmenüeintrag | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Seitenlayout | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Seitenlayout-Registerkarte | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Befehlsmenü-Eintrag | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Ansichtsfeld | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Verbindungsanbieter | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## Was der Scaffolder generiert
@@ -5,10 +5,10 @@ icon: wrench
---
* **Docker-Fehler** — Stellen Sie sicher, dass Docker Desktop (oder der Daemon) läuft, bevor Sie `yarn twenty docker:start` ausführen. Die Fehlermeldung zeigt den richtigen Startbefehl für Ihr Betriebssystem an.
* **Falsche Node-Version** — 24+ erforderlich. Prüfen Sie mit `node -v`.
* **Falsche Node-Version** — Es wird 24.5+ benötigt (`engines.node: ^24.5.0`). Prüfen Sie mit `node -v`.
* **Yarn 4 fehlt** — Führen Sie `corepack enable` aus.
* **Abhängigkeiten defekt** — `rm -rf node_modules && yarn install`.
* **`twenty-sdk`-Fehler nach dem Upgrade auf v2.8.0** — es wurde in v2.8.0 von `dependencies` zu `devDependencies` verschoben. Siehe [Projektstruktur → Abhängigkeiten](/l/de/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` warnt vor `twenty-client-sdk` unter `dependencies`** — Es wird zur Laufzeit von Twenty bereitgestellt, daher sollte es in `devDependencies` neben `twenty-sdk` verschoben werden. Siehe [Projektstruktur → Abhängigkeiten](/l/de/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` warnt vor `twenty-client-sdk` unter `dependencies`** — Es wird zur Laufzeit von Twenty bereitgestellt, daher sollte es in `devDependencies` neben `twenty-sdk` verschoben werden. Siehe [Projektstruktur → Abhängigkeiten](/l/de/developers/extend/apps/getting-started/project-structure#dependencies).
Hängen Sie fest? Bitten Sie im [Twenty-Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) um Hilfe.
@@ -13,7 +13,6 @@ export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
icon: 'IconLayoutDashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
@@ -22,51 +21,23 @@ export default defineCommandMenuItem({
## Konfigurationsfelder
| Feld | Erforderlich | Beschreibung |
| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `universalIdentifier` | Ja | Stabile eindeutige ID für den Befehl |
| `label` | Ja | Vollständiges Label, das im Befehlsmenü (Cmd+K) angezeigt wird |
| `frontComponentUniversalIdentifier` | Ja | Der `universalIdentifier` der Front-Komponente, die dieser Befehl öffnet |
| `shortLabel` | Nein | Kürzeres Label, das auf der angehefteten Schnellaktionsschaltfläche angezeigt wird |
| `icon` | Nein | Neben dem Label angezeigter Icon-Name (z. B. 'IconBolt', 'IconSend') |
| `isPinned` | Nein | Bei `true` wird der Befehl als Schnellaktionsschaltfläche oben rechts auf der Seite angezeigt |
| `availabilityType` | Nein | Steuert, wo der Befehl erscheint: 'GLOBAL' (immer verfügbar), 'RECORD_SELECTION' (nur wenn Datensätze ausgewählt sind) oder 'FALLBACK' (wird angezeigt, wenn keine anderen Befehle passen) |
| `availabilityObjectUniversalIdentifier` | Nein | Beschränken Sie den Befehl auf Seiten eines bestimmten Objekttyps (z. B. nur bei Company-Datensätzen) |
| `conditionalAvailabilityExpression` | Nein | Ein boolescher Ausdruck, der die Sichtbarkeit dynamisch steuert (siehe unten) |
| Feld | Erforderlich | Beschreibung |
| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Ja | Stabile eindeutige ID für den Befehl |
| `label` | Ja | Vollständiges Label, das im Befehlsmenü (Cmd+K) angezeigt wird |
| `frontComponentUniversalIdentifier` | Ja | Der `universalIdentifier` der Front-Komponente, die dieser Befehl öffnet |
| `shortLabel` | Nein | Kürzeres Label, das auf der angehefteten Schnellaktionsschaltfläche angezeigt wird |
| `icon` | Nein | **Veraltet** — wird zugunsten des Anwendungssymbols ignoriert; der Build gibt eine Warnung aus, wenn gesetzt |
| `isPinned` | Nein | Bei `true` wird der Befehl als Schnellaktionsschaltfläche oben rechts auf der Seite angezeigt |
| `availabilityType` | Nein | Steuert, wo der Befehl erscheint: `'GLOBAL'` (immer verfügbar), `'GLOBAL_OBJECT_CONTEXT'` (nur auf Seiten mit einem Objektkontext Index- und Datensatzseiten), `'RECORD_SELECTION'` (nur wenn Datensätze ausgewählt sind) oder `'FALLBACK'` (wird angezeigt, wenn keine anderen Befehle passen) |
| `availabilityObjectUniversalIdentifier` | Nein | Beschränken Sie den Befehl auf Seiten eines bestimmten Objekttyps (z. B. nur bei Company-Datensätzen) |
| `conditionalAvailabilityExpression` | Nein | Ein boolescher Ausdruck, der die Sichtbarkeit dynamisch steuert (siehe unten) |
## Headless-Befehle
Ein Befehlsmenü-Eintrag, der mit einer [Headless-Front-Komponente](/l/de/developers/extend/apps/layout/front-components#headless-vs-non-headless) gekoppelt ist, ist die idiomatische Art, eine One-Click-Aktion bereitzustellen Code ausführen, navigieren oder bestätigen und ausführen. Die Seite „Front Components“ behandelt die [SDK Command-Komponenten](/l/de/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`), die das Action-and-Unmount-Muster handhaben.
Ein typischer Ablauf:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
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 <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
Ein typischer Ablauf: Eine kopflose Komponente rendert `<Command execute={...} />` (siehe das [vollständige Beispiel](/l/de/developers/extend/apps/layout/front-components#sdk-command-components)), und der Befehl-Menüeintrag verweist darauf:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -74,7 +45,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -49,14 +49,13 @@ export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Nach dem Synchronisieren mit `yarn twenty dev` (oder durch einmaliges Ausführen von `yarn twenty dev --once`) erscheint die Schnellaktion oben rechts auf der Seite:
Nach dem Synchronisieren mit `yarn twenty dev` (oder durch einmaliges Ausführen von `yarn twenty apply`) erscheint die Schnellaktion oben rechts auf der Seite:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Schnellaktionsschaltfläche oben rechts" />
@@ -88,11 +87,11 @@ Front-Komponenten gibt es in zwei Rendering-Modi, die durch die Option `isHeadle
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
@@ -116,7 +115,7 @@ Da die Komponente `null` zurückgibt, überspringt Twenty das Rendern eines Cont
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`:
Importieren Sie sie aus `twenty-sdk/front-component`:
* **`Command`** — Führt einen asynchronen Callback über das Prop `execute` aus.
* **`CommandLink`** — Navigiert zu einem App-Pfad. Props: `to`, `params`, `queryParams`, `options`.
@@ -127,8 +126,8 @@ Hier ist ein vollständiges Beispiel einer Headless-Front-Komponente, die `Comma
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
@@ -160,7 +159,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -169,7 +167,7 @@ Und ein Beispiel, das `CommandModal` verwendet, um vor der Ausführung um Bestä
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
@@ -202,7 +200,7 @@ Front-Komponenten laufen browserseitig in einem isolierten Web Worker, während
Eine mit `httpRouteTriggerSettings` deklarierte Logikfunktion ist über HTTP unter ihrem Routenpfad erreichbar. Twenty injiziert die Basis-URL, unter der deine Funktionen bereitgestellt werden, als `TWENTY_FUNCTIONS_URL` in den Worker, zusammen mit dem `TWENTY_APP_ACCESS_TOKEN`, das den Aufruf authentifiziert. Es gibt noch keinen eigenen SDK-Client zum Aufrufen deiner eigenen Funktionen, daher rufe sie mit einem einfachen `fetch` auf:
> **In Twenty Cloud werden HTTP-ausgelöste Logikfunktionen auf einer eigenen, arbeitsbereichsspezifischen Domain bereitgestellt** unter `https://\<your-workspace-subdomain>.twenty.com\<path>` — genau darauf verweist `TWENTY_FUNCTIONS_URL`. Für externe Aufrufer kopiere die exakte URL aus den **HTTP trigger**-Einstellungen der Funktion oder aus dem **Settings**-Tab der Anwendung.
> **In Twenty Cloud werden HTTP-ausgelöste Logikfunktionen auf einer eigenen, arbeitsbereichsspezifischen Domain bereitgestellt** unter `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — genau darauf verweist `TWENTY_FUNCTIONS_URL`. Für externe Aufrufer kopiere die exakte URL aus den **HTTP trigger**-Einstellungen der Funktion oder aus dem **Settings**-Tab der Anwendung.
<Warning>
Die `/s/`-Funktionsroute ist **veraltet** und wird **am 2026-07-24 deaktiviert**. Verwende stattdessen `TWENTY_FUNCTIONS_URL` (oben) und migriere alle hart codierten `/s/`-URLs vor diesem Datum. Die `/s/`-Route bleibt für Self-Hosting verfügbar.
@@ -212,7 +210,7 @@ Eine headless Front-Komponente kann den Aufruf beim Mounten über die `Command`-
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
@@ -316,13 +314,13 @@ Verwenden Sie innerhalb Ihrer Komponente SDK-Hooks, um auf den aktuellen Benutze
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
@@ -405,12 +403,11 @@ Hier ist ein Beispiel, das die Host-API verwendet, um nach Abschluss einer Aktio
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
@@ -451,10 +448,10 @@ export default defineFrontComponent({
Verwenden Sie `useSelectedRecordIds()`, um mehrere ausgewählte Datensätze zu verwalten. Dies ist nützlich für Stapelvorgänge:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
@@ -492,12 +489,19 @@ export default defineFrontComponent({
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
Stellen Sie sie mit einem auf Datensatzauswahlen beschränkten [Befehlmenüeintrag](/l/de/developers/extend/apps/layout/command-menu-items) bereit:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
@@ -35,6 +35,8 @@ export default defineNavigationMenuItem({
* `position` steuert die Reihenfolge in der Seitenleiste.
* Das Enum enthält außerdem `NavigationMenuItemType.RECORD`, das intern für vom Benutzer erstellte Datensatzfavoriten verwendet wird — es ist in einem App-Manifest nicht verwendbar (es gibt kein Feld, um auf einen Datensatz zu verweisen).
* `icon` und `color` sind optional und passen das Erscheinungsbild des Eintrags an.
* `folderUniversalIdentifier` ist ebenfalls bei jedem Eintrag verfügbar, um ihn innerhalb eines übergeordneten Elements vom Typ `FOLDER` zu verschachteln.
@@ -33,17 +33,32 @@ export default defineView({
## Hauptpunkte
* `objectUniversalIdentifier` gibt an, auf welches Objekt diese Ansicht angewendet wird. Es kann sich um ein von Ihnen definiertes benutzerdefiniertes Objekt oder ein Standardobjekt von Twenty handeln.
* `key` bestimmt den Ansichtstyp `ViewKey.INDEX` ist die Hauptlistenansicht für das Objekt.
* `key: ViewKey.INDEX` markiert die Ansicht als die Hauptlistenansicht des Objekts (diejenige, die ein `OBJECT`-Navigationselement öffnet).
* `fields` steuert, welche Spalten erscheinen und in welcher Reihenfolge. Jedes Feld referenziert einen `fieldMetadataUniversalIdentifier`.
* Für erweiterte Konfigurationen können Sie außerdem `filters`, `filterGroups`, `groups` und `fieldGroups` deklarieren.
* Für erweiterte Konfigurationen können Sie außerdem `filters`, `filterGroups`, `sorts`, `groups` und `fieldGroups` deklarieren.
* `position` steuert die Reihenfolge, wenn mehrere Ansichten für dasselbe Objekt existieren.
## Optionale Eigenschaften
| Eigenschaft | Werte | Beschreibung |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | `ViewType.TABLE` (Standard), `ViewType.KANBAN`, `ViewType.CALENDAR` | Wie Datensätze angeordnet werden. (`FIELDS_WIDGET` / `TABLE_WIDGET` existieren ebenfalls, werden aber intern von Page-Layout-Widgets verwendet.) |
| `visibility` | `ViewVisibility.WORKSPACE` (Standard), `ViewVisibility.UNLISTED` | Ob die Ansicht für den gesamten Workspace aufgelistet oder in Auswahlelementen verborgen ist. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (Standard), `ViewOpenRecordIn.RECORD_PAGE` | Wo ein Klick auf einen Datensatz diesen öffnet. |
| `sortierungen` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Standard-Sortierreihenfolge. |
| `isCompact` | `boolean` | Kompakte Zeilenanzeige. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Datensätze (z. B. Kanban-Spalten) nach einem Feld gruppieren. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Aggregationen und Größen von Kanban-Spalten. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Kalenderansichten: Layout und das Datumsfeld, das die Position der Datensätze bestimmt. |
Alle oben genannten Enums werden aus `twenty-sdk/define` exportiert.
## Filter
Eine Ansicht kann mit vorab angewendeten Filtern ausgeliefert werden. Jeder Filter hat drei Koordinaten: das **Feld**, das gefiltert wird, der **Operand** (wie verglichen wird) und der **Wert** (womit verglichen wird). Alle drei müssen übereinstimmen — die Verwendung eines Operanden, der nicht auf einen Feldtyp anwendbar ist, wird bei der Synchronisierung zurückgewiesen.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
Verfügbare Trigger-Typen:
* **httpRoute**: Stellt Ihre Funktion unter einem HTTP-Pfad und einer Methode **unter dem Endpunkt `/s/`** bereit:
> z. B. `path: '/post-card/create'` ist unter `https://your-twenty-server.com/s/post-card/create` aufrufbar
* **httpRoute**: Enthält deine Funktion auf einem HTTP-Pfad und -Methode an der **Funktions-Basis-URL deines Arbeitsbereichs** — der Wert 20 Injekte als `TWENTY_FUNCTIONS_URL` (auf 20 Cloud, eine dedizierte Domain pro Arbeitsbereich):
> z. B. `path: '/post-card/create'` ist unter `https://your-workspace.withtwenty.com/post-card/create` aufrufbar
<Warning>
Die alte `/s/` Präfix Route (`https://your-twenty-server.com/s/post-card/create`) ist **veraltet in 20 Cloud** und wird auf **2026-07-24** deaktiviert. Es bleibt für selbstgehostete und lokale Instanzen verfügbar, die keine isolierte Funktionsdomain konfigurieren — benutze `TWENTY_FUNCTIONS_URL` wenn diese gesetzt ist und zurück fallen auf `\<server-url>/s/\<path>` sonst nicht.
</Warning>
<Note>
Um eine routenausgelöste Logikfunktion von einer (headless) Front-Komponente aus aufzurufen, siehe [Aufrufen einer Logikfunktion](/l/de/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -42,7 +42,7 @@ Eine Logikfunktion wählt einen oder mehrere Auslöser jeder Eintrag unten i
| Auslöser | Wann sie ausgeführt wird | Einstellung |
| --------------------- | ------------------------------------------------------------------ | ------------------------------- |
| **HTTP-Route** | Eine Anfrage erreicht Ihren `/s/\<path>`-Endpunkt | `httpRouteTriggerSettings` |
| **HTTP-Route** | Eine Anfrage trifft die öffentliche URL Ihrer Funktion | `httpRouteTriggerSettings` |
| **Cron** | Ein CRON-Ausdruck trifft zu | `cronTriggerSettings` |
| **Datenbankereignis** | Ein Workspace-Datensatz wird erstellt, aktualisiert oder gelöscht | `databaseEventTriggerSettings` |
| **KI-Tool** | Eine Twenty-KI-Funktion entscheidet sich, Ihre Funktion aufzurufen | `toolTriggerSettings` |
@@ -4,7 +4,25 @@ description: yarn twenty Befehle zum Ausführen von Funktionen, Streamen von Log
icon: terminal
---
Zusätzlich zu `dev`, `dev:build`, `dev:add` und `dev:typecheck` bietet die `yarn twenty` CLI Befehle zum Ausführen von Funktionen, Anzeigen von Logs und Verwalten von App-Installationen.
Die `yarn twenty` CLI ist Ihre Schnittstelle für alles, was mit der App zu tun hat. Vollständige Befehlsliste:
| Befehl | Was es tut | Dokumentiert in |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `dev` | Überwacht Ihre Quelldateien und synchronisiert Änderungen in Echtzeit | [Schnellstart](/l/de/developers/extend/apps/getting-started/quick-start) |
| `plan` | Metadatenänderungen anzeigen, ohne sie anzuwenden | [Synchronisierung & Wiederherstellung](/l/de/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `apply` | Metadatenänderungen anwenden, nachdem der Plan angezeigt wurde | [Synchronisierung & Wiederherstellung](/l/de/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Die App kompilieren und den API-Client generieren (`--tarball`, um ein `.tgz` zu packen) | [Veröffentlichen](/l/de/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | TypeScript-Typprüfung ausführen | [Tests](/l/de/developers/extend/apps/operations/testing) |
| `dev:add` | Eine neue Entität erstellen (Scaffolding) | [Scaffolding](/l/de/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Den typisierten API-Client erneut generieren | diese Seite |
| `dev:function:exec` / `dev:function:logs` | Funktionen ausführen und ihre Protokolle streamen | diese Seite |
| `dev:translations-extract` | Übersetzbare Zeichenketten in `locales/`-Kataloge extrahieren | [Übersetzungen](/l/de/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | Eine Synchronisierung des Marktplatzkatalogs auslösen | [Veröffentlichen](/l/de/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Release-Lebenszyklus | [Veröffentlichen](/l/de/developers/extend/apps/operations/publishing) und diese Seite |
| `docker:*` | Den lokalen Twenty-Server-Container verwalten | [Lokaler Server](/l/de/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Serververbindungen verwalten | diese Seite |
Jeder Befehl akzeptiert `-r, --remote \<name>`, um ein bestimmtes Remote statt des Standard-Remotes anzusteuern.
## Funktionen ausführen (`yarn twenty dev:function:exec`)
@@ -20,8 +38,9 @@ yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the post-install function
# Execute the install hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
## Funktionsprotokolle ansehen (`yarn twenty dev:function:logs`)
@@ -100,6 +119,12 @@ yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Ihre Anmeldedaten werden in `~/.twenty/config.json` gespeichert.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
Die im Marktplatz angezeigten Metadaten stammen aus Ihrer `defineApplication()`-Konfiguration — Felder wie `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` und `termsUrl`.
Die im Marketplace angezeigten Metadaten stammen aus deiner `defineApplication()`-Konfiguration siehe oben unter [Marketplace-Metadaten](#marketplace-metadata).
<Note>
Wenn Ihre App keine `aboutDescription` in `defineApplication()` definiert, verwendet der Marktplatz automatisch die `README.md` Ihres Pakets von npm als Inhalt der Über-uns-Seite. Das bedeutet, dass Sie eine einzige README sowohl für npm als auch für den Twenty-Marktplatz pflegen können. Wenn Sie im Marktplatz eine andere Beschreibung möchten, setzen Sie `aboutDescription` explizit.
@@ -12,16 +12,20 @@ Die lokale App-Entwicklung dreht sich um **Syncing**: Die CLI baut Ihr Manifest
Für die tägliche lokale Iteration sollten Sie fast immer `yarn twenty dev` verwenden. Bereitstellen und Veröffentlichen sind zum Ausliefern von Releases gedacht, **nicht** für den lokalen Entwicklungszyklus.
</Note>
| Sie möchten … | Befehl | Notizen |
| ------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Lokal mit Live-Sync iterieren | `yarn twenty dev` | Überwacht Ihre Dateien und synchronisiert bei jeder Änderung. |
| Einmal synchronisieren und beenden (CI, Skripte, Hooks) | `yarn twenty dev --once` | Führt einen Build und einen Sync aus und beendet sich anschließend. |
| Änderungen **anzeigen, ohne sie anzuwenden** | `yarn twenty dev --once --dry-run` | Berechnet und druckt das Diff; schreibt nichts. |
| Die App aus dem Workspace entfernen | `yarn twenty app:uninstall` | Fügen Sie `--yes` hinzu, um die Abfrage zu überspringen. |
| Einen Tarball an einen Server ausliefern | `yarn twenty app:publish --private` | Erfordert eine strikt höhere `package.json`-Version siehe [Veröffentlichen](/l/de/developers/extend/apps/operations/publishing). |
| Im Marketplace (npm) veröffentlichen | `yarn twenty app:publish` | — |
| Eine bereitgestellte Version installieren/aktualisieren | `yarn twenty app:install` | Installiert die aktuell bereitgestellte Version. |
| Den lokalen Server zurücksetzen und sauber neu starten | `yarn twenty docker:reset` | Löscht **alle** lokalen Daten letztes Mittel. |
| Sie möchten … | Befehl | Notizen |
| ------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Lokal mit Live-Sync iterieren | `yarn twenty dev` | Überwacht Ihre Dateien und synchronisiert bei jeder Änderung. |
| Einmal synchronisieren und beenden (CI, Skripte, Hooks) | `yarn twenty apply` | Führt einen Build und einen Sync aus und beendet sich anschließend. Fügen Sie `--force` hinzu, um die Bestätigung für destruktive Änderungen zu überspringen. |
| Änderungen **anzeigen, ohne sie anzuwenden** | `yarn twenty plan` | Berechnet und druckt das Diff; schreibt nichts. |
| Die App aus dem Workspace entfernen | `yarn twenty app:uninstall` | Fügen Sie `--yes` hinzu, um die Abfrage zu überspringen. |
| Einen Tarball an einen Server ausliefern | `yarn twenty app:publish --private` | Erfordert eine strikt höhere `package.json`-Version siehe [Veröffentlichen](/l/de/developers/extend/apps/operations/publishing). |
| Im Marketplace (npm) veröffentlichen | `yarn twenty app:publish` | — |
| Eine bereitgestellte Version installieren/aktualisieren | `yarn twenty app:install` | Installiert die aktuell bereitgestellte Version. |
| Den lokalen Server zurücksetzen und sauber neu starten | `yarn twenty docker:reset` | Löscht **alle** lokalen Daten letztes Mittel. |
<Note>
`yarn twenty dev --once` und `yarn twenty dev --once --dry-run` funktionieren weiterhin als veraltete Aliase für `yarn twenty apply` und `yarn twenty plan`.
</Note>
### Lokaler Sync benötigt keinen Versionssprung
@@ -29,19 +33,26 @@ Die strikt steigende `version`-Regel (`VERSION_ALREADY_EXISTS` beim Deploy, `APP
## Die Sync-Ausgabe lesen
Jeder Sync gibt die Metadatenänderungen aus, die er angewendet hat (oder anwenden würde, mit `--dry-run`):
Jeder Sync gibt die Metadatenänderungen aus, die angewendet wurden (oder angewendet würden, mit `plan`), im Terraform-Stil ein Block pro Entity mit ihren Attributen, danach eine zusammenfassende Zeile:
```text filename="Terminal"
Metadata changes: 2 created, 1 updated, 1 deleted
created objectMetadata rocket
created fieldMetadata timelineActivities
updated fieldMetadata launchedAt
deleted pageLayout legacyTab
✓ Synced
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
Dies ist Ihre erste Diagnose: Sie zeigt Ihnen genau, welche Objekte, Felder und Layouts sich geändert haben, sodass Sie bestätigen können, dass ein Sync das Erwartete getan hat, bevor Sie die UI prüfen.
Destruktive Änderungen (`to destroy`) werden zusammen mit dem, was sie entfernen, aufgeführt (z.B. `objectMetadata "auditNote" — drops the table and all its rows`) und erfordern eine interaktive Bestätigung oder `--force` in Skripten.
Wenn ein Sync bei einer einzelnen Entität fehlschlägt, nennt der Fehler die betreffende Entität und ihren `universalIdentifier`, zum Beispiel:
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
Verwenden Sie diesen Bezeichner, um die Entität in Ihrem Manifest (und bei Bedarf im Workspace) zu finden, anstatt zu raten, welche in Konflikt steht.
## Änderungen vorab ansehen (Dry Run)
## Änderungen vorab ansehen (Plan)
`yarn twenty dev --once --dry-run` baut Ihr Manifest, fragt den Server nach dem Migrationsplan und gibt ihn aus **ohne irgendetwas anzuwenden**. Dies ist der sichere Weg, um zu beantworten: "Was würde dieser Sync ändern?", bevor Sie sich darauf festlegen.
`yarn twenty plan` baut Ihr Manifest, fragt den Server nach dem Migrationsplan und gibt ihn aus **ohne irgendetwas anzuwenden**. Dies ist der sichere Weg, um zu beantworten: "Was würde dieser Sync ändern?", bevor Sie sich darauf festlegen.
```bash filename="Terminal"
yarn twenty dev --once --dry-run
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata diff (dry run, nothing will be applied)...
Metadata changes: 1 created, 1 updated
created fieldMetadata timelineActivities
updated objectMetadata rocket
✓ Dry run complete for My App — no changes were applied
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "timelineActivities" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Ein Dry Run:
Ein Plan:
* **Schreibt nichts** keine Metadatenmigration, kein Update von App-Einträgen, keine Änderungen an Standardrollen/-Tabs und keine API-Client-Generierung.
* Liefert dasselbe **Diff**, das ein echter Sync anwenden würde, sodass Sie erstellte/aktualisierte/gelöschte Entitäten im Voraus prüfen können.
* Ist nützlich vor einer riskanten Änderung, bei der Überprüfung einer KI-generierten Änderung oder in einem Skript, das fehlschlagen soll, wenn eine unerwartete Änderung kurz vor der Anwendung steht.
<Note>
Ein Dry Run zeigt nur **Metadaten**-Änderungen an und erfordert, dass die App mindestens einmal synchronisiert wurde (damit der Workspace sie kennt). Wenn Sie ihn gegen eine App ausführen, die noch nie synchronisiert wurde, meldet der Server, dass die App nicht installiert ist führen Sie zuerst einmal `yarn twenty dev` aus.
Ein Plan zeigt nur **Metadaten**-Änderungen an und erfordert, dass die App mindestens einmal synchronisiert wurde (damit der Workspace sie kennt). Wenn Sie ihn gegen eine App ausführen, die noch nie synchronisiert wurde, meldet der Server, dass die App nicht installiert ist führen Sie zuerst einmal `yarn twenty dev` aus.
</Note>
## Wiederherstellungsleiter
Wenn lokale Metadaten falsch aussehen, eskalieren Sie in dieser Reihenfolge und stoppen Sie, sobald Sie nicht mehr blockiert sind. Jeder Schritt ist störender als der vorherige.
1. **Erneut synchronisieren.** Führen Sie `yarn twenty dev --once` erneut aus. Syncs sind idempotent das erneute Ausführen eines sauberen Manifests ist sicher und löst oft eine vorübergehende Störung.
2. **Plan ansehen.** Führen Sie `yarn twenty dev --once --dry-run` aus, um genau zu sehen, was der nächste Sync zu ändern beabsichtigt, ohne es anzuwenden.
1. **Erneut synchronisieren.** Führen Sie `yarn twenty apply` erneut aus. Syncs sind idempotent das erneute Ausführen eines sauberen Manifests ist sicher und löst oft eine vorübergehende Störung.
2. **Plan ansehen.** Führen Sie `yarn twenty plan` aus, um genau zu sehen, was der nächste Sync zu ändern beabsichtigt, ohne es anzuwenden.
3. **Benannten Fehler lesen.** Wenn ein Sync fehlschlägt, notieren Sie sich den Metadatentyp und den `universalIdentifier` in der Meldung (siehe oben) und lokalisieren Sie diese Entität in Ihrem Manifest. Ein Konflikt weist in der Regel auf einen doppelten oder wiederverwendeten Bezeichner hin.
4. **Deinstallieren und neu installieren.** `yarn twenty app:uninstall`, dann erneut synchronisieren (`yarn twenty dev`). Dies baut die Metadaten der App aus einem sauberen Zustand wieder auf, während der Rest Ihres Workspaces intakt bleibt.
5. **Vollständiger Reset (letztes Mittel).** `yarn twenty docker:reset`, dann erneut seeden und synchronisieren.
@@ -78,6 +78,13 @@ Erstellen Sie eine `vitest.config.ts` im Stammverzeichnis Ihrer App:
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
@@ -88,66 +95,74 @@ export default defineConfig({
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
setupFiles: ['src/__tests__/setup-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL: 'http://localhost:2020',
TWENTY_API_KEY: 'your-api-key',
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Erstellen Sie eine Setup-Datei, die vor dem Testlauf überprüft, dass der Server erreichbar ist:
Erstellen Sie eine globale Setup-Datei, die überprüft, ob der Server erreichbar ist, eine Testkonfiguration für das SDK schreibt (`~/.twenty/config.test.json`) und die App synchronisiert, bevor die Tests ausgeführt werden:
```ts src/__tests__/setup-test.ts
```ts src/__tests__/global-setup.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');
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
beforeAll(async () => {
// Verify the server is running
const response = await fetch(`${TWENTY_API_URL}/healthz`);
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
'Start the server before running integration tests.',
);
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write a temporary config for the SDK
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(TEST_CONFIG_DIR, 'config.json'),
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
});
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## Programmgesteuerte SDK-APIs
Der Subpfad `twenty-sdk/cli` exportiert Funktionen, die Sie direkt aus Testcode aufrufen können:
| Funktion | Beschreibung |
| -------------- | ----------------------------------------------------- |
| `appBuild` | Die App bauen und optional ein Tarball erstellen |
| `appDeploy` | Ein Tarball auf den Server hochladen |
| `appInstall` | Die App im aktiven Arbeitsbereich installieren |
| `appUninstall` | Die App aus dem aktiven Arbeitsbereich deinstallieren |
| Funktion | Beschreibung |
| -------------- | --------------------------------------------------------------------------- |
| `appBuild` | Die App bauen und optional ein Tarball erstellen |
| `appDeploy` | Ein Tarball auf den Server hochladen |
| `appDevOnce` | Erstellt und synchronisiert die App einmal (entspricht `yarn twenty apply`) |
| `appInstall` | Die App im aktiven Arbeitsbereich installieren |
| `appUninstall` | Die App aus dem aktiven Arbeitsbereich deinstallieren |
Jede Funktion gibt ein Ergebnisobjekt mit `success: boolean` und entweder `data` oder `error` zurück.
@@ -238,64 +253,10 @@ Sie können die Typprüfung Ihrer App auch ohne Tests ausführen:
yarn twenty dev:typecheck
```
Dies führt `tsc --noEmit` aus und meldet etwaige Typfehler.
Dies führt `tsc --noEmit` gegen die `tsconfig.json` Ihrer App aus und meldet etwaige Typfehler. Gerüstete Apps liefern außerdem ein `yarn typecheck`-Skript mit, das auch Testdateien abdeckt (`tsconfig.spec.json`).
## CI mit GitHub Actions
Das Scaffolding-Tool erzeugt einen einsatzbereiten GitHub-Actions-Workflow in `.github/workflows/ci.yml`. Er führt Ihre Integrationstests automatisch bei jedem Push auf `main` und bei Pull Requests aus.
Das Scaffolding-Tool erzeugt einen einsatzbereiten Workflow unter `.github/workflows/ci.yml`. Bei jedem Push auf `main` und jeder Pull-Request startet es einen kurzlebigen Twenty-Server im Runner (über die Aktion `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`) und führt anschließend `yarn lint`, `yarn typecheck`, `yarn test:unit` und `yarn test` aus, wobei `TWENTY_API_URL` / `TWENTY_API_KEY` auf diesen Server verweisen. Es sind keine Geheimnisse erforderlich, und Sie können die Serverversion über die Umgebungsvariable `TWENTY_VERSION` oben im Workflow fixieren.
Der Workflow:
1. Checkt Ihren Code aus
2. Startet einen temporären Twenty-Server mit der Aktion `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Installiert Abhängigkeiten mit `yarn install --immutable`
4. Führt `yarn test` aus, wobei `TWENTY_API_URL` und `TWENTY_API_KEY` aus den Aktionsausgaben injiziert werden.
```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 }}
```
Sie müssen keine Secrets konfigurieren — die Aktion `spawn-twenty-docker-image` startet einen flüchtigen Twenty-Server direkt im Runner und gibt die Verbindungsdetails aus. Das Secret `GITHUB_TOKEN` wird automatisch von GitHub bereitgestellt.
Um eine bestimmte Twenty-Version statt `latest` festzulegen, ändern Sie die Umgebungsvariable `TWENTY_VERSION` oben im Workflow.
Unter [Veröffentlichen → Automatisiertes CI/CD](/l/de/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) finden Sie eine vollständige Schritt-für-Schritt-Anleitung zu beiden eingerichteten Workflows (`ci.yml` und der `cd.yml`-Bereitstellungspipeline).
@@ -91,9 +91,11 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
const apiBaseUrl = process.env.TWENTY_API_URL;
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
@@ -186,7 +188,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,8 +9,15 @@ Der gleiche Handler kann auch HTTP-Anfragen beantworten. Wir werden zwei Routen
* ein **POST** Endpunkt der UI-Aufrufe, um ein Dokument zu generieren, und
* ein öffentlicher **GET** Endpunkt, der ein Dokument als druckbare Webseite darstellt.
Beide verwenden `httpRouteTriggerSettings`. App-Routen werden unter `/s` auf Ihrem
20 Server bedient (z.B. `http://localhost:2020/s/documents/generate`).
Beide verwenden `httpRouteTriggerSettings`. Auf dem lokalen Dev-Server werden App-Routen
unter dem Präfix `/s` bedient (z.B. `http://localhost:2020/s/documents/generate`).
<Note>
Bei 20 Cloud werden Routen in der dedizierten Funktion des Arbeitsbereichs, der Domain
die URL 20 injiziert als `TWENTY_FUNCTIONS_URL`, ohne `/s` Präfix. Das `/s`
Präfix ist dort veraltet und bleibt nur für selbstgehostete und lokale Instanzen übrig.
Siehe [Aufruf einer Logikfunktion](/l/de/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## POST-Route — bei Bedarf generieren
@@ -77,11 +77,11 @@ Führe die gleichen Tore CI aus:
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty dev --once --dry-run # preview the metadata diff
yarn twenty plan # preview the metadata diff
```
Der Trockenlauf druckt genau das, was sich auf dem Server ändern würde, ohne es anzuwenden —
eine gute abschließende Vernunftprüfung. Siehe
Der Plan gibt genau aus, was sich auf dem Server ändern würde, ohne die Änderungen anzuwenden —
eine gute abschließende Plausibilitätsprüfung. Siehe
[Testing](/l/de/developers/extend/apps/operations/testing) und
[Synchronisieren & Wiederherstellen](/l/de/developers/extend/apps/operations/sync-and-recovery).
@@ -4,9 +4,9 @@ description: "Ejecuta lógica antes o después de la instalación: introduce dat
icon: wrench
---
Los hooks de instalación son funciones de lógica especiales que se ejecutan durante el ciclo de vida de la instalación o actualización. Comparten el mismo tiempo de ejecución del controlador que las [logic functions](/l/es/developers/extend/apps/logic/logic-functions) normales y reciben un `InstallPayload`, pero se declaran con sus propias funciones de definición — `definePostInstallLogicFunction()` y `definePreInstallLogicFunction()` — y están fuera del modelo de desencadenadores normal (HTTP, cron, eventos de base de datos).
Los hooks de instalación son funciones de lógica especiales que se ejecutan durante el ciclo de vida de la instalación o actualización. Comparten el mismo tiempo de ejecución del handler que las [logic functions](/l/es/developers/extend/apps/logic/logic-functions) normales y reciben un `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` es `undefined` en una instalación nueva), pero se declaran con sus propias funciones define y viven fuera del modelo de disparadores normal (HTTP, cron, eventos de base de datos).
Cada aplicación puede definir **como máximo una función de preinstalación** y **como máximo una función de posinstalación**. La compilación del manifiesto genera un error si se detecta más de una de cualquiera de las dos.
Cada aplicación puede definir **como máximo una función de preinstalación** y **como máximo una función de posinstalación**. La compilación del manifiesto genera un error si se detecta más de una de cualquiera de las dos.
```
┌─────────────────────────────────────────────────────────────┐
@@ -19,111 +19,59 @@ Cada aplicación puede definir **como máximo una función de preinstalación**
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Se ejecuta después de que se aplique la migración de metadatos del espacio de trabajo">
## De un vistazo
Una función de posinstalación se ejecuta automáticamente una vez que tu aplicación ha terminado de instalarse en un espacio de trabajo. El servidor la ejecuta **después** de que se hayan sincronizado los metadatos de la aplicación y se haya generado el cliente del SDK, de modo que el espacio de trabajo esté completamente listo para usarse y el nuevo esquema esté disponible. Los casos de uso típicos incluyen poblar datos predeterminados, crear registros iniciales, configurar los ajustes del espacio de trabajo o aprovisionar recursos en servicios de terceros.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ejecuciones | Antes de la migración de metadatos — el esquema y los datos **anteriores** siguen intactos | Después de la migración y la generación del SDK — el esquema **nuevo** está en su lugar |
| Ejecución | Siempre síncrona; bloquea la instalación | Asíncrona de forma predeterminada (en cola, 3 reintentos); modo síncrono opcional mediante `shouldRunSynchronously: true` |
| En caso de fallo | La instalación se **aborta** antes de cualquier cambio de esquema | Asíncrono: se vuelve a intentar hasta 3 veces. Síncrono: quien realiza la llamada recibe `POST_INSTALL_ERROR` (los cambios de esquema **no** se revierten) |
| Uso típico | Hacer copia de seguridad o corregir datos que una migración perdería; rechazar una actualización arriesgada lanzando una excepción | Sembrar datos predeterminados, configurar el espacio de trabajo, registrar recursos externos |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Regla general:** usa post-install de forma predeterminada. Recurra a la pre-instalación solo cuando la propia migración sea destructiva y necesite interceptar el estado anterior antes de que desaparezca.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Quiere... | Usar |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Sembrar datos, configurar el espacio de trabajo, registrar recursos externos | `post-install` |
| Trabajo de larga duración que no debería bloquear la respuesta de instalación | `post-install` (modo asíncrono predeterminado, con reintentos del worker) |
| Configuración rápida de la que el cliente depende inmediatamente después de que finaliza la instalación | `post-install` con `shouldRunSynchronously: true` |
| Leer o hacer copia de seguridad de datos que la próxima migración perdería | `pre-install` |
| Rechazar una actualización que corrompería datos existentes | `pre-install` (lanzar desde el controlador) |
| Reconciliación en cada actualización | Cualquiera de los hooks con `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## Comportamiento compartido por ambos hooks
También puedes ejecutar manualmente la función de posinstalación en cualquier momento usando la CLI:
* La configuración es una configuración de `defineLogicFunction` menos los ajustes de disparador, más `shouldRunOnVersionUpgrade`.
* **Cuándo se ejecuta**: solo en instalaciones nuevas, de forma predeterminada. Configura `shouldRunOnVersionUpgrade: true` para que también se ejecute en las actualizaciones. Usa `previousVersion` / `newVersion` para ramificar según la ruta de actualización.
* **La idempotencia es importante**: el post-install asíncrono puede reintentarse y cualquiera de los hooks se vuelve a ejecutar en las actualizaciones cuando `shouldRunOnVersionUpgrade` está activado.
* El entorno habitual de las logic functions (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) se inyecta, por lo que puedes llamar a la API de Twenty con el token de tu app.
* El hook se adjunta automáticamente al manifiesto de la aplicación en tiempo de compilación (`preInstallLogicFunction` / `postInstallLogicFunction`) — no hay nada que referenciar en [`defineApplication()`](/l/es/developers/extend/apps/config/application).
* El `timeoutSeconds` predeterminado es 300 para permitir tareas de configuración más largas como la siembra de datos.
* **No se ejecuta en modo de desarrollo**: `yarn twenty dev` omite el flujo de instalación y sincroniza los archivos directamente, por lo que los hooks nunca se ejecutan ahí. En su lugar, dispáralos manualmente:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Puntos clave:
* Las funciones de posinstalación usan `definePostInstallLogicFunction()` — una variante especializada que omite la configuración de desencadenadores (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* El controlador recibe un `InstallPayload` con `{ previousVersion?: string; newVersion: string }` — `newVersion` es la versión que se está instalando, y `previousVersion` es la versión que se instaló previamente (o `undefined` en una instalación nueva). Use estos valores para distinguir instalaciones nuevas de actualizaciones y para ejecutar lógica de migración específica de la versión.
* **Cuándo se ejecuta el hook**: solo en instalaciones nuevas, de forma predeterminada. Pase `shouldRunOnVersionUpgrade: true` si también quiere que se ejecute cuando la app se actualice desde una versión anterior. Si se omite, el indicador es `false` por defecto y las actualizaciones omiten el hook.
* **Modelo de ejecución — asíncrono por defecto, sincronía opcional**: el indicador `shouldRunSynchronously` controla *cómo* se ejecuta la post-instalación.
* `shouldRunSynchronously: false` *(predeterminado)* — el hook se **encola en la cola de mensajes** con `retryLimit: 3` y se ejecuta de forma asíncrona en un worker. La respuesta de instalación se devuelve tan pronto como el trabajo se encola, por lo que un controlador lento o con fallos no bloquea al solicitante. El worker reintentará hasta tres veces. **Úselo para trabajos de larga duración** — sembrar conjuntos de datos grandes, llamar a APIs de terceros lentas, aprovisionar recursos externos, cualquier cosa que pueda exceder una ventana de respuesta HTTP razonable.
* `shouldRunSynchronously: true` — el hook se ejecuta **en línea durante el flujo de instalación** (el mismo ejecutor que la pre-instalación). La solicitud de instalación se bloquea hasta que el controlador finaliza y, si arroja una excepción, quien realiza la instalación recibe un `POST_INSTALL_ERROR`. Sin reintentos automáticos. **Úselo para trabajo rápido que debe completarse antes de la respuesta** — por ejemplo, emitir un error de validación al usuario, o una configuración rápida de la que el cliente dependerá inmediatamente después de que regrese la llamada de instalación. Tenga en cuenta que la migración de metadatos ya se ha aplicado cuando se ejecuta la post-instalación, por lo que un fallo en modo síncrono **no** revierte los cambios de esquema — solo expone el error.
* Asegúrese de que su controlador sea idempotente. En modo asíncrono, la cola puede reintentar hasta tres veces; en cualquier modo, el hook puede ejecutarse de nuevo en las actualizaciones cuando `shouldRunOnVersionUpgrade: true`.
* Las variables de entorno `APPLICATION_ID`, `APP_ACCESS_TOKEN` y `API_URL` están disponibles dentro del controlador (igual que en cualquier otra función de lógica), por lo que puede llamar a la API de Twenty con un token de acceso de aplicación con alcance a su app.
* Solo se permite una función de posinstalación por aplicación. La compilación del manifiesto generará un error si se detecta más de una.
* Los `universalIdentifier`, `shouldRunOnVersionUpgrade` y `shouldRunSynchronously` de la función se adjuntan automáticamente al manifiesto de la aplicación en el campo `postInstallLogicFunction` durante la compilación; no es necesario que los referencies en [`defineApplication()`](/l/es/developers/extend/apps/config/application).
* El tiempo de espera predeterminado se establece en 300 segundos (5 minutos) para permitir tareas de configuración más largas como la carga inicial de datos.
* **No se ejecuta en modo de desarrollo**: cuando una app se registra localmente (mediante `yarn twenty dev`), el servidor omite por completo el flujo de instalación y sincroniza archivos directamente a través del observador de la CLI — por lo tanto, la post-instalación nunca se ejecuta en modo de desarrollo, independientemente de `shouldRunSynchronously`. Use `yarn twenty dev:function:exec --postInstall` para activarlo manualmente en un espacio de trabajo en ejecución.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Se ejecuta antes de que se aplique la migración de metadatos del espacio de trabajo">
Una función de preinstalación se ejecuta automáticamente durante la instalación, **antes de que se aplique la migración de metadatos del espacio de trabajo**. Comparte la misma forma de payload que la post-instalación (`InstallPayload`), pero está situada antes en el flujo de instalación para poder preparar el estado del que depende la próxima migración — usos típicos incluyen hacer copias de seguridad de datos, validar la compatibilidad con el nuevo esquema o archivar registros que están a punto de ser reestructurados o eliminados.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
También puedes ejecutar manualmente la función de preinstalación en cualquier momento usando la CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Puntos clave:
* Las funciones de pre-instalación usan `definePreInstallLogicFunction()` — la misma configuración especializada que la post-instalación, solo que adjunta a un punto diferente del ciclo de vida.
* Tanto los controladores de pre- como de post-instalación reciben el mismo tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Impórtelo una vez y reutilícelo para ambos hooks.
* **Cuándo se ejecuta el hook**: se ubica justo antes de la migración de metadatos del espacio de trabajo (`synchronizeFromManifest`). Antes de ejecutarse, el servidor realiza una "sincronización simplificada" puramente aditiva que registra la función de pre-instalación de la versión **nueva** en los metadatos del espacio de trabajo — no se toca nada más — y luego la ejecuta. Debido a que esta sincronización es solo aditiva, los objetos, campos y datos de la versión anterior siguen intactos cuando se ejecuta su controlador: puede leer y respaldar de forma segura el estado premigración.
* **Modelo de ejecución**: la pre-instalación se ejecuta **de forma síncrona** y **bloquea la instalación**. Si el controlador lanza una excepción, la instalación se aborta antes de que se apliquen cambios de esquema — el espacio de trabajo permanece en la versión anterior en un estado consistente. Esto es intencional: la pre-instalación es su última oportunidad para rechazar una actualización arriesgada.
* Al igual que con la post-instalación, solo se permite una función de preinstalación por aplicación. Se adjunta automáticamente al manifiesto de la aplicación bajo `preInstallLogicFunction` durante la compilación.
* **No se ejecuta en modo de desarrollo**: igual que la post-instalación — el flujo de instalación se omite por completo para las apps registradas localmente, por lo que la pre-instalación nunca se ejecuta con `yarn twenty dev`. Use `yarn twenty dev:function:exec --preInstall` para activarlo manualmente.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Se ejecuta después de que se aplique la migración de metadatos del espacio de trabajo">
</Accordion>
<Accordion title="Pre-instalación vs post-instalación: cuándo usar cada una" description="Elegir el hook de instalación adecuado">
Ambos hooks forman parte del mismo flujo de instalación y reciben el mismo `InstallPayload`. La diferencia es **cuándo** se ejecutan con respecto a la migración de metadatos del espacio de trabajo, y eso cambia qué datos pueden tocar de forma segura.
La pre-instalación siempre es **síncrona** (bloquea la instalación y puede abortarla). La post-instalación es **asíncrona por defecto** — se pone en cola en un worker con reintentos automáticos — pero puede optar por ejecución síncrona con `shouldRunSynchronously: true`. Consulte el acordeón `definePostInstallLogicFunction` de arriba para saber cuándo usar cada modo.
**Use `post-install` para cualquier cosa que necesite que exista el nuevo esquema.** Este es el caso más común:
* Sembrar datos predeterminados (crear registros iniciales, vistas predeterminadas, contenido de demostración) sobre objetos y campos recién añadidos.
* Registrar webhooks con servicios de terceros ahora que la app ya tiene sus credenciales.
* Llamar a su propia API para finalizar una configuración que depende de los metadatos sincronizados.
* Lógica idempotente de "asegurar que esto exista" que debe reconciliar el estado en cada actualización — combínela con `shouldRunOnVersionUpgrade: true`.
Ejemplo — sembrar un registro `PostCard` predeterminado después de la instalación:
Se ejecuta una vez que tu app ha terminado de instalarse: metadatos sincronizados, cliente SDK generado, nuevo esquema disponible para consulta. Ejemplo — sembrar un registro predeterminado en instalaciones nuevas:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**Use `pre-install` cuando una migración, de otro modo, destruiría o corrompería datos existentes.** Como la pre-instalación se ejecuta contra el esquema *anterior* y su fallo revierte la actualización, es el lugar adecuado para cualquier cosa arriesgada:
El flag `shouldRunSynchronously` controla el modelo de ejecución:
* **Hacer copia de seguridad de datos que están a punto de eliminarse o reestructurarse** — p. ej., está quitando un campo en la v2 y necesita copiar sus valores a otro campo o exportarlos a almacenamiento antes de que se ejecute la migración.
* **Archivar registros que una nueva restricción invalidaría** — p. ej., un campo pasará a ser `NOT NULL` y primero necesita eliminar o corregir filas con valores nulos.
* **Validar la compatibilidad y rechazar la actualización si los datos actuales no pueden migrarse limpiamente** — lance desde el controlador y la instalación se abortará sin aplicar cambios. Esto es más seguro que descubrir la incompatibilidad a mitad de la migración.
* **Renombrar o reasignar claves de datos** antes de un cambio de esquema que perdería la asociación.
* `false` *(predeterminado)* — encolado en la cola de mensajes (`retryLimit: 3`) y ejecutado por un worker. La respuesta de instalación se devuelve tan pronto como el trabajo se pone en la cola. **Usar para trabajo de larga duración** — siembra de grandes conjuntos de datos, APIs de terceros lentas.
* `true` — se ejecuta en línea durante el flujo de instalación. La solicitud de instalación se bloquea hasta que el handler finaliza; un error lanzado aparece como `POST_INSTALL_ERROR` para quien realiza la llamada (sin reintentos). **Usar para trabajo rápido que debe completarse antes de la respuesta.** La migración ya se ha aplicado en este punto, por lo que un fallo no revierte los cambios de esquema — solo expone el error.
Ejemplo — archivar registros antes de una migración destructiva:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Se ejecuta antes de que se aplique la migración de metadatos del espacio de trabajo">
Se ejecuta antes de la migración de metadatos, contra el esquema **anterior** — el lugar adecuado para hacer una copia de seguridad de los datos que una migración perdería o para rechazar una actualización arriesgada. Antes de ejecutarse, el servidor realiza una "sincronización simplificada" puramente aditiva que registra solo la función de pre-instalación de la versión nueva; todo lo demás — los objetos, campos y datos de la versión anterior — permanece sin cambios cuando se ejecuta tu handler.
La pre-instalación siempre es **síncrona** y bloquea la instalación. Si el handler lanza una excepción, la instalación se aborta antes de cualquier cambio de esquema — el espacio de trabajo permanece en la versión anterior en un estado consistente. Esto es intencional: la pre-instalación es su última oportunidad para rechazar una actualización arriesgada.
Ejemplo — copiar los valores de un campo heredado antes de que la migración lo elimine:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**Regla general:**
| Quiere... | Usar |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Sembrar datos predeterminados, configurar el espacio de trabajo, registrar recursos externos | `post-install` |
| Ejecutar siembras de larga duración o llamadas a terceros que no deberían bloquear la respuesta de instalación | `post-install` (predeterminado — `shouldRunSynchronously: false`, con reintentos del worker) |
| Ejecutar una configuración rápida de la que el cliente dependerá inmediatamente después de que regrese la llamada de instalación | `post-install` con `shouldRunSynchronously: true` |
| Leer o hacer copia de seguridad de datos que la próxima migración perdería | `pre-install` |
| Rechazar una actualización que corrompería datos existentes | `pre-install` (lanzar desde el controlador) |
| Ejecutar reconciliación en cada actualización | `post-install` con `shouldRunOnVersionUpgrade: true` |
| Realizar una configuración única solo en la primera instalación | `post-install` con `shouldRunOnVersionUpgrade: false` (predeterminado) |
<Note>
En caso de duda, elija **post-install** como predeterminado. Recurra a la pre-instalación solo cuando la propia migración sea destructiva y necesite interceptar el estado anterior antes de que desaparezca.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**Los campos base se añaden automáticamente.** Cuando defines un objeto personalizado, Twenty crea campos estándar como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` y `deletedAt` por ti. No necesitas declararlos en tu matriz `fields`, solo tus campos personalizados. Puedes sobrescribir un campo predeterminado declarando uno con el mismo nombre, pero esto rara vez es una buena idea.
</Note>
## Tipos de campo
El conjunto completo de valores de `FieldType`, exportados desde `twenty-sdk/define`:
| Categoría | Tipos |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Texto | `TEXT`, `RICH_TEXT`, `ARRAY` (de cadenas), `RAW_JSON` |
| Numérico | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (precisión arbitraria), `RATING`, `POSITION` |
| Fechas | `DATE`, `DATE_TIME` |
| Opción | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Compuesto | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identificadores y relaciones | `UUID`, `RELATION`, `MORPH_RELATION` (ver [Relations](/l/es/developers/extend/apps/data/relations)) |
| Sistema | `TS_VECTOR` (vector de búsqueda de texto completo, gestionado por el servidor) |
Los tipos compuestos almacenan múltiples subcampos (por ejemplo, `FULL_NAME` = nombre + apellido; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` y `MULTI_SELECT` requieren un arreglo `options` como en el ejemplo anterior.
## Valores predeterminados
Los valores predeterminados de cadenas literales deben ir entre comillas simples **dentro** de la cadena — `defaultValue: "'Draft'"`, no `defaultValue: "Draft"`. Por eso el campo `status` anterior utiliza `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## Archivos clave
| Archivo / Carpeta | Propósito |
| ---------------------------------------- | ----------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Obligatorio.** El archivo de configuración principal de tu app. |
| `src/default-role.ts` | Rol predeterminado que controla a qué pueden acceder tus funciones lógicas. |
| `src/constants/universal-identifiers.ts` | UUIDs generados automáticamente y metadatos de la app (nombre para mostrar, descripción). |
| `src/__tests__/` | Pruebas de integración (configuración + prueba de ejemplo). |
| `public/` | Recursos estáticos (imágenes, fuentes) servidos con tu app. |
| Archivo / Carpeta | Propósito |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Obligatorio.** El archivo de configuración principal de tu app. |
| `src/default-role.ts` | Rol predeterminado que controla a qué pueden acceder tus funciones lógicas. |
| `src/constants/universal-identifiers.ts` | UUIDs generados automáticamente y metadatos de la app (nombre para mostrar, descripción). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Una página de bienvenida inicial: un front component renderizado por un page layout independiente, accesible desde la barra lateral. |
| `src/__tests__/` | Una prueba unitaria más una prueba de integración (con su configuración global) que sincroniza la aplicación contra un servidor real. |
| `public/` | Recursos estáticos (imágenes, fuentes) servidos con tu app. |
| `AGENTS.md` / `CLAUDE.md` | Guía para agentes de IA de programación que trabajan en la aplicación. |
<Note>
**La organización de archivos depende de ti.** Las carpetas anteriores son convenciones: el SDK detecta entidades mediante análisis AST en llamadas a `export default defineEntity(...)`, sin importar dónde se encuentre el archivo.
@@ -47,15 +60,18 @@ Ambos paquetes del SDK de Twenty pertenecen a `devDependencies`, no a `dependenc
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
El generador fija `twenty-sdk` y `twenty-client-sdk` a su propia versión; mantén ambos sincronizados al actualizar.
* **`twenty-sdk`** incluye el CLI `twenty` y las herramientas de build/scaffolding. Solo se ejecuta en el desarrollo y durante el build, y nunca lo importa el runtime de la app que publicas.
* **`twenty-client-sdk`** *sí* es importado por el código de tu app (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), pero Twenty lo proporciona en tiempo de ejecución: las funciones lógicas lo obtienen de una capa SDK generada y los componentes de front lo resuelven desde módulos servidos por el servidor. Tu copia instalada solo se utiliza para la comprobación de tipos y el build en tiempo de despliegue, por lo que nunca necesita incluirse en el bundle desplegado.
Mantener cualquiera de los paquetes bajo `dependencies` lo introduce en el bundle de runtime de la app instalada, donde es peso muerto. `twenty build` emite una advertencia cuando cualquiera de ellos sigue listado bajo `dependencies`.
Mantener cualquiera de los paquetes bajo `dependencies` lo introduce en el bundle de runtime de la app instalada, donde es peso muerto. `twenty dev:build` emite una advertencia cuando cualquiera de ellos sigue listado bajo `dependencies`.
Añade las dependencias de runtime propias de tu app (las bibliotecas que tus funciones lógicas realmente importan en tiempo de ejecución) bajo `dependencies` como de costumbre.
@@ -6,17 +6,17 @@ description: Crea tu primera aplicación de Twenty en minutos.
## Prerrequisitos
* **Node.js 24+** — [Descargar](https://nodejs.org/)
* **Node.js 24.5+** — [Descargar](https://nodejs.org/)
* **Yarn 4** — incluido con Node.js a través de Corepack. Actívalo: `corepack enable`
* **Docker** — [Descargar](https://www.docker.com/products/docker-desktop/). Necesario para ejecutar un servidor local de Twenty. Omítelo si ya tienes Twenty ejecutándose en otro lugar.
La creación de una app de Twenty tiene tres fases. El generador las combina en un único comando de ruta ideal, pero cada fase es un concepto independiente — cuando algo falla, saber en qué fase estás te indica qué debes corregir.
| Fase | Qué haces | Herramienta | Resultado |
| --------------------------- | --------------------------------------------------- | ----------------------------- | ------------------------------------ |
| **1. Generar estructura** | Genera el código fuente de la app | `npx create-twenty-app` | Un proyecto de TypeScript en disco |
| **2. Ejecutar un servidor** | Inicia un servidor de Twenty con el que sincronizar | Docker + `yarn twenty server` | Una instancia de Twenty en ejecución |
| **3. Sincronizar** | Sincroniza en vivo tu código con el servidor | `yarn twenty dev` | Tus cambios aparecen en la UI |
| Fase | Qué haces | Herramienta | Resultado |
| --------------------------- | --------------------------------------------------- | ----------------------------------- | ------------------------------------ |
| **1. Generar estructura** | Genera el código fuente de la app | `npx create-twenty-app` | Un proyecto de TypeScript en disco |
| **2. Ejecutar un servidor** | Inicia un servidor de Twenty con el que sincronizar | Docker + `yarn twenty docker:start` | Una instancia de Twenty en ejecución |
| **3. Sincronizar** | Sincroniza en vivo tu código con el servidor | `yarn twenty dev` | Tus cambios aparecen en la UI |
---
@@ -28,7 +28,7 @@ Crea una app nueva a partir de la plantilla:
npx create-twenty-app@latest my-twenty-app
```
Se te pedirá un nombre y una descripción — pulsa **Enter** para usar los valores predeterminados. Esto genera un proyecto de TypeScript en `my-twenty-app/` con un `application-config.ts` inicial, un rol predeterminado, un flujo de trabajo de CI y una prueba de integración.
El generador no es interactivo: el nombre del directorio se convierte en el nombre de la aplicación. Pasa `--display-name` y `--description` para personalizar los metadatos generados (también puedes editarlos más tarde en `src/constants/universal-identifiers.ts`). Esto genera un proyecto de TypeScript en `my-twenty-app/` con un `application-config.ts` inicial, un rol predeterminado, flujos de trabajo de CI/CD y una prueba de integración.
**Después de esta fase:** tienes el código fuente de tu app en tu máquina. Aún no se está ejecutando — esa es la Fase 2.
@@ -38,28 +38,14 @@ Se te pedirá un nombre y una descripción — pulsa **Enter** para usar los val
Tu app necesita un servidor de Twenty con el que sincronizar. El servidor es una instancia completa de Twenty — UI, API GraphQL, PostgreSQL — ejecutándose localmente en Docker. Tu código local sube sus definiciones a ese servidor, lo que hace que aparezcan en la UI.
El generador ofrece iniciar uno por ti:
El generador inicia uno por ti: con Docker en ejecución, extrae la imagen `twentycrm/twenty-app-dev`, la inicia en el puerto `2020` y autentica la CLI contra el espacio de trabajo de demostración preconfigurado (`tim@apple.dev`), sin necesidad de iniciar sesión.
> **¿Te gustaría configurar una instancia local de Twenty?**
* **Sí (recomendado)** — descarga la imagen de Docker `twentycrm/twenty-app-dev` y la inicia en el puerto `2020`. Asegúrate de que Docker esté en ejecución antes.
* **No** — elige esto si ya tienes un servidor de Twenty al que te quieres conectar. Puedes conectarlo más tarde con `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="¿Debería iniciar una instancia local?" />
</div>
Una vez que el servidor esté en marcha, se abrirá un navegador para iniciar sesión. Inicia sesión con la cuenta de demostración precargada:
* **Correo electrónico:** `tim@apple.dev`
* **Contraseña:** `tim@apple.dev`
Para conectarte a un servidor Twenty existente en su lugar, pasa `--url \<your-server-url>`. Los servidores remotos se autentican con OAuth: se abre un navegador para que puedas iniciar sesión y hacer clic en **Authorize**, lo que le da a la CLI acceso a tu espacio de trabajo. (También puedes optar por usar OAuth localmente con `--authentication-method oauth`: inicia sesión con `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Pantalla de inicio de sesión de Twenty" />
</div>
Haz clic en **Authorize** en la siguiente pantalla — esto le da a la CLI acceso a tu espacio de trabajo.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Pantalla de autorización de la CLI de Twenty" />
</div>
@@ -117,27 +103,31 @@ Haz clic en **View installed app** para ver la instalación en el espacio de tra
### Sincronización de una sola vez para CI y scripts
Pasa `--once` para ejecutar una sola compilación + sincronización y salir — mismo pipeline, sin watcher:
Usa `plan` y `apply` para ejecutar la misma canalización una vez, sin observador:
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Comando | Comportamiento | Cuándo usarlo |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `yarn twenty dev` | Supervisa tus archivos fuente y vuelve a sincronizar en cada cambio. Se ejecuta hasta que lo detengas. | Desarrollo local interactivo. |
| `yarn twenty dev --once` | Realiza una sola compilación + sincronización y luego sale con el código `0` si tiene éxito o `1` si falla. | CI, hooks de pre-commit, agentes de IA, flujos de trabajo con scripts. |
| `yarn twenty dev --once --dry-run` | Genera y muestra los cambios de metadatos **sin aplicarlos**. | Inspeccionar qué cambiaría una sincronización antes de confirmarla. |
| Comando | Comportamiento | Cuándo usarlo |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `yarn twenty dev` | Supervisa tus archivos fuente y vuelve a sincronizar en cada cambio. Se ejecuta hasta que lo detengas. | Desarrollo local interactivo. |
| `yarn twenty apply` | Realiza una sola compilación + sincronización y luego sale con el código `0` si tiene éxito o `1` si falla. Pide confirmación para cambios destructivos (pasa `--force` para omitirla). | CI, hooks de pre-commit, agentes de IA, flujos de trabajo con scripts. |
| `yarn twenty plan` | Genera y muestra los cambios de metadatos **sin aplicarlos**. | Inspeccionar qué cambiaría una sincronización antes de confirmarla. |
Ambos modos necesitan un remoto autenticado. Consulta [Sincronización y recuperación](/l/es/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run) para obtener más información sobre `--dry-run`.
Todos los modos necesitan un remoto autenticado. Consulta [Sincronización y recuperación](/l/es/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) para obtener más información sobre `plan`.
<Note>
`yarn twenty dev --once` y `yarn twenty dev --once --dry-run` son alias obsoletos de `yarn twenty apply` y `yarn twenty plan`.
</Note>
### Opciones del modo de desarrollo
| Opción | Descripción |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--once` | Compila y sincroniza una vez y luego finaliza. |
| `--dry-run` | Con `--once`, obtén una vista previa de los cambios de metadatos sin aplicarlos. No escribe nada. |
| `--debounceMs \<ms>` | Establece el tiempo de antirrebote para los cambios de archivo en milisegundos (valor predeterminado: `2000`). |
| `--force` | Aplica cambios destructivos (eliminaciones) sin confirmación. |
| `--debounceMs \<ms>` | Establece el tiempo de antirrebote para los cambios de archivo en milisegundos (valor predeterminado: `1000`). |
| `--verbose` / `--debug` | Muestra registros de compilación detallados, solicitudes de sincronización y seguimientos de errores. |
## Lo que puedes crear
@@ -34,6 +34,10 @@ yarn twenty dev:add frontComponent
| Vista | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Elemento del menú de navegación | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Diseño de página | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Pestaña Diseño de página | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Elemento del menú de comandos | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Campo de vista | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Proveedor de conexión | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## Qué genera el generador
@@ -5,10 +5,10 @@ icon: wrench
---
* **Errores de Docker** — Asegúrate de que Docker Desktop (o el daemon) esté en ejecución antes de `yarn twenty docker:start`. El mensaje de error mostrará el comando de inicio correcto para tu sistema operativo.
* **Versión de Node incorrecta** — Se requiere 24+. Compruébalo con `node -v`.
* **Versión de Node incorrecta** — Se necesita la 24.5+ (`engines.node: ^24.5.0`). Compruébalo con `node -v`.
* **Falta Yarn 4** — Ejecuta `corepack enable`.
* **Dependencias rotas** — `rm -rf node_modules && yarn install`.
* **Errores de `twenty-sdk` tras actualizar a la v2.8.0** — Pasó de `dependencies` a `devDependencies` en la v2.8.0. Consulta [Estructura del proyecto → Dependencias](/l/es/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` muestra una advertencia sobre `twenty-client-sdk` en `dependencies`** — Twenty lo proporciona en tiempo de ejecución, por lo que debería trasladarse a `devDependencies` junto con `twenty-sdk`. Consulta [Estructura del proyecto → Dependencias](/l/es/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` muestra una advertencia sobre `twenty-client-sdk` en `dependencies`** — Twenty lo proporciona en tiempo de ejecución, por lo que debería trasladarse a `devDependencies` junto con `twenty-sdk`. Consulta [Estructura del proyecto → Dependencias](/l/es/developers/extend/apps/getting-started/project-structure#dependencies).
¿Atascado? Pide ayuda en el [Discord de Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -13,7 +13,6 @@ export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
icon: 'IconLayoutDashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
@@ -22,51 +21,23 @@ export default defineCommandMenuItem({
## Campos de configuración
| Campo | Obligatorio | Descripción |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Sí | ID único estable para el comando |
| `label` | Sí | Etiqueta completa mostrada en el menú de comandos (Cmd+K) |
| `frontComponentUniversalIdentifier` | Sí | El `universalIdentifier` del componente de frontend que abre este comando |
| `shortLabel` | No | Etiqueta corta mostrada en el botón de acción rápida anclado |
| `icon` | No | Nombre del ícono mostrado junto a la etiqueta (p. ej., 'IconBolt', 'IconSend') |
| `isPinned` | No | Cuando es `true`, muestra el comando como un botón de acción rápida en la esquina superior derecha de la página |
| `availabilityType` | No | Controla dónde aparece el comando: 'GLOBAL' (siempre disponible), 'RECORD_SELECTION' (solo cuando hay registros seleccionados) o 'FALLBACK' (se muestra cuando ningún otro comando coincide) |
| `availabilityObjectUniversalIdentifier` | No | Restringe el comando a páginas de un tipo de objeto específico (p. ej., solo en registros de Company) |
| `conditionalAvailabilityExpression` | No | Una expresión booleana que controla dinámicamente la visibilidad (ver abajo) |
| Campo | Obligatorio | Descripción |
| --------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Sí | ID único estable para el comando |
| `label` | Sí | Etiqueta completa mostrada en el menú de comandos (Cmd+K) |
| `frontComponentUniversalIdentifier` | Sí | El `universalIdentifier` del componente de frontend que abre este comando |
| `shortLabel` | No | Etiqueta corta mostrada en el botón de acción rápida anclado |
| `icon` | No | **Obsoleto**: se ignora en favor del icono de la aplicación; la compilación emite una advertencia si se establece |
| `isPinned` | No | Cuando es `true`, muestra el comando como un botón de acción rápida en la esquina superior derecha de la página |
| `availabilityType` | No | Controla dónde aparece el comando: `'GLOBAL'` (siempre disponible), `'GLOBAL_OBJECT_CONTEXT'` (solo en páginas con un contexto de objeto: páginas de índice y de registro), `'RECORD_SELECTION'` (solo cuando hay registros seleccionados) o `'FALLBACK'` (se muestra cuando ningún otro comando coincide) |
| `availabilityObjectUniversalIdentifier` | No | Restringe el comando a páginas de un tipo de objeto específico (p. ej., solo en registros de Company) |
| `conditionalAvailabilityExpression` | No | Una expresión booleana que controla dinámicamente la visibilidad (ver abajo) |
## Comandos sin interfaz
Un elemento del menú de comandos emparejado con un [headless front component](/l/es/developers/extend/apps/layout/front-components#headless-vs-non-headless) es la forma idónea de ofrecer una acción de un solo clic: ejecutar código, navegar o confirmar y ejecutar. La página Front Components abarca los [SDK Command components](/l/es/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) que gestionan el patrón de acción y desmontaje.
Un flujo típico:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
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 <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
Un flujo típico: un componente sin interfaz gráfica renderiza `<Command execute={...} />` (consulta el [ejemplo completo](/l/es/developers/extend/apps/layout/front-components#sdk-command-components)), y el elemento del menú de comandos lo señala:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -74,7 +45,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -49,14 +49,13 @@ export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Después de sincronizar con `yarn twenty dev` (o ejecutar una sola vez `yarn twenty dev --once`), la acción rápida aparece en la esquina superior derecha de la página:
Después de sincronizar con `yarn twenty dev` (o ejecutar una sola vez `yarn twenty apply`), la acción rápida aparece en la esquina superior derecha de la página:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Botón de acción rápida en la esquina superior derecha" />
@@ -88,11 +87,11 @@ Los componentes de front vienen en dos modos de renderizado controlados por la o
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
@@ -116,7 +115,7 @@ Como el componente devuelve `null`, Twenty omite renderizar un contenedor para
El paquete `twenty-sdk` proporciona cuatro componentes auxiliares Command diseñados para componentes de front headless. Cada componente ejecuta una acción al montarse, gestiona los errores mostrando una notificación tipo snackbar y desmonta automáticamente el componente de front al finalizar.
Impórtalos desde `twenty-sdk/command`:
Impórtalos desde `twenty-sdk/front-component`:
* **`Command`** — Ejecuta un callback asíncrono mediante la prop `execute`.
* **`CommandLink`** — Navega a una ruta de la aplicación. Props: `to`, `params`, `queryParams`, `options`.
@@ -127,8 +126,8 @@ Aquí tienes un ejemplo completo de un componente de front headless que usa `Com
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
@@ -160,7 +159,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -169,7 +167,7 @@ Y un ejemplo que usa `CommandModal` para pedir confirmación antes de ejecutar:
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
@@ -202,7 +200,7 @@ Los componentes de front se ejecutan en el navegador dentro de un Web Worker ais
Una función de lógica declarada con `httpRouteTriggerSettings` es accesible por HTTP en su ruta. Twenty inyecta en el worker la URL base desde la que se sirven tus funciones como `TWENTY_FUNCTIONS_URL`, junto con el `TWENTY_APP_ACCESS_TOKEN` que autentica la llamada. Todavía no hay un cliente SDK dedicado para invocar tus propias funciones, así que llámalas con un simple `fetch`:
> **En Twenty Cloud, las funciones de lógica activadas por HTTP se sirven en un dominio dedicado por espacio de trabajo** en `https://\<your-workspace-subdomain>.twenty.com\<path>` — esto es exactamente a lo que se resuelve `TWENTY_FUNCTIONS_URL`. Para clientes externos, copia la URL exacta desde la configuración de **HTTP trigger** de la función o desde la pestaña **Settings** de la aplicación.
> **En Twenty Cloud, las funciones de lógica activadas por HTTP se sirven en un dominio dedicado por espacio de trabajo** en `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — esto es exactamente a lo que se resuelve `TWENTY_FUNCTIONS_URL`. Para clientes externos, copia la URL exacta desde la configuración de **HTTP trigger** de la función o desde la pestaña **Settings** de la aplicación.
<Warning>
La ruta heredada de la función `/s/` está **obsoleta** y será **desactivada el 2026-07-24**. En su lugar, utiliza `TWENTY_FUNCTIONS_URL` (arriba) y migra cualquier URL de `/s/` codificada de forma fija antes de esa fecha. La ruta `/s/` sigue disponible para autoalojamiento.
@@ -212,7 +210,7 @@ Un componente de front sin interfaz (headless) puede ejecutar la llamada al mont
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
@@ -316,13 +314,13 @@ Dentro de tu componente, usa hooks del SDK para acceder al usuario actual, el re
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
@@ -405,12 +403,11 @@ Aquí tienes un ejemplo que usa la API del host para mostrar un snackbar y cerra
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
@@ -451,10 +448,10 @@ export default defineFrontComponent({
Usa `useSelectedRecordIds()` para manejar varios registros seleccionados. Esto es útil para operaciones por lotes:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
@@ -492,12 +489,19 @@ export default defineFrontComponent({
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
Muéstralo con un [elemento de menú de comando](/l/es/developers/extend/apps/layout/command-menu-items) restringido a selecciones de registros:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
@@ -35,6 +35,8 @@ export default defineNavigationMenuItem({
* `position` controla el orden en la barra lateral.
* El enum también contiene `NavigationMenuItemType.RECORD`, que se usa internamente para los favoritos de registros creados por el usuario; no se puede usar desde un manifiesto de aplicación (no hay ningún campo para hacer referencia a un registro).
* `icon` y `color` son opcionales y personalizan el aspecto de la entrada.
* `folderUniversalIdentifier` también está disponible en cualquier elemento para anidarlo dentro de un elemento padre de tipo `FOLDER`.
@@ -33,17 +33,32 @@ export default defineView({
## Puntos clave
* `objectUniversalIdentifier` especifica a qué objeto se aplica esta vista. Puede ser un objeto personalizado que hayas definido o un objeto estándar de Twenty.
* `key` determina el tipo de vista — `ViewKey.INDEX` es la vista de lista principal para el objeto.
* `key: ViewKey.INDEX` marca la vista como la vista de lista principal del objeto (la que abre un elemento de navegación `OBJECT`).
* `fields` controla qué columnas aparecen y en qué orden. Cada campo referencia un `fieldMetadataUniversalIdentifier`.
* También puedes definir `filters`, `filterGroups`, `groups` y `fieldGroups` para configuraciones avanzadas.
* También puedes declarar `filters`, `filterGroups`, `sorts`, `groups` y `fieldGroups` para configuraciones avanzadas.
* `position` controla el orden cuando existen múltiples vistas para el mismo objeto.
## Propiedades opcionales
| Propiedad | Valores | Descripción |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | `ViewType.TABLE` (predeterminado), `ViewType.KANBAN`, `ViewType.CALENDAR` | Cómo se presentan los registros. (`FIELDS_WIDGET` / `TABLE_WIDGET` también existen, pero son usados internamente por los widgets de diseño de página). |
| `visibility` | `ViewVisibility.WORKSPACE` (predeterminado), `ViewVisibility.UNLISTED` | Si la vista se muestra para todo el espacio de trabajo o se oculta en los selectores. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (predeterminado), `ViewOpenRecordIn.RECORD_PAGE` | Dónde se abre un registro al hacer clic en él. |
| `criterios de ordenación` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Orden de clasificación predeterminado. |
| `isCompact` | `boolean` | Visualización compacta de filas. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Agrupar registros (por ejemplo, columnas de kanban) por un campo. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Agregados y tamaño de columnas kanban. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Vistas de calendario: diseño y el campo de fecha que posiciona los registros. |
Todos los enums anteriores se exportan desde `twenty-sdk/define`.
## Filtros
Una vista puede incluir filtros preaplicados. Cada filtro tiene tres coordenadas: el **campo** que se está filtrando, el **operando** (cómo comparar) y el **valor** (contra qué comparar). Las tres deben alinearse: usar un operando que no aplique a un tipo de campo será rechazado en el momento de la sincronización.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
Tipos de desencadenadores disponibles:
* **httpRoute**: Expone tu función en una ruta y método HTTP **bajo el endpoint `/s/`**:
> p. ej., `path: '/post-card/create'` se puede invocar en `https://your-twenty-server.com/s/post-card/create`
* **httpRoute**: Expone tu función en una ruta HTTP y método en la **URL base de las funciones de tu espacio de trabajo** — el valor Veinte inyectos como `TWENTY_FUNCTIONS_URL` (en la nube veinte, un dominio dedicado por área de trabajo):
> p. ej., `path: '/post-card/create'` se puede invocar en `https://your-workspace.withtwenty.com/post-card/create`
<Warning>
El prefijo heredado `/s/` (`https://your-twenty-server.com/s/post-card/create`) está \*\*obsoleto en 20 nubes y será desactivado en **2026-07-24**. Sigue disponible para instancias locales y autosuficientes que no configuran un dominio de funciones aisladas — use `TWENTY_FUNCTIONS_URL` cuando está definido. y vuelve a `\<server-url>/s/\<path>` de lo contrario.
</Warning>
<Note>
Para invocar una función de lógica activada por una ruta desde un componente de frontend (headless), consulta [Llamar a una función de lógica](/l/es/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -42,7 +42,7 @@ Una función de lógica selecciona uno o más disparadores: cada entrada a conti
| Disparador | Cuándo se ejecuta | Configuración |
| ------------------------------- | --------------------------------------------------------------- | ------------------------------- |
| **Ruta HTTP** | Una solicitud llega a tu endpoint `/s/\<path>` | `httpRouteTriggerSettings` |
| **Ruta HTTP** | Una solicitud llega a la URL pública de tu función | `httpRouteTriggerSettings` |
| **Cron** | Coincide una expresión CRON | `cronTriggerSettings` |
| **Evento de base de datos** | Se crea, actualiza o elimina un registro del espacio de trabajo | `databaseEventTriggerSettings` |
| **Herramienta de IA** | Una funcionalidad de IA de Twenty decide llamar a tu función | `toolTriggerSettings` |
@@ -4,7 +4,25 @@ description: Comandos de `yarn twenty` para ejecutar funciones, transmitir regis
icon: terminal
---
Más allá de `dev`, `dev:build`, `dev:add` y `dev:typecheck`, la CLI de `yarn twenty` proporciona comandos para ejecutar funciones, ver registros y gestionar instalaciones de aplicaciones.
La CLI de `yarn twenty` es tu interfaz para todo lo relacionado con la aplicación. Lista completa de comandos:
| Comando | Qué hace | Documentado en |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `dev` | Supervisar archivos fuente y sincronizar en vivo los cambios | [Inicio rápido](/l/es/developers/extend/apps/getting-started/quick-start) |
| `plan` | Previsualizar cambios de metadatos sin aplicarlos | [Sincronización y recuperación](/l/es/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `apply` | Aplicar cambios de metadatos después de mostrar el plan | [Sincronización y recuperación](/l/es/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Compilar la aplicación y generar el cliente de la API (`--tarball` para empaquetar un `.tgz`) | [Publicación](/l/es/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Ejecutar la comprobación de tipos de TypeScript | [Pruebas](/l/es/developers/extend/apps/operations/testing) |
| `dev:add` | Crear una nueva entidad con scaffolding | [Scaffolding](/l/es/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Regenerar el cliente de API tipado | esta página |
| `dev:function:exec` / `dev:function:logs` | Ejecutar funciones y transmitir sus registros | esta página |
| `dev:translations-extract` | Extraer cadenas traducibles en catálogos de `locales/` | [Traducciones](/l/es/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | Activar la sincronización del catálogo del marketplace | [Publicación](/l/es/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Ciclo de vida de la publicación | [Publicación](/l/es/developers/extend/apps/operations/publishing) y esta página |
| `docker:*` | Administrar el contenedor del servidor local de Twenty | [Servidor local](/l/es/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Administrar conexiones de servidor | esta página |
Todos los comandos aceptan `-r, --remote \<name>` para dirigirse a un remoto específico en lugar del predeterminado.
## Ejecutar funciones (`yarn twenty dev:function:exec`)
@@ -20,8 +38,9 @@ yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the post-install function
# Execute the install hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
## Ver registros de funciones (`yarn twenty dev:function:logs`)
@@ -100,6 +119,12 @@ yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Tus credenciales se almacenan en `~/.twenty/config.json`.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
Los metadatos que se muestran en el marketplace provienen de tu configuración de `defineApplication()` — campos como `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` y `termsUrl`.
Los metadatos que se muestran en el marketplace provienen de tu configuración de `defineApplication()`; consulta [Metadatos del marketplace](#marketplace-metadata) arriba.
<Note>
Si tu aplicación no define un `aboutDescription` en `defineApplication()`, el marketplace usará automáticamente el `README.md` de tu paquete en npm como el contenido de la página Acerca de. Esto significa que puedes mantener un único README tanto para npm como para el marketplace de Twenty. Si quieres una descripción diferente en el marketplace, establece explícitamente `aboutDescription`.
@@ -15,33 +15,44 @@ Para la iteración local del día a día casi siempre quieres `yarn twenty dev`.
| Quieres… | Comando | Notas |
| --------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Iterar localmente con sincronización en tiempo real | `yarn twenty dev` | Supervisa tus archivos y sincroniza en cada cambio. |
| Sincronizar una vez y salir (CI, scripts, hooks) | `yarn twenty dev --once` | Una compilación + sincronización, luego sale. |
| Previsualizar cambios **sin aplicarlos** | `yarn twenty dev --once --dry-run` | Calcula e imprime el diff; no escribe nada. |
| Sincronizar una vez y salir (CI, scripts, hooks) | `yarn twenty apply` | Una compilación + sincronización, luego sale. Añade `--force` para omitir la confirmación de cambio destructivo. |
| Previsualizar cambios **sin aplicarlos** | `yarn twenty plan` | Calcula e imprime el diff; no escribe nada. |
| Eliminar la aplicación del espacio de trabajo | `yarn twenty app:uninstall` | Agrega `--yes` para omitir la confirmación. |
| Enviar un tarball a un servidor | `yarn twenty app:publish --private` | Requiere una versión de `package.json` **estrictamente superior**; consulta [Publicación](/l/es/developers/extend/apps/operations/publishing). |
| Publicar en el marketplace (npm) | `yarn twenty app:publish` | — |
| Instalar / actualizar una versión implementada | `yarn twenty app:install` | Instala la versión actualmente implementada. |
| Borrar el servidor local y empezar desde cero | `yarn twenty docker:reset` | Elimina **todos** los datos locales: último recurso. |
<Note>
`yarn twenty dev --once` y `yarn twenty dev --once --dry-run` siguen funcionando como alias obsoletos de `yarn twenty apply` y `yarn twenty plan`.
</Note>
### La sincronización local no necesita un aumento de versión
La regla de `version` estrictamente creciente (`VERSION_ALREADY_EXISTS` al implementar, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` al instalar) se aplica a **`app:publish` / `app:install`**: la ruta de publicación. `yarn twenty dev` sincroniza tu manifiesto en su lugar y nunca requiere un cambio de versión, por lo que no necesitas tocar `package.json` para iterar. Si te encuentras aumentando la versión para probar un cambio local, estás usando la ruta de publicación cuando lo que quieres es el ciclo de desarrollo.
## Leer la salida de la sincronización
Cada sincronización muestra los cambios de metadatos que aplicó (o aplicaría, con `--dry-run`):
Cada sincronización imprime los cambios de metadatos que aplicó (o aplicaría, con `plan`), al estilo de Terraform: un bloque por entidad con sus atributos y luego una línea de resumen:
```text filename="Terminal"
Metadata changes: 2 created, 1 updated, 1 deleted
created objectMetadata rocket
created fieldMetadata timelineActivities
updated fieldMetadata launchedAt
deleted pageLayout legacyTab
✓ Synced
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
Este es tu primer diagnóstico: te indica exactamente qué objetos, campos y diseños cambiaron, para que puedas confirmar que una sincronización hizo lo que esperabas antes de revisar la interfaz de usuario.
Los cambios destructivos (`to destroy`) se enumeran con lo que eliminan (p. ej., `objectMetadata "auditNote" — drops the table and all its rows`) y requieren confirmación interactiva, o `--force` en scripts.
Cuando una sincronización falla en una sola entidad, el error nombra la entidad implicada y su `universalIdentifier`, por ejemplo:
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
Usa ese identificador para encontrar la entidad en tu manifiesto (y, si es necesario, en el espacio de trabajo) en lugar de adivinar cuál entra en conflicto.
## Previsualizar cambios (simulación)
## Previsualizar cambios (plan)
`yarn twenty dev --once --dry-run` compila tu manifiesto, le pide al servidor el plan de migración y lo imprime, **sin aplicar nada**. Es la forma segura de responder "¿qué cambiaría esta sincronización?" antes de comprometerte a ella.
`yarn twenty plan` compila tu manifiesto, le pide al servidor el plan de migración y lo imprime, **sin aplicar nada**. Es la forma segura de responder "¿qué cambiaría esta sincronización?" antes de comprometerte a ella.
```bash filename="Terminal"
yarn twenty dev --once --dry-run
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata diff (dry run, nothing will be applied)...
Metadata changes: 1 created, 1 updated
created fieldMetadata timelineActivities
updated objectMetadata rocket
✓ Dry run complete for My App — no changes were applied
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "timelineActivities" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Una simulación:
Un plan:
* **No escribe nada**: sin migración de metadatos, sin actualización del registro de la aplicación, sin cambios de roles/pestañas predeterminados y sin generación del cliente de la API.
* Devuelve el **mismo diff** que aplicaría una sincronización real, para que puedas revisar por adelantado las entidades creadas/actualizadas/eliminadas.
* Es útil antes de un cambio arriesgado, al revisar un cambio generado por IA o en un script que deba fallar si está a punto de producirse un cambio inesperado.
<Note>
Una simulación solo previsualiza cambios de **metadatos** y requiere que la aplicación se haya sincronizado al menos una vez (para que el espacio de trabajo la conozca). Si la ejecutas con una aplicación que nunca se sincronizó, el servidor indicará que la aplicación no está instalada; ejecuta `yarn twenty dev` una vez primero.
Un plan solo previsualiza cambios de **metadatos** y requiere que la aplicación se haya sincronizado al menos una vez (para que el espacio de trabajo la conozca). Si la ejecutas con una aplicación que nunca se sincronizó, el servidor indicará que la aplicación no está instalada; ejecuta `yarn twenty dev` una vez primero.
</Note>
## Escalera de recuperación
Cuando los metadatos locales parezcan incorrectos, ve escalando en este orden y detente en cuanto te hayas desbloqueado. Cada paso es más disruptivo que el anterior.
1. **Volver a sincronizar.** Ejecuta `yarn twenty dev --once` de nuevo. Las sincronizaciones son idempotentes: volver a ejecutar un manifiesto limpio es seguro y suele resolver un problema transitorio.
2. **Previsualizar el plan.** Ejecuta `yarn twenty dev --once --dry-run` para ver exactamente qué pretende cambiar la siguiente sincronización, sin aplicarlo.
1. **Volver a sincronizar.** Ejecuta `yarn twenty apply` de nuevo. Las sincronizaciones son idempotentes: volver a ejecutar un manifiesto limpio es seguro y suele resolver un problema transitorio.
2. **Previsualizar el plan.** Ejecuta `yarn twenty plan` para ver exactamente qué pretende cambiar la siguiente sincronización, sin aplicarlo.
3. Lee el error identificado. Un conflicto suele señalar un identificador duplicado o reutilizado.
4. **Desinstalar y volver a instalar.** `yarn twenty app:uninstall`, luego vuelve a sincronizar (`yarn twenty dev`). Esto reconstruye los metadatos de la aplicación desde cero manteniendo intacto el resto de tu espacio de trabajo.
5. **Restablecimiento completo (último recurso).** `yarn twenty docker:reset`, luego vuelve a sembrar los datos y a sincronizar.
@@ -78,6 +78,13 @@ Crea un `vitest.config.ts` en la raíz de tu aplicación:
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
@@ -88,66 +95,74 @@ export default defineConfig({
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
setupFiles: ['src/__tests__/setup-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL: 'http://localhost:2020',
TWENTY_API_KEY: 'your-api-key',
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Crea un archivo de configuración que verifique que el servidor es accesible antes de ejecutar las pruebas:
Crea un archivo de configuración global que verifique que el servidor es accesible, escriba una configuración de prueba para el SDK (`~/.twenty/config.test.json`) y sincronice la aplicación antes de que se ejecuten las pruebas:
```ts src/__tests__/setup-test.ts
```ts src/__tests__/global-setup.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');
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
beforeAll(async () => {
// Verify the server is running
const response = await fetch(`${TWENTY_API_URL}/healthz`);
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
'Start the server before running integration tests.',
);
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write a temporary config for the SDK
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(TEST_CONFIG_DIR, 'config.json'),
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
});
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## APIs programáticas del SDK
La subruta `twenty-sdk/cli` exporta funciones que puedes invocar directamente desde el código de pruebas:
| Función | Descripción |
| -------------- | ------------------------------------------------------------ |
| `appBuild` | Compilar la aplicación y opcionalmente empaquetar un tarball |
| `appDeploy` | Subir un tarball al servidor |
| `appInstall` | Instalar la aplicación en el espacio de trabajo activo |
| `appUninstall` | Desinstalar la aplicación del espacio de trabajo activo |
| Función | Descripción |
| -------------- | -------------------------------------------------------------------------- |
| `appBuild` | Compilar la aplicación y opcionalmente empaquetar un tarball |
| `appDeploy` | Subir un tarball al servidor |
| `appDevOnce` | Compila y sincroniza la aplicación una vez (igual que `yarn twenty apply`) |
| `appInstall` | Instalar la aplicación en el espacio de trabajo activo |
| `appUninstall` | Desinstalar la aplicación del espacio de trabajo activo |
Cada función devuelve un objeto de resultado con `success: boolean` y `data` o `error`.
@@ -238,64 +253,10 @@ También puedes ejecutar la comprobación de tipos en tu aplicación sin ejecuta
yarn twenty dev:typecheck
```
Esto ejecuta `tsc --noEmit` e informa cualquier error de tipo.
Esto ejecuta `tsc --noEmit` contra el `tsconfig.json` de tu aplicación e informa cualquier error de tipo. Las aplicaciones generadas también incluyen un script `yarn typecheck` que también cubre los archivos de prueba (`tsconfig.spec.json`).
## CI con GitHub Actions
El generador crea un flujo de trabajo de GitHub Actions listo para usar en `.github/workflows/ci.yml`. Ejecuta tus pruebas de integración automáticamente en cada push a `main` y en los pull requests.
El generador crea un flujo de trabajo listo para usar en `.github/workflows/ci.yml`. En cada push a `main` y en cada pull request, inicia un servidor efímero de Twenty en el runner (mediante la acción `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), luego ejecuta `yarn lint`, `yarn typecheck`, `yarn test:unit` y `yarn test` con `TWENTY_API_URL` / `TWENTY_API_KEY` apuntando a ese servidor. No se requieren secretos y puedes fijar la versión del servidor mediante la variable de entorno `TWENTY_VERSION` en la parte superior del flujo de trabajo.
El flujo de trabajo:
1. Obtiene tu código
2. Inicia un servidor temporal de Twenty usando la acción `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instala las dependencias con `yarn install --immutable`
4. Ejecuta `yarn test` con `TWENTY_API_URL` y `TWENTY_API_KEY` inyectados a partir de las salidas de la acción
```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 }}
```
No necesitas configurar secretos: la acción `spawn-twenty-docker-image` inicia un servidor efímero de Twenty directamente en el runner y devuelve los detalles de conexión. El secreto `GITHUB_TOKEN` lo proporciona GitHub automáticamente.
Para fijar una versión específica de Twenty en lugar de `latest`, cambia la variable de entorno `TWENTY_VERSION` al inicio del flujo de trabajo.
Consulta [Publicación → CI/CD automatizado](/l/es/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) para ver una guía completa de ambos flujos de trabajo generados (`ci.yml` y la canalización de despliegue `cd.yml`).
@@ -91,9 +91,11 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
const apiBaseUrl = process.env.TWENTY_API_URL;
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
@@ -185,7 +187,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,8 +9,15 @@ El mismo manejador también puede responder a peticiones HTTP. Añadiremos dos r
* un endpoint **POST** para generar un documento, y
* un endpoint público **GET** que renderiza un documento como una página web imprimible.
Ambos usan `httpRouteTriggerSettings`. Las rutas de la aplicación se sirven bajo `/s` en tu servidor
Veenty (por ejemplo, `http://localhost:2020/s/documents/generate`).
Ambos usan `httpRouteTriggerSettings`. En el servidor dev local, las rutas de la aplicación son
servidas bajo el prefijo `/s` (por ejemplo, `http://localhost:2020/s/documents/generate`).
<Note>
En 20 nubes las rutas se sirven en el dominio
de funciones dedicadas del espacio de trabajo — la URL Veinte inyectos como `TWENTY_FUNCTIONS_URL`, sin prefijo `/s`. El prefijo
`/s` está obsoleto allí y sólo permanece para instancias locales y autoalojadas.
Ver [Llamar a una función lógica](/l/es/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## Ruta POST — generar bajo demanda
@@ -77,11 +77,11 @@ Ejecuta las mismas puertas que CI hace:
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty dev --once --dry-run # preview the metadata diff
yarn twenty plan # preview the metadata diff
```
La ejecución seca imprime exactamente lo que cambiaría en el servidor sin aplicarlo —
una buena comprobación final de sanidad. Ver
El plan muestra exactamente qué cambiaría en el servidor sin aplicarlos
una buena comprobación final. Ver
[Testing](/l/es/developers/extend/apps/operations/testing) y
[Sincronizando y recuperando](/l/es/developers/extend/apps/operations/sync-and-recovery).
@@ -4,9 +4,9 @@ description: Exécutez de la logique avant ou après l'installation — initiali
icon: wrench
---
Les hooks d'installation sont des fonctions logiques spéciales qui s'exécutent pendant le cycle de vie d'installation ou de mise à niveau. Ils partagent le même environnement d'exécution que les [fonctions logiques](/l/fr/developers/extend/apps/logic/logic-functions) classiques et reçoivent un `InstallPayload`, mais ils sont déclarés avec leurs propres fonctions de définition — `definePostInstallLogicFunction()` et `definePreInstallLogicFunction()` — et ne relèvent pas du modèle de déclencheur habituel (HTTP, cron, événements de base de données).
Les hooks d'installation sont des fonctions logiques spéciales qui s'exécutent pendant le cycle de vie d'installation ou de mise à niveau. Ils partagent le même environnement d'exécution de gestionnaire que les [fonctions logiques](/l/fr/developers/extend/apps/logic/logic-functions) classiques et reçoivent un `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` est `undefined` lors d'une nouvelle installation), mais ils sont déclarés avec leurs propres fonctions de définition et vivent en dehors du modèle de déclencheur normal (HTTP, cron, événements de base de données).
Chaque application peut définir **au maximum une pré-installation** et **au maximum une post-installation**. La génération du manifeste renverra une erreur si plus d'une fonction de l'un ou l'autre type est détectée.
Chaque application peut définir **au maximum une pré-installation** et **au maximum une post-installation**. La génération du manifeste renvoie une erreur si plus d'une fonction de l'un ou l'autre type est détectée.
```
┌─────────────────────────────────────────────────────────────┐
@@ -19,111 +19,59 @@ Chaque application peut définir **au maximum une pré-installation** et **au ma
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="S'exécute après l'application de la migration des métadonnées de l'espace de travail">
## En un coup d'œil
Une fonction post-installation s'exécute automatiquement une fois l'installation de votre application sur un espace de travail terminée. Le serveur l'exécute **après** que les métadonnées de l'application ont été synchronisées et que le client du SDK a été généré, afin que l'espace de travail soit entièrement prêt à l'emploi et que le nouveau schéma soit en place. Les cas d'utilisation typiques incluent le préremplissage de données par défaut, la création d'enregistrements initiaux, la configuration des paramètres de l'espace de travail ou le provisionnement de ressources sur des services tiers.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Exécutions | Avant la migration des métadonnées — le schéma et les données **précédents** sont toujours intacts | Après la migration et la génération du SDK — le **nouveau** schéma est en place |
| Exécution | Toujours synchrone ; bloque l'installation | Asynchrone par défaut (mis en file d'attente, 3 nouvelles tentatives) ; mode synchrone en option via `shouldRunSynchronously: true` |
| En cas d'échec | L'installation est **abandonnée** avant toute modification du schéma | Asynchrone : nouvelle tentative jusqu'à 3 fois. Synchrone : l'appelant reçoit `POST_INSTALL_ERROR` (les modifications de schéma **ne** sont pas annulées) |
| Utilisation typique | Sauvegarder ou corriger les données qu'une migration ferait perdre ; refuser une mise à niveau risquée en levant une exception | Initialiser des données par défaut, configurer l'espace de travail, enregistrer des ressources externes |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Règle empirique :** privilégiez post-install par défaut. Ne recourez à la pré-installation que lorsque la migration elle-même est destructive et que vous devez intercepter l'état précédent avant qu'il ne disparaisse.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Vous souhaitez... | Utiliser |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Initialiser des données, configurer l'espace de travail, enregistrer des ressources externes | `post-install` |
| Travail de longue durée qui ne doit pas bloquer la réponse d'installation | `post-install` (mode asynchrone par défaut, avec nouvelles tentatives du worker) |
| Configuration rapide dont l'appelant dépend immédiatement après le retour de l'installation | `post-install` avec `shouldRunSynchronously: true` |
| Lire ou sauvegarder des données que la migration à venir ferait perdre | `pre-install` |
| Rejeter une mise à niveau qui corromprait des données existantes | `pre-install` (lancer une exception depuis le gestionnaire) |
| Réconciliation à chaque mise à niveau | N'importe quel hook avec `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## Comportement partagé par les deux hooks
Vous pouvez également exécuter manuellement la fonction de post-installation à tout moment à l'aide de la CLI :
* La configuration est une configuration `defineLogicFunction` moins les paramètres de déclencheur, plus `shouldRunOnVersionUpgrade`.
* **Quand il s'exécute** : uniquement lors des nouvelles installations, par défaut. Définissez `shouldRunOnVersionUpgrade: true` pour qu'il s'exécute également lors des mises à niveau. Utilisez `previousVersion` / `newVersion` pour bifurquer selon le chemin de mise à niveau.
* **L'idempotence est importante** : le post-install asynchrone peut être relancé, et chaque hook est réexécuté lors des mises à niveau lorsque `shouldRunOnVersionUpgrade` est activé.
* L'environnement habituel des fonctions logiques (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) est injecté, ce qui vous permet d'appeler l'API Twenty avec le jeton de votre application.
* Le hook est rattaché automatiquement au manifeste de l'application au moment de la compilation (`preInstallLogicFunction` / `postInstallLogicFunction`) — rien à référencer dans [`defineApplication()`](/l/fr/developers/extend/apps/config/application).
* La valeur par défaut de `timeoutSeconds` est 300 pour permettre des tâches de configuration plus longues comme l'initialisation des données.
* **Non exécuté en mode dev** : `yarn twenty dev` ignore le flux d'installation et synchronise directement les fichiers, donc les hooks ne s'exécutent jamais dans ce cas. Déclenchez-les manuellement à la place :
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Points clés :
* Les fonctions de post-installation utilisent `definePostInstallLogicFunction()` — une variante spécialisée qui omet les paramètres de déclencheur (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* Le gestionnaire reçoit un `InstallPayload` avec `{ previousVersion?: string; newVersion: string }` — `newVersion` est la version en cours d'installation, et `previousVersion` est la version précédemment installée (ou `undefined` lors d'une nouvelle installation). Utilisez ces valeurs pour distinguer les nouvelles installations des mises à niveau et pour exécuter une logique de migration spécifique à la version.
* **Quand le hook s'exécute** : uniquement lors des nouvelles installations, par défaut. Passez `shouldRunOnVersionUpgrade: true` si vous souhaitez également qu'il s'exécute lorsque l'application est mise à niveau depuis une version précédente. S'il est omis, l'indicateur vaut `false` par défaut et les mises à niveau ignorent le hook.
* **Modèle d'exécution — asynchrone par défaut, synchrone sur opt-in** : l'indicateur `shouldRunSynchronously` contrôle *comment* post-install est exécuté.
* `shouldRunSynchronously: false` *(par défaut)* — le hook est **placé dans la file de messages** avec `retryLimit: 3` et s'exécute de manière asynchrone dans un worker. La réponse d'installation est renvoyée dès que la tâche est mise en file d'attente, de sorte qu'un gestionnaire lent ou défaillant ne bloque pas l'appelant. Le worker réessaiera jusqu'à trois fois. **Utilisez ceci pour les tâches de longue durée** — initialisation de grands jeux de données, appel d'API tierces lentes, provisionnement de ressources externes, tout ce qui pourrait dépasser une fenêtre de réponse HTTP raisonnable.
* `shouldRunSynchronously: true` — le hook est exécuté **en ligne pendant le flux d'installation** (même exécuteur que pre-install). La requête d'installation est bloquée jusqu'à la fin du gestionnaire et, s'il lève une exception, l'appelant de l'installation reçoit un `POST_INSTALL_ERROR`. Aucun réessai automatique. **Utilisez ceci pour un travail rapide devant être terminé avant la réponse** — par exemple, émettre une erreur de validation à l'utilisateur, ou une configuration rapide dont le client dépendra immédiatement après le retour de l'appel d'installation. Gardez à l'esprit que la migration des métadonnées a déjà été appliquée au moment où post-install s'exécute, donc un échec en mode synchrone ne **rétablit pas** les modifications du schéma — il ne fait qu'exposer l'erreur.
* Assurez-vous que votre gestionnaire est idempotent. En mode asynchrone, la file peut réessayer jusqu'à trois fois ; dans les deux modes, le hook peut s'exécuter à nouveau lors des mises à niveau lorsque `shouldRunOnVersionUpgrade: true`.
* Les variables d'environnement `APPLICATION_ID`, `APP_ACCESS_TOKEN` et `API_URL` sont disponibles dans le gestionnaire (comme pour toute autre fonction logique), vous pouvez donc appeler l'API Twenty avec un jeton d'accès d'application limité à votre application.
* Une seule fonction de post-installation est autorisée par application. La génération du manifeste renverra une erreur si plusieurs sont détectées.
* Les propriétés `universalIdentifier`, `shouldRunOnVersionUpgrade` et `shouldRunSynchronously` de la fonction sont automatiquement attachées au manifeste de l'application sous le champ `postInstallLogicFunction` pendant le build — vous n'avez pas besoin de les référencer dans [`defineApplication()`](/l/fr/developers/extend/apps/config/application).
* Le délai d'expiration par défaut est défini à 300 secondes (5 minutes) pour permettre des tâches de configuration plus longues comme l'initialisation des données.
* **Non exécuté en mode dev** : lorsqu'une application est enregistrée localement (via `yarn twenty dev`), le serveur saute complètement le flux d'installation et synchronise les fichiers directement via le watcher de la CLI — ainsi, post-install ne s'exécute jamais en mode dev, quel que soit `shouldRunSynchronously`. Utilisez `yarn twenty dev:function:exec --postInstall` pour le déclencher manuellement sur un espace de travail en cours d'exécution.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="S'exécute avant l'application de la migration des métadonnées de l'espace de travail">
Une fonction de pré-installation s'exécute automatiquement pendant l'installation, **avant que la migration des métadonnées de l'espace de travail soit appliquée**. Elle partage la même forme de payload que post-install (`InstallPayload`), mais elle est positionnée plus tôt dans le flux d'installation afin de pouvoir préparer l'état dont dépend la migration à venir — les usages typiques incluent la sauvegarde de données, la validation de la compatibilité avec le nouveau schéma, ou l'archivage d'enregistrements sur le point d'être restructurés ou supprimés.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
Vous pouvez également exécuter manuellement la fonction de pré-installation à tout moment à l'aide de la CLI :
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Points clés :
* Les fonctions de pré-installation utilisent `definePreInstallLogicFunction()` — même configuration spécialisée que post-install, simplement attachée à un autre emplacement du cycle de vie.
* Les gestionnaires de pré- et post-install reçoivent le même type `InstallPayload` : `{ previousVersion?: string; newVersion: string }`. Importez-le une fois et réutilisez-le pour les deux hooks.
* **Quand le hook s'exécute** : positionné juste avant la migration des métadonnées de l'espace de travail (`synchronizeFromManifest`). Avant l'exécution, le serveur lance une « synchronisation réduite » purement additive qui enregistre la fonction de pré-installation de la **nouvelle** version dans les métadonnées de l'espace de travail — rien d'autre n'est modifié — puis l'exécute. Comme cette synchronisation est uniquement additive, les objets, champs et données de la version précédente sont toujours intacts lorsque votre gestionnaire s'exécute : vous pouvez lire et sauvegarder en toute sécurité l'état pré-migration.
* **Modèle d'exécution** : la pré-installation est exécutée **de façon synchrone** et **bloque l'installation**. Si le gestionnaire lève une exception, l'installation est abandonnée avant que des modifications du schéma ne soient appliquées — l'espace de travail reste sur la version précédente dans un état cohérent. C'est intentionnel : la pré-installation est votre dernière chance de refuser une mise à niveau risquée.
* Comme pour post-install, une seule fonction de pré-installation est autorisée par application. Elle est automatiquement attachée au manifeste de l'application sous `preInstallLogicFunction` pendant le build.
* **Non exécuté en mode dev** : comme pour post-install — le flux d'installation est entièrement ignoré pour les applications enregistrées localement, donc la pré-installation ne s'exécute jamais sous `yarn twenty dev`. Utilisez `yarn twenty dev:function:exec --preInstall` pour la déclencher manuellement.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="S'exécute après l'application de la migration des métadonnées de l'espace de travail">
</Accordion>
<Accordion title="Pré-installation vs post-installation : quand utiliser quoi" description="Choisir le bon hook d'installation">
Les deux hooks font partie du même flux d'installation et reçoivent le même `InstallPayload`. La différence tient au **moment** où ils s'exécutent par rapport à la migration des métadonnées de l'espace de travail, et cela change les données qu'ils peuvent manipuler en toute sécurité.
La pré-installation est toujours **synchrone** (elle bloque l'installation et peut l'interrompre). Post-install est **asynchrone par défaut** — mis en file d'attente sur un worker avec des réessais automatiques — mais peut opter pour une exécution synchrone avec `shouldRunSynchronously: true`. Voir l'accordéon `definePostInstallLogicFunction` ci-dessus pour savoir quand utiliser chaque mode.
**Utilisez `post-install` pour tout ce qui nécessite l'existence du nouveau schéma.** C'est le cas le plus courant :
* Initialiser des données par défaut (création d'enregistrements initiaux, de vues par défaut, de contenu de démonstration) sur des objets et champs nouvellement ajoutés.
* Enregistrer des webhooks auprès de services tiers maintenant que l'application dispose de ses identifiants.
* Appeler votre propre API pour finaliser une configuration qui dépend des métadonnées synchronisées.
* Logique idempotente « assurer l'existence de cet élément » qui doit réconcilier l'état à chaque mise à niveau — à combiner avec `shouldRunOnVersionUpgrade: true`.
Exemple — initialiser un enregistrement `PostCard` par défaut après l'installation :
S'exécute une fois que votre application a terminé son installation : métadonnées synchronisées, client SDK généré, nouveau schéma interrogeable. Exemple — initialiser un enregistrement par défaut lors des nouvelles installations :
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**Utilisez `pre-install` lorsqu'une migration détruirait ou corromprait autrement des données existantes.** Comme la pré-installation s'exécute sur le schéma *précédent* et qu'un échec annule la mise à niveau, c'est l'endroit approprié pour tout ce qui est risqué :
Le drapeau `shouldRunSynchronously` contrôle le modèle d'exécution :
* **Sauvegarder des données sur le point d'être supprimées ou restructurées** — par exemple, vous supprimez un champ en v2 et devez copier ses valeurs dans un autre champ ou les exporter vers un stockage avant l'exécution de la migration.
* **Archiver des enregistrements qu'une nouvelle contrainte invaliderait** — par exemple, un champ devient `NOT NULL` et vous devez d'abord supprimer ou corriger les lignes avec des valeurs nulles.
* **Valider la compatibilité et refuser la mise à niveau si les données actuelles ne peuvent pas être migrées proprement** — lancez une exception depuis le gestionnaire et l'installation s'interrompt sans appliquer de modifications. C'est plus sûr que de découvrir l'incompatibilité en cours de migration.
* **Renommer ou réassigner les clés des données** avant une modification du schéma qui ferait perdre l'association.
* `false` *(par défaut)* — mis en file d'attente dans la file de messages (`retryLimit: 3`) et exécuté par un worker. La réponse d'installation est renvoyée dès que la tâche est mise en file d'attente. **À utiliser pour les travaux de longue durée** — initialisation de grands ensembles de données, API tierces lentes.
* `true` — exécuté en ligne pendant le flux d'installation. La requête d'installation est bloquée jusqu'à ce que le gestionnaire ait terminé ; une erreur levée apparaît comme `POST_INSTALL_ERROR` pour l'appelant (aucune nouvelle tentative). **À utiliser pour les travaux rapides qui doivent être terminés avant la réponse.** La migration a déjà été appliquée à ce stade, donc un échec n'annule pas les modifications du schéma — il ne fait que remonter l'erreur.
Exemple — archiver des enregistrements avant une migration destructive :
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="S'exécute avant l'application de la migration des métadonnées de l'espace de travail">
S'exécute avant la migration des métadonnées, sur le schéma **précédent** — l'endroit idéal pour sauvegarder des données qu'une migration ferait perdre, ou pour refuser une mise à niveau risquée. Avant l'exécution, le serveur effectue une « synchronisation réduite » purement additive qui enregistre uniquement la fonction de pré-installation de la nouvelle version ; tout le reste — les objets, champs et données de la version précédente — reste intact lorsque votre gestionnaire s'exécute.
La pré-installation est toujours **synchrone** et bloque l'installation. Si le gestionnaire lève une exception, l'installation est abandonnée avant toute modification du schéma — l'espace de travail reste sur la version précédente dans un état cohérent. C'est intentionnel : la pré-installation est votre dernière chance de refuser une mise à niveau risquée.
Exemple — copier les valeurs d'un champ hérité avant que la migration ne le supprime :
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**Règle générale :**
| Vous souhaitez... | Utiliser |
| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| Initialiser des données par défaut, configurer l'espace de travail, enregistrer des ressources externes | `post-install` |
| Exécuter une initialisation longue ou des appels tiers qui ne doivent pas bloquer la réponse d'installation | `post-install` (par défaut — `shouldRunSynchronously: false`, avec des réessais du worker) |
| Exécuter une configuration rapide dont l'appelant dépendra immédiatement après le retour de l'appel d'installation | `post-install` avec `shouldRunSynchronously: true` |
| Lire ou sauvegarder des données que la migration à venir ferait perdre | `pre-install` |
| Rejeter une mise à niveau qui corromprait des données existantes | `pre-install` (lancer une exception depuis le gestionnaire) |
| Exécuter une réconciliation à chaque mise à niveau | `post-install` avec `shouldRunOnVersionUpgrade: true` |
| Effectuer une configuration ponctuelle uniquement lors de la première installation | `post-install` avec `shouldRunOnVersionUpgrade: false` (par défaut) |
<Note>
En cas de doute, privilégiez **post-install**. Ne recourez à la pré-installation que lorsque la migration elle-même est destructive et que vous devez intercepter l'état précédent avant qu'il ne disparaisse.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**Les champs de base sont ajoutés automatiquement.** Lorsque vous définissez un objet personnalisé, Twenty crée pour vous des champs standard comme `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` et `deletedAt`. Vous navez pas besoin de les déclarer dans votre tableau `fields` — uniquement vos champs personnalisés. Vous pouvez remplacer un champ par défaut en en déclarant un avec le même nom, mais cest rarement une bonne idée.
</Note>
## Types de champ
Lensemble complet des valeurs de `FieldType`, exportées depuis `twenty-sdk/define` :
| Catégorie | Types |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Texte | `TEXT`, `RICH_TEXT`, `ARRAY` (de chaînes), `RAW_JSON` |
| Numérique | `NUMBER` (`universalSettings.dataType` : `'float'` / `'int'` / `'bigint'`), `NUMERIC` (précision arbitraire), `RATING`, `POSITION` |
| Dates | `DATE`, `DATE_TIME` |
| Choix | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Composés | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identifiants et relations | `UUID`, `RELATION`, `MORPH_RELATION` (voir [Relations](/l/fr/developers/extend/apps/data/relations)) |
| Système | `TS_VECTOR` (vecteur de recherche en texte intégral, géré par le serveur) |
Les types composés stockent plusieurs sous-champs (par exemple `FULL_NAME` = prénom + nom de famille ; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` et `MULTI_SELECT` nécessitent un tableau `options` comme dans lexemple ci-dessus.
## Valeurs par défaut
Les valeurs par défaut de type chaîne littérale doivent être entourées de guillemets simples **à lintérieur** de la chaîne — `defaultValue: "'Draft'"`, et non `defaultValue: "Draft"`. Cest pourquoi le champ `status` ci-dessus utilise `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## Fichiers clés
| Fichier / Dossier | Objectif |
| ---------------------------------------- | -------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Requis.** Le fichier de configuration principal de votre application. |
| `src/default-role.ts` | Rôle par défaut qui contrôle ce à quoi vos fonctions de logique peuvent accéder. |
| `src/constants/universal-identifiers.ts` | UUID générés automatiquement et métadonnées de lapplication (nom daffichage, description). |
| `src/__tests__/` | Tests dintégration (configuration + test dexemple). |
| `public/` | Ressources statiques (images, polices) servies avec votre application. |
| Fichier / Dossier | Objectif |
| -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Requis.** Le fichier de configuration principal de votre application. |
| `src/default-role.ts` | Rôle par défaut qui contrôle ce à quoi vos fonctions de logique peuvent accéder. |
| `src/constants/universal-identifiers.ts` | UUID générés automatiquement et métadonnées de lapplication (nom daffichage, description). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Une page daccueil de démarrage : un composant frontal rendu par une mise en page de page autonome, accessible depuis la barre latérale. |
| `src/__tests__/` | Un test unitaire plus un test dintégration (avec sa configuration globale) qui synchronise lapplication avec un serveur réel. |
| `public/` | Ressources statiques (images, polices) servies avec votre application. |
| `AGENTS.md` / `CLAUDE.md` | Consignes pour les agents d’écriture de code IA qui travaillent sur lapplication. |
<Note>
**Lorganisation des fichiers vous revient.** Les dossiers ci-dessus sont des conventions — le SDK détecte les entités via une analyse AST sur les appels à `export default defineEntity(...)` quel que soit lemplacement du fichier.
@@ -47,15 +60,18 @@ Les deux packages du SDK Twenty doivent être placés dans `devDependencies`, et
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
Le générateur de projet fige `twenty-sdk` et `twenty-client-sdk` sur sa propre version — gardez les deux synchronisés lors de la mise à niveau.
* **`twenty-sdk`** fournit le CLI `twenty` ainsi que les outils de build et de scaffolding. Il ne sexécute quau moment du développement et du build et nest jamais importé par le runtime de lapplication que vous publiez.
* **`twenty-client-sdk`** *est* importé par le code de votre application (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), mais Twenty le fournit au moment de lexécution : les fonctions de logique lobtiennent à partir dune couche SDK générée, et les composants front le résolvent à partir de modules servis par le serveur. La copie que vous avez installée est uniquement utilisée pour la vérification de type et le build au moment du déploiement, elle na donc jamais besoin d’être incluse dans le bundle déployé.
Conserver lun ou lautre package dans `dependencies` lintègre dans le bundle runtime de lapplication installée, où il ne fait que lalourdir inutilement. `twenty build` émet un avertissement lorsque lun ou lautre est encore répertorié dans `dependencies`.
Conserver lun ou lautre package dans `dependencies` lintègre dans le bundle runtime de lapplication installée, où il ne fait que lalourdir inutilement. `twenty dev:build` émet un avertissement lorsque lun ou lautre est encore répertorié dans `dependencies`.
Ajoutez les dépendances runtime propres à votre application (les bibliothèques que vos fonctions logiques importent réellement à lexécution) dans `dependencies` comme dhabitude.
@@ -6,17 +6,17 @@ description: Créez votre première application Twenty en quelques minutes.
## Prérequis
* **Node.js 24+** — [Télécharger](https://nodejs.org/)
* **Node.js 24.5+** — [Télécharger](https://nodejs.org/)
* **Yarn 4** — fourni avec Node.js via Corepack. Activez-le : `corepack enable`
* **Docker** — [Télécharger](https://www.docker.com/products/docker-desktop/). Nécessaire pour exécuter un serveur Twenty local. Ignorez si vous avez déjà Twenty en cours dexécution ailleurs.
La création dune application Twenty comporte trois phases. Le générateur les regroupe en une seule commande pour le parcours idéal, mais chaque phase est un concept distinct — en cas d’échec, savoir dans quelle phase vous vous trouvez indique ce quil faut corriger.
| Phase | Ce que vous faites | Outil | Résultat |
| -------------------------- | ------------------------------------------------------ | ----------------------------- | ----------------------------------------------------------- |
| **1. Génération** | Générer le code source de lapplication | `npx create-twenty-app` | Un projet TypeScript sur le disque |
| **2. Exécuter un serveur** | Démarrer un serveur Twenty vers lequel se synchroniser | Docker + `yarn twenty server` | Une instance Twenty en cours dexécution |
| **3. Synchroniser** | Synchroniser en direct votre code avec le serveur | `yarn twenty dev` | Vos modifications apparaissent dans linterface utilisateur |
| Phase | Ce que vous faites | Outil | Résultat |
| -------------------------- | ------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------- |
| **1. Génération** | Générer le code source de lapplication | `npx create-twenty-app` | Un projet TypeScript sur le disque |
| **2. Exécuter un serveur** | Démarrer un serveur Twenty vers lequel se synchroniser | Docker + `yarn twenty docker:start` | Une instance Twenty en cours dexécution |
| **3. Synchroniser** | Synchroniser en direct votre code avec le serveur | `yarn twenty dev` | Vos modifications apparaissent dans linterface utilisateur |
---
@@ -28,7 +28,7 @@ Créez une nouvelle application à partir du modèle :
npx create-twenty-app@latest my-twenty-app
```
On vous demandera un nom et une description — appuyez sur **Entrée** pour utiliser les valeurs par défaut. Cela génère un projet TypeScript dans `my-twenty-app/` avec un fichier de démarrage `application-config.ts`, un rôle par défaut, un workflow CI et un test dintégration.
Le générateur est non interactif : le nom du répertoire devient le nom de lapplication. Passez `--display-name` et `--description` pour personnaliser les métadonnées générées (vous pouvez également les modifier plus tard dans `src/constants/universal-identifiers.ts`). Cela génère un projet TypeScript dans `my-twenty-app/` avec un fichier de démarrage `application-config.ts`, un rôle par défaut, des workflows CI/CD et un test dintégration.
**Après cette phase :** vous disposez du code source dune application sur votre machine. Elle ne sexécute pas encore — cest la phase 2.
@@ -38,28 +38,14 @@ On vous demandera un nom et une description — appuyez sur **Entrée** pour uti
Votre application a besoin dun serveur Twenty vers lequel se synchroniser. Le serveur est une instance Twenty complète — interface utilisateur, API GraphQL, PostgreSQL — exécutée localement dans Docker. Votre code local envoie ses définitions à ce serveur, qui les fait apparaître dans linterface utilisateur.
Le générateur propose den démarrer un pour vous :
Le générateur de projet en crée un pour vous : avec Docker en cours dexécution, il récupère limage `twentycrm/twenty-app-dev`, la démarre sur le port `2020`, et authentifie le CLI auprès de lespace de travail de démonstration prérempli (`tim@apple.dev`) — aucune connexion requise.
> **Souhaitez-vous configurer une instance locale de Twenty ?**
* **Oui (recommandé)** — récupère limage Docker `twentycrm/twenty-app-dev` et la démarre sur le port `2020`. Assurez-vous dabord que Docker est en cours dexécution.
* **Non** — choisissez cette option si vous avez déjà un serveur Twenty auquel vous souhaitez vous connecter. Vous pourrez le connecter plus tard avec `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Faut-il démarrer linstance locale ?" />
</div>
Une fois le serveur démarré, un navigateur souvre pour la connexion. Utilisez le compte de démonstration prérempli :
* **E-mail :** `tim@apple.dev`
* **Mot de passe :** `tim@apple.dev`
Pour vous connecter à un serveur Twenty existant à la place, passez `--url \<your-server-url>`. Les serveurs distants sauthentifient avec OAuth : un navigateur souvre pour que vous puissiez vous connecter et cliquer sur **Authorize**, ce qui donne au CLI laccès à votre espace de travail. (Vous pouvez aussi choisir OAuth en local avec `--authentication-method oauth` — connectez-vous avec `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Écran de connexion Twenty" />
</div>
Cliquez sur **Authorize** sur l’écran suivant — cela donne à la CLI laccès à votre espace de travail.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Écran dautorisation de la CLI Twenty" />
</div>
@@ -117,27 +103,31 @@ Cliquez sur **View installed app** pour voir linstallation dans lespace de
### Synchronisation ponctuelle pour la CI et les scripts
Passez `--once` pour exécuter une seule opération de build + synchronisation puis quitter — même pipeline, pas de watcher :
Utilisez `plan` et `apply` pour exécuter une fois le même pipeline, sans surveillance :
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Commande | Comportement | Quand l'utiliser : |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `yarn twenty dev` | Surveille et resynchronise à chaque modification. Reste en cours dexécution jusqu’à ce que vous larrêtiez. | Développement local interactif. |
| `yarn twenty dev --once` | Une seule opération de build + synchronisation, se termine avec le code `0` en cas de réussite et `1` en cas d’échec. | CI, hooks de pré-commit, agents IA et flux de travail scriptés. |
| `yarn twenty dev --once --dry-run` | Construit et affiche les modifications de métadonnées **sans les appliquer**. | Inspection de ce quune synchronisation changerait avant de sy engager. |
| Commande | Comportement | Quand l'utiliser : |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `yarn twenty dev` | Surveille et resynchronise à chaque modification. Reste en cours dexécution jusqu’à ce que vous larrêtiez. | Développement local interactif. |
| `yarn twenty apply` | Une seule opération de build + synchronisation, se termine avec le code `0` en cas de réussite et `1` en cas d’échec. Demande une confirmation pour les modifications destructrices (passez `--force` pour lignorer). | CI, hooks de pré-commit, agents IA et flux de travail scriptés. |
| `yarn twenty plan` | Construit et affiche les modifications de métadonnées **sans les appliquer**. | Inspection de ce quune synchronisation changerait avant de sy engager. |
Les deux modes nécessitent un serveur distant authentifié. Voir [synchronisation et récupération](/l/fr/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run) pour plus dinformations sur `--dry-run`.
Tous les modes nécessitent un serveur distant authentifié. Voir [synchronisation et récupération](/l/fr/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) pour plus dinformations sur `plan`.
<Note>
`yarn twenty dev --once` et `yarn twenty dev --once --dry-run` sont des alias obsolètes pour `yarn twenty apply` et `yarn twenty plan`.
</Note>
### Options du mode de développement
| Option | Description |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--once` | Construire et synchroniser une fois, puis quitter. |
| `--dry-run` | Avec `--once`, prévisualisez les modifications de métadonnées sans les appliquer. N’écrit rien. |
| `--debounceMs \<ms>` | Définir le délai de temporisation des modifications de fichiers en millisecondes (valeur par défaut : `2000`). |
| `--force` | Applique les modifications destructrices (suppressions) sans confirmation. |
| `--debounceMs \<ms>` | Définir le délai de temporisation des modifications de fichiers en millisecondes (valeur par défaut : `1000`). |
| `--verbose` / `--debug` | Afficher des journaux de build détaillés, les requêtes de synchronisation et les traces derreur. |
## Ce que vous pouvez créer
@@ -34,6 +34,10 @@ yarn twenty dev:add frontComponent
| Vue | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Élément de menu de navigation | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Mise en page | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Onglet Mise en page | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Élément du menu de commande | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Champ de vue | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Fournisseur de connexion | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## Ce que génère l'outil de génération
@@ -5,10 +5,10 @@ icon: wrench
---
* **Erreurs Docker** — Assurez-vous que Docker Desktop (ou le démon) est en cours dexécution avant `yarn twenty docker:start`. Le message derreur indiquera la bonne commande de démarrage pour votre système dexploitation.
* **Mauvaise version de Node** — la version 24+ est requise. Vérifiez avec `node -v`.
* **Mauvaise version de Node** — Version 24.5+ requise (`engines.node: ^24.5.0`). Vérifiez avec `node -v`.
* **Yarn 4 manquant** — Exécutez `corepack enable`.
* **Dépendances cassées** — `rm -rf node_modules && yarn install`.
* **Erreurs de `twenty-sdk` après la mise à niveau vers la v2.8.0** — il est passé de `dependencies` à `devDependencies` dans la v2.8.0. Voir [Structure du projet → Dépendances](/l/fr/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` avertit au sujet de `twenty-client-sdk` dans `dependencies`** — il est fourni au moment de lexécution par Twenty, donc il devrait être déplacé vers `devDependencies` avec `twenty-sdk`. Voir [Structure du projet → Dépendances](/l/fr/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` avertit au sujet de `twenty-client-sdk` dans `dependencies`** — il est fourni au moment de l'exécution par Twenty, donc il devrait être déplacé vers `devDependencies` avec `twenty-sdk`. Voir [Structure du projet → Dépendances](/l/fr/developers/extend/apps/getting-started/project-structure#dependencies).
Bloqué ? Demandez de laide sur le [Discord de Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -13,7 +13,6 @@ export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
icon: 'IconLayoutDashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
@@ -22,51 +21,23 @@ export default defineCommandMenuItem({
## Champs de configuration
| Champ | Obligatoire | Description |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Oui | ID unique et stable pour la commande |
| `label` | Oui | Libellé complet affiché dans le menu de commande (Cmd+K) |
| `frontComponentUniversalIdentifier` | Oui | L'`universalIdentifier` du composant frontal que cette commande ouvre |
| `shortLabel` | Non | Libellé plus court affiché sur le bouton d'action rapide épinglé |
| `icon` | Non | Nom de l'icône affiché à côté du libellé (p. ex. `'IconBolt'`, `'IconSend'`) |
| `isPinned` | Non | Lorsque `true`, affiche la commande comme un bouton d'action rapide dans le coin supérieur droit de la page |
| `availabilityType` | Non | Contrôle l'emplacement d'apparition de la commande : `'GLOBAL'` (toujours disponible), `'RECORD_SELECTION'` (uniquement lorsque des enregistrements sont sélectionnés) ou `'FALLBACK'` (affichée lorsqu'aucune autre commande ne correspond) |
| `availabilityObjectUniversalIdentifier` | Non | Restreint la commande aux pages dun type dobjet spécifique (p. ex., uniquement sur les enregistrements « Company ») |
| `conditionalAvailabilityExpression` | Non | Une expression booléenne qui contrôle dynamiquement la visibilité (voir ci-dessous) |
| Champ | Obligatoire | Description |
| --------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Oui | ID unique et stable pour la commande |
| `label` | Oui | Libellé complet affiché dans le menu de commande (Cmd+K) |
| `frontComponentUniversalIdentifier` | Oui | L'`universalIdentifier` du composant frontal que cette commande ouvre |
| `shortLabel` | Non | Libellé plus court affiché sur le bouton d'action rapide épinglé |
| `icon` | Non | **Obsolète** — ignoré au profit de licône de lapplication ; la build émet un avertissement si elle est définie |
| `isPinned` | Non | Lorsque `true`, affiche la commande comme un bouton d'action rapide dans le coin supérieur droit de la page |
| `availabilityType` | Non | Contrôle lemplacement dapparition de la commande : `'GLOBAL'` (toujours disponible), `'GLOBAL_OBJECT_CONTEXT'` (uniquement sur les pages avec un contexte dobjet — pages dindex et denregistrement), `'RECORD_SELECTION'` (uniquement lorsque des enregistrements sont sélectionnés) ou `'FALLBACK'` (affichée lorsquaucune autre commande ne correspond) |
| `availabilityObjectUniversalIdentifier` | Non | Restreint la commande aux pages dun type dobjet spécifique (p. ex., uniquement sur les enregistrements « Company ») |
| `conditionalAvailabilityExpression` | Non | Une expression booléenne qui contrôle dynamiquement la visibilité (voir ci-dessous) |
## Commandes sans interface
Un élément de menu de commande associé à un [headless front component](/l/fr/developers/extend/apps/layout/front-components#headless-vs-non-headless) est la manière idiomatique de proposer une action en un clic — exécuter du code, naviguer, ou confirmer puis exécuter. La page Front Components couvre les [SDK Command components](/l/fr/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) qui gèrent le modèle action-et-démontage.
Un flux typique :
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
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 <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
Un flux typique : un composant headless affiche `<Command execute={...} />` (voir [lexemple complet](/l/fr/developers/extend/apps/layout/front-components#sdk-command-components)), et l’élément de menu de commande y pointe :
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -74,7 +45,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -49,14 +49,13 @@ export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Après la synchronisation avec `yarn twenty dev` (ou en exécutant une seule fois `yarn twenty dev --once`), l'action rapide apparaît dans le coin supérieur droit de la page :
Après la synchronisation avec `yarn twenty dev` (ou en exécutant une seule fois `yarn twenty apply`), l'action rapide apparaît dans le coin supérieur droit de la page :
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Bouton d'action rapide dans le coin supérieur droit" />
@@ -88,11 +87,11 @@ Les composants frontaux existent en deux modes de rendu contrôlés par lopti
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
@@ -116,7 +115,7 @@ Comme le composant retourne `null`, Twenty n'affiche pas de conteneur pour celui
Le package `twenty-sdk` fournit quatre composants utilitaires Command conçus pour les composants frontaux headless. Chaque composant exécute une action au montage, gère les erreurs en affichant une notification snackbar et démonte automatiquement le composant frontal une fois terminé.
Importez-les depuis `twenty-sdk/command` :
Importez-les depuis `twenty-sdk/front-component` :
* **`Command`** — Exécute un callback asynchrone via la prop `execute`.
* **`CommandLink`** — Navigue vers un chemin d'application. Props : `to`, `params`, `queryParams`, `options`.
@@ -127,8 +126,8 @@ Voici un exemple complet d'un composant frontal headless utilisant `Command` pou
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
@@ -160,7 +159,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -169,7 +167,7 @@ Et un exemple utilisant `CommandModal` pour demander une confirmation avant l'ex
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
@@ -202,7 +200,7 @@ Les composants front sexécutent côté navigateur dans un Web Worker isolé
Une fonction logique déclarée avec `httpRouteTriggerSettings` est accessible via HTTP à son chemin de route. Twenty injecte dans le worker lURL de base à partir de laquelle vos fonctions sont servies sous la forme de `TWENTY_FUNCTIONS_URL`, ainsi que le `TWENTY_APP_ACCESS_TOKEN` qui authentifie lappel. Il nexiste pas encore de client SDK dédié pour invoquer vos propres fonctions, donc appelez-les avec un simple `fetch` :
> **Sur Twenty Cloud, les fonctions logiques déclenchées par HTTP sont servies sur un domaine dédié par espace de travail** à ladresse `https://\<your-workspace-subdomain>.twenty.com\<path>` — cest exactement ce à quoi `TWENTY_FUNCTIONS_URL` correspond. Pour les appelants externes, copiez lURL exacte à partir des paramètres **HTTP trigger** de la fonction ou de longlet **Settings** de lapplication.
> **Sur Twenty Cloud, les fonctions logiques déclenchées par HTTP sont servies sur un domaine dédié par espace de travail** à ladresse `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — cest exactement ce à quoi `TWENTY_FUNCTIONS_URL` correspond. Pour les appelants externes, copiez lURL exacte à partir des paramètres **HTTP trigger** de la fonction ou de longlet **Settings** de lapplication.
<Warning>
Lancienne route de fonction `/s/` est **obsolète** et sera **désactivée le 2026-07-24**. Utilisez plutôt `TWENTY_FUNCTIONS_URL` (ci-dessus), et migrez toutes les URL `/s/` en dur avant cette date. La route `/s/` reste disponible pour lauto-hébergement.
@@ -212,7 +210,7 @@ Un composant front sans interface (headless) peut effectuer lappel au montage
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
@@ -316,13 +314,13 @@ Dans votre composant, utilisez les hooks du SDK pour accéder à l'utilisateur a
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
@@ -405,12 +403,11 @@ Voici un exemple qui utilise l'API hôte pour afficher une snackbar et fermer le
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
@@ -451,10 +448,10 @@ export default defineFrontComponent({
Utilisez `useSelectedRecordIds()` pour gérer plusieurs enregistrements sélectionnés. C'est utile pour les opérations groupées :
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
@@ -492,12 +489,19 @@ export default defineFrontComponent({
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
Affichez-la avec un [élément de menu de commande](/l/fr/developers/extend/apps/layout/command-menu-items) limité aux sélections d'enregistrements :
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
@@ -35,6 +35,8 @@ export default defineNavigationMenuItem({
* `position` contrôle lordre dans la barre latérale.
* L’énumération contient également `NavigationMenuItemType.RECORD`, utilisé en interne pour les favoris denregistrements créés par lutilisateur — il nest pas utilisable depuis un manifeste dapplication (il nexiste aucun champ pour référencer un enregistrement).
* `icon` et `color` sont facultatifs et personnalisent lapparence de lentrée.
* `folderUniversalIdentifier` est également disponible sur nimporte quel élément pour limbriquer dans un parent de type `FOLDER`.
@@ -33,17 +33,32 @@ export default defineView({
## Points clés
* `objectUniversalIdentifier` spécifie à quel objet cette vue s'applique. Il peut sagir dun objet personnalisé que vous avez défini ou dun objet Twenty standard.
* `key` détermine le type de vue — `ViewKey.INDEX` est la vue de liste principale pour lobjet.
* "`key: ViewKey.INDEX`" marque la vue comme la vue de liste principale de lobjet (celle quun élément de navigation "`OBJECT`" ouvre).
* `fields` contrôle les colonnes affichées et leur ordre. Chaque champ référence un `fieldMetadataUniversalIdentifier`.
* Vous pouvez également définir `filters`, `filterGroups`, `groups` et `fieldGroups` pour des configurations plus avancées.
* Vous pouvez également déclarer `filters`, `filterGroups`, `sorts`, `groups` et `fieldGroups` pour des configurations plus avancées.
* `position` contrôle lordre lorsquil existe plusieurs vues pour le même objet.
## Propriétés optionnelles
| Propriété | Valeurs | Description |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | `ViewType.TABLE` (par défaut), `ViewType.KANBAN`, `ViewType.CALENDAR` | Comment les enregistrements sont disposés. (`FIELDS_WIDGET` / `TABLE_WIDGET` existent également mais sont utilisés en interne par les widgets de mise en page de page.) |
| `visibility` | `ViewVisibility.WORKSPACE` (par défaut), `ViewVisibility.UNLISTED` | Indique si la vue est listée pour lensemble de lespace de travail ou masquée dans les sélecteurs. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (par défaut), `ViewOpenRecordIn.RECORD_PAGE` | Endroit où un clic sur un enregistrement louvre. |
| `tris` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Ordre de tri par défaut. |
| `isCompact` | `boolean` | Affichage compact des lignes. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Regrouper les enregistrements (par exemple, les colonnes kanban) par un champ. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Agrégats et dimensionnement des colonnes Kanban. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Vues de calendrier : disposition et champ de date qui positionne les enregistrements. |
Tous les enums ci-dessus sont exportés depuis `twenty-sdk/define`.
## Filtres
Une vue peut être livrée avec des filtres préappliqués. Chaque filtre possède trois coordonnées : le **champ** faisant lobjet du filtrage, l**opérateur** (comment comparer) et la **valeur** (par rapport à quoi comparer). Les trois doivent être alignées : lutilisation dun opérateur qui ne sapplique pas à un type de champ sera rejetée au moment de la synchronisation.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
Types de déclencheurs disponibles :
* **httpRoute** : Expose votre fonction sur un chemin et une méthode HTTP **sous l'endpoint `/s/`** :
> p. ex. `path: '/post-card/create'` est appelable à `https://your-twenty-server.com/s/post-card/create`
* **httpRoute** : Expose votre fonction sur un chemin HTTP et une méthode dans l'URL de **fonctions de base** de votre espace de travail — la valeur de 20 injects en tant que `TWENTY_FUNCTIONS_URL` (sur Twenty Cloud, un domaine dédié par espace de travail):
> p. ex. `path: '/post-card/create'` est appelable à `https://your-workspace.withtwenty.com/post-card/create`
<Warning>
L'ancienne route de préfixe `/s/` (`https://your-twenty-server.com/s/post-card/create`) est **obsolète sur Twenty Cloud** et sera désactivée le **2026-07-24**. Il reste disponible pour les instances auto-hébergées et locales qui ne configurent pas un domaine de fonctions isolées — utilisez `TWENTY_FUNCTIONS_URL` quand il est défini, et revenez à `\<server-url>/s/\<path>` autrement.
</Warning>
<Note>
Pour appeler une fonction logique déclenchée par une route depuis un composant frontal (sans interface), consultez [Appeler une fonction logique](/l/fr/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -40,13 +40,13 @@ La **couche logique** dune application Twenty est le code qui *sexécute*
Une fonction logique choisit un ou plusieurs déclencheurs — chaque entrée ci-dessous est un champ distinct sur `defineLogicFunction()`:
| Déclencheur | Moment dexécution | Paramètre |
| -------------------------------- | ---------------------------------------------------------------------------- | ------------------------------- |
| **Route HTTP** | Une requête atteint votre point de terminaison `/s/\<path>` | `httpRouteTriggerSettings` |
| **Cron** | Une expression CRON correspond | `cronTriggerSettings` |
| **Événement de base de données** | Un enregistrement de lespace de travail est créé, mis à jour ou supprimé | `databaseEventTriggerSettings` |
| **Outil IA** | Une fonctionnalité IA de Twenty décide dappeler votre fonction | `toolTriggerSettings` |
| **Action de flux de travail** | Une étape de flux de travail invoque votre fonction | `workflowActionTriggerSettings` |
| Déclencheur | Moment dexécution | Paramètre |
| -------------------------------- | ------------------------------------------------------------------------- | ------------------------------- |
| **Route HTTP** | Une requête atteint l'URL publique de votre fonction | `httpRouteTriggerSettings` |
| **Cron** | Une expression CRON correspond | `cronTriggerSettings` |
| **Événement de base de données** | Un enregistrement de lespace de travail est créé, mis à jour ou supprimé | `databaseEventTriggerSettings` |
| **Outil IA** | Une fonctionnalité IA de Twenty décide dappeler votre fonction | `toolTriggerSettings` |
| **Action de flux de travail** | Une étape de flux de travail invoque votre fonction | `workflowActionTriggerSettings` |
Les fonctions sexécutent dans un environnement isolé dans des processus Node.js sandboxés et accèdent à lespace de travail via un client API typé, limité au rôle déclaré sur [`defineApplication()`](/l/fr/developers/extend/apps/config/application).
@@ -4,7 +4,25 @@ description: Les commandes `yarn twenty` pour exécuter des fonctions, diffuser
icon: terminal
---
Au-delà de `dev`, `dev:build`, `dev:add` et `dev:typecheck`, la CLI `yarn twenty` fournit des commandes pour exécuter des fonctions, consulter les journaux et gérer les installations d'applications.
Le CLI `yarn twenty` est votre interface pour tout ce qui concerne les applications. Liste complète des commandes :
| Commande | Ce que cela fait | Documenté dans |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `dev` | Surveille les fichiers sources et synchronise en direct les modifications | [Prise en main rapide](/l/fr/developers/extend/apps/getting-started/quick-start) |
| `plan` | Prévisualiser les modifications de métadonnées sans les appliquer | [Synchronisation et récupération](/l/fr/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `appliquer` | Appliquer les modifications de métadonnées après avoir affiché le plan | [Synchronisation et récupération](/l/fr/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Compiler lapplication et générer le client dAPI (`--tarball` pour empaqueter un `.tgz`) | [Publication](/l/fr/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Exécuter la vérification des types TypeScript | [Tests](/l/fr/developers/extend/apps/operations/testing) |
| `dev:add` | Générer la structure dune nouvelle entité | [Génération de structure](/l/fr/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Régénérer le client dAPI typé | cette page |
| `dev:function:exec` / `dev:function:logs` | Exécuter des fonctions et diffuser leurs journaux | cette page |
| `dev:translations-extract` | Extraire les chaînes traduisibles dans les catalogues `locales/` | [Traductions](/l/fr/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | Déclencher la synchronisation du catalogue de la place de marché | [Publication](/l/fr/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Cycle de vie de la mise en production | [Publication](/l/fr/developers/extend/apps/operations/publishing) et cette page |
| `docker:*` | Gérer le conteneur du serveur Twenty local | [Serveur local](/l/fr/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Gérer les connexions serveur | cette page |
Chaque commande accepte `-r, --remote \<name>` pour cibler un serveur distant spécifique au lieu de celui par défaut.
## Exécuter des fonctions (`yarn twenty dev:function:exec`)
@@ -20,8 +38,9 @@ yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the post-install function
# Execute the install hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
## Afficher les journaux des fonctions (`yarn twenty dev:function:logs`)
@@ -100,6 +119,12 @@ yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Vos identifiants sont stockés dans `~/.twenty/config.json`.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
Les métadonnées affichées dans la place de marché proviennent de votre configuration `defineApplication()` — des champs comme `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` et `termsUrl`.
Les métadonnées affichées dans la marketplace proviennent de votre configuration `defineApplication()` — voir [Métadonnées de la marketplace](#marketplace-metadata) ci-dessus.
<Note>
Si votre application ne définit pas de `aboutDescription` dans `defineApplication()`, la place de marché utilisera automatiquement le `README.md` de votre package depuis npm comme contenu de la page À propos. Cela signifie que vous pouvez maintenir un seul README à la fois pour npm et pour la place de marché Twenty. Si vous souhaitez une description différente dans la place de marché, définissez explicitement `aboutDescription`.
@@ -15,33 +15,44 @@ Pour l'itération locale au quotidien, vous voudrez presque toujours `yarn twent
| Vous souhaitez… | Commande | Notes |
| ------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Itérer localement avec la synchronisation en direct | `yarn twenty dev` | Surveille vos fichiers et synchronise à chaque modification. |
| Synchroniser une fois puis quitter (CI, scripts, hooks) | `yarn twenty dev --once` | Effectue une compilation + synchronisation, puis quitte. |
| Prévisualiser les changements **sans les appliquer** | `yarn twenty dev --once --dry-run` | Calcule et affiche le diff ; n'écrit rien. |
| Synchroniser une fois puis quitter (CI, scripts, hooks) | `yarn twenty apply` | Effectue une compilation + synchronisation, puis quitte. Ajoutez `--force` pour ignorer la confirmation des changements destructifs. |
| Prévisualiser les changements **sans les appliquer** | `yarn twenty plan` | Calcule et affiche le diff ; n'écrit rien. |
| Retirer l'application de l'espace de travail | `yarn twenty app:uninstall` | Ajoutez `--yes` pour ignorer la confirmation. |
| Envoyer une archive tarball vers un serveur | `yarn twenty app:publish --private` | Nécessite une version de `package.json` **strictement supérieure** — voir [Publication](/l/fr/developers/extend/apps/operations/publishing). |
| Publier sur la place de marché (npm) | `yarn twenty app:publish` | — |
| Installer / mettre à niveau une version déployée | `yarn twenty app:install` | Installe la version actuellement déployée. |
| Effacer le serveur local et repartir de zéro | `yarn twenty docker:reset` | Supprime **toutes** les données locales — en dernier recours. |
<Note>
`yarn twenty dev --once` et `yarn twenty dev --once --dry-run` fonctionnent toujours comme alias obsolètes de `yarn twenty apply` et `yarn twenty plan`.
</Note>
### La synchronisation locale n'a pas besoin d'un incrément de version
La règle de `version` strictement croissante (`VERSION_ALREADY_EXISTS` lors du déploiement, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` lors de l'installation) s'applique à **`app:publish` / `app:install`** — le chemin de mise en production. `yarn twenty dev` synchronise votre manifeste sur place et ne nécessite jamais de changement de version, vous n'avez donc pas besoin de toucher à `package.json` pour itérer. Si vous vous surprenez à incrémenter la version pour tester un changement local, c'est que vous utilisez le chemin de mise en production alors que vous voulez la boucle de développement.
## Lire la sortie de synchronisation
Chaque synchronisation affiche les changements de métadonnées qu'elle a appliqués (ou appliquerait, avec `--dry-run`) :
Chaque synchronisation affiche les changements de métadonnées qu'elle a appliqués (ou qu'elle appliquerait, avec `plan`), à la manière de Terraform — un bloc par entité avec ses attributs, puis une ligne récapitulative :
```text filename="Terminal"
Metadata changes: 2 created, 1 updated, 1 deleted
created objectMetadata rocket
created fieldMetadata timelineActivities
updated fieldMetadata launchedAt
deleted pageLayout legacyTab
✓ Synced
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
C'est votre premier diagnostic : il vous indique exactement quels objets, champs et mises en page ont changé, afin que vous puissiez confirmer qu'une synchronisation a fait ce que vous attendiez avant de vérifier l'interface utilisateur.
Les changements destructifs (`to destroy`) sont listés avec ce qu'ils suppriment (par ex. `objectMetadata "auditNote" — drops the table and all its rows`) et nécessitent une confirmation interactive, ou `--force` dans les scripts.
Lorsqu'une synchronisation échoue sur une seule entité, l'erreur nomme l'entité en cause et son `universalIdentifier`, par exemple :
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
Utilisez cet identifiant pour trouver l'entité dans votre manifeste (et, si nécessaire, dans l'espace de travail) plutôt que de deviner laquelle est en conflit.
## Prévisualiser les changements (dry run)
## Prévisualiser les changements (plan)
`yarn twenty dev --once --dry-run` construit votre manifeste, demande au serveur le plan de migration et l'affiche — **sans rien appliquer**. C'est le moyen sûr de répondre « que changerait cette synchronisation ? » avant de s'y engager.
`yarn twenty plan` construit votre manifeste, demande au serveur le plan de migration et l'affiche — **sans rien appliquer**. C'est le moyen sûr de répondre « que changerait cette synchronisation ? » avant de s'y engager.
```bash filename="Terminal"
yarn twenty dev --once --dry-run
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata diff (dry run, nothing will be applied)...
Metadata changes: 1 created, 1 updated
created fieldMetadata timelineActivities
updated objectMetadata rocket
✓ Dry run complete for My App — no changes were applied
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "timelineActivities" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Un dry run :
Un plan :
* **N'écrit rien** — aucune migration de métadonnées, aucune mise à jour de l'enregistrement d'application, aucun changement de rôle/onglet par défaut, et aucune génération de client d'API.
* Renvoie le **même diff** qu'une synchronisation réelle appliquerait, afin que vous puissiez examiner à l'avance les entités créées/mises à jour/supprimées.
* Est utile avant un changement risqué, lors de la révision d'un changement généré par une IA, ou dans un script qui doit échouer si un changement inattendu est sur le point d'être appliqué.
<Note>
Un dry run ne prévisualise que les changements de **métadonnées**, et il nécessite que l'application ait été synchronisée au moins une fois (pour que l'espace de travail la connaisse). Si vous l'exécutez sur une application qui n'a jamais été synchronisée, le serveur indique que l'application n'est pas installée — exécutez d'abord une fois `yarn twenty dev`.
Un plan ne prévisualise que les changements de **métadonnées**, et il nécessite que l'application ait été synchronisée au moins une fois (pour que l'espace de travail la connaisse). Si vous l'exécutez sur une application qui n'a jamais été synchronisée, le serveur indique que l'application n'est pas installée — exécutez d'abord une fois `yarn twenty dev`.
</Note>
## Échelle de récupération
Lorsque les métadonnées locales semblent incorrectes, augmentez le niveau de manière progressive dans cet ordre et arrêtez-vous dès que vous êtes débloqué. Chaque étape est plus perturbatrice que la précédente.
1. **Resynchroniser.** Exécutez à nouveau `yarn twenty dev --once`. Les synchronisations sont idempotentes — réexécuter un manifeste propre est sûr et résout souvent un incident passager.
2. **Prévisualiser le plan.** Exécutez `yarn twenty dev --once --dry-run` pour voir exactement ce que la prochaine synchronisation compte changer, sans l'appliquer.
1. **Resynchroniser.** Exécutez à nouveau `yarn twenty apply`. Les synchronisations sont idempotentes — réexécuter un manifeste propre est sûr et résout souvent un incident passager.
2. **Prévisualiser le plan.** Exécutez `yarn twenty plan` pour voir exactement ce que la prochaine synchronisation compte changer, sans l'appliquer.
3. **Lire l'erreur nommée.** Si une synchronisation échoue, relevez le type de métadonnées et l'`universalIdentifier` dans le message (voir ci-dessus) et localisez cette entité dans votre manifeste. Un conflit pointe généralement vers un identifiant dupliqué ou réutilisé.
4. **Désinstaller et réinstaller.** `yarn twenty app:uninstall`, puis synchronisez à nouveau (`yarn twenty dev`). Cette opération reconstruit les métadonnées de l'application à partir d'une base saine tout en gardant le reste de votre espace de travail intact.
5. **Réinitialisation complète (en dernier recours).** `yarn twenty docker:reset`, puis réinjectez des données et resynchronisez.
@@ -78,6 +78,13 @@ Créez un `vitest.config.ts` à la racine de votre application :
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
@@ -88,66 +95,74 @@ export default defineConfig({
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
setupFiles: ['src/__tests__/setup-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL: 'http://localhost:2020',
TWENTY_API_KEY: 'your-api-key',
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Créez un fichier de configuration qui vérifie que le serveur est joignable avant l'exécution des tests :
Créez un fichier de configuration globale qui vérifie que le serveur est joignable, écrit une configuration de test pour le SDK (`~/.twenty/config.test.json`) et synchronise lapplication avant lexécution des tests :
```ts src/__tests__/setup-test.ts
```ts src/__tests__/global-setup.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');
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
beforeAll(async () => {
// Verify the server is running
const response = await fetch(`${TWENTY_API_URL}/healthz`);
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
'Start the server before running integration tests.',
);
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write a temporary config for the SDK
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(TEST_CONFIG_DIR, 'config.json'),
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
});
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## APIs programmatiques du SDK
Le sous-chemin `twenty-sdk/cli` exporte des fonctions que vous pouvez appeler directement depuis le code de test :
| Fonction | Description |
| -------------- | -------------------------------------------------------------------- |
| `appBuild` | Construire l'application et éventuellement créer une archive tarball |
| `appDeploy` | Téléverser une archive tarball vers le serveur |
| `appInstall` | Installer l'application sur l'espace de travail actif |
| `appUninstall` | Désinstaller l'application de l'espace de travail actif |
| Fonction | Description |
| -------------- | ----------------------------------------------------------------------------------- |
| `appBuild` | Construire l'application et éventuellement créer une archive tarball |
| `appDeploy` | Téléverser une archive tarball vers le serveur |
| `appDevOnce` | Construire et synchroniser lapplication une fois (identique à `yarn twenty apply`) |
| `appInstall` | Installer l'application sur l'espace de travail actif |
| `appUninstall` | Désinstaller l'application de l'espace de travail actif |
Chaque fonction retourne un objet résultat avec `success: boolean` et soit `data` soit `error`.
@@ -238,64 +253,10 @@ Vous pouvez également exécuter une vérification des types sur votre applicati
yarn twenty dev:typecheck
```
Cela exécute `tsc --noEmit` et signale toute erreur de type.
Cela exécute `tsc --noEmit` sur le `tsconfig.json` de votre application et signale toute erreur de type. Les applications générées contiennent également un script `yarn typecheck` qui couvre aussi les fichiers de test (`tsconfig.spec.json`).
## CI avec GitHub Actions
Le générateur crée un workflow GitHub Actions prêt à lemploi dans `.github/workflows/ci.yml`. Il exécute automatiquement vos tests dintégration à chaque push sur `main` et sur les pull requests.
Le générateur crée un workflow prêt à lemploi dans `.github/workflows/ci.yml`. À chaque push sur `main` et à chaque pull request, il lance un serveur Twenty éphémère dans le runner (via laction `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), puis exécute `yarn lint`, `yarn typecheck`, `yarn test:unit` et `yarn test` avec `TWENTY_API_URL` / `TWENTY_API_KEY` pointant vers ce serveur. Aucun secret nest requis, et vous pouvez fixer la version du serveur via la variable denvironnement `TWENTY_VERSION` en haut du workflow.
Le workflow :
1. Récupère votre code
2. Lance un serveur Twenty temporaire en utilisant laction `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Installe les dépendances avec `yarn install --immutable`
4. Exécute `yarn test` avec `TWENTY_API_URL` et `TWENTY_API_KEY` injectés à partir des sorties de laction
```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 }}
```
Vous navez pas besoin de configurer de secrets — laction `spawn-twenty-docker-image` démarre un serveur Twenty éphémère directement dans le runner et fournit les détails de connexion. Le secret `GITHUB_TOKEN` est fourni automatiquement par GitHub.
Pour épingler une version spécifique de Twenty au lieu de `latest`, modifiez la variable denvironnement `TWENTY_VERSION` en haut du workflow.
Voir [Publication → CI/CD automatisé](/l/fr/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) pour un guide complet des deux workflows générés (`ci.yml` et le pipeline de déploiement `cd.yml`).
@@ -91,9 +91,11 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
const apiBaseUrl = process.env.TWENTY_API_URL;
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
@@ -185,7 +187,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,8 +9,15 @@ Le même gestionnaire peut également répondre aux requêtes HTTP. Nous allons
* un point de terminaison **POST** que l'interface utilisateur appelle pour générer un document, et
* un point de terminaison public **GET** qui rend un document en tant que page web imprimable.
Les deux utilisent `httpRouteTriggerSettings`. Les routes des applis sont servies dans `/s` sur votre
Serveur Vingt (par exemple `http://localhost:2020/s/documents/generate`).
Les deux utilisent `httpRouteTriggerSettings`. Sur le serveur de développement local, les routes des applications sont
servies sous le préfixe `/s` (par exemple `http://localhost:2020/s/documents/generate`).
<Note>
Sur Twenty Cloud, les routes sont servies sur le domaine de fonctions dédiées à l'espace de travail
— l'URL 20 injecte en tant que `TWENTY_FUNCTIONS_URL`, sans préfixe `/s`. Le préfixe `/s`
est déprécié là-bas et ne reste que pour les instances auto-hébergées et locales.
Voir [Appel à une fonction logique] (/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## Itinéraire POST — générer à la demande
@@ -77,11 +77,11 @@ Exécuter les mêmes portes CI :
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty dev --once --dry-run # preview the metadata diff
yarn twenty plan # preview the metadata diff
```
La course à sec imprime exactement ce qui pourrait changer sur le serveur sans l'appliquer —
une bonne vérification de l'état d'esprit. Voir
Le plan affiche exactement ce qui changerait sur le serveur sans les appliquer —
un bon dernier contrôle de cohérence. Voir
[Testing](/l/fr/developers/extend/apps/operations/testing) et
[Synchronisation et récupération](/l/fr/developers/extend/apps/operations/sync-and-recovery).
@@ -4,9 +4,9 @@ description: Esegui logica prima o dopo l'installazione — popola i dati, esegu
icon: wrench
---
Gli hook di installazione sono funzioni logiche speciali che vengono eseguite durante il ciclo di vita di installazione o aggiornamento. Condividono lo stesso runtime del gestore delle [logic functions](/l/it/developers/extend/apps/logic/logic-functions) normali e ricevono un `InstallPayload`, ma sono dichiarati con le proprie funzioni di definizione — `definePostInstallLogicFunction()` e `definePreInstallLogicFunction()` — e vivono al di fuori del normale modello di trigger (eventi HTTP, cron, database).
Gli hook di installazione sono funzioni logiche speciali che vengono eseguite durante il ciclo di vita di installazione o aggiornamento. Condividono lo stesso runtime del gestore delle [logic functions](/l/it/developers/extend/apps/logic/logic-functions) normali e ricevono un `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` è `undefined` in una nuova installazione), ma sono dichiarati con le proprie funzioni di definizione e vivono al di fuori del normale modello di trigger (HTTP, eventi cron, eventi del database).
Ogni app può definire **al massimo una funzione di pre-installazione** e **al massimo una funzione di post-installazione**. La build del manifesto genererà un errore se ne viene rilevata più di una per ciascun tipo.
Ogni app può definire **al massimo una funzione di pre-installazione** e **al massimo una funzione di post-installazione**. La build del manifesto genera un errore se ne viene rilevata più di una per ciascun tipo.
```
┌─────────────────────────────────────────────────────────────┐
@@ -19,111 +19,59 @@ Ogni app può definire **al massimo una funzione di pre-installazione** e **al m
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Viene eseguita dopo che la migrazione dei metadati dello spazio di lavoro è stata applicata">
## A colpo d'occhio
Una funzione di post-installazione viene eseguita automaticamente una volta che la tua app ha terminato l'installazione in uno spazio di lavoro. Il server la esegue **dopo** che i metadati dell'app sono stati sincronizzati e il client SDK è stato generato, così lo spazio di lavoro è completamente pronto per l'uso e il nuovo schema è attivo. I casi d'uso tipici includono il popolamento di dati predefiniti, la creazione di record iniziali, la configurazione delle impostazioni dello spazio di lavoro o il provisioning di risorse su servizi di terze parti.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Esecuzioni | Prima della migrazione dei metadati — lo schema e i dati **precedenti** sono ancora intatti | Dopo la migrazione e la generazione dell'SDK — il **nuovo** schema è in vigore |
| Esecuzione | Sempre sincrona; blocca l'installazione | Async per impostazione predefinita (in coda, 3 tentativi); modalità sync tramite opt-in con `shouldRunSynchronously: true` |
| In caso di errore | L'installazione viene **annullata** prima di qualsiasi modifica allo schema | Async: ritentato fino a 3 volte. Sync: il chiamante riceve `POST_INSTALL_ERROR` (le modifiche allo schema **non** vengono annullate) |
| Uso tipico | Eseguire il backup o correggere dati che una migrazione perderebbe; rifiutare un aggiornamento rischioso lanciando un'eccezione | Popolare dati predefiniti, configurare il workspace, registrare risorse esterne |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Regola generale:** usa post-install come impostazione predefinita. Ricorri al pre-install solo quando la migrazione stessa è distruttiva e devi intercettare lo stato precedente prima che vada perso.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Vuoi... | Usa |
| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| Popolare i dati, configurare il workspace, registrare risorse esterne | `post-install` |
| Lavoro di lunga durata che non dovrebbe bloccare la risposta dell'installazione | `post-install` (modalità async predefinita, con retry del worker) |
| Eseguire un setup rapido da cui il chiamante dipende immediatamente dopo il completamento dell'installazione | `post-install` con `shouldRunSynchronously: true` |
| Leggere o eseguire il backup dei dati che la prossima migrazione perderebbe | `pre-install` |
| Rifiutare un aggiornamento che corromperebbe i dati esistenti | `pre-install` (genera un'eccezione dall'handler) |
| Riconciliazione a ogni aggiornamento | Uno qualsiasi dei due hook con `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## Comportamento condiviso da entrambi gli hook
Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI:
* La config è una config di `defineLogicFunction` meno le impostazioni di trigger, più `shouldRunOnVersionUpgrade`.
* **Quando viene eseguito**: solo sulle nuove installazioni, per impostazione predefinita. Imposta `shouldRunOnVersionUpgrade: true` per eseguirlo anche sugli upgrade. Usa `previousVersion` / `newVersion` per ramificare in base al percorso di upgrade.
* **L'idempotenza è importante**: il post-install async può essere ritentato e qualsiasi hook viene rieseguito sugli upgrade quando `shouldRunOnVersionUpgrade` è attivo.
* Il consueto ambiente delle logic-function (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) viene iniettato, così puoi chiamare le API di Twenty con il token della tua app.
* L'hook viene collegato automaticamente al manifesto dell'applicazione in fase di build (`preInstallLogicFunction` / `postInstallLogicFunction`) — non c'è nulla da referenziare in [`defineApplication()`](/l/it/developers/extend/apps/config/application).
* Il `timeoutSeconds` predefinito è 300 per consentire attività di setup più lunghe, come il seeding dei dati.
* **Non eseguito in modalità dev**: `yarn twenty dev` salta il flusso di installazione e sincronizza direttamente i file, quindi gli hook non vengono mai eseguiti in quell'ambiente. Attivali invece manualmente:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Punti chiave:
* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* L'handler riceve un `InstallPayload` con `{ previousVersion?: string; newVersion: string }` — `newVersion` è la versione in fase di installazione e `previousVersion` è la versione installata in precedenza (oppure `undefined` in caso di nuova installazione). Usa questi valori per distinguere le nuove installazioni dagli aggiornamenti e per eseguire logiche di migrazione specifiche per versione.
* **Quando viene eseguito l'hook**: solo sulle nuove installazioni, per impostazione predefinita. Passa `shouldRunOnVersionUpgrade: true` se vuoi che venga eseguito anche quando l'app viene aggiornata da una versione precedente. Se omesso, il flag è `false` per impostazione predefinita e gli aggiornamenti saltano l'hook.
* **Modello di esecuzione — asincrono per impostazione predefinita, sincrono su richiesta**: il flag `shouldRunSynchronously` controlla *come* viene eseguito il post-install.
* `shouldRunSynchronously: false` *(default)* — l'hook viene **messo in coda nella coda dei messaggi** con `retryLimit: 3` ed eseguito in modo asincrono in un worker. La risposta di installazione viene restituita non appena il job è messo in coda, quindi un handler lento o in errore non blocca il chiamante. Il worker riproverà fino a tre volte. **Usalo per job di lunga durata** — popolamento di dataset di grandi dimensioni, chiamate a API di terze parti lente, provisioning di risorse esterne, qualsiasi cosa che possa superare una finestra di risposta HTTP ragionevole.
* `shouldRunSynchronously: true` — l'hook viene eseguito **inline durante il flusso di installazione** (stesso executor del pre-install). La richiesta di installazione rimane bloccata finché l'handler non termina e, se genera un'eccezione, il chiamante dell'installazione riceve un `POST_INSTALL_ERROR`. Nessun tentativo automatico. **Usalo per attività rapide che devono completarsi prima della risposta** — ad esempio, emettere un errore di validazione all'utente, oppure un setup rapido di cui il client avrà bisogno immediatamente dopo il ritorno della chiamata di installazione. Tieni presente che la migrazione dei metadati è già stata applicata quando viene eseguito il post-install, quindi un errore in modalità sincrona **non** annulla le modifiche allo schema — si limita a far emergere l'errore.
* Assicurati che il tuo handler sia idempotente. In modalità asincrona la coda può riprovare fino a tre volte; in entrambe le modalità l'hook può essere eseguito di nuovo durante gli aggiornamenti quando `shouldRunOnVersionUpgrade: true`.
* Le variabili d'ambiente `APPLICATION_ID`, `APP_ACCESS_TOKEN` e `API_URL` sono disponibili all'interno dell'handler (come in qualsiasi altra funzione logica), quindi puoi chiamare le API di Twenty con un token di accesso applicativo con ambito sulla tua app.
* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una.
* I campi `universalIdentifier`, `shouldRunOnVersionUpgrade` e `shouldRunSynchronously` della funzione vengono associati automaticamente al manifest dell'applicazione nel campo `postInstallLogicFunction` durante la build — non è necessario referenziarli in [`defineApplication()`](/l/it/developers/extend/apps/config/application).
* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati.
* **Non eseguito in modalità dev**: quando un'app è registrata in locale (tramite `yarn twenty dev`), il server salta completamente il flusso di installazione e sincronizza i file direttamente tramite il watcher della CLI — quindi il post-install non viene mai eseguito in modalità dev, indipendentemente da `shouldRunSynchronously`. Usa `yarn twenty dev:function:exec --postInstall` per attivarlo manualmente su un workspace in esecuzione.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Viene eseguita prima che la migrazione dei metadati dello spazio di lavoro sia applicata">
Una funzione di pre-installazione viene eseguita automaticamente durante l'installazione, **prima che venga applicata la migrazione dei metadati dello spazio di lavoro**. Condivide la stessa struttura di payload del post-install (`InstallPayload`), ma è posizionata prima nel flusso di installazione così da poter preparare lo stato da cui dipenderà la migrazione imminente — usi tipici includono il backup dei dati, la validazione della compatibilità con il nuovo schema o l'archiviazione di record che stanno per essere ristrutturati o eliminati.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Punti chiave:
* Le funzioni di pre-install usano `definePreInstallLogicFunction()` — stessa configurazione specialistica del post-install, solo agganciata a uno slot di ciclo di vita diverso.
* Sia gli handler di pre- sia quelli di post-install ricevono lo stesso tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importalo una volta e riutilizzalo per entrambi gli hook.
* **Quando viene eseguito l'hook**: posizionato appena prima della migrazione dei metadati del workspace (`synchronizeFromManifest`). Prima dell'esecuzione, il server esegue una "sincronizzazione ridotta" puramente additiva che registra nei metadati del workspace la funzione di pre-install della versione **nuova** — nient'altro viene toccato — e poi la esegue. Poiché questa sincronizzazione è solo additiva, gli oggetti, i campi e i dati della versione precedente restano intatti quando il tuo handler viene eseguito: puoi leggere ed eseguire in sicurezza il backup dello stato pre-migrazione.
* **Modello di esecuzione**: il pre-install è eseguito **in modo sincrono** e **blocca l'installazione**. Se l'handler genera un'eccezione, l'installazione viene interrotta prima che vengano applicate modifiche allo schema — il workspace rimane sulla versione precedente in uno stato coerente. Questo è intenzionale: il pre-install è la tua ultima possibilità per rifiutare un aggiornamento rischioso.
* Come per il post-install, è consentita una sola funzione di pre-installazione per applicazione. Viene collegata automaticamente al manifest dell'applicazione nel campo `preInstallLogicFunction` durante la build.
* **Non eseguito in modalità dev**: come per il post-install — il flusso di installazione viene completamente saltato per le app registrate localmente, quindi il pre-install non viene mai eseguito con `yarn twenty dev`. Usa `yarn twenty dev:function:exec --preInstall` per attivarlo manualmente.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Viene eseguita dopo che la migrazione dei metadati dello spazio di lavoro è stata applicata">
</Accordion>
<Accordion title="Pre-install vs post-install: quando usare l'uno o l'altro" description="Scegliere l'hook di installazione giusto">
Entrambi gli hook fanno parte dello stesso flusso di installazione e ricevono lo stesso `InstallPayload`. La differenza è **quando** vengono eseguiti rispetto alla migrazione dei metadati del workspace, e questo modifica quali dati possono gestire in sicurezza.
Il pre-install è sempre **sincrono** (blocca l'installazione e può interromperla). Il post-install è **asincrono per impostazione predefinita** — messo in coda su un worker con retry automatici — ma può optare per l'esecuzione sincrona con `shouldRunSynchronously: true`. Vedi l'accordion `definePostInstallLogicFunction` sopra per quando usare ciascuna modalità.
**Usa `post-install` per tutto ciò che richiede l'esistenza del nuovo schema.** Questo è il caso più comune:
* Popolamento di dati predefiniti (creazione di record iniziali, viste predefinite, contenuti demo) su oggetti e campi appena aggiunti.
* Registrazione di webhook con servizi di terze parti ora che l'app ha le proprie credenziali.
* Chiamare la tua API per completare il setup che dipende dai metadati sincronizzati.
* Logica idempotente di "ensure this exists" che dovrebbe riconciliare lo stato a ogni aggiornamento — da combinare con `shouldRunOnVersionUpgrade: true`.
Esempio — eseguire il seeding di un record `PostCard` predefinito dopo l'installazione:
Viene eseguito una volta che l'installazione della tua app è terminata: metadati sincronizzati, client SDK generato, nuovo schema interrogabile. Esempio — eseguire il seeding di un record predefinito nelle nuove installazioni:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**Usa `pre-install` quando una migrazione altrimenti distruggerebbe o corromperebbe i dati esistenti.** Poiché il pre-install viene eseguito contro lo schema *precedente* e un suo fallimento annulla l'aggiornamento, è il posto giusto per qualsiasi operazione rischiosa:
Il flag `shouldRunSynchronously` controlla il modello di esecuzione:
* **Eseguire il backup dei dati che stanno per essere eliminati o ristrutturati** — ad esempio, stai rimuovendo un campo nella v2 e devi copiarne i valori in un altro campo o esportarli su uno storage prima che venga eseguita la migrazione.
* **Archiviare i record che un nuovo vincolo renderebbe non validi** — ad esempio, un campo sta diventando `NOT NULL` e devi prima eliminare o correggere le righe con valori nulli.
* **Validare la compatibilità e rifiutare l'aggiornamento se i dati attuali non possono essere migrati correttamente** — genera un'eccezione dall'handler e l'installazione si interrompe senza applicare modifiche. Questo è più sicuro che scoprire l'incompatibilità a migrazione in corso.
* **Rinominare o rigenerare le chiavi dei dati** prima di una modifica dello schema che farebbe perdere l'associazione.
* `false` *(predefinito)* — messo in coda nella message queue (`retryLimit: 3`) ed eseguito da un worker. La risposta dell'installazione ritorna non appena il job viene messo in coda. **Da usare per lavoro di lunga durata** — seeding di grandi dataset, API di terze parti lente.
* `true` — eseguito inline durante il flusso di installazione. La richiesta di installazione rimane bloccata finché l'handler non termina; un errore lanciato viene esposto al chiamante come `POST_INSTALL_ERROR` (nessun retry). **Da usare per lavoro rapido che deve completarsi prima della risposta.** La migrazione è già stata applicata a questo punto, quindi un errore non annulla le modifiche allo schema — si limita a esporre l'errore.
Esempio — archiviare i record prima di una migrazione distruttiva:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Viene eseguita prima che la migrazione dei metadati dello spazio di lavoro sia applicata">
Viene eseguito prima della migrazione dei metadati, contro lo schema **precedente** — il posto giusto per eseguire il backup di dati che una migrazione perderebbe o per rifiutare un upgrade rischioso. Prima dell'esecuzione, il server esegue una "sincronizzazione ridotta" puramente additiva che registra solo la funzione di pre-install della versione nuova; tutto il resto — oggetti, campi e dati della versione precedente — rimane intatto quando il tuo handler viene eseguito.
Il pre-install è sempre **sincrono** e blocca l'installazione. Se l'handler genera un'eccezione, l'installazione viene interrotta prima che venga applicata qualsiasi modifica allo schema — il workspace rimane sulla versione precedente in uno stato coerente. Questo è intenzionale: il pre-install è la tua ultima possibilità per rifiutare un aggiornamento rischioso.
Esempio — copiare i valori di un campo legacy prima che la migrazione lo elimini:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**Regola generale:**
| Vuoi... | Usa |
| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Popolare dati predefiniti, configurare il workspace, registrare risorse esterne | `post-install` |
| Eseguire seeding di lunga durata o chiamate a terze parti che non dovrebbero bloccare la risposta dell'installazione | `post-install` (predefinito — `shouldRunSynchronously: false`, con retry del worker) |
| Eseguire un setup rapido di cui il chiamante farà affidamento immediatamente dopo il ritorno della chiamata di installazione | `post-install` con `shouldRunSynchronously: true` |
| Leggere o eseguire il backup dei dati che la prossima migrazione perderebbe | `pre-install` |
| Rifiutare un aggiornamento che corromperebbe i dati esistenti | `pre-install` (genera un'eccezione dall'handler) |
| Eseguire la riconciliazione a ogni aggiornamento | `post-install` con `shouldRunOnVersionUpgrade: true` |
| Eseguire un setup una tantum solo alla prima installazione | `post-install` con `shouldRunOnVersionUpgrade: false` (predefinito) |
<Note>
In caso di dubbio, usa **post-install**. Ricorri al pre-install solo quando la migrazione stessa è distruttiva e devi intercettare lo stato precedente prima che vada perso.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**I campi base vengono aggiunti automaticamente.** Quando definisci un oggetto personalizzato, Twenty crea per te campi standard come `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. Non è necessario dichiararli nel tuo array `fields` — solo i tuoi campi personalizzati. Puoi sovrascrivere un campo predefinito dichiarandone uno con lo stesso nome, ma è raramente una buona idea.
</Note>
## Tipi di campo
Linsieme completo dei valori di `FieldType`, esportati da `twenty-sdk/define`:
| Categoria | Tipi |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Testo | `TEXT`, `RICH_TEXT`, `ARRAY` (di stringhe), `RAW_JSON` |
| Numerico | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (precisione arbitraria), `RATING`, `POSITION` |
| Date | `DATE`, `DATE_TIME` |
| Scelta | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Composito | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identificatori e relazioni | `UUID`, `RELATION`, `MORPH_RELATION` (vedi [Relazioni](/l/it/developers/extend/apps/data/relations)) |
| Sistema | `TS_VECTOR` (vettore per la ricerca full-text, gestito dal server) |
I tipi compositi memorizzano più sotto-campi (ad es. `FULL_NAME` = nome + cognome; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` e `MULTI_SELECT` richiedono un array `options` come nellesempio sopra.
## Valori predefiniti
I valori predefiniti letterali devono essere racchiusi tra apici singoli **all'interno** della stringa — `defaultValue: "'Draft'"`, non `defaultValue: "Draft"`. Ecco perché il campo `status` sopra utilizza `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## File principali
| File / Cartella | Scopo |
| ---------------------------------------- | -------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Obbligatorio.** Il file di configurazione principale della tua app. |
| `src/default-role.ts` | Ruolo predefinito che controlla a cosa possono accedere le tue funzioni logiche. |
| `src/constants/universal-identifiers.ts` | UUID generati automaticamente e metadati (nome visualizzato, descrizione). |
| `src/__tests__/` | Test di integrazione (setup + test di esempio). |
| `public/` | Asset statici (immagini, font) serviti insieme alla tua app. |
| File / Cartella | Scopo |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Obbligatorio.** Il file di configurazione principale della tua app. |
| `src/default-role.ts` | Ruolo predefinito che controlla a cosa possono accedere le tue funzioni logiche. |
| `src/constants/universal-identifiers.ts` | UUID generati automaticamente e metadati (nome visualizzato, descrizione). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Una pagina di benvenuto iniziale: un front component eseguito da un page layout autonomo, raggiungibile dalla sidebar. |
| `src/__tests__/` | Un test unitario più un test di integrazione (con il relativo setup globale) che sincronizza l'app con un server reale. |
| `public/` | Asset statici (immagini, font) serviti insieme alla tua app. |
| `AGENTS.md` / `CLAUDE.md` | Linee guida per gli agenti di codice AI che lavorano sull'app. |
<Note>
**L'organizzazione dei file dipende da te.** Le cartelle sopra sono convenzioni — l'SDK rileva le entità tramite analisi AST sulle chiamate a `export default defineEntity(...)` indipendentemente da dove si trova il file.
@@ -47,15 +60,18 @@ Entrambi i pacchetti Twenty SDK devono essere inseriti sotto `devDependencies`,
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
Lo scaffolder blocca `twenty-sdk` e `twenty-client-sdk` alla propria versione — mantieni i due allineati durante l'aggiornamento.
* **`twenty-sdk`** fornisce la CLI `twenty` e gli strumenti di build/scaffolding. Viene eseguito solo in fase di sviluppo e di build e non viene mai importato dal runtime dell'app pubblicata.
* **`twenty-client-sdk`** *viene* importato dal codice della tua app (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), ma Twenty lo fornisce a runtime: le funzioni di logica lo ricevono da un layer SDK generato e i componenti di front-end lo risolvono da moduli forniti dal server. La copia installata viene utilizzata solo per il type checking e per la build al momento del deploy, quindi non è mai necessario includerla nel bundle distribuito.
Mantenere uno qualsiasi dei pacchetti sotto `dependencies` lo inserisce nel bundle di runtime dell'app installata, dove rappresenta solo zavorra. `twenty build` emette un avviso quando uno dei due è ancora elencato sotto `dependencies`.
Mantenere uno qualsiasi dei pacchetti sotto `dependencies` lo inserisce nel bundle di runtime dell'app installata, dove rappresenta solo zavorra. `twenty dev:build` emette un avviso quando uno dei due è ancora elencato sotto `dependencies`.
Aggiungi come di consueto le dipendenze di runtime proprie della tua app (librerie che le tue funzioni di logica importano effettivamente a runtime) sotto `dependencies`.
@@ -6,17 +6,17 @@ description: Crea la tua prima app Twenty in pochi minuti.
## Prerequisiti
* **Node.js 24+** — [Scarica](https://nodejs.org/)
* **Node.js 24.5+** — [Scarica](https://nodejs.org/)
* **Yarn 4** — incluso con Node.js tramite Corepack. Abilitalo: `corepack enable`
* **Docker** — [Scarica](https://www.docker.com/products/docker-desktop/). Necessario per eseguire un server Twenty locale. Salta se hai già Twenty in esecuzione altrove.
La creazione di un'app Twenty ha tre fasi. Lo strumento di scaffolding le combina in un unico comando per il percorso ottimale, ma ogni fase è un concetto distinto — quando qualcosa fallisce, sapere in quale fase ti trovi indica cosa correggere.
| Fase | Cosa fai | Strumento | Risultato |
| ----------------------- | ------------------------------------------------------ | ----------------------------- | ---------------------------------- |
| **1. Crea struttura** | Genera il codice sorgente dell'app | `npx create-twenty-app` | Un progetto TypeScript sul disco |
| **2. Esegui un server** | Avvia un server Twenty con cui sincronizzare | Docker + `yarn twenty server` | Un'istanza Twenty in esecuzione |
| **3. Sincronizza** | Sincronizza in tempo reale il tuo codice con il server | `yarn twenty dev` | Le tue modifiche compaiono nell'UI |
| Fase | Cosa fai | Strumento | Risultato |
| ----------------------- | ------------------------------------------------------ | ----------------------------------- | ---------------------------------- |
| **1. Crea struttura** | Genera il codice sorgente dell'app | `npx create-twenty-app` | Un progetto TypeScript sul disco |
| **2. Esegui un server** | Avvia un server Twenty con cui sincronizzare | Docker + `yarn twenty docker:start` | Un'istanza Twenty in esecuzione |
| **3. Sincronizza** | Sincronizza in tempo reale il tuo codice con il server | `yarn twenty dev` | Le tue modifiche compaiono nell'UI |
---
@@ -28,7 +28,7 @@ Crea una nuova app dal modello:
npx create-twenty-app@latest my-twenty-app
```
Ti verrà chiesto un nome e una descrizione — premi **Invio** per usare i valori predefiniti. Questo genera un progetto TypeScript in `my-twenty-app/` con un `application-config.ts` iniziale, un ruolo predefinito, un workflow CI e un test di integrazione.
Lo scaffolder è non interattivo: il nome della directory diventa il nome dell'app. Passa `--display-name` e `--description` per personalizzare i metadati generati (puoi anche modificarli in seguito in `src/constants/universal-identifiers.ts`). Questo genera un progetto TypeScript in `my-twenty-app/` con un `application-config.ts` iniziale, un ruolo predefinito, workflow CI/CD e un test di integrazione.
**Dopo questa fase:** hai il codice sorgente dell'app sulla tua macchina. Non è ancora in esecuzione — questa è la Fase 2.
@@ -38,28 +38,14 @@ Ti verrà chiesto un nome e una descrizione — premi **Invio** per usare i valo
La tua app ha bisogno di un server Twenty con cui sincronizzarsi. Il server è un'istanza Twenty completa — UI, API GraphQL, PostgreSQL — in esecuzione in locale su Docker. Il tuo codice locale carica le sue definizioni su quel server, che le rende visibili nell'UI.
Lo strumento di scaffolding ti propone di avviarne uno per te:
Lo scaffolder avvia un'istanza per te: con Docker in esecuzione, scarica l'immagine `twentycrm/twenty-app-dev`, la avvia sulla porta `2020` e autentica la CLI sullo spazio di lavoro demo prepopolato (`tim@apple.dev`) — non è necessario effettuare l'accesso.
> **Vuoi configurare un'istanza locale di Twenty?**
* **Sì (consigliato)** — scarica l'immagine Docker `twentycrm/twenty-app-dev` e la avvia sulla porta `2020`. Assicurati prima che Docker sia in esecuzione.
* **No** — scegli questa opzione se hai già un server Twenty a cui vuoi connetterti. Puoi collegarlo in seguito con `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Avviare l'istanza locale?" />
</div>
Quando il server è attivo, si apre il browser per l'accesso. Usa l'account demo preconfigurato:
* **Email:** `tim@apple.dev`
* **Password:** `tim@apple.dev`
Per connetterti invece a un server Twenty esistente, passa `--url \<your-server-url>`. I server remoti eseguono l'autenticazione con OAuth: si apre un browser così puoi effettuare l'accesso e fare clic su **Authorize**, concedendo alla CLI l'accesso al tuo spazio di lavoro. (Puoi anche scegliere di utilizzare OAuth in locale con `--authentication-method oauth` — accedi con `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Schermata di accesso di Twenty" />
</div>
Fai clic su **Authorize** nella schermata successiva — questo concede alla CLI l'accesso al tuo spazio di lavoro.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Schermata di autorizzazione della CLI di Twenty" />
</div>
@@ -117,28 +103,32 @@ Fai clic su **View installed app** per vedere l'installazione nello spazio di la
### Sincronizzazione una tantum per CI e script
Passa `--once` per eseguire una singola build + sincronizzazione ed uscire — stessa pipeline, nessun watcher:
Usa `plan` e `apply` per eseguire la stessa pipeline una volta, senza watcher:
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Comando | Comportamento | Quando usarlo |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `yarn twenty dev` | Monitora e risincronizza a ogni modifica. Rimane in esecuzione finché non lo interrompi. | Sviluppo locale interattivo. |
| `yarn twenty dev --once` | Singola build + sincronizzazione, termina con codice `0` in caso di successo, `1` in caso di errore. | CI, hook pre-commit, agenti IA, flussi di lavoro scriptati. |
| `yarn twenty dev --once --dry-run` | Crea e stampa le modifiche ai metadati **senza applicarle**. | Ispezionare quali modifiche verrebbero apportate da una sincronizzazione prima di confermarla. |
| Comando | Comportamento | Quando usarlo |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `yarn twenty dev` | Monitora e risincronizza a ogni modifica. Rimane in esecuzione finché non lo interrompi. | Sviluppo locale interattivo. |
| `yarn twenty apply` | Singola build + sincronizzazione, termina con codice `0` in caso di successo, `1` in caso di errore. Richiede conferma per le modifiche distruttive (passa `--force` per saltarla). | CI, hook pre-commit, agenti IA, flussi di lavoro scriptati. |
| `yarn twenty plan` | Crea e stampa le modifiche ai metadati **senza applicarle**. | Ispezionare quali modifiche verrebbero apportate da una sincronizzazione prima di confermarla. |
Entrambe le modalità richiedono un remoto autenticato. Vedi [Sincronizzazione e ripristino](/l/it/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run) per maggiori informazioni su `--dry-run`.
Tutte le modalità richiedono un remoto autenticato. Vedi [Sincronizzazione e ripristino](/l/it/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) per maggiori informazioni su `plan`.
<Note>
`yarn twenty dev --once` e `yarn twenty dev --once --dry-run` sono alias deprecati di `yarn twenty apply` e `yarn twenty plan`.
</Note>
### Opzioni della modalità di sviluppo
| Opzione | Descrizione |
| ------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `--once` | Esegui una build e una sincronizzazione una sola volta, quindi esci. |
| `--dry-run` | Con `--once`, visualizza in anteprima le modifiche ai metadati senza applicarle. Non scrive nulla. |
| `--debounceMs \<ms>` | Imposta il ritardo di debounce delle modifiche ai file in millisecondi (predefinito: `2000`). |
| `--verbose` / `--debug` | Mostra log di build dettagliati, richieste di sincronizzazione e tracce di errore. |
| Opzione | Descrizione |
| ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `--force` | Applica modifiche distruttive (eliminazioni) senza conferma. |
| `--debounceMs \<ms>` | Imposta il ritardo di debounce delle modifiche ai file in millisecondi (predefinito: `1000`). |
| `--verbose` / `--debug` | Mostra log di build dettagliati, richieste di sincronizzazione e tracce di errore. |
## Cosa puoi creare
@@ -34,6 +34,10 @@ yarn twenty dev:add frontComponent
| Vista | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Voce del menu di navigazione | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Layout di pagina | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Scheda layout di pagina | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Voce del menu comandi | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Campo vista | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Provider di connessione | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## Cosa genera lo scaffolder

Some files were not shown because too many files have changed in this diff Show More