i18n - docs translations (#17134)

Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
github-actions[bot]
2026-01-14 09:29:34 +01:00
committed by GitHub
parent 2845f7e666
commit c3dff19f5d
18 changed files with 1772 additions and 169 deletions
@@ -1,34 +1,34 @@
---
title: Twenty Apps
description: Build and manage Twenty customizations as code.
title: تطبيقات Twenty
description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
---
<Warning>
Apps are currently in alpha testing. The feature is functional but still evolving.
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
## What Are Apps?
## ما هي التطبيقات؟
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف بلا خادم في الكود — مما يجعل الإنشاء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
**What you can do today:**
**ما الذي يمكنك فعله اليوم:**
* Define custom objects and fields as code (managed data model)
* Build serverless functions with custom triggers
* Deploy the same app across multiple workspaces
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
* أنشئ وظائف بلا خادم مع مشغلات مخصصة
* انشر التطبيق نفسه عبر مساحات عمل متعددة
**Coming soon:**
**قريبًا:**
* Custom UI layouts and components
* تخطيطات ومكونات واجهة مستخدم مخصصة
## Prerequisites
## المتطلبات الأساسية
* Node.js 24+ and Yarn 4
* A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
* Node.js 24+ وYarn 4
* مساحة عمل Twenty ومفتاح واجهة برمجة التطبيقات (أنشئ واحدًا على https://app.twenty.com/settings/api-webhooks)
## البدء
Create a new app using the official scaffolder, then authenticate and start developing:
أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير:
```bash filename="Terminal"
# Scaffold a new app
@@ -64,18 +64,18 @@ yarn uninstall
yarn help
```
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
## Project structure (scaffolded)
## هيكل المشروع (مُنشأ بالقالب)
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي:
* Copies a minimal base application into `my-twenty-app/`
* Adds a local `twenty-sdk` dependency and Yarn 4 configuration
* Creates config files and scripts wired to the `twenty` CLI
* Generates a default application config and a default function role
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
* يُولّد ضبطًا افتراضيًا للتطبيق ودورًا افتراضيًا للوظيفة
A freshly scaffolded app looks like this:
يبدو التطبيق المُنشأ حديثًا بالقالب كما يلي:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -99,21 +99,21 @@ my-twenty-app/
utils/ # Optional - handler implementations & utilities
```
### Convention-over-configuration
### الاتفاقية فوق التهيئة
Applications use a **convention-over-configuration** approach where entities are detected by their file suffix. This allows flexible organization within the `src/app/` folder:
تستخدم التطبيقات نهج **الاتفاقية فوق التهيئة** حيث تُكتشف الكيانات عبر لاحقة اسم الملف. يتيح ذلك تنظيمًا مرنًا داخل مجلد `src/app/`:
| File suffix | Entity type |
| --------------- | ------------------------------- |
| `*.object.ts` | Custom object definitions |
| `*.function.ts` | Serverless function definitions |
| `*.role.ts` | Role definitions |
| لاحقة الملف | نوع الكيان |
| --------------- | ---------------------- |
| `*.object.ts` | تعريفات كائنات مخصصة |
| `*.function.ts` | تعريفات وظائف بلا خادم |
| `*.role.ts` | تعريفات الأدوار |
### Supported folder organizations
### طرق تنظيم المجلدات المدعومة
You can organize your entities in any of these patterns:
يمكنك تنظيم الكيانات بأي من الأنماط التالية:
**Traditional (by type):**
**تقليدي (حسب النوع):**
```text
src/app/
@@ -126,7 +126,7 @@ src/app/
└── admin.role.ts
```
**Feature-based:**
**حسب الميزة:**
```text
src/app/
@@ -137,7 +137,7 @@ src/app/
└── postCardAdmin.role.ts
```
**Flat:**
**مسطح:**
```text
src/app/
@@ -147,34 +147,34 @@ src/app/
└── admin.role.ts
```
At a high level:
بشكل عام:
* **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
* **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
* **.nvmrc**: Pins the Node.js version expected by the project.
* **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
* **README.md**: A short README in the app root with basic instructions.
* **src/app/**: The main place where you define your application-as-code:
* `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See "Application config" below.
* `*.role.ts`: Role definitions used by your serverless functions. See "Default function role" below.
* `*.object.ts`: Custom object definitions.
* `*.function.ts`: Serverless function definitions.
* **src/utils/**: Optional folder for handler implementations and utilities.
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` فضلًا عن نصوص مثل `dev` و`sync` و`generate` و`create-entity` و`logs` و`uninstall` و`auth` التي تفوِّض إلى `twenty` CLI المحلي.
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **eslint.config.mjs** و**tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
* **src/app/**: المكان الرئيسي حيث تعرّف تطبيقك ككود:
* `application.config.ts`: التكوين العام لتطبيقك (بيانات وصفية وربط وقت التشغيل). انظر "تكوين التطبيق" أدناه.
* `*.role.ts`: تعريفات الأدوار المستخدمة بواسطة وظائفك بلا خادم. انظر "الدور الافتراضي للوظيفة" أدناه.
* `*.object.ts`: تعريفات كائنات مخصصة.
* `*.function.ts`: تعريفات وظائف بلا خادم.
* **src/utils/**: مجلد اختياري لتنفيذات المعالجات والأدوات المساعدة.
Later commands will add more files and folders:
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
* `yarn generate` will create a `generated/` folder (typed Twenty client + workspace types).
* `yarn create-entity` will add entity definition files under `src/app/` for your custom objects, functions, or roles.
* `yarn generate` سيُنشئ مجلدًا `generated/` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
* `yarn create-entity` سيضيف ملفات تعريف الكيانات تحت `src/app/` لكائناتك المخصصة أو الوظائف أو الأدوار.
## المصادقة
The first time you run `yarn auth`, you'll be prompted for:
في المرة الأولى التي تشغل فيها `yarn auth`، سيُطلب منك إدخال:
* API URL (defaults to http://localhost:3000 or your current workspace profile)
* API key
* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك)
* مفتاح واجهة برمجة التطبيقات
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch using `--workspace <name>`.
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. يمكنك الاحتفاظ بملفات تعريف متعددة والتبديل باستخدام `--workspace <name>`.
الأمثلة:
@@ -186,26 +186,26 @@ yarn auth
yarn auth --workspace my-custom-workspace
```
## Use the SDK resources (types & config)
## استخدم موارد SDK (الأنواع والتكوين)
The twenty-sdk provides typed building blocks and helper functions you use inside your app. Below are the key pieces you'll touch most often.
يوفّر twenty-sdk كتلَ بناءٍ مضبوطة الأنواع ودوال مساعدة تستخدمها داخل تطبيقك. فيما يلي الأجزاء الأساسية التي ستتعامل معها غالبًا.
### Helper functions
### دوال مساعدة
The SDK provides four helper functions with built-in validation for defining your app entities:
يوفّر SDK أربع دوال مساعدة مع تحقق مدمج لتعريف كيانات تطبيقك:
| Function | الغرض |
| ------------------ | -------------------------------------------- |
| `defineApp()` | Configure application metadata |
| `defineObject()` | Define custom objects with fields |
| `defineFunction()` | Define serverless functions with handlers |
| `defineRole()` | Configure role permissions and object access |
| دالة | الغرض |
| ------------------ | ---------------------------------------- |
| `defineApp()` | تهيئة بيانات التطبيق الوصفية |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineFunction()` | تعريف وظائف بلا خادم مع معالجات |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
These functions validate your configuration at runtime and provide better IDE autocompletion and type safety.
تتحقق هذه الدوال من تكوينك في وقت التشغيل وتوفر إكمالًا تلقائيًا أفضل في بيئة التطوير وأمان أنواع أعلى.
### Defining objects
### تعريف الكائنات
Custom objects describe both schema and behavior for records in your workspace. Use `defineObject()` to define objects with built-in validation:
تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج:
```typescript
// src/app/postCard.object.ts
@@ -276,20 +276,20 @@ export default defineObject({
});
```
Key points:
النقاط الرئيسية:
* Use `defineObject()` for built-in validation and better IDE support.
* The `universalIdentifier` must be unique and stable across deployments.
* Each field requires a `name`, `type`, `label`, and its own stable `universalIdentifier`.
* The `fields` array is optional — you can define objects without custom fields.
* You can scaffold new objects using `yarn create-entity`, which guides you through naming, fields, and relationships.
* استخدم `defineObject()` للحصول على تحقق مدمج ودعم أفضل من IDE.
* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر.
* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به.
* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة.
* يمكنك إنشاء كائنات جديدة باستخدام `yarn create-entity`، والذي يرشدك خلال التسمية والحقول والعلاقات.
<Note>
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields such as `name`, `createdAt`, `updatedAt`, `createdBy`, `position`, and `deletedAt`. You don't need to define these in your `fields` array — only add your custom fields.
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية مثل `name` و`createdAt` و`updatedAt` و`createdBy` و`position` و`deletedAt`. لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
</Note>
<Accordion title="Alternative: Decorator-based syntax">
You can also define objects using TypeScript decorators. This approach uses class-based syntax with `@Object`, `@Field`, and `@Relation` decorators:
<Accordion title="بديل: صياغة قائمة على المزيّنات">
يمكنك أيضًا تعريف كائنات باستخدام مزيّنات TypeScript. يستخدم هذا النهج صياغة معتمدة على الأصناف مع مزيّنات `@Object` و`@Field` و`@Relation`:
```typescript
import {
@@ -336,18 +336,18 @@ Key points:
}
```
Note: The decorator approach requires `experimentalDecorators` in your TypeScript config.
ملاحظة: يتطلب نهج المزيّنات `experimentalDecorators` في تهيئة TypeScript لديك.
</Accordion>
### Application config (application.config.ts)
### تكوين التطبيق (application.config.ts)
Every app has a single `application.config.ts` file that describes:
كل تطبيق لديه ملف واحد `application.config.ts` يصف:
* **Who the app is**: identifiers, display name, and description.
* **How its functions run**: which role they use for permissions.
* **(Optional) variables**: keyvalue pairs exposed to your functions as environment variables.
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
Use `defineApp()` to define your application configuration:
استخدم `defineApp()` لتعريف تهيئة تطبيقك:
```typescript
// src/app/application.config.ts
@@ -371,23 +371,23 @@ export default defineApp({
});
```
Notes:
الملاحظات:
* `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
* `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
* `functionRoleUniversalIdentifier` must match the role you define in your `*.role.ts` file (see below).
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
* `functionRoleUniversalIdentifier` يجب أن يطابق الدور الذي تعرّفه في ملف `*.role.ts` (انظر أدناه).
#### Roles and permissions
#### الأدوار والصلاحيات
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's serverless functions.
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `functionRoleUniversalIdentifier` في `application.config.ts` الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
* The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
* The typed client will be restricted to the permissions granted to that role.
* Follow leastprivilege: create a dedicated role with only the permissions your functions need, then reference its universal identifier.
* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا.
* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور.
* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا بالأذونات التي تحتاجها وظائفك فقط، ثم أشِر إلى معرّفه الشامل.
##### Default function role (\*.role.ts)
##### الدور الافتراضي للوظيفة (\*.role.ts)
When you scaffold a new app, the CLI also creates a default role file. Use `defineRole()` to define roles with built-in validation:
عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج:
```typescript
// src/app/default-function.role.ts
@@ -429,21 +429,21 @@ export default defineRole({
});
```
The `universalIdentifier` of this role is then referenced in `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application.config.ts` باسم `functionRoleUniversalIdentifier`. بعبارة أخرى:
* **\*.role.ts** defines what the default function role can do.
* **application.config.ts** points to that role so your functions inherit its permissions.
* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة.
* **application.config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك صلاحياته.
Notes:
الملاحظات:
* Start from the scaffolded role, then progressively restrict it following leastprivilege.
* Replace the `objectPermissions` and `fieldPermissions` with the objects/fields your functions need.
* `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
* See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز.
* استبدل `objectPermissions` و`fieldPermissions` بالكائنات/الحقول التي تحتاجها وظائفك.
* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه.
* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Serverless function config and entrypoint
### تكوين وظيفة بلا خادم ونقطة الدخول
Each function file uses `defineFunction()` to export a configuration with a handler and optional triggers. Use the `*.function.ts` file suffix for automatic detection.
كل ملف وظيفة يستخدم `defineFunction()` لتصدير تكوين مع معالج ومشغلات اختيارية. استخدم لاحقة الملف `*.function.ts` للاكتشاف التلقائي.
```typescript
// src/app/createPostCard.function.ts
@@ -502,30 +502,30 @@ export default defineFunction({
});
```
Common trigger types:
أنواع المشغلات الشائعة:
* **route**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
* **route**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**:
> e.g. `path: '/post-card/create',` -> call on `<APP_URL>/s/post-card/create`
> مثال: `path: '/post-card/create',` -> الاستدعاء على `<APP_URL>/s/post-card/create`
* **cron**: Runs your function on a schedule using a CRON expression.
* **databaseEvent**: Runs on workspace object lifecycle events
* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON.
* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل
> e.g. `person.created`
> مثال: `person.created`
Notes:
الملاحظات:
* The `triggers` array is optional. Functions without triggers can be used as utility functions called by other functions.
* You can mix multiple trigger types in a single function.
* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى.
* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة.
You can create new functions in two ways:
يمكنك إنشاء وظائف جديدة بطريقتين:
* **Scaffolded**: Run `yarn create-entity` and choose the option to add a new function. This generates a starter file with a handler and config.
* **Manual**: Create a new `*.function.ts` file and use `defineFunction()`, following the same pattern.
* **مُنشأ بالقالب**: شغّل `yarn create-entity` واختر خيار إضافة وظيفة جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
* **يدوي**: أنشئ ملفًا جديدًا `*.function.ts` واستخدم `defineFunction()` مع اتباع النمط نفسه.
### Generated typed client
### عميل مُولَّد مضبوط الأنواع
Run yarn generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
شغّل yarn generate لإنشاء عميل محلي مضبوط الأنواع في generated/ استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
```typescript
import Twenty from './generated';
@@ -534,34 +534,34 @@ const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
The client is re-generated by `yarn generate`. Re-run after changing your objects and `yarn sync` or when onboarding to a new workspace.
يُعاد توليد العميل بواسطة `yarn generate`. أعِد تشغيله بعد تغيير كائناتك وتشغيل `yarn sync` أو عند الانضمام إلى مساحة عمل جديدة.
#### Runtime credentials in serverless functions
#### بيانات الاعتماد في وقت التشغيل في الوظائف بلا خادم
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
عندما تعمل وظيفتك على Twenty، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئة قبل تنفيذ كودك:
* `TWENTY_API_URL`: Base URL of the Twenty API your app targets.
* `TWENTY_API_KEY`: Shortlived key scoped to your application's default function role.
* `TWENTY_API_URL`: عنوان URL الأساسي لواجهة Twenty البرمجية التي يستهدفها تطبيقك.
* `TWENTY_API_KEY`: مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك.
الملاحظات:
* You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
* The API key's permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
* Applications can define roles to follow leastprivilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role's universal identifier.
* لا تحتاج إلى تمرير عنوان URL أو مفتاح واجهة برمجة التطبيقات إلى العميل المُولَّد. يقوم بقراءة `TWENTY_API_URL` و`TWENTY_API_KEY` من process.env وقت التشغيل.
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application.config.ts` عبر `functionRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `functionRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
### Hello World example
### مثال Hello World
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف ومشغلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Manual setup (without the scaffolder)
## إعداد يدوي (بدون المهيئ)
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire scripts in your package.json:
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي ووصل السكربتات في ملف package.json لديك:
```bash filename="Terminal"
yarn add -D twenty-sdk
```
Then add scripts like these:
ثم أضف نصوصًا مثل هذه:
```json filename="package.json"
{
@@ -578,13 +578,13 @@ Then add scripts like these:
}
```
Now you can run the same commands via Yarn, e.g. `yarn dev`, `yarn sync`, etc.
يمكنك الآن تشغيل الأوامر نفسها عبر Yarn، مثل `yarn dev` و`yarn sync`، إلخ.
## استكشاف الأخطاء وإصلاحها
* Authentication errors: run `yarn auth` and ensure your API key has the required permissions.
* Cannot connect to server: verify the API URL and that the Twenty server is reachable.
* Types or client missing/outdated: run `yarn generate` and then `yarn dev`.
* Dev mode not syncing: ensure `yarn dev` is running and that changes are not ignored by your environment.
* أخطاء المصادقة: شغّل `yarn auth` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول.
* الأنواع أو العميل مفقود/قديم: شغّل `yarn generate` ثم `yarn dev`.
* وضع التطوير لا يزامن: تأكد من أن `yarn dev` قيد التشغيل وأن التغييرات ليست متجاهلة من بيئتك.
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -1,53 +1,53 @@
---
title: I Don't See Emails on Records
description: Troubleshooting missing emails on records.
title: لا أرى رسائل البريد الإلكتروني على السجلات
description: استكشاف أخطاء رسائل البريد الإلكتروني المفقودة على السجلات وإصلاحها.
---
## Common Reasons
## أسباب شائعة
### 1. Initial Sync Still in Progress
### 1. لا تزال المزامنة الأولية قيد التنفيذ
Email sync takes time, especially for large mailboxes. Twenty imports emails at approximately **400 messages per minute** (limited by Gmail API rate limiting).
تستغرق مزامنة البريد الإلكتروني وقتًا، خاصةً لصناديق البريد الكبيرة. يقوم Twenty باستيراد رسائل البريد الإلكتروني بمعدل يقارب **400 رسالة في الدقيقة** (محدود بتقييد المعدل من Gmail API).
* **Calendar sync**: Completes in minutes
* **Email sync**: Depends on mailbox size (e.g., 10,000 emails ≈ 25 minutes, 50,000 emails ≈ 2 hours)
* **مزامنة التقويم**: تكتمل خلال دقائق
* **مزامنة البريد الإلكتروني**: تعتمد على حجم صندوق البريد (مثال: 10,000 رسالة ≈ 25 دقيقة، 50,000 رسالة ≈ ساعتان)
**Solution**: Wait for the initial import to complete. You can estimate timing based on your mailbox size.
**الحل**: انتظر حتى يكتمل الاستيراد الأولي. يمكنك تقدير الوقت استنادًا إلى حجم صندوق بريدك.
### ٢. Contact Doesn't Exist in Twenty
### ٢. جهة الاتصال غير موجودة في Twenty
Emails only appear on existing People records. If the contact wasn't created yet:
تظهر رسائل البريد الإلكتروني فقط في سجلات الأشخاص الموجودة. إذا لم يتم إنشاء جهة الاتصال بعد:
* Enable **Contact Auto-Creation** in your mailbox settings
* Or manually create the Person record first
* فعّل **الإنشاء التلقائي لجهات الاتصال** في إعدادات صندوق بريدك
* أو أنشئ سجل الشخص يدويًا أولًا
**Solution**: Go to **Settings → Accounts**, select your mailbox, and enable contact auto-creation.
**الحل**: انتقل إلى **Settings → Accounts**، واختر صندوق بريدك، وفعّل الإنشاء التلقائي لجهات الاتصال.
### 3. Internal Emails Are Excluded
### 3. رسائل البريد الإلكتروني الداخلية مستبعدة
Emails between colleagues (same email domain) are never synced to maintain privacy.
لا تتم مزامنة رسائل البريد الإلكتروني بين الزملاء (نفس نطاق البريد الإلكتروني) مطلقًا حفاظًا على الخصوصية.
**Solution**: This is expected behavior. Only external emails are synced.
**الحل**: هذا سلوك متوقع. لا تتم مزامنة سوى رسائل البريد الإلكتروني الخارجية.
### 4. Email Is from a Group or Distribution List
### 4. البريد الإلكتروني من مجموعة أو قائمة توزيع
Group emails and distribution lists are excluded from sync.
رسائل البريد الإلكتروني الخاصة بالمجموعات وقوائم التوزيع مستبعدة من المزامنة.
**Solution**: This is expected behavior.
**الحل**: هذا سلوك متوقع.
### 5. Folder Not Selected for Sync
### 5. المجلد غير محدد للمزامنة
If you're using the Message Folder feature, some folders might be excluded.
إذا كنت تستخدم ميزة مجلد الرسائل، فقد تكون بعض المجلدات مستبعدة.
**Solution**: Go to **Settings → Accounts**, select your mailbox, and check folder sync settings.
**الحل**: انتقل إلى **Settings → Accounts**، واختر صندوق بريدك، وتحقق من إعدادات مزامنة المجلدات.
### 6. Wrong Email Address on Record
### 6. عنوان البريد الإلكتروني في السجل غير صحيح
The Person record might have a different email address than the one used in the email.
قد يحتوي سجل الشخص على عنوان بريد إلكتروني مختلف عن العنوان المستخدم في البريد الإلكتروني.
**Solution**: Add the correct email address to the Person record.
**الحل**: أضف عنوان البريد الإلكتروني الصحيح إلى سجل الشخص.
## Still Not Working?
## ما يزال لا يعمل؟
1. Try disconnecting and reconnecting your mailbox
2. Contact support if issues persist
1. جرّب فصل صندوق بريدك وإعادة توصيله
2. تواصل مع الدعم إذا استمرت المشكلات
@@ -5,7 +5,7 @@ description: سواء كنت بحاجة إلى مساعدة في البدء أو
## حزم الانضمام
Get help from our core team to set up your Twenty workspace with our 4-hour Onboarding packs:
اطلب المساعدة من فريقنا الأساسي لإعداد مساحة العمل في Twenty باستخدام حزم الانضمام لمدة 4 ساعات:
* **تصميم نموذج البيانات**: صمم وأنشئ نموذج البيانات المخصص الخاص بك مع الكائنات، الحقول والعلاقات
* **هجرة البيانات**: قم بترحيل البيانات الحالية من نظام إدارة علاقات العملاء لديك إلى توينتي
@@ -13,4 +13,4 @@ Get help from our core team to set up your Twenty workspace with our 4-hour Onbo
## شركاء التنفيذ
العمل مع شركاء معتمدين لتوينتي للحصول على تخصيصات وعمليات دمج أكثر تقدمًا. Reach out to our team via [contact@twenty.com](mailto:contact@twenty.com) to be matched with our partners.
العمل مع شركاء معتمدين لتوينتي للحصول على تخصيصات وعمليات دمج أكثر تقدمًا. تواصل مع فريقنا عبر [contact@twenty.com](mailto:contact@twenty.com) ليتم ربطك بشركائنا.
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## نظرة عامة
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## المتطلبات الأساسية
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## إعداد خطوة بخطوة
### الخطوة 1: تهيئة المشغّل
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | القيمة |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| كائن | اسم الحقل |
| ---------- | -------------------- |
| الشركة | `companyId` |
| شخص | `personId` |
| الفرصة | `opportunityId` |
| كائن مخصّص | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### الخطوة 4: الاختبار والتفعيل
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. فعّل سير العمل
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| الخطوة | الإجراء | الغرض |
| ------ | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | البحث عن سجل | Get Opportunity or line item details |
| 3 | طلب HTTP | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## استكشاف الأخطاء وإصلاحها
| المشكلة | الحل |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## ذات صلة
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/ar/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## ذات صلة
* [Generate a PDF from Twenty](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — attach generated PDFs to records
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [إجراءات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-actions)
* [أتمتة Closed Won](/l/ar/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## Přehled
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## Předpoklady
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Nastavení krok za krokem
### Krok 1: Nakonfigurujte spouštěč
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | Hodnota |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| Objekt | Název pole |
| -------------- | -------------------- |
| Společnost | `companyId` |
| Osoba | `personId` |
| Příležitost | `opportunityId` |
| Vlastní objekt | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Krok 4: Otestujte a aktivujte
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. Aktivujte pracovní postup
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| Krok | Akce | Účel |
| ---- | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | Vyhledat záznam | Get Opportunity or line item details |
| 3 | HTTP požadavek | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## Řešení potíží
| Problém | Řešení |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## Související
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/cs/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## Související
* [Generate a PDF from Twenty](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — attach generated PDFs to records
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Akce pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-actions)
* [Automatizace pro Uzavřeno vyhráno](/l/cs/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## Übersicht
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## Voraussetzungen
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Schritt-für-Schritt-Einrichtung
### Schritt 1: Trigger konfigurieren
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | Wert |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| Objekt | Feldname |
| -------------------------- | -------------------- |
| Unternehmen | `companyId` |
| Person | `personId` |
| Opportunity | `opportunityId` |
| Benutzerdefiniertes Objekt | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Schritt 4: Testen und aktivieren
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. Aktivieren Sie den Workflow
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| Schritt | Aktion | Zweck |
| ------- | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | Datensatz suchen | Get Opportunity or line item details |
| 3 | HTTP-Anfrage | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## Fehlerbehebung
| Problem | Lösung |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## Verwandt
* [Workflow-Trigger](/l/de/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/de/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## Verwandt
* [Generate a PDF from Twenty](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — attach generated PDFs to records
* [Workflow-Trigger](/l/de/user-guide/workflows/capabilities/workflow-triggers)
* [Workflow-Aktionen](/l/de/user-guide/workflows/capabilities/workflow-actions)
* [Automatisierungen für Closed Won](/l/de/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## Panoramica
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## Prerequisiti
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Configurazione passo-passo
### Passaggio 1: Configura il trigger
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | Valore |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| Oggetto | Nome del campo |
| ---------------------- | -------------------- |
| Azienda | `companyId` |
| Persona | `personId` |
| Opportunità | `opportunityId` |
| Oggetto personalizzato | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Passaggio 4: Testa e attiva
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. Attiva il flusso di lavoro
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| Passaggio | Azione | Scopo |
| --------- | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | Cerca record | Get Opportunity or line item details |
| 3 | Richiesta HTTP | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## Risoluzione dei problemi
| Problema | Soluzione |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## Correlati
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/it/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/it/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## Correlati
* [Generate a PDF from Twenty](/l/it/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — attach generated PDFs to records
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Automazioni Closed Won](/l/it/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
@@ -285,7 +285,7 @@ Puncte cheie:
* Puteți genera obiecte noi folosind `yarn create-entity`, care vă ghidează prin denumire, câmpuri și relații.
<Note>
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields such as `name`, `createdAt`, `updatedAt`, `createdBy`, `position`, and `deletedAt`. You don't need to define these in your `fields` array — only add your custom fields.
**Câmpurile de bază sunt create automat.** Când definiți un obiect personalizat, Twenty adaugă automat câmpuri standard precum `name`, `createdAt`, `updatedAt`, `createdBy`, `position` și `deletedAt`. Nu trebuie să le definiți în tabloul `fields` — adăugați doar câmpurile personalizate proprii.
</Note>
<Accordion title="Alternativă: Sintaxă bazată pe decoratori">
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## Prezentare generală
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## Cerințe
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Configurare pas cu pas
### Pasul 1: Configurați declanșatorul
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | Valoare |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| Obiect | Nume câmp |
| ------------------- | -------------------- |
| Companie | `companyId` |
| Persoană | `personId` |
| Oportunitate | `opportunityId` |
| Obiect personalizat | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Pasul 4: Testați și activați
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. Activați fluxul de lucru
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| Pas | Acțiune | Scop |
| --- | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | Căutare înregistrare | Get Opportunity or line item details |
| 3 | Solicitare HTTP | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## Depanare
| Problemă | Soluție |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## Conexe
* [Declanșatoare ale fluxurilor de lucru](/l/ro/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/ro/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/ro/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## Conexe
* [Generați un PDF din Twenty](/l/ro/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — atașați PDF-uri generate la înregistrări
* [Declanșatoare ale fluxurilor de lucru](/l/ro/user-guide/workflows/capabilities/workflow-triggers)
* [Acțiuni ale fluxurilor de lucru](/l/ro/user-guide/workflows/capabilities/workflow-actions)
* [Automatizări pentru Closed Won](/l/ro/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## Обзор
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## Требования
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Пошаговая настройка
### Шаг 1: Настройте триггер
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | Значение |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| Объект | Имя поля |
| ----------------------- | -------------------- |
| Компания | `companyId` |
| Контакт | `personId` |
| Сделка | `opportunityId` |
| Пользовательский объект | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Шаг 4: Протестируйте и активируйте
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. Активируйте рабочий процесс
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| Шаг | Действие | Назначение |
| --- | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | Поиск записи | Get Opportunity or line item details |
| 3 | HTTP-запрос | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## Устранение неполадок
| Проблема | Решение |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## Связанные материалы
* [Триггеры рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/ru/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/ru/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## Связанные материалы
* [Generate a PDF from Twenty](/l/ru/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — attach generated PDFs to records
* [Триггеры рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-triggers)
* [Действия рабочего процесса](/l/ru/user-guide/workflows/capabilities/workflow-actions)
* [Автоматизации «Закрыто — выиграно»](/l/ru/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
@@ -0,0 +1,228 @@
---
title: Generate a PDF from Twenty
description: Create a workflow to generate and attach a PDF (such as a quote) to a record.
---
Automatically generate or fetch a PDF and attach it to a record in Twenty. This is commonly used to create quotes, invoices, or reports that are linked to Companies, Opportunities, or other objects.
## Genel Bakış
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
## Ön Gereksinimler
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Adım Adım Kurulum
### Adım 1: Tetikleyiciyi Yapılandırın
1. Go to **Workflows** and create a new workflow
2. Select **Manual Trigger**
3. Choose the object you want to attach PDFs to (e.g., **Company** or **Opportunity**)
<Tip>
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
1. Add a **Serverless Function** action
2. Create a new function with the code below
3. Configure the input parameters
#### Input Parameters
| Parameter | Değer |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
</Note>
#### Serverless Function Code
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Step 3: Customize for Your Use Case
#### To attach to a different object
Replace `companyId` with the appropriate field:
| Nesne | Alan Adı |
| ---------- | -------------------- |
| Şirket | `companyId` |
| Kişi | `personId` |
| Fırsat | `opportunityId` |
| Özel Nesne | `yourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
#### To use a dynamic PDF URL
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Adım 4: Test Edin ve Etkinleştirin
1. Save the workflow
2. Navigate to a Company record
3. Click the **⋮** menu and select your workflow
4. Check the **Attachments** section on the record to verify the PDF was attached
5. İş akışını etkinleştirin
## Combining with PDF Generation Services
For creating dynamic quotes or invoices:
### Example: Generate Quote → Attach PDF
| Adım | Eylem | Amaç |
| ---- | ------------------------ | ---------------------------------------- |
| 1 | Manual Trigger (Company) | User initiates on a record |
| 2 | Kayıt Ara | Get Opportunity or line item details |
| 3 | HTTP İsteği | Call PDF generation API with record data |
| 4 | Serverless Function | Download and attach the generated PDF |
### Popular PDF Generation Services
* **Carbone** - Template-based document generation
* **PDFMonkey** - Dynamic PDF creation from templates
* **DocuSeal** - Document automation platform
* **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
## Sorun Giderme
| Sorun | Çözüm |
| ---------------------------- | ---------------------------------------------------------- |
| "Failed to download PDF" | Check the PDF URL is accessible and returns a valid PDF |
| "Upload failed" | Verify your API key is valid and has write permissions |
| "Attachment creation failed" | Ensure the object ID field name matches your target object |
## İlgili
* [İş Akışı Tetikleyicileri](/l/tr/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless Functions](/l/tr/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Generate a Quote or Invoice from Twenty](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -138,6 +138,7 @@ Body: {{code.invoice}}
## İlgili
* [Generate a PDF from Twenty](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — attach generated PDFs to records
* [İş Akışı Tetikleyicileri](/l/tr/user-guide/workflows/capabilities/workflow-triggers)
* [İş Akışı Eylemleri](/l/tr/user-guide/workflows/capabilities/workflow-actions)
* [Kazanıldı Otomasyonları](/l/tr/user-guide/workflows/how-tos/crm-automations/closed-won-automations)