Compare commits

...

66 Commits

Author SHA1 Message Date
prastoin 396efd2d96 generate typeorm migration cli 2026-04-02 16:06:10 +02:00
prastoin d7d279a4b4 wip 2026-04-02 14:46:58 +02:00
prastoin 86a9d3832b remove finally from runners 2026-04-02 13:54:06 +02:00
prastoin dc298bf548 fix merge 2026-04-02 13:46:15 +02:00
prastoin 17ce7ed781 Merge remote-tracking branch 'origin/main' into upgrade-command-refactor 2026-04-02 13:41:59 +02:00
prastoin c2872f8e7d refactor(server): commander options 2026-04-02 13:38:07 +02:00
prastoin ac02aa3ce6 deprecated report 2026-04-02 13:35:05 +02:00
Etienne 579714b62f Investigate memory leak (#19213)
In search for cache leak + Remove old instrumentation
2026-04-02 11:23:10 +00:00
prastoin 3ea466755a naming 2026-04-02 13:22:21 +02:00
Baptiste Devessier 885ab8b444 Seed Front Components (#19220)
https://github.com/user-attachments/assets/6b0887e8-8ba4-49c9-9371-e3db3e9fdde8
2026-04-02 11:20:08 +00:00
Abdullah. 268543a08e Complete sections of the new website. (#19239)
This PR completes the markup for all the sections of all pages of the
new website. There are a lot of improvements to come, but the entire
structure is in place now.
2026-04-02 11:17:30 +00:00
nitin c854d28d60 fix: make models.dev provider logos theme-aware (#19225)
closes
https://discord.com/channels/1130383047699738754/1486675857245212682
2026-04-02 11:10:30 +00:00
prastoin d219c83815 workspace migration runner extends commandRunner directly 2026-04-02 13:04:25 +02:00
nitin 223943550c [AI] Unify code-interpreter streaming rendering and fix assistant width jitter (#19235)
closes
https://discord.com/channels/1130383047699738754/1480991390782455838

- Use data-code-execution as the streaming source of truth and hide
duplicate code-interpreter tool parts (including tool-execute_tool
wrappers).
- Ensure wrapped execute_tool code-interpreter outputs still render
correctly after refetch.
- Gate code-interpreter server behavior by enablement state and keep
assistant messages full-width to avoid streaming vs completed width
shifts.

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-04-02 10:58:14 +00:00
github-actions[bot] 1c7bda8448 i18n - docs translations (#19251)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 12:36:19 +02:00
github-actions[bot] 391f6c0dab i18n - translations (#19250)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 12:33:12 +02:00
Charles Bochet 81f10c586f Add workspace DDL lock env var and maintenance mode UI (#19130)
## Summary

- Add `WORKSPACE_SCHEMA_DDL_LOCKED` env-only boolean config variable
that blocks all workspace schema DDL changes when set to `true`. This is
intended for hot upgrades where logical replication cannot handle DDL
changes. Enforced at two chokepoints:
- `WorkspaceMigrationRunnerService.run` — blocks all metadata-driven DDL
(object/field/index CRUD, app sync/uninstall, standard app sync, upgrade
commands)
- `WorkspaceDataSourceService.createWorkspaceDBSchema` /
`deleteWorkspaceDBSchema` — blocks workspace creation (sign-up) and hard
deletion. Uses a dedicated `WorkspaceDataSourceException` (not
ForbiddenException)

- Add maintenance mode feature with Admin Panel UI and user-facing
banner:
- **Backend**: `MaintenanceModeService` stores maintenance window
(startAt, endAt, optional link) in `core.keyValuePair` as
`CONFIG_VARIABLE`. Validates endAt > startAt. Uses `GraphQLISODateTime`
scalar for date fields. Exposed via `clientConfig` REST endpoint and
admin GraphQL mutations (`setMaintenanceMode`, `clearMaintenanceMode`)
- **Admin Panel**: New "Maintenance Mode" section in Health tab with UTC
datetime pickers and activate/deactivate controls
- **Banner**: `InformationBannerMaintenance` displayed at the top of
`DefaultLayout` for all users, using Temporal API for timezone-aware
formatting with an optional "Learn more" link

These two features are **independent** — the DDL lock is controlled via
env var for operational use, while maintenance mode is a UI notification
mechanism controlled from the admin panel.
2026-04-02 10:17:04 +00:00
github-actions[bot] 9438b9869c i18n - translations (#19249)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 12:06:28 +02:00
prastoin ab16ae6995 review 2026-04-02 12:00:57 +02:00
Raphaël Bosi f688db30a9 Command to deduplicate command menu items (#19202)
- Deduplicate standard record command menu items by merging
single-record and multi-record delete, restore, destroy, and export
actions into shared record commands.
- Update frontend command registrations and engine component mappings to
use the new unified keys, while keeping deprecated engine keys
temporarily mapped for backward compatibility.
- Add a 1-21 upgrade command that removes legacy command menu items from
workspaces and creates the new unified standard items.
2026-04-02 09:51:15 +00:00
prastoin 49d19494b9 avoid mutations 2026-04-02 11:45:06 +02:00
prastoin 5172572d15 refactor(server): remove last upgrade command runner extends 2026-04-02 11:39:31 +02:00
Raphaël Bosi 16033e9f99 Fix front component worker re-creation on every render (#19245)
- `frontComponentHostCommunicationApi` gets a new object reference on
every render, causing the `useMemo`/`useEffect` in
`FrontComponentWorkerEffect` to tear down and re-create the web worker
each time.
- Decouple the host API lifecycle from the worker lifecycle by moving
thread.exports updates into a dedicated
`FrontComponentUpdateHostCommunicationApiEffect` that mutates the
thread's exports object in place via Object.assign.
- Rename `FrontComponentHostCommunicationApiEffect` to
`FrontComponentInitializeHostCommunicationApiEffect` for clarity.

## Before


https://github.com/user-attachments/assets/6f3a5c14-2ae7-4317-82b5-1625abb4143e

## After


https://github.com/user-attachments/assets/1059d6cd-e02c-4477-b3e8-8e965a716434
2026-04-02 09:29:42 +00:00
prastoin b5492cff64 workspace iterator callback 2026-04-02 11:12:55 +02:00
prastoin 0cb231a407 revert instance commands stuff 2026-04-02 10:49:33 +02:00
dependabot[bot] 2c73d47555 Bump @storybook/react-vite from 10.2.13 to 10.3.3 (#19232)
Bumps
[@storybook/react-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite)
from 10.2.13 to 10.3.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/releases"><code>@​storybook/react-vite</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v10.3.3</h2>
<h2>10.3.3</h2>
<ul>
<li>Addon-Vitest: Streamline vite(st) config detection across init and
postinstall - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34193">#34193</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>v10.3.2</h2>
<h2>10.3.2</h2>
<ul>
<li>CLI: Shorten CTA link messages - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34236">#34236</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
<li>React Native Web: Fix vite8 support by bumping vite-plugin-rnw - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34231">#34231</a>,
thanks <a
href="https://github.com/dannyhw"><code>@​dannyhw</code></a>!</li>
</ul>
<h2>v10.3.1</h2>
<h2>10.3.1</h2>
<ul>
<li>CLI: Use npm info to fetch versions in repro command - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34214">#34214</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Prevent story-local viewport from persisting in URL - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34153">#34153</a>,
thanks <a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>!</li>
</ul>
<h2>v10.3.0</h2>
<h2>10.3.0</h2>
<p><em>&gt; Improved developer experience, AI-assisting tools, and
broader ecosystem support</em></p>
<p>Storybook 10.3 contains hundreds of fixes and improvements
including:</p>
<ul>
<li>🤖 Storybook MCP: Agentic component dev, docs, and test (Preview
release for React)</li>
<li> Vite 8 support</li>
<li>▲ Next.js 16.2 support</li>
<li>📝 ESLint 10 support</li>
<li>〰️ Addon Pseudo-States: Tailwind v4 support</li>
<li>🔧 Addon-Vitest: Simplified configuration - no more setup files
required</li>
<li> Numerous accessibility improvements across the UI</li>
</ul>
<!-- raw HTML omitted -->
<ul>
<li>A11y: Add ScrollArea prop focusable for when it has static children
- <a
href="https://redirect.github.com/storybookjs/storybook/pull/33876">#33876</a>,
thanks <a
href="https://github.com/Sidnioulz"><code>@​Sidnioulz</code></a>!</li>
<li>A11y: Ensure popover dialogs have an ARIA label - <a
href="https://redirect.github.com/storybookjs/storybook/pull/33500">#33500</a>,
thanks <a
href="https://github.com/gayanMatch"><code>@​gayanMatch</code></a>!</li>
<li>A11y: Make resize handles for addon panel and sidebar accessible <a
href="https://redirect.github.com/storybookjs/storybook/pull/33980">#33980</a></li>
<li>A11y: Underline MDX links for WCAG SC 1.4.1 compliance - <a
href="https://redirect.github.com/storybookjs/storybook/pull/33139">#33139</a>,
thanks <a
href="https://github.com/NikhilChowdhury27"><code>@​NikhilChowdhury27</code></a>!</li>
<li>Actions: Add expandLevel parameter to configure tree depth - <a
href="https://redirect.github.com/storybookjs/storybook/pull/33977">#33977</a>,
thanks <a
href="https://github.com/mixelburg"><code>@​mixelburg</code></a>!</li>
<li>Actions: Fix HandlerFunction type to support async callback props -
<a
href="https://redirect.github.com/storybookjs/storybook/pull/33864">#33864</a>,
thanks <a
href="https://github.com/mixelburg"><code>@​mixelburg</code></a>!</li>
<li>Addon-Docs: Add React as optimizeDeps entry - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34176">#34176</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Addon-Docs: Add support for `sourceState: 'none'` to canvas block
parameters - <a
href="https://redirect.github.com/storybookjs/storybook/pull/33627">#33627</a>,
thanks <a
href="https://github.com/quisido"><code>@​quisido</code></a>!</li>
<li>Addon-docs: Restore `docs.components` overrides for doc blocks <a
href="https://redirect.github.com/storybookjs/storybook/pull/34111">#34111</a></li>
<li>Addon-Vitest: Add channel API to programmatically trigger test runs
- <a
href="https://redirect.github.com/storybookjs/storybook/pull/33206">#33206</a>,
thanks <a
href="https://github.com/JReinhold"><code>@​JReinhold</code></a>!</li>
<li>Addon-Vitest: Handle additional vitest config export patterns in
postinstall - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34106">#34106</a>,
thanks <a
href="https://github.com/copilot-swe-agent"><code>@​copilot-swe-agent</code></a>!</li>
<li>Addon-Vitest: Make Playwright `--with-deps` platform-aware to avoid
`sudo` prompt on Linux <a
href="https://redirect.github.com/storybookjs/storybook/pull/34121">#34121</a></li>
<li>Addon-Vitest: Refactor Vitest setup to eliminate the need for a
dedicated setup file - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34025">#34025</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Addon-Vitest: Support Vitest canaries - <a
href="https://redirect.github.com/storybookjs/storybook/pull/33833">#33833</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Angular: Add moduleResolution: bundler to tsconfig - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34085">#34085</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md"><code>@​storybook/react-vite</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>10.3.3</h2>
<ul>
<li>Addon-Vitest: Streamline vite(st) config detection across init and
postinstall - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34193">#34193</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>10.3.2</h2>
<ul>
<li>CLI: Shorten CTA link messages - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34236">#34236</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
<li>React Native Web: Fix vite8 support by bumping vite-plugin-rnw - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34231">#34231</a>,
thanks <a
href="https://github.com/dannyhw"><code>@​dannyhw</code></a>!</li>
</ul>
<h2>10.3.1</h2>
<ul>
<li>CLI: Use npm info to fetch versions in repro command - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34214">#34214</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Prevent story-local viewport from persisting in URL - <a
href="https://redirect.github.com/storybookjs/storybook/pull/34153">#34153</a>,
thanks <a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>!</li>
</ul>
<h2>10.3.0</h2>
<p><em>&gt; Improved developer experience, AI-assisting tools, and
broader ecosystem support</em></p>
<p>Storybook 10.3 contains hundreds of fixes and improvements
including:</p>
<ul>
<li>🤖 Storybook MCP: Agentic component dev, docs, and test (Preview
release for React)</li>
<li> Vite 8 support</li>
<li>▲ Next.js 16.2 support</li>
<li>📝 ESLint 10 support</li>
<li>〰️ Addon Pseudo-States: Tailwind v4 support</li>
<li>🔧 Addon-Vitest: Simplified configuration - no more setup files
required</li>
<li> Numerous accessibility improvements across the UI</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/storybookjs/storybook/commit/b0acfb41eb86f7e167ffba404acade8c397681df"><code>b0acfb4</code></a>
Bump version from &quot;10.3.2&quot; to &quot;10.3.3&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/308656fe0f4c6d783b72e24390d6b26ce23e8a91"><code>308656f</code></a>
Bump version from &quot;10.3.1&quot; to &quot;10.3.2&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/24c2c2c3f2221844406694acc2241e6cdaeb51ac"><code>24c2c2c</code></a>
Bump version from &quot;10.3.0&quot; to &quot;10.3.1&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/06cb6a6874742c8815f29f650b4b8d0a5273b46e"><code>06cb6a6</code></a>
Bump version from &quot;10.3.0-beta.3&quot; to &quot;10.3.0&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/94b94304e47ed422010a061beb9f31c12c07d242"><code>94b9430</code></a>
Bump version from &quot;10.3.0-beta.2&quot; to &quot;10.3.0-beta.3&quot;
[skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/af5b7de899701eb55e511197dbb0420850156125"><code>af5b7de</code></a>
Bump version from &quot;10.3.0-beta.1&quot; to &quot;10.3.0-beta.2&quot;
[skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/a571619e5c16b91c3d9f7b52c74955804ef6287c"><code>a571619</code></a>
Bump version from &quot;10.3.0-beta.0&quot; to &quot;10.3.0-beta.1&quot;
[skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/546aece1ec7381700b603eece590c0048d16205d"><code>546aece</code></a>
Bump version from &quot;10.3.0-alpha.17&quot; to
&quot;10.3.0-beta.0&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/ceda0b4de602b3cc5675684bcfbad66fab3601dc"><code>ceda0b4</code></a>
Bump version from &quot;10.3.0-alpha.16&quot; to
&quot;10.3.0-alpha.17&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/1ed871cb53eff30e332b080c3d73ba0cd50acff3"><code>1ed871c</code></a>
Bump version from &quot;10.3.0-alpha.15&quot; to
&quot;10.3.0-alpha.16&quot; [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/storybookjs/storybook/commits/v10.3.3/code/frameworks/react-vite">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@storybook/react-vite&package-manager=npm_and_yarn&previous-version=10.2.13&new-version=10.3.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
2026-04-02 08:49:11 +00:00
prastoin 5484d0b776 feat(server): factorize workspace iterator usage 2026-04-02 10:36:43 +02:00
dependabot[bot] 6d5580c6fd Bump qs from 6.14.2 to 6.15.0 (#19233)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.2 to 6.15.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/qs/blob/main/CHANGELOG.md">qs's
changelog</a>.</em></p>
<blockquote>
<h2><strong>6.15.0</strong></h2>
<ul>
<li>[New] <code>parse</code>: add <code>strictMerge</code> option to
wrap object/primitive conflicts in an array (<a
href="https://redirect.github.com/ljharb/qs/issues/425">#425</a>, <a
href="https://redirect.github.com/ljharb/qs/issues/122">#122</a>)</li>
<li>[Fix] <code>duplicates</code> option should not apply to bracket
notation keys (<a
href="https://redirect.github.com/ljharb/qs/issues/514">#514</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ljharb/qs/commit/d9b4c66303375493c68c42d68e363e50b1753771"><code>d9b4c66</code></a>
v6.15.0</li>
<li><a
href="https://github.com/ljharb/qs/commit/cb41a545a32422ad3044584d3c4fa8f953552605"><code>cb41a54</code></a>
[New] <code>parse</code>: add <code>strictMerge</code> option to wrap
object/primitive conflicts in...</li>
<li><a
href="https://github.com/ljharb/qs/commit/88e15636da953397262bd3014ab8b0d17d5c8039"><code>88e1563</code></a>
[Fix] <code>duplicates</code> option should not apply to bracket
notation keys</li>
<li><a
href="https://github.com/ljharb/qs/commit/9d441d270486c3cc77f17289a9e0921c0f742aff"><code>9d441d2</code></a>
Merge backport release tags v6.0.6–v6.13.3 into main</li>
<li><a
href="https://github.com/ljharb/qs/commit/85cc8cac6b444c9b4cb1172a151ac8fdee0a0301"><code>85cc8ca</code></a>
v6.12.5</li>
<li><a
href="https://github.com/ljharb/qs/commit/ffc12aa71030f508ab28cccbb1987424abf52379"><code>ffc12aa</code></a>
v6.11.4</li>
<li><a
href="https://github.com/ljharb/qs/commit/0506b11e457f6b3847b1dcf65b5c11c0eaf5dfb9"><code>0506b11</code></a>
[actions] update reusable workflows</li>
<li><a
href="https://github.com/ljharb/qs/commit/6a37fafc75ce8a3d00ef611c9d7acfccc6ec449c"><code>6a37faf</code></a>
[actions] update reusable workflows</li>
<li><a
href="https://github.com/ljharb/qs/commit/8e8df5a3b147ec2f86830c2e3de1016a7ecbc18b"><code>8e8df5a</code></a>
[Fix] fix regressions from robustness refactor</li>
<li><a
href="https://github.com/ljharb/qs/commit/d60bab35a42b3c789d7a1461ea176eaee74eb751"><code>d60bab3</code></a>
v6.10.7</li>
<li>Additional commits viewable in <a
href="https://github.com/ljharb/qs/compare/v6.14.2...v6.15.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=qs&package-manager=npm_and_yarn&previous-version=6.14.2&new-version=6.15.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 08:35:51 +00:00
Raphaël Bosi 691c86a4a7 Fix front component not opening in side panel (#19238)
# PR Description

Fix branching priority in `useCommandMenuItemsFromBackend` so
non-headless front components are routed through
`FrontComponentCommandMenuItem` (which opens the side panel) instead of
always going through the `HeadlessCommandMenuItem` path.

# Video QA

## Before


https://github.com/user-attachments/assets/4449b849-ae71-4ca2-b22e-0efd62ad1b1b

## After


https://github.com/user-attachments/assets/d361d1d1-83fb-4588-a5da-d1bae8747954
2026-04-02 08:33:58 +00:00
prastoin bbc4d50d4f feat(server): workspace iterator service 2026-04-02 10:33:18 +02:00
dependabot[bot] 1af7ac5fc4 Bump react-hotkeys-hook from 4.5.0 to 4.6.2 (#19231)
Bumps
[react-hotkeys-hook](https://github.com/JohannesKlauss/react-keymap-hook)
from 4.5.0 to 4.6.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/JohannesKlauss/react-keymap-hook/releases">react-hotkeys-hook's
releases</a>.</em></p>
<blockquote>
<h2>v4.6.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Update advanced-usage.mdx by <a
href="https://github.com/VladimirTambovtsev"><code>@​VladimirTambovtsev</code></a>
in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1232">JohannesKlauss/react-hotkeys-hook#1232</a></li>
<li>feature(addEventListener): passthrough event listener options by <a
href="https://github.com/wiserockryan"><code>@​wiserockryan</code></a>
in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1234">JohannesKlauss/react-hotkeys-hook#1234</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/VladimirTambovtsev"><code>@​VladimirTambovtsev</code></a>
made their first contribution in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1232">JohannesKlauss/react-hotkeys-hook#1232</a></li>
<li><a
href="https://github.com/wiserockryan"><code>@​wiserockryan</code></a>
made their first contribution in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1234">JohannesKlauss/react-hotkeys-hook#1234</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/compare/v4.6.1...v4.6.2">https://github.com/JohannesKlauss/react-hotkeys-hook/compare/v4.6.1...v4.6.2</a></p>
<h2>v4.6.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Consider custom element when checking if event is by <a
href="https://github.com/HJK181"><code>@​HJK181</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1164">JohannesKlauss/react-hotkeys-hook#1164</a></li>
<li>Bump http-proxy-middleware from 2.0.6 to 2.0.7 in /documentation by
<a href="https://github.com/dependabot"><code>@​dependabot</code></a> in
<a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1217">JohannesKlauss/react-hotkeys-hook#1217</a></li>
<li>Bump express from 4.19.2 to 4.21.0 in /documentation by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1210">JohannesKlauss/react-hotkeys-hook#1210</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/HJK181"><code>@​HJK181</code></a> made
their first contribution in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1164">JohannesKlauss/react-hotkeys-hook#1164</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/compare/v4.6.0...v4.6.1">https://github.com/JohannesKlauss/react-hotkeys-hook/compare/v4.6.0...v4.6.1</a></p>
<h2>v4.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1204">JohannesKlauss/react-hotkeys-hook#1204</a></li>
<li>Feat: Helps to identify which Shortcut was triggered exactly by <a
href="https://github.com/prostoandrei"><code>@​prostoandrei</code></a>
in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1219">JohannesKlauss/react-hotkeys-hook#1219</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/prostoandrei"><code>@​prostoandrei</code></a>
made their first contribution in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1219">JohannesKlauss/react-hotkeys-hook#1219</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/compare/v4.5.1...v4.6.0">https://github.com/JohannesKlauss/react-hotkeys-hook/compare/v4.5.1...v4.6.0</a></p>
<h2>v4.5.1</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1136">JohannesKlauss/react-hotkeys-hook#1136</a></li>
<li>chore(deps): update dependency <code>@​types/react</code> to
v18.2.56 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1140">JohannesKlauss/react-hotkeys-hook#1140</a></li>
<li>fix: example code in use-hotkeys docs by <a
href="https://github.com/jvn4dev"><code>@​jvn4dev</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1142">JohannesKlauss/react-hotkeys-hook#1142</a></li>
<li>chore(deps): update actions/setup-node action to v4 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1141">JohannesKlauss/react-hotkeys-hook#1141</a></li>
<li>chore(deps): update actions/checkout action to v4 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1137">JohannesKlauss/react-hotkeys-hook#1137</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1147">JohannesKlauss/react-hotkeys-hook#1147</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1149">JohannesKlauss/react-hotkeys-hook#1149</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1156">JohannesKlauss/react-hotkeys-hook#1156</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1158">JohannesKlauss/react-hotkeys-hook#1158</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1162">JohannesKlauss/react-hotkeys-hook#1162</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1166">JohannesKlauss/react-hotkeys-hook#1166</a></li>
<li>chore(deps): update dependency <code>@​types/react</code> to
v18.2.79 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1169">JohannesKlauss/react-hotkeys-hook#1169</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1171">JohannesKlauss/react-hotkeys-hook#1171</a></li>
<li>chore(deps): update all non-major dependencies to v7.24.5 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1173">JohannesKlauss/react-hotkeys-hook#1173</a></li>
<li>chore(deps): update dependency <code>@​types/react</code> to v18.3.2
by <a href="https://github.com/renovate"><code>@​renovate</code></a> in
<a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1175">JohannesKlauss/react-hotkeys-hook#1175</a></li>
<li>chore(deps): update all non-major dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohannesKlauss/react-hotkeys-hook/pull/1178">JohannesKlauss/react-hotkeys-hook#1178</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/47f15a6554a60ea3ccaaf62c49f925295c852165"><code>47f15a6</code></a>
4.6.2</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/bfe319bb5fea77e2ecd3ef7fdafe5c274ec1e371"><code>bfe319b</code></a>
Merge pull request <a
href="https://redirect.github.com/JohannesKlauss/react-keymap-hook/issues/1234">#1234</a>
from wiserockryan/feature/passthrough-event-listener...</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/67b1f2e3d903365999f89b5aa8374d51d93a641c"><code>67b1f2e</code></a>
Merge pull request <a
href="https://redirect.github.com/JohannesKlauss/react-keymap-hook/issues/1232">#1232</a>
from VladimirTambovtsev/patch-1</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/aa6ce063788e5b01cf0d806fb137c9a57f749bbe"><code>aa6ce06</code></a>
feature(addEventListener): fix removeEventListener and add abort signal
test</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/e1cb7b41f2b95e195d413c765fbc4c16bb8652b4"><code>e1cb7b4</code></a>
feat(addEventListener): passthrough event listener options</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/157b512253d2f5e128b790aae1d5c122df7d6f50"><code>157b512</code></a>
Update advanced-usage.mdx</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/bc55a281f1d212d09de786aeb5cd236c58d9531d"><code>bc55a28</code></a>
4.6.1</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/0e41a2ac4a3a7b01159632614b552f51726756cd"><code>0e41a2a</code></a>
Fix ts error</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/1f95c28ee20dbf018a0493a2dba2495988e08398"><code>1f95c28</code></a>
Fix ts error</li>
<li><a
href="https://github.com/JohannesKlauss/react-hotkeys-hook/commit/4668ebb8c69c66dc61e4bae681c6daab3120474b"><code>4668ebb</code></a>
Merge pull request <a
href="https://redirect.github.com/JohannesKlauss/react-keymap-hook/issues/1210">#1210</a>
from JohannesKlauss/dependabot/npm_and_yarn/document...</li>
<li>Additional commits viewable in <a
href="https://github.com/JohannesKlauss/react-keymap-hook/compare/v4.5.0...v4.6.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=react-hotkeys-hook&package-manager=npm_and_yarn&previous-version=4.5.0&new-version=4.6.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 08:29:48 +00:00
github-actions[bot] 72ac10fb7a i18n - translations (#19242)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 10:40:25 +02:00
github-actions[bot] 986256f56d i18n - docs translations (#19240)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 10:39:32 +02:00
nitin ade6ed9c32 [AI] agent node prompt tab new design + refactor (#19012) 2026-04-02 08:24:58 +00:00
nitin 19c710b87f fix read only text editor color (#19223) 2026-04-02 10:23:46 +02:00
github-actions[bot] 7991ce7823 i18n - translations (#19237)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 10:21:19 +02:00
Félix Malfait fd7387928c feat: queue messages + replace AI SDK with GraphQL SSE subscription (#19203)
## Summary

- **Queue messages while streaming**: Messages sent during active AI
streaming are queued server-side and auto-flushed when the current
stream completes. Frontend renders queued messages optimistically in a
dedicated queue UI.
- **Drop `@ai-sdk/react` + `resumable-stream`**: Replace the dual HTTP
SSE + AI SDK client architecture with a single GraphQL SSE subscription
per thread. All events (token chunks, message persistence, queue
updates, errors) flow through Redis PubSub → GraphQL subscription.
- **Server-driven architecture**: The server decides whether to queue or
stream (via `POST /:threadId/message`). The frontend mirrors this
decision for optimistic rendering but defers to the server response.
- **Reuse AI SDK accumulation logic**: `readUIMessageStream` from the
`ai` package handles chunk-to-message accumulation on the frontend,
avoiding a custom 780-line accumulator.

## Key files

**Backend:**
- `agent-chat-event-publisher.service.ts` — publishes events to Redis
PubSub
- `agent-chat-subscription.resolver.ts` — GraphQL subscription resolver
- `stream-agent-chat.job.ts` — publishes chunks via PubSub instead of
resumable-stream
- `agent-chat.controller.ts` — unified `POST /:threadId/message`
endpoint

**Frontend:**
- `useAgentChatSubscription.ts` — subscribes to `onAgentChatEvent`,
bridges to `readUIMessageStream`
- `useAgentChat.ts` — send/stop/optimistic rendering (no more AI SDK)
- `AgentChatStreamSubscriptionEffect.tsx` — replaces
`AgentChatAiSdkStreamEffect.tsx`

## Test plan

- [ ] Send message on new thread → optimistic render, streaming response
appears
- [ ] Send message while streaming → queued instantly (no flash in main
thread)
- [ ] Queued message auto-flushes after current stream completes
- [ ] Remove queued message via queue UI
- [ ] Stop streaming mid-response
- [ ] Leave chat idle for several minutes → streaming still works after
(SSE client recycling)
- [ ] Token refresh during session → requests succeed (authenticated
fetch)
- [ ] Switch threads while streaming → clean subscription handoff


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 10:10:13 +02:00
github-actions[bot] 3733a5a763 i18n - translations (#19236)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 09:30:11 +02:00
Weiko 0b2f4cdb57 Add delete widget action in fields widget side panel (#19167)
## Context
Add a new "Manage" section in FIELDS widget side panel with a "Delete
widget" action

Next step: Deleting a non-custom fields widget should be reversible
("Reset to default" followup)

<img width="1286" height="398" alt="Screenshot 2026-03-31 at 15 17 31"
src="https://github.com/user-attachments/assets/9ee63c14-52f1-470e-9112-4538271dc6fc"
/>
2026-04-02 07:15:19 +00:00
github-actions[bot] 1622c87b7a i18n - docs translations (#19234)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 08:44:39 +02:00
github-actions[bot] f3e2e00e79 i18n - docs translations (#19229)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 07:00:47 +02:00
github-actions[bot] 5de5ed2cb4 i18n - docs translations (#19228)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 05:20:37 +02:00
github-actions[bot] 6eb4c4ca4b i18n - docs translations (#19227)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 03:04:12 +02:00
github-actions[bot] ae202a1b59 i18n - docs translations (#19226)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-02 00:27:21 +02:00
martmull 16e3e38b79 Improve getting started doc (#19138)
- improves
`packages/twenty-docs/developers/extend/apps/getting-started.mdx`

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-04-01 20:39:44 +00:00
github-actions[bot] 4cc3deb937 i18n - docs translations (#19217)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-01 18:40:26 +02:00
prastoin 10b37a6b39 refactor(server): upgrade and typeorm migrations nesting 2026-04-01 18:39:48 +02:00
github-actions[bot] e15feda3c3 i18n - translations (#19216)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-01 18:15:16 +02:00
Raphaël Bosi 9f95c4763c [COMMAND MENU ITEMS] Remove deprecated code (#19199)
This PR is the first one of a cleanup after upgrading command menu items
to V2.
2026-04-01 15:56:52 +00:00
github-actions[bot] e6fe48b66d i18n - translations (#19215)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-01 18:00:09 +02:00
Baptiste Devessier 20ac5b7e84 Field widget edition (#19209)
https://github.com/user-attachments/assets/2f1a5847-3375-414f-a2b8-ce4b533b5512

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-01 15:44:49 +00:00
github-actions[bot] 136f362b24 i18n - translations (#19214)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-01 17:40:00 +02:00
Baptiste Devessier 291792f864 Repair Edit Layout command menu item and add a button in settings to start edition too (#19208)
https://github.com/user-attachments/assets/f23f0777-abf3-4ff4-8fab-ec2004df60bc
2026-04-01 15:24:05 +00:00
Raphaël Bosi 9054d3aef6 [COMMAND MENU ITEMS] Resolve object metadata label in dynamic command menu item label (#19211)
- Adds a resolveObjectMetadataLabel utility that returns the singular or
plural label from object metadata based on the number of selected
records (e.g., "person" vs "people").
- Exposes a pre-computed objectMetadataLabel string on
CommandMenuContextApi, making it available for label interpolation
(e.g., Delete ${capitalize(objectMetadataLabel)}).
2026-04-01 15:02:24 +00:00
Thomas Trompette 19dd4d6c1b Fix workflow date fields (#19210)
Before: broken on forms, missing border right, no fullWidth, not
properly saved
<img width="220" height="160" alt="Capture d’écran 2026-03-31 à 17 10
47"
src="https://github.com/user-attachments/assets/4143fcb7-909f-42a3-b05e-39185395f657"
/> <img width="231" height="102" alt="Capture d’écran 2026-03-31 à 17
11 05"
src="https://github.com/user-attachments/assets/3989f6b8-ef8a-42a3-9ccc-35a9be1fb67f"
/>
<img width="230" height="75" alt="Capture d’écran 2026-03-31 à 17 12
15"
src="https://github.com/user-attachments/assets/67f5f93f-887f-4e3b-95c2-f5076f41fb21"
/>

After: save and validate on edition, fix design
<img width="231" height="132" alt="Capture d’écran 2026-03-31 à 17 15
08"
src="https://github.com/user-attachments/assets/d1aa0a64-499d-479d-8d5c-e5e104ad6464"
/>
<img width="231" height="75" alt="Capture d’écran 2026-03-31 à 17 15
33"
src="https://github.com/user-attachments/assets/8d668713-8466-4e81-8901-4b12b9271244"
/>
<img width="461" height="156" alt="Capture d’écran 2026-03-31 à 17 14
54"
src="https://github.com/user-attachments/assets/f9d33242-f1cc-48f7-9f63-322799e1f9b8"
/>

Simplifying FormDateInput using the existing DatePicker
2026-04-01 14:44:24 +00:00
github-actions[bot] a0c6727a61 i18n - docs translations (#19212)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-01 16:48:34 +02:00
BOHEUS b002930554 Update documentation on how to upload a file (#19197)
As per title, update a documentation on how to upload a file given
increasing amount of questions for this problem

CC: @StephanieJoly4

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-04-01 14:15:43 +00:00
Marie aa0ea96582 Improve app errors logs at sync (#19174)
1. Fix scrollbar
Before

https://github.com/user-attachments/assets/29792a71-b2dd-49f6-bb90-9d15feeb95aa

After

https://github.com/user-attachments/assets/939a000a-b787-4ea5-a9f0-61fbac886025

2. Introduce verbose vs non-verbose
verbose = what we have today (very detailed)
non-verbose = summarized (with a log to say add --verbose for full
logs!)

without --verbose
<img width="1256" height="876" alt="updated_non_verbose"
src="https://github.com/user-attachments/assets/d6194c41-2366-4297-a7ac-b3f3b27e08dd"
/>


with --verbose
<img width="422" height="819" alt="verbose_logs"
src="https://github.com/user-attachments/assets/409e2e88-ec3d-4bab-957c-ef319895f8c5"
/>
2026-04-01 13:46:37 +00:00
Gabriel 36dece43c7 Fix: Upgrade Nodemailer to address SMTP command injection vulnerability (#19151)
📄 Summary

This PR upgrades the nodemailer dependency to a secure version (≥ 8.0.4)
to fix a known SMTP command injection vulnerability
(GHSA-c7w3-x93f-qmm8).

🚨 Issue

The current version used in twenty-server (^7.0.11, resolved to 7.0.11 /
7.0.13) is vulnerable to SMTP command injection due to improper
sanitization of the envelope.size parameter.
This could allow CRLF injection, potentially enabling attackers to add
unauthorized recipients to outgoing emails.

🔍 Root Cause

The vulnerability originates from insufficient validation of
user-controlled input in the SMTP envelope, specifically the size field,
which can be exploited via crafted input containing CRLF sequences.

 Changes
Upgraded nodemailer to version ^8.0.4
Ensured compatibility with existing email sending logic
Verified that no breaking changes affect current usage

🔐 Security Impact

This update mitigates the risk of:

SMTP command injection
Unauthorized email recipient manipulation
Potential data leakage via crafted email payloads
📎 References
GHSA: GHSA-c7w3-x93f-qmm8
CVE: (see linked report in issue)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-31 19:55:50 +00:00
Charles Bochet ac8e0d4217 Replace twentycrm/twenty-postgres-spilo with official postgres:16 in CI (#19182)
## Summary
- Replaces `twentycrm/twenty-postgres-spilo` with the official
`postgres:16` image across all 7 CI workflow files
- Removes Docker Hub `credentials` blocks from all service containers
(postgres, redis, clickhouse)
- Removes the `Login to Docker Hub` step from the breaking changes
workflow

## Context
Fork PRs cannot access repository secrets/variables, causing `${{
vars.DOCKERHUB_USERNAME }}` and `${{ secrets.DOCKERHUB_PASSWORD }}` to
resolve to empty strings. GitHub Actions rejects empty credential values
at template validation time, failing the job before any step runs.

The custom spilo image was the original reason credentials were needed
(to avoid Docker Hub rate limits on non-official images). The only
Postgres extensions required in CI (`uuid-ossp`, `unaccent`) are built
into the official `postgres:16` image. Official Docker Hub images have
significantly higher pull rate limits and don't require authentication.
2026-03-31 21:41:42 +02:00
Charles Bochet ee3ebd0ca0 Add "search" to reserved metadata name keywords (#19181)
## Summary
- Adds `search` and `searches` to the `RESERVED_METADATA_NAME_KEYWORDS`
list in `twenty-shared`
- Prevents users from creating custom objects named "search", which
collides with the core `search` GraphQL resolver
2026-03-31 21:16:46 +02:00
Baptiste Devessier c11e4ece39 Fallback to field metadata (#19131)
Rely on the field metadata items to always display all object's fields
in the fields widget configuration editor. If fields are missing in the
returned view fields, we add the missing fields through object metadata.


https://github.com/user-attachments/assets/3c4d45e8-05d0-4943-be4b-bcf1e310155c
2026-03-31 19:00:16 +00:00
Charles Bochet 5bbfce7789 Add 1-21 upgrade command to backfill datasource to workspace table (#19180)
## Summary
- Adds a new `upgrade:1-21:backfill-datasource-to-workspace` command
that copies `dataSource.schema` into `workspace.databaseSchema` for all
active/suspended workspaces that haven't been migrated yet
- Registers the command in the 1-21 upgrade module and wires it into the
upgrade runner (runs before other 1-21 commands)
- Part of the ongoing deprecation of the `dataSource` table in favor of
storing `databaseSchema` directly on `WorkspaceEntity`
2026-03-31 20:34:46 +02:00
Etienne 887e0283c5 Direct execution - Follow up (#19177)
Feedbacks from https://github.com/twentyhq/twenty/pull/18972
2026-03-31 17:38:24 +00:00
Abdullah. 94e019f012 Complete the structure for homepage in new website. (#19162)
This PR completes the sections we need for the Homepage. Assets, such as
images, are still placeholder, and will be replaced as they become
available. We're still waiting on Lottie and 3d asset files from the
design team.
2026-03-31 17:19:50 +00:00
Abdul Rahman c23961fa81 Fix navbar object color not updating immediately when changed in edit mode (#19075)
https://github.com/user-attachments/assets/f2a8f2af-b92f-4d2e-9570-fe66f0a3eca0
2026-03-31 16:51:01 +00:00
936 changed files with 42203 additions and 52310 deletions
+3 -20
View File
@@ -35,15 +35,10 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -53,16 +48,10 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
@@ -421,12 +410,6 @@ jobs:
echo "valid=$valid" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Install OpenAPI Diff Tool
run: |
# Using the Java-based OpenAPITools/openapi-diff via Docker
+4 -12
View File
@@ -36,15 +36,10 @@ jobs:
runs-on: ubuntu-latest-4-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -54,9 +49,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
env:
@@ -164,7 +156,7 @@ jobs:
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty remote add --token $SEED_API_KEY --url http://localhost:3000
npx --no-install twenty remote add --api-key $SEED_API_KEY --api-url http://localhost:3000
- name: Deploy scaffolded app
run: |
+3 -11
View File
@@ -25,15 +25,10 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -43,9 +38,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
+3 -11
View File
@@ -53,15 +53,10 @@ jobs:
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -71,9 +66,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
env:
+6 -25
View File
@@ -83,15 +83,10 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -101,9 +96,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
@@ -231,15 +223,10 @@ jobs:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -249,16 +236,10 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
@@ -121,7 +121,7 @@ jobs:
- name: Start container
run: |
docker run -d --name twenty-app-dev \
-p 3000:3000 \
-p 2020:2020 \
twenty-app-dev-ci
docker logs twenty-app-dev -f &
- name: Wait for server health
@@ -129,10 +129,10 @@ jobs:
echo "Waiting for twenty-app-dev to become healthy..."
count=0
while true; do
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/healthz 2>/dev/null || echo "000")
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2020/healthz 2>/dev/null || echo "000")
if [ "$status" = "200" ]; then
echo "Server is healthy!"
curl -s http://localhost:3000/healthz
curl -s http://localhost:2020/healthz
break
fi
+3 -8
View File
@@ -26,15 +26,10 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
+3 -11
View File
@@ -29,15 +29,10 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
image: postgres:18
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
@@ -47,9 +42,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
+7 -6
View File
@@ -82,11 +82,11 @@
"@sentry/types": "^8",
"@storybook-community/storybook-addon-cookie": "^5.0.0",
"@storybook/addon-coverage": "^3.0.0",
"@storybook/addon-docs": "^10.2.13",
"@storybook/addon-links": "^10.2.13",
"@storybook/addon-vitest": "^10.2.13",
"@storybook/addon-docs": "^10.3.3",
"@storybook/addon-links": "^10.3.3",
"@storybook/addon-vitest": "^10.3.3",
"@storybook/icons": "^2.0.1",
"@storybook/react-vite": "^10.2.13",
"@storybook/react-vite": "^10.3.3",
"@storybook/test-runner": "^0.24.2",
"@swc-node/register": "^1.11.1",
"@swc/cli": "^0.7.10",
@@ -154,9 +154,9 @@
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
"source-map-support": "^0.5.20",
"storybook": "^10.2.13",
"storybook": "^10.3.3",
"storybook-addon-mock-date": "2.0.0",
"storybook-addon-pseudo-states": "^10.2.13",
"storybook-addon-pseudo-states": "^10.3.3",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.2.3",
@@ -180,6 +180,7 @@
"graphql": "16.8.1",
"type-fest": "4.10.1",
"typescript": "5.9.2",
"nodemailer": "8.0.4",
"graphql-redis-subscriptions/ioredis": "^5.6.0",
"@lingui/core": "5.1.2",
"@types/qs": "6.9.16",
+23 -138
View File
@@ -12,164 +12,49 @@
</div>
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a readytorun project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
- Zeroconfig project bootstrap
- Preconfigured scripts for auth, dev mode (watch & sync), uninstall, and function management
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- Docker (for the local Twenty dev server)
The official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). Sets up a ready-to-run project with [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
## Quick start
```bash
# Scaffold a new app — the CLI will offer to start a local Twenty server
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# The scaffolder can automatically:
# 1. Start a local Twenty server (Docker)
# 2. Open the browser to log in (tim@apple.dev / tim@apple.dev)
# 3. Authenticate your app via OAuth
# Or do it manually:
yarn twenty server start # Start local Twenty server
yarn twenty remote add http://localhost:2020 --as local # Authenticate via OAuth
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates typed CoreApiClient — MetadataApiClient ships pre-built — both available via `twenty-client-sdk`)
yarn twenty dev
# Watch your application's function logs
yarn twenty logs
# Execute a function with a JSON payload
yarn twenty exec -n my-function -p '{"key": "value"}'
# Execute the pre-install function
yarn twenty exec --preInstall
# Execute the post-install function
yarn twenty exec --postInstall
# Build the app for distribution
yarn twenty build
# Publish the app to npm or directly to a Twenty server
yarn twenty publish
# Uninstall the application from the current workspace
yarn twenty uninstall
```
The scaffolder will:
1. Create a new project with TypeScript, linting, and a preconfigured `twenty` CLI
2. Optionally start a local Twenty server (Docker)
3. Open the browser for OAuth authentication
4. Scaffold example entities and an integration test
## Scaffolding modes
Control which example files are included when creating a new app:
| Flag | Behavior |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `--minimal` | **(default)** Creates only core files (`application-config.ts`, `default-role.ts`, pre/post-install functions) |
| `--exhaustive` | Creates all example entities |
| Flag | Behavior |
| ------------------ | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
Other flags:
```bash
# Default: all examples included
npx create-twenty-app@latest my-app
- `--name <name>` — set the app name (skips the prompt)
- `--display-name <displayName>` — set the display name (skips the prompt)
- `--description <description>` — set the description (skips the prompt)
- `--skip-local-instance` — skip the local server setup prompt
# Minimal: only core files
npx create-twenty-app@latest my-app -m
```
## Documentation
## What gets scaffolded
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started)**:
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
- TypeScript configuration, Oxlint, package.json, .gitignore
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**Example files (controlled by scaffolding mode):**
- `objects/example-object.ts` — Example custom object with a text field
- `fields/example-field.ts` — Example standalone field extending the example object
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
- `front-components/hello-world.tsx` — Example front component
- `views/example-view.ts` — Example saved view for the example object
- `navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
- `skills/example-skill.ts` — Example AI agent skill definition
- `__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
## Local server
The scaffolder can start a local Twenty dev server for you (all-in-one Docker image with PostgreSQL, Redis, server, and worker on port 2020). These commands only apply to the Docker-based dev server — they do not manage a Twenty instance started from source (e.g. `npx nx start twenty-server` on port 3000). You can also manage it manually:
```bash
yarn twenty server start # Start (pulls image if needed)
yarn twenty server status # Check if it's healthy
yarn twenty server logs # Stream logs
yarn twenty server stop # Stop (data is preserved)
yarn twenty server reset # Wipe all data and start fresh
```
The server is pre-seeded with a workspace and user (`tim@apple.dev` / `tim@apple.dev`).
## Next steps
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty remote add <url>` to authenticate with your Twenty workspace via OAuth.
- Explore the generated project and add your first entity with `yarn twenty add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- `CoreApiClient` is auto-generated by `yarn twenty dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient } from 'twenty-client-sdk/core'` and `import { MetadataApiClient } from 'twenty-client-sdk/metadata'`.
## Build and publish your application
Once your app is ready, build and publish it using the CLI:
```bash
# Build the app (output goes to .twenty/output/)
yarn twenty build
# Build and create a tarball (.tgz) for distribution
yarn twenty build --tarball
# Publish to npm (requires npm login)
yarn twenty publish
# Publish with a dist-tag (e.g. beta, next)
yarn twenty publish --tag beta
# Deploy directly to a Twenty server (builds, uploads, and installs in one step)
yarn twenty deploy
```
### Publish to the Twenty marketplace
You can also contribute your application to the curated marketplace:
```bash
git clone https://github.com/twentyhq/twenty.git
cd twenty
git checkout -b feature/my-awesome-app
```
- Copy your app folder into `twenty/packages/twenty-apps`.
- Commit your changes and open a pull request on https://github.com/twentyhq/twenty
Our team reviews contributions for quality, security, and reusability before merging.
- [Getting Started](https://docs.twenty.com/developers/extend/apps/getting-started) — step-by-step setup, project structure, server management, CI
- [Building Apps](https://docs.twenty.com/developers/extend/apps/building) — entity definitions, API clients, testing
- [Publishing](https://docs.twenty.com/developers/extend/apps/publishing) — deploy, npm publish, marketplace
## Troubleshooting
- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`.
- Auth not working: make sure you're logged in to Twenty in the browser first, then run `yarn twenty remote add <url>`.
- Auth not working: make sure you are logged in to Twenty in the browser, then run `yarn twenty remote add`.
- Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client.
## Contributing
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.8.0-canary.7",
"version": "0.8.0-canary.8",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+5 -3
View File
@@ -13,10 +13,10 @@ const program = new Command(packageJson.name)
'Output the current version of create-twenty-app.',
)
.argument('[directory]')
.option('-e, --exhaustive', 'Create all example entities (default)')
.option('-e, --exhaustive', 'Create all example entities')
.option(
'-m, --minimal',
'Create only core entities (application-config and default-role)',
'Create only core entities (application-config and default-role) (default)',
)
.option('-n, --name <name>', 'Application name (skips prompt)')
.option(
@@ -69,7 +69,9 @@ const program = new Command(packageJson.name)
process.exit(1);
}
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
const mode: ScaffoldingMode = options?.exhaustive
? 'exhaustive'
: 'minimal';
await new CreateAppCommand().execute({
directory,
@@ -39,7 +39,7 @@ export class CreateAppCommand {
try {
const exampleOptions = this.resolveExampleOptions(
options.mode ?? 'exhaustive',
options.mode ?? 'minimal',
);
await this.validateDirectory(appDirectory);
@@ -163,7 +163,7 @@ export class CreateAppCommand {
includeExampleNavigationMenuItem: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleIntegrationTest: false,
includeExampleIntegrationTest: true,
};
}
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -9,17 +9,7 @@
},
"packageManager": "yarn@4.9.2",
"scripts": {
"remote:add": "twenty remote add --local",
"remote:status": "twenty remote status",
"remote:switch": "twenty remote switch",
"remote:list": "twenty remote list",
"remote:remove": "twenty remote remove",
"dev": "twenty dev",
"add": "twenty add",
"logs": "twenty logs",
"exec": "twenty exec",
"uninstall": "twenty uninstall",
"help": "twenty help",
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -9,17 +9,7 @@
},
"packageManager": "yarn@4.9.2",
"scripts": {
"remote:add": "twenty remote add --local",
"remote:status": "twenty remote status",
"remote:switch": "twenty remote switch",
"remote:list": "twenty remote list",
"remote:remove": "twenty remote remove",
"dev": "twenty dev",
"add": "twenty add",
"logs": "twenty logs",
"exec": "twenty exec",
"uninstall": "twenty uninstall",
"help": "twenty help",
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -9,7 +9,9 @@
},
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty"
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest",
+2 -2
View File
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn twenty remote add http://localhost:2020 --as local
yarn twenty remote add --api-url http://localhost:2020 --as local
```
Then, start development mode to sync your app and watch for changes:
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
```bash
# Remotes & Authentication
yarn twenty remote add http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote status # Check auth status
yarn twenty remote switch # Switch default remote
yarn twenty remote list # List all configured remotes
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn twenty remote add http://localhost:2020 --as local
yarn twenty remote add --api-url http://localhost:2020 --as local
```
Then, start development mode to sync your app and watch for changes:
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
```bash
# Remotes & Authentication
yarn twenty remote add http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote status # Check auth status
yarn twenty remote switch # Switch default remote
yarn twenty remote list # List all configured remotes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "0.8.0-canary.7",
"version": "0.8.0-canary.8",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -1720,23 +1720,28 @@ enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED
IS_JSON_FILTER_ENABLED
IS_AI_ENABLED
IS_COMMAND_MENU_ITEM_ENABLED
IS_MARKETPLACE_ENABLED
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
IS_COMMAND_MENU_ITEM_ENABLED
IS_DRAFT_EMAIL_ENABLED
IS_USAGE_ANALYTICS_ENABLED
IS_RICH_TEXT_V1_MIGRATED
IS_DIRECT_GRAPHQL_EXECUTION_ENABLED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_CONNECTED_ACCOUNT_MIGRATED
IS_GRAPHQL_QUERY_TIMING_ENABLED
IS_RECORD_TABLE_WIDGET_ENABLED
IS_DATASOURCE_MIGRATED
}
type ClientConfigMaintenanceMode {
startAt: DateTime!
endAt: DateTime!
link: String
}
type ClientConfig {
appVersion: String
authProviders: AuthProviders!
@@ -1765,6 +1770,8 @@ type ClientConfig {
calendarBookingPageId: String
isCloudflareIntegrationEnabled: Boolean!
isClickHouseConfigured: Boolean!
isWorkspaceSchemaDDLLocked: Boolean!
maintenance: ClientConfigMaintenanceMode
}
type UsageBreakdownItem {
@@ -1961,6 +1968,12 @@ type AdminPanelHealthServiceData {
queues: [AdminPanelWorkerQueueHealth!]
}
type MaintenanceMode {
startAt: DateTime!
endAt: DateTime!
link: String
}
type ModelsDevModelSuggestion {
modelId: String!
name: String!
@@ -2267,6 +2280,33 @@ type FieldConnection {
edges: [FieldEdge!]!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type FrontComponent {
id: UUID!
name: String!
description: String
sourceComponentPath: String!
builtComponentPath: String!
componentName: String!
builtComponentChecksum: String!
universalIdentifier: UUID
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
}
type LogicFunctionLogs {
"""Execution Logs"""
logs: String!
@@ -2289,11 +2329,6 @@ type AuthorizeApp {
redirectUrl: String!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type AuthTokenPair {
accessOrWorkspaceAgnosticToken: AuthToken!
refreshToken: AuthToken!
@@ -2402,11 +2437,6 @@ type WorkspaceMigration {
actions: JSON!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type File {
id: UUID!
path: String!
@@ -2526,23 +2556,6 @@ type PostgresCredentials {
workspaceId: UUID!
}
type FrontComponent {
id: UUID!
name: String!
description: String
sourceComponentPath: String!
builtComponentPath: String!
componentName: String!
builtComponentChecksum: String!
universalIdentifier: UUID
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
}
type CommandMenuItem {
id: UUID!
workflowVersionId: UUID
@@ -2567,20 +2580,15 @@ enum EngineComponentKey {
NAVIGATE_TO_NEXT_RECORD
NAVIGATE_TO_PREVIOUS_RECORD
CREATE_NEW_RECORD
DELETE_SINGLE_RECORD
DELETE_MULTIPLE_RECORDS
RESTORE_SINGLE_RECORD
RESTORE_MULTIPLE_RECORDS
DESTROY_SINGLE_RECORD
DESTROY_MULTIPLE_RECORDS
DELETE_RECORDS
RESTORE_RECORDS
DESTROY_RECORDS
ADD_TO_FAVORITES
REMOVE_FROM_FAVORITES
EXPORT_NOTE_TO_PDF
EXPORT_FROM_RECORD_INDEX
EXPORT_FROM_RECORD_SHOW
EXPORT_RECORDS
UPDATE_MULTIPLE_RECORDS
MERGE_MULTIPLE_RECORDS
EXPORT_MULTIPLE_RECORDS
IMPORT_RECORDS
EXPORT_VIEW
SEE_DELETED_RECORDS
@@ -2623,6 +2631,15 @@ enum EngineComponentKey {
VIEW_PREVIOUS_AI_CHATS
TRIGGER_WORKFLOW_VERSION
FRONT_COMPONENT_RENDERER
DELETE_SINGLE_RECORD
DELETE_MULTIPLE_RECORDS
RESTORE_SINGLE_RECORD
RESTORE_MULTIPLE_RECORDS
DESTROY_SINGLE_RECORD
DESTROY_MULTIPLE_RECORDS
EXPORT_FROM_RECORD_INDEX
EXPORT_FROM_RECORD_SHOW
EXPORT_MULTIPLE_RECORDS
}
enum CommandMenuItemAvailabilityType {
@@ -2787,10 +2804,12 @@ type AgentChatThread {
type AgentMessage {
id: UUID!
threadId: UUID!
turnId: UUID!
turnId: UUID
agentId: UUID
role: String!
status: String!
parts: [AgentMessagePart!]!
processedAt: DateTime
createdAt: DateTime!
}
@@ -2805,6 +2824,22 @@ type AISystemPromptPreview {
estimatedTokenCount: Int!
}
type ChatStreamCatchupChunks {
chunks: [JSON!]!
maxSeq: Int!
}
type SendChatMessageResult {
messageId: String!
queued: Boolean!
streamId: String
}
type AgentChatEvent {
threadId: String!
event: JSON!
}
type AgentChatThreadEdge {
"""The node containing the AgentChatThread"""
node: AgentChatThread!
@@ -3152,6 +3187,7 @@ type Query {
minimalMetadata: MinimalMetadata!
chatThread(id: UUID!): AgentChatThread!
chatMessages(threadId: UUID!): [AgentMessage!]!
chatStreamCatchupChunks(threadId: UUID!): ChatStreamCatchupChunks!
getAISystemPromptPreview: AISystemPromptPreview!
skills: [Skill!]!
skill(id: UUID!): Skill
@@ -3202,6 +3238,7 @@ type Query {
getModelsDevProviders: [ModelsDevProviderSuggestion!]!
getModelsDevSuggestions(providerType: String!): [ModelsDevModelSuggestion!]!
getAdminAiUsageByWorkspace(periodStart: DateTime, periodEnd: DateTime): [UsageBreakdownItem!]!
getMaintenanceMode: MaintenanceMode
getUsageAnalytics(input: UsageAnalyticsInput): UsageAnalytics!
getPostgresCredentials: PostgresCredentials
findManyPublicDomains: [PublicDomain!]!
@@ -3452,6 +3489,9 @@ type Mutation {
updateWebhook(input: UpdateWebhookInput!): Webhook!
deleteWebhook(id: UUID!): Webhook!
createChatThread: AgentChatThread!
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String): SendChatMessageResult!
stopAgentChatStream(threadId: UUID!): Boolean!
deleteQueuedChatMessage(messageId: UUID!): Boolean!
createSkill(input: CreateSkillInput!): Skill!
updateSkill(input: UpdateSkillInput!): Skill!
deleteSkill(id: UUID!): Skill!
@@ -3519,6 +3559,8 @@ type Mutation {
removeAiProvider(providerName: String!): Boolean!
addModelToProvider(providerName: String!, modelConfig: JSON!): Boolean!
removeModelFromProvider(providerName: String!, modelName: String!): Boolean!
setMaintenanceMode(startAt: DateTime!, endAt: DateTime!, link: String): Boolean!
clearMaintenanceMode: Boolean!
enablePostgresProxy: PostgresCredentials!
disablePostgresProxy: PostgresCredentials!
createPublicDomain(domain: String!): PublicDomain!
@@ -3766,8 +3808,13 @@ input UpsertFieldsWidgetGroupInput {
}
input UpsertFieldsWidgetFieldInput {
"""The id of the view field"""
viewFieldId: UUID!
"""The id of the view field. Required if fieldMetadataId is not provided."""
viewFieldId: UUID
"""
The id of the field metadata. Used to create a new view field when viewFieldId is not provided.
"""
fieldMetadataId: UUID
isVisible: Boolean!
position: Float!
}
@@ -4536,6 +4583,7 @@ enum FileFolder {
type Subscription {
onEventSubscription(eventStreamId: String!): EventSubscription
logicFunctionLogs(input: LogicFunctionLogsInput!): LogicFunctionLogs!
onAgentChatEvent(threadId: UUID!): AgentChatEvent!
}
input LogicFunctionLogsInput {
@@ -1422,7 +1422,14 @@ export interface PublicFeatureFlag {
__typename: 'PublicFeatureFlag'
}
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_GRAPHQL_QUERY_TIMING_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
export interface ClientConfigMaintenanceMode {
startAt: Scalars['DateTime']
endAt: Scalars['DateTime']
link?: Scalars['String']
__typename: 'ClientConfigMaintenanceMode'
}
export interface ClientConfig {
appVersion?: Scalars['String']
@@ -1452,6 +1459,8 @@ export interface ClientConfig {
calendarBookingPageId?: Scalars['String']
isCloudflareIntegrationEnabled: Scalars['Boolean']
isClickHouseConfigured: Scalars['Boolean']
isWorkspaceSchemaDDLLocked: Scalars['Boolean']
maintenance?: ClientConfigMaintenanceMode
__typename: 'ClientConfig'
}
@@ -1621,6 +1630,13 @@ export interface AdminPanelHealthServiceData {
__typename: 'AdminPanelHealthServiceData'
}
export interface MaintenanceMode {
startAt: Scalars['DateTime']
endAt: Scalars['DateTime']
link?: Scalars['String']
__typename: 'MaintenanceMode'
}
export interface ModelsDevModelSuggestion {
modelId: Scalars['String']
name: Scalars['String']
@@ -1939,6 +1955,36 @@ export interface FieldConnection {
__typename: 'FieldConnection'
}
export interface AuthToken {
token: Scalars['String']
expiresAt: Scalars['DateTime']
__typename: 'AuthToken'
}
export interface ApplicationTokenPair {
applicationAccessToken: AuthToken
applicationRefreshToken: AuthToken
__typename: 'ApplicationTokenPair'
}
export interface FrontComponent {
id: Scalars['UUID']
name: Scalars['String']
description?: Scalars['String']
sourceComponentPath: Scalars['String']
builtComponentPath: Scalars['String']
componentName: Scalars['String']
builtComponentChecksum: Scalars['String']
universalIdentifier?: Scalars['UUID']
applicationId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isHeadless: Scalars['Boolean']
usesSdkClient: Scalars['Boolean']
applicationTokenPair?: ApplicationTokenPair
__typename: 'FrontComponent'
}
export interface LogicFunctionLogs {
/** Execution Logs */
logs: Scalars['String']
@@ -1966,12 +2012,6 @@ export interface AuthorizeApp {
__typename: 'AuthorizeApp'
}
export interface AuthToken {
token: Scalars['String']
expiresAt: Scalars['DateTime']
__typename: 'AuthToken'
}
export interface AuthTokenPair {
accessOrWorkspaceAgnosticToken: AuthToken
refreshToken: AuthToken
@@ -2101,12 +2141,6 @@ export interface WorkspaceMigration {
__typename: 'WorkspaceMigration'
}
export interface ApplicationTokenPair {
applicationAccessToken: AuthToken
applicationRefreshToken: AuthToken
__typename: 'ApplicationTokenPair'
}
export interface File {
id: Scalars['UUID']
path: Scalars['String']
@@ -2233,24 +2267,6 @@ export interface PostgresCredentials {
__typename: 'PostgresCredentials'
}
export interface FrontComponent {
id: Scalars['UUID']
name: Scalars['String']
description?: Scalars['String']
sourceComponentPath: Scalars['String']
builtComponentPath: Scalars['String']
componentName: Scalars['String']
builtComponentChecksum: Scalars['String']
universalIdentifier?: Scalars['UUID']
applicationId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isHeadless: Scalars['Boolean']
usesSdkClient: Scalars['Boolean']
applicationTokenPair?: ApplicationTokenPair
__typename: 'FrontComponent'
}
export interface CommandMenuItem {
id: Scalars['UUID']
workflowVersionId?: Scalars['UUID']
@@ -2272,7 +2288,7 @@ export interface CommandMenuItem {
__typename: 'CommandMenuItem'
}
export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'EXPORT_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'GO_TO_WORKFLOWS' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'GO_TO_RUNS' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' | 'TRIGGER_WORKFLOW_VERSION' | 'FRONT_COMPONENT_RENDERER'
export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_RECORDS' | 'RESTORE_RECORDS' | 'DESTROY_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_RECORDS' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'GO_TO_WORKFLOWS' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'GO_TO_RUNS' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' | 'TRIGGER_WORKFLOW_VERSION' | 'FRONT_COMPONENT_RENDERER' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'EXPORT_MULTIPLE_RECORDS'
export type CommandMenuItemAvailabilityType = 'GLOBAL' | 'RECORD_SELECTION' | 'FALLBACK'
@@ -2448,10 +2464,12 @@ export interface AgentChatThread {
export interface AgentMessage {
id: Scalars['UUID']
threadId: Scalars['UUID']
turnId: Scalars['UUID']
turnId?: Scalars['UUID']
agentId?: Scalars['UUID']
role: Scalars['String']
status: Scalars['String']
parts: AgentMessagePart[]
processedAt?: Scalars['DateTime']
createdAt: Scalars['DateTime']
__typename: 'AgentMessage'
}
@@ -2469,6 +2487,25 @@ export interface AISystemPromptPreview {
__typename: 'AISystemPromptPreview'
}
export interface ChatStreamCatchupChunks {
chunks: Scalars['JSON'][]
maxSeq: Scalars['Int']
__typename: 'ChatStreamCatchupChunks'
}
export interface SendChatMessageResult {
messageId: Scalars['String']
queued: Scalars['Boolean']
streamId?: Scalars['String']
__typename: 'SendChatMessageResult'
}
export interface AgentChatEvent {
threadId: Scalars['String']
event: Scalars['JSON']
__typename: 'AgentChatEvent'
}
export interface AgentChatThreadEdge {
/** The node containing the AgentChatThread */
node: AgentChatThread
@@ -2713,6 +2750,7 @@ export interface Query {
minimalMetadata: MinimalMetadata
chatThread: AgentChatThread
chatMessages: AgentMessage[]
chatStreamCatchupChunks: ChatStreamCatchupChunks
getAISystemPromptPreview: AISystemPromptPreview
skills: Skill[]
skill?: Skill
@@ -2754,6 +2792,7 @@ export interface Query {
getModelsDevProviders: ModelsDevProviderSuggestion[]
getModelsDevSuggestions: ModelsDevModelSuggestion[]
getAdminAiUsageByWorkspace: UsageBreakdownItem[]
getMaintenanceMode?: MaintenanceMode
getUsageAnalytics: UsageAnalytics
getPostgresCredentials?: PostgresCredentials
findManyPublicDomains: PublicDomain[]
@@ -2899,6 +2938,9 @@ export interface Mutation {
updateWebhook: Webhook
deleteWebhook: Webhook
createChatThread: AgentChatThread
sendChatMessage: SendChatMessageResult
stopAgentChatStream: Scalars['Boolean']
deleteQueuedChatMessage: Scalars['Boolean']
createSkill: Skill
updateSkill: Skill
deleteSkill: Skill
@@ -2966,6 +3008,8 @@ export interface Mutation {
removeAiProvider: Scalars['Boolean']
addModelToProvider: Scalars['Boolean']
removeModelFromProvider: Scalars['Boolean']
setMaintenanceMode: Scalars['Boolean']
clearMaintenanceMode: Scalars['Boolean']
enablePostgresProxy: PostgresCredentials
disablePostgresProxy: PostgresCredentials
createPublicDomain: PublicDomain
@@ -3001,6 +3045,7 @@ export type FileFolder = 'ProfilePicture' | 'WorkspaceLogo' | 'Attachment' | 'Pe
export interface Subscription {
onEventSubscription?: EventSubscription
logicFunctionLogs: LogicFunctionLogs
onAgentChatEvent: AgentChatEvent
__typename: 'Subscription'
}
@@ -4482,6 +4527,14 @@ export interface PublicFeatureFlagGenqlSelection{
__scalar?: boolean | number
}
export interface ClientConfigMaintenanceModeGenqlSelection{
startAt?: boolean | number
endAt?: boolean | number
link?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface ClientConfigGenqlSelection{
appVersion?: boolean | number
authProviders?: AuthProvidersGenqlSelection
@@ -4510,6 +4563,8 @@ export interface ClientConfigGenqlSelection{
calendarBookingPageId?: boolean | number
isCloudflareIntegrationEnabled?: boolean | number
isClickHouseConfigured?: boolean | number
isWorkspaceSchemaDDLLocked?: boolean | number
maintenance?: ClientConfigMaintenanceModeGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -4685,6 +4740,14 @@ export interface AdminPanelHealthServiceDataGenqlSelection{
__scalar?: boolean | number
}
export interface MaintenanceModeGenqlSelection{
startAt?: boolean | number
endAt?: boolean | number
link?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface ModelsDevModelSuggestionGenqlSelection{
modelId?: boolean | number
name?: boolean | number
@@ -5034,6 +5097,39 @@ export interface FieldConnectionGenqlSelection{
__scalar?: boolean | number
}
export interface AuthTokenGenqlSelection{
token?: boolean | number
expiresAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface ApplicationTokenPairGenqlSelection{
applicationAccessToken?: AuthTokenGenqlSelection
applicationRefreshToken?: AuthTokenGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FrontComponentGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
sourceComponentPath?: boolean | number
builtComponentPath?: boolean | number
componentName?: boolean | number
builtComponentChecksum?: boolean | number
universalIdentifier?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isHeadless?: boolean | number
usesSdkClient?: boolean | number
applicationTokenPair?: ApplicationTokenPairGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface LogicFunctionLogsGenqlSelection{
/** Execution Logs */
logs?: boolean | number
@@ -5066,13 +5162,6 @@ export interface AuthorizeAppGenqlSelection{
__scalar?: boolean | number
}
export interface AuthTokenGenqlSelection{
token?: boolean | number
expiresAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AuthTokenPairGenqlSelection{
accessOrWorkspaceAgnosticToken?: AuthTokenGenqlSelection
refreshToken?: AuthTokenGenqlSelection
@@ -5223,13 +5312,6 @@ export interface WorkspaceMigrationGenqlSelection{
__scalar?: boolean | number
}
export interface ApplicationTokenPairGenqlSelection{
applicationAccessToken?: AuthTokenGenqlSelection
applicationRefreshToken?: AuthTokenGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FileGenqlSelection{
id?: boolean | number
path?: boolean | number
@@ -5366,25 +5448,6 @@ export interface PostgresCredentialsGenqlSelection{
__scalar?: boolean | number
}
export interface FrontComponentGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
sourceComponentPath?: boolean | number
builtComponentPath?: boolean | number
componentName?: boolean | number
builtComponentChecksum?: boolean | number
universalIdentifier?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isHeadless?: boolean | number
usesSdkClient?: boolean | number
applicationTokenPair?: ApplicationTokenPairGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface CommandMenuItemGenqlSelection{
id?: boolean | number
workflowVersionId?: boolean | number
@@ -5598,7 +5661,9 @@ export interface AgentMessageGenqlSelection{
turnId?: boolean | number
agentId?: boolean | number
role?: boolean | number
status?: boolean | number
parts?: AgentMessagePartGenqlSelection
processedAt?: boolean | number
createdAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
@@ -5619,6 +5684,28 @@ export interface AISystemPromptPreviewGenqlSelection{
__scalar?: boolean | number
}
export interface ChatStreamCatchupChunksGenqlSelection{
chunks?: boolean | number
maxSeq?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface SendChatMessageResultGenqlSelection{
messageId?: boolean | number
queued?: boolean | number
streamId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AgentChatEventGenqlSelection{
threadId?: boolean | number
event?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AgentChatThreadEdgeGenqlSelection{
/** The node containing the AgentChatThread */
node?: AgentChatThreadGenqlSelection
@@ -5868,6 +5955,7 @@ export interface QueryGenqlSelection{
minimalMetadata?: MinimalMetadataGenqlSelection
chatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID']} })
chatMessages?: (AgentMessageGenqlSelection & { __args: {threadId: Scalars['UUID']} })
chatStreamCatchupChunks?: (ChatStreamCatchupChunksGenqlSelection & { __args: {threadId: Scalars['UUID']} })
getAISystemPromptPreview?: AISystemPromptPreviewGenqlSelection
skills?: SkillGenqlSelection
skill?: (SkillGenqlSelection & { __args: {id: Scalars['UUID']} })
@@ -5915,6 +6003,7 @@ export interface QueryGenqlSelection{
getModelsDevProviders?: ModelsDevProviderSuggestionGenqlSelection
getModelsDevSuggestions?: (ModelsDevModelSuggestionGenqlSelection & { __args: {providerType: Scalars['String']} })
getAdminAiUsageByWorkspace?: (UsageBreakdownItemGenqlSelection & { __args?: {periodStart?: (Scalars['DateTime'] | null), periodEnd?: (Scalars['DateTime'] | null)} })
getMaintenanceMode?: MaintenanceModeGenqlSelection
getUsageAnalytics?: (UsageAnalyticsGenqlSelection & { __args?: {input?: (UsageAnalyticsInput | null)} })
getPostgresCredentials?: PostgresCredentialsGenqlSelection
findManyPublicDomains?: PublicDomainGenqlSelection
@@ -6079,6 +6168,9 @@ export interface MutationGenqlSelection{
updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} })
deleteWebhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} })
createChatThread?: AgentChatThreadGenqlSelection
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null)} })
stopAgentChatStream?: { __args: {threadId: Scalars['UUID']} }
deleteQueuedChatMessage?: { __args: {messageId: Scalars['UUID']} }
createSkill?: (SkillGenqlSelection & { __args: {input: CreateSkillInput} })
updateSkill?: (SkillGenqlSelection & { __args: {input: UpdateSkillInput} })
deleteSkill?: (SkillGenqlSelection & { __args: {id: Scalars['UUID']} })
@@ -6146,6 +6238,8 @@ export interface MutationGenqlSelection{
removeAiProvider?: { __args: {providerName: Scalars['String']} }
addModelToProvider?: { __args: {providerName: Scalars['String'], modelConfig: Scalars['JSON']} }
removeModelFromProvider?: { __args: {providerName: Scalars['String'], modelName: Scalars['String']} }
setMaintenanceMode?: { __args: {startAt: Scalars['DateTime'], endAt: Scalars['DateTime'], link?: (Scalars['String'] | null)} }
clearMaintenanceMode?: boolean | number
enablePostgresProxy?: PostgresCredentialsGenqlSelection
disablePostgresProxy?: PostgresCredentialsGenqlSelection
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String']} })
@@ -6258,8 +6352,10 @@ fields?: (UpsertFieldsWidgetFieldInput[] | null)}
export interface UpsertFieldsWidgetGroupInput {id: Scalars['UUID'],name: Scalars['String'],position: Scalars['Float'],isVisible: Scalars['Boolean'],fields: UpsertFieldsWidgetFieldInput[]}
export interface UpsertFieldsWidgetFieldInput {
/** The id of the view field */
viewFieldId: Scalars['UUID'],isVisible: Scalars['Boolean'],position: Scalars['Float']}
/** The id of the view field. Required if fieldMetadataId is not provided. */
viewFieldId?: (Scalars['UUID'] | null),
/** The id of the field metadata. Used to create a new view field when viewFieldId is not provided. */
fieldMetadataId?: (Scalars['UUID'] | null),isVisible: Scalars['Boolean'],position: Scalars['Float']}
export interface CreateApiKeyInput {name: Scalars['String'],expiresAt: Scalars['String'],revokedAt?: (Scalars['String'] | null),roleId: Scalars['UUID']}
@@ -6494,6 +6590,7 @@ export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationAc
export interface SubscriptionGenqlSelection{
onEventSubscription?: (EventSubscriptionGenqlSelection & { __args: {eventStreamId: Scalars['String']} })
logicFunctionLogs?: (LogicFunctionLogsGenqlSelection & { __args: {input: LogicFunctionLogsInput} })
onAgentChatEvent?: (AgentChatEventGenqlSelection & { __args: {threadId: Scalars['UUID']} })
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -7445,6 +7542,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const ClientConfigMaintenanceMode_possibleTypes: string[] = ['ClientConfigMaintenanceMode']
export const isClientConfigMaintenanceMode = (obj?: { __typename?: any } | null): obj is ClientConfigMaintenanceMode => {
if (!obj?.__typename) throw new Error('__typename is missing in "isClientConfigMaintenanceMode"')
return ClientConfigMaintenanceMode_possibleTypes.includes(obj.__typename)
}
const ClientConfig_possibleTypes: string[] = ['ClientConfig']
export const isClientConfig = (obj?: { __typename?: any } | null): obj is ClientConfig => {
if (!obj?.__typename) throw new Error('__typename is missing in "isClientConfig"')
@@ -7605,6 +7710,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const MaintenanceMode_possibleTypes: string[] = ['MaintenanceMode']
export const isMaintenanceMode = (obj?: { __typename?: any } | null): obj is MaintenanceMode => {
if (!obj?.__typename) throw new Error('__typename is missing in "isMaintenanceMode"')
return MaintenanceMode_possibleTypes.includes(obj.__typename)
}
const ModelsDevModelSuggestion_possibleTypes: string[] = ['ModelsDevModelSuggestion']
export const isModelsDevModelSuggestion = (obj?: { __typename?: any } | null): obj is ModelsDevModelSuggestion => {
if (!obj?.__typename) throw new Error('__typename is missing in "isModelsDevModelSuggestion"')
@@ -7917,6 +8030,30 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const AuthToken_possibleTypes: string[] = ['AuthToken']
export const isAuthToken = (obj?: { __typename?: any } | null): obj is AuthToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthToken"')
return AuthToken_possibleTypes.includes(obj.__typename)
}
const ApplicationTokenPair_possibleTypes: string[] = ['ApplicationTokenPair']
export const isApplicationTokenPair = (obj?: { __typename?: any } | null): obj is ApplicationTokenPair => {
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationTokenPair"')
return ApplicationTokenPair_possibleTypes.includes(obj.__typename)
}
const FrontComponent_possibleTypes: string[] = ['FrontComponent']
export const isFrontComponent = (obj?: { __typename?: any } | null): obj is FrontComponent => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFrontComponent"')
return FrontComponent_possibleTypes.includes(obj.__typename)
}
const LogicFunctionLogs_possibleTypes: string[] = ['LogicFunctionLogs']
export const isLogicFunctionLogs = (obj?: { __typename?: any } | null): obj is LogicFunctionLogs => {
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionLogs"')
@@ -7957,14 +8094,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const AuthToken_possibleTypes: string[] = ['AuthToken']
export const isAuthToken = (obj?: { __typename?: any } | null): obj is AuthToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthToken"')
return AuthToken_possibleTypes.includes(obj.__typename)
}
const AuthTokenPair_possibleTypes: string[] = ['AuthTokenPair']
export const isAuthTokenPair = (obj?: { __typename?: any } | null): obj is AuthTokenPair => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthTokenPair"')
@@ -8133,14 +8262,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const ApplicationTokenPair_possibleTypes: string[] = ['ApplicationTokenPair']
export const isApplicationTokenPair = (obj?: { __typename?: any } | null): obj is ApplicationTokenPair => {
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationTokenPair"')
return ApplicationTokenPair_possibleTypes.includes(obj.__typename)
}
const File_possibleTypes: string[] = ['File']
export const isFile = (obj?: { __typename?: any } | null): obj is File => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFile"')
@@ -8253,14 +8374,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const FrontComponent_possibleTypes: string[] = ['FrontComponent']
export const isFrontComponent = (obj?: { __typename?: any } | null): obj is FrontComponent => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFrontComponent"')
return FrontComponent_possibleTypes.includes(obj.__typename)
}
const CommandMenuItem_possibleTypes: string[] = ['CommandMenuItem']
export const isCommandMenuItem = (obj?: { __typename?: any } | null): obj is CommandMenuItem => {
if (!obj?.__typename) throw new Error('__typename is missing in "isCommandMenuItem"')
@@ -8421,6 +8534,30 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const ChatStreamCatchupChunks_possibleTypes: string[] = ['ChatStreamCatchupChunks']
export const isChatStreamCatchupChunks = (obj?: { __typename?: any } | null): obj is ChatStreamCatchupChunks => {
if (!obj?.__typename) throw new Error('__typename is missing in "isChatStreamCatchupChunks"')
return ChatStreamCatchupChunks_possibleTypes.includes(obj.__typename)
}
const SendChatMessageResult_possibleTypes: string[] = ['SendChatMessageResult']
export const isSendChatMessageResult = (obj?: { __typename?: any } | null): obj is SendChatMessageResult => {
if (!obj?.__typename) throw new Error('__typename is missing in "isSendChatMessageResult"')
return SendChatMessageResult_possibleTypes.includes(obj.__typename)
}
const AgentChatEvent_possibleTypes: string[] = ['AgentChatEvent']
export const isAgentChatEvent = (obj?: { __typename?: any } | null): obj is AgentChatEvent => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAgentChatEvent"')
return AgentChatEvent_possibleTypes.includes(obj.__typename)
}
const AgentChatThreadEdge_possibleTypes: string[] = ['AgentChatThreadEdge']
export const isAgentChatThreadEdge = (obj?: { __typename?: any } | null): obj is AgentChatThreadEdge => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAgentChatThreadEdge"')
@@ -8947,19 +9084,18 @@ export const enumFeatureFlagKey = {
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
IS_AI_ENABLED: 'IS_AI_ENABLED' as const,
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
IS_MARKETPLACE_ENABLED: 'IS_MARKETPLACE_ENABLED' as const,
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const,
IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const,
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
IS_DIRECT_GRAPHQL_EXECUTION_ENABLED: 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' as const,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
IS_GRAPHQL_QUERY_TIMING_ENABLED: 'IS_GRAPHQL_QUERY_TIMING_ENABLED' as const,
IS_RECORD_TABLE_WIDGET_ENABLED: 'IS_RECORD_TABLE_WIDGET_ENABLED' as const,
IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const
}
@@ -9059,20 +9195,15 @@ export const enumEngineComponentKey = {
NAVIGATE_TO_NEXT_RECORD: 'NAVIGATE_TO_NEXT_RECORD' as const,
NAVIGATE_TO_PREVIOUS_RECORD: 'NAVIGATE_TO_PREVIOUS_RECORD' as const,
CREATE_NEW_RECORD: 'CREATE_NEW_RECORD' as const,
DELETE_SINGLE_RECORD: 'DELETE_SINGLE_RECORD' as const,
DELETE_MULTIPLE_RECORDS: 'DELETE_MULTIPLE_RECORDS' as const,
RESTORE_SINGLE_RECORD: 'RESTORE_SINGLE_RECORD' as const,
RESTORE_MULTIPLE_RECORDS: 'RESTORE_MULTIPLE_RECORDS' as const,
DESTROY_SINGLE_RECORD: 'DESTROY_SINGLE_RECORD' as const,
DESTROY_MULTIPLE_RECORDS: 'DESTROY_MULTIPLE_RECORDS' as const,
DELETE_RECORDS: 'DELETE_RECORDS' as const,
RESTORE_RECORDS: 'RESTORE_RECORDS' as const,
DESTROY_RECORDS: 'DESTROY_RECORDS' as const,
ADD_TO_FAVORITES: 'ADD_TO_FAVORITES' as const,
REMOVE_FROM_FAVORITES: 'REMOVE_FROM_FAVORITES' as const,
EXPORT_NOTE_TO_PDF: 'EXPORT_NOTE_TO_PDF' as const,
EXPORT_FROM_RECORD_INDEX: 'EXPORT_FROM_RECORD_INDEX' as const,
EXPORT_FROM_RECORD_SHOW: 'EXPORT_FROM_RECORD_SHOW' as const,
EXPORT_RECORDS: 'EXPORT_RECORDS' as const,
UPDATE_MULTIPLE_RECORDS: 'UPDATE_MULTIPLE_RECORDS' as const,
MERGE_MULTIPLE_RECORDS: 'MERGE_MULTIPLE_RECORDS' as const,
EXPORT_MULTIPLE_RECORDS: 'EXPORT_MULTIPLE_RECORDS' as const,
IMPORT_RECORDS: 'IMPORT_RECORDS' as const,
EXPORT_VIEW: 'EXPORT_VIEW' as const,
SEE_DELETED_RECORDS: 'SEE_DELETED_RECORDS' as const,
@@ -9114,7 +9245,16 @@ export const enumEngineComponentKey = {
ASK_AI: 'ASK_AI' as const,
VIEW_PREVIOUS_AI_CHATS: 'VIEW_PREVIOUS_AI_CHATS' as const,
TRIGGER_WORKFLOW_VERSION: 'TRIGGER_WORKFLOW_VERSION' as const,
FRONT_COMPONENT_RENDERER: 'FRONT_COMPONENT_RENDERER' as const
FRONT_COMPONENT_RENDERER: 'FRONT_COMPONENT_RENDERER' as const,
DELETE_SINGLE_RECORD: 'DELETE_SINGLE_RECORD' as const,
DELETE_MULTIPLE_RECORDS: 'DELETE_MULTIPLE_RECORDS' as const,
RESTORE_SINGLE_RECORD: 'RESTORE_SINGLE_RECORD' as const,
RESTORE_MULTIPLE_RECORDS: 'RESTORE_MULTIPLE_RECORDS' as const,
DESTROY_SINGLE_RECORD: 'DESTROY_SINGLE_RECORD' as const,
DESTROY_MULTIPLE_RECORDS: 'DESTROY_MULTIPLE_RECORDS' as const,
EXPORT_FROM_RECORD_INDEX: 'EXPORT_FROM_RECORD_INDEX' as const,
EXPORT_FROM_RECORD_SHOW: 'EXPORT_FROM_RECORD_SHOW' as const,
EXPORT_MULTIPLE_RECORDS: 'EXPORT_MULTIPLE_RECORDS' as const
}
export const enumCommandMenuItemAvailabilityType = {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
/bin/sh /etc/s6-overlay/scripts/register-crons.sh
@@ -1,10 +1,13 @@
#!/bin/sh
set -e
step_start() { echo "==> START $1"; }
step_done() { echo "==> DONE"; }
# Wait for PostgreSQL to be ready (timeout after 60s)
echo "Waiting for PostgreSQL..."
step_start "Waiting for PostgreSQL"
TRIES=0
until su-exec postgres pg_isready -h localhost; do
until su-exec postgres pg_isready -h localhost > /dev/null 2>&1; do
TRIES=$((TRIES + 1))
if [ "$TRIES" -ge 120 ]; then
echo "ERROR: PostgreSQL did not become ready within 60s"
@@ -12,7 +15,7 @@ until su-exec postgres pg_isready -h localhost; do
fi
sleep 0.5
done
echo "PostgreSQL is ready."
step_done
# Create role if it doesn't exist
su-exec postgres psql -h localhost -tc \
@@ -31,26 +34,36 @@ has_schema=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
"SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')")
if [ "$has_schema" = "f" ]; then
echo "Database appears to be empty, running initial setup..."
step_start "Running initial database setup"
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/database/scripts/setup-db.js
step_done
fi
# Always run migrations (idempotent — skips already-applied ones)
step_start "Running migrations"
yarn database:migrate:prod --force
step_done
step_start "Flushing cache"
yarn command:prod cache:flush
step_done
step_start "Running upgrade"
yarn command:prod upgrade
step_done
step_start "Flushing cache"
yarn command:prod cache:flush
step_done
# Only seed on first boot — check if the dev workspace already exists
has_workspace=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
"SELECT EXISTS (SELECT 1 FROM core.workspace WHERE id = '20202020-1c25-4d02-bf25-6aeccf7ea419')")
if [ "$has_workspace" = "f" ]; then
echo "Seeding app dev data..."
step_start "Seeding workspace data"
yarn command:prod workspace:seed:dev --light || true
else
echo "Dev workspace already seeded, skipping."
step_done
fi
echo "Database initialization complete."
echo "==> START Database ready"
echo "==> DONE"
@@ -0,0 +1,9 @@
#!/bin/sh
set -e
echo "==> START Registering cron jobs"
cd /app/packages/twenty-server
yarn command:prod cron:register:all --dev-mode
echo "==> DONE"
+5 -5
View File
@@ -195,9 +195,9 @@ RUN find /app/packages/twenty-server/dist -name '*.js.map' -delete
# s6 service definitions
COPY packages/twenty-docker/twenty-app-dev/rootfs/ /
RUN mkdir -p /data/postgres /data/redis /app/.local-storage \
RUN mkdir -p /data/postgres /data/redis /app/packages/twenty-server/.local-storage \
&& chown -R postgres:postgres /data/postgres \
&& chown 1000:1000 /data/redis /app/.local-storage
&& chown 1000:1000 /data/redis /app/packages/twenty-server/.local-storage
ARG REACT_APP_SERVER_BASE_URL
ARG APP_VERSION=0.0.0
@@ -211,14 +211,14 @@ ENV PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/default \
REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL \
APP_VERSION=$APP_VERSION \
NODE_ENV=development \
NODE_PORT=3000 \
NODE_PORT=2020 \
DISABLE_DB_MIGRATIONS=true \
DISABLE_CRON_JOBS_REGISTRATION=true \
IS_BILLING_ENABLED=false \
SIGN_IN_PREFILLED=true
EXPOSE 3000
VOLUME ["/data/postgres", "/app/.local-storage"]
EXPOSE 2020
VOLUME ["/data/postgres", "/app/packages/twenty-server/.local-storage"]
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
LABEL org.opencontainers.image.description="All-in-one Twenty image for local development and SDK usage. Includes PostgreSQL, Redis, server, and worker."
File diff suppressed because it is too large Load Diff
@@ -4,72 +4,142 @@ description: Create your first Twenty app in minutes.
---
<Warning>
Apps are currently in alpha testing. The feature is functional but still evolving.
Apps are currently in alpha. The feature works but is still evolving.
</Warning>
Apps let you extend Twenty with custom objects, fields, logic functions, AI skills, and UI components — all managed as code.
**What you can build:**
- Custom objects, fields, views, and navigation items to shape your data model
- Logic functions triggered by HTTP routes, cron schedules, or database events
- Front components that render directly inside Twenty's UI
- Skills that extend Twenty's AI agents
- Deploy an app across multiple workspaces
## Prerequisites
- Node.js 24+
- Yarn 4
- Docker (or a running local Twenty instance)
Before you begin, make sure the following is installed on your machine:
## Getting Started
- **Node.js 24+** — [Download here](https://nodejs.org/)
- **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable`
- **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running.
Create a new app using the official scaffolder, then authenticate and start developing:
## Step 1: Scaffold your app
Open a terminal and run:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Use `--minimal` option to scaffold a minimal installation
You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults.
From here you can:
This creates a new folder called `my-twenty-app` with everything you need.
<Note>
The scaffolder supports these flags:
- `--minimal` — scaffold only the essential files, no examples (default)
- `--exhaustive` — scaffold all example entities
- `--name <name>` — set the app name (skips the prompt)
- `--display-name <displayName>` — set the display name (skips the prompt)
- `--description <description>` — set the description (skips the prompt)
- `--skip-local-instance` — skip the local server setup prompt
</Note>
## Step 2: Set up a local Twenty instance
The scaffolder will ask:
> **Would you like to set up a local Twenty instance?**
- **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue.
- **Type `no`** — Choose this if you already have a Twenty server running locally.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Should start local instance?" />
</div>
## Step 3: Sign in to your workspace
Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account:
- **Email:** `tim@apple.dev`
- **Password:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty login screen" />
</div>
## Step 4: Authorize the app
After you sign in, you will see an authorization screen. This lets your app interact with your workspace.
Click **Authorize** to continue.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI authorization screen" />
</div>
Once authorized, your terminal will confirm that everything is set up.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App scaffolded successfully" />
</div>
## Step 5: Start developing
Go into your new app folder and start the development server:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
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).
This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal.
## Project structure (scaffolded)
For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag:
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
```bash filename="Terminal"
yarn twenty dev --verbose
```
- 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 core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode
<Warning>
Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/developers/extend/apps/publishing) for details.
</Warning>
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Dev mode terminal output" />
</div>
## Step 6: See your app in Twenty
Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps list showing My twenty app" />
</div>
Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Application registration details" />
</div>
Click **View installed app** to see the installed app. The **About** tab shows the current version and management options:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installed app — About tab" />
</div>
Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installed app — Content tab" />
</div>
You are all set! Edit any file in `src/` and the changes will be picked up automatically.
Head over to [Building Apps](/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more.
---
## Project structure
The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -82,123 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).
By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above.
At a high level:
### Key files
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus a `twenty` script that delegates to the local `twenty` CLI. Run `yarn twenty help` to list all available commands.
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `.twenty/`, `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.
- **.oxlintrc.json** 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.
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
- **src/**: The main place where you define your application-as-code
| File / Folder | Purpose |
|---|---|
| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. |
| `src/application-config.ts` | **Required.** The main configuration file for your app. |
| `src/roles/` | Defines roles that control what your logic functions can access. |
| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. |
| `src/front-components/` | React components that render inside Twenty's UI. |
| `src/objects/` | Custom object definitions to extend your data model. |
| `src/fields/` | Custom fields added to existing objects. |
| `src/views/` | Saved view configurations. |
| `src/navigation-menu-items/` | Custom links in the sidebar navigation. |
| `src/skills/` | Skills that extend Twenty's AI agents. |
| `src/agents/` | AI agents with custom prompts. |
| `src/page-layouts/` | Custom page layouts for record views. |
| `src/__tests__/` | Integration tests (setup + example test). |
| `public/` | Static assets (images, fonts) served with your app. |
### Entity detection
## Managing remotes
The SDK detects entities by parsing your TypeScript files for **`export default define<Entity>({...})`** calls. Each entity type has a corresponding helper function exported from `twenty-sdk`:
| Helper function | Entity type |
|-----------------|-------------|
| `defineObject` | Custom object definitions |
| `defineLogicFunction` | Logic function definitions |
| `definePreInstallLogicFunction` | Pre-install logic function (runs before installation) |
| `definePostInstallLogicFunction` | Post-install logic function (runs after installation) |
| `defineFrontComponent` | Front component definitions |
| `defineRole` | Role definitions |
| `defineField` | Field extensions for existing objects |
| `defineView` | Saved view definitions |
| `defineNavigationMenuItem` | Navigation menu item definitions |
| `defineSkill` | AI agent skill definitions |
<Note>
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
</Note>
Example of a detected entity:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Later commands will add more files and folders:
- `yarn twenty dev` will auto-generate the typed `CoreApiClient` (for workspace data via `/graphql`) into `node_modules/twenty-client-sdk/`. The `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built and is available immediately. Import them from `twenty-client-sdk/core` and `twenty-client-sdk/metadata` respectively.
- `yarn twenty add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
## Authentication
The first time you run `yarn twenty auth:login`, you'll be prompted for:
- API URL (defaults to http://localhost:3000 or your current workspace profile)
- API key
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch between them.
### Managing workspaces
A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
Once you've switched workspaces with `yarn twenty auth:switch`, all subsequent commands will use that workspace by default. You can still override it temporarily with `--workspace <name>`.
Your credentials are stored in `~/.twenty/config.json`.
## Local development server (`yarn twenty server`)
The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually.
### Starting the server
```bash filename="Terminal"
yarn twenty server start
```
This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning.
Two Docker volumes are created to persist data between restarts:
- `twenty-app-dev-data` — PostgreSQL database
- `twenty-app-dev-storage` — file storage
If port 2020 is already in use, you can start on a different port:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly.
Once started, the server is automatically registered as the `local` remote in your CLI config.
### Checking server status
```bash filename="Terminal"
yarn twenty server status
```
Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`).
### Viewing server logs
```bash filename="Terminal"
yarn twenty server logs
```
Streams the container logs. Use `--lines` to control how many recent lines to show:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Stopping the server
```bash filename="Terminal"
yarn twenty server stop
```
Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off.
### Resetting the server
```bash filename="Terminal"
yarn twenty server reset
```
Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance.
<Note>
The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started.
</Note>
### Command reference
| Command | Description |
|---------|-------------|
| `yarn twenty server start` | Start the local server (pulls image if needed) |
| `yarn twenty server start --port 3030` | Start on a custom port |
| `yarn twenty server stop` | Stop the server (preserves data) |
| `yarn twenty server status` | Show server status, URL, and credentials |
| `yarn twenty server logs` | Stream server logs |
| `yarn twenty server logs --lines 100` | Show the last 100 log lines |
| `yarn twenty server reset` | Delete all data and start fresh |
## CI with GitHub Actions
The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests.
The workflow:
1. Checks out your code
2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action
3. Installs dependencies with `yarn install --immutable`
4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
```
You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub.
To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow.
## 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 a single script in your package.json:
If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps.
**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Then add a `twenty` script:
**2. Add a `twenty` script to your `package.json`:**
```json filename="package.json"
{
@@ -208,25 +393,19 @@ Then add a `twenty` script:
}
```
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty dev`, `yarn twenty help`, etc.
You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands.
## How to use a local Twenty instance
If you're already running a Twenty instance locally (e.g. via `npx nx start twenty-server`), you can connect to it instead of using Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version.
</Note>
## Troubleshooting
- Authentication errors: run `yarn twenty auth:login` 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: restart `yarn twenty dev` — it auto-generates the typed client.
- Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment.
If you run into issues:
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
- Make sure **Docker is running** before starting the scaffolder with a local instance.
- Make sure you are using **Node.js 24+** (`node -v` to check).
- Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available.
- Try deleting `node_modules` and running `yarn install` again if dependencies seem broken.
Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,34 +4,76 @@ description: Distribute your Twenty app to the marketplace or deploy it internal
---
<Warning>
Apps are currently in alpha testing. The feature is functional but still evolving.
Apps are currently in alpha. The feature works but is still evolving.
</Warning>
## Overview
Once your app is [built and tested locally](/developers/extend/apps/building), you have two paths for distributing it:
- **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install.
- **Deploy a tarball** — upload your app directly to a specific Twenty server for internal or private use.
- **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install.
Both paths start from the same **build** step.
## Building your app
The `build` command compiles your TypeScript sources, transpiles logic functions and front components, and generates a `manifest.json` that describes your app's contents:
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
The output is written to `.twenty/output/`. This directory contains everything needed for distribution: compiled code, assets, the manifest, and a copy of your `package.json`.
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
To also create a `.tgz` tarball (used by the deploy command internally, or for manual distribution):
## Deploying to a server (tarball)
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can deploy a tarball directly to a Twenty server.
### Prerequisites
Before deploying, you need a configured remote pointing to the target server. Remotes store the server URL and authentication credentials locally in `~/.twenty/config.json`.
Add a remote:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Deploying
Build and upload your app to the server in one step:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Sharing a deployed app
Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. To share a deployed app:
1. Go to **Settings > Applications > Registrations** and open your app
2. In the **Distribution** tab, click **Copy share link**
3. Share this link with users on other workspaces — it takes them directly to the app's install page
The share link uses the server's base URL (without any workspace subdomain) so it works for any workspace on the server.
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### Version management
To release an update:
1. Bump the `version` field in your `package.json`
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Workspaces that have the app installed will see the upgrade available in their settings
{/* TODO: add screenshot of the Upgrade button */}
## Publishing to npm
Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twenty workspace can browse, install, and upgrade marketplace apps directly from the UI.
@@ -39,41 +81,42 @@ Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twe
### Requirements
- An [npm](https://www.npmjs.com) account
- The `twenty-app` keyword **must** be listed in your `package.json` `keywords` array
### Adding the required keyword
The Twenty marketplace discovers apps by searching the npm registry for packages with the `twenty-app` keyword. Add it to your `package.json`:
- The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
The marketplace searches for `keywords:twenty-app` on the npm registry. Without this keyword, your package won't appear in the marketplace even if it has the `twenty-app-` name prefix.
</Note>
### Marketplace metadata
### Steps
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
1. **Build your app:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **Publish to npm:**
See the [defineApplication accordion](/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publish
```bash filename="Terminal"
yarn twenty publish
```
This runs `npm publish` from the `.twenty/output/` directory.
To publish under a specific dist-tag (e.g., `beta` or `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### How marketplace discovery works
The Twenty server syncs its marketplace catalog from the npm registry **every hour**:
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
1. It searches for all npm packages with the `keywords:twenty-app` keyword
2. For each package, it fetches the `manifest.json` from the npm CDN
3. The app's metadata (name, description, author, logo, screenshots, category) is extracted from the manifest and displayed in the marketplace
After publishing, your app can take up to one hour to appear in the marketplace. To trigger the sync immediately instead of waiting for the next hourly run:
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
To target a specific remote:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
The metadata shown in the marketplace comes from your `defineApplication()` call in your app source code — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
If your app does not define an `aboutDescription` in `defineApplication()`, the marketplace will automatically use your package's `README.md` from npm as the about page content. This means you can maintain a single README for both npm and the Twenty marketplace. If you want a different description in the marketplace, explicitly set `aboutDescription`.
@@ -108,7 +143,7 @@ If your app does not define an `aboutDescription` in `defineApplication()`, the
### CI publishing
The scaffolded project includes a GitHub Actions workflow that publishes on every release:
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`.
<Tip>
<Note>
**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions.
</Tip>
## Deploying to a server (tarball)
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can deploy a tarball directly to a Twenty server.
### Prerequisites
Before deploying, you need a configured remote pointing to the target server. Remotes store the server URL and authentication credentials locally in `~/.twenty/config.json`.
Add a remote:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
For a local development server:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
You can also authenticate with an API key for non-interactive environments:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Manage your remotes:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Deploying
Build and upload your app to the server in one step:
```bash filename="Terminal"
yarn twenty deploy
```
This builds the app with `--tarball`, then uploads the tarball to the default remote via a GraphQL multipart upload.
To deploy to a specific remote:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Sharing a deployed app
Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. To share a deployed app:
1. Go to **Settings > Applications > Registrations** and open your app
2. In the **Distribution** tab, click **Copy share link**
3. Share this link with users on other workspaces — it takes them directly to the app's install page
The share link uses the server's base URL (without any workspace subdomain) so it works for any workspace on the server.
### Version management
To release an update:
1. Bump the `version` field in your `package.json`
2. Run `yarn twenty deploy` (or `yarn twenty deploy -r production`)
3. Workspaces that have the app installed will see the upgrade available in their settings
</Note>
## Installing apps
Once an app is published (npm) or deployed (tarball), workspaces install it through the UI:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
```bash filename="Terminal"
yarn twenty install
```
Or from the **Settings > Applications** page in the Twenty UI, where both marketplace and tarball-deployed apps can be browsed and installed.
## App distribution categories
Twenty organizes apps into three categories based on how they're distributed:
| Category | How it works | Visible in marketplace? |
|----------|-------------|------------------------|
| **Development** | Local dev mode apps running via `yarn twenty dev`. Used for building and testing. | No |
| **Published (npm)** | Apps published to npm with the `twenty-app` keyword. Listed in the marketplace for any workspace to install. | Yes |
| **Internal (tarball)** | Apps deployed via tarball to a specific server. Available only to workspaces on that server via a share link. | No |
<Tip>
Start in **Development** mode while building your app. When it's ready, choose **Published** (npm) for broad distribution or **Internal** (tarball) for private deployment.
</Tip>
## CLI reference
| Command | Description | Key flags |
|---------|-------------|-----------|
| `yarn twenty build` | Compile app and generate manifest | `--tarball` — also create a `.tgz` package |
| `yarn twenty publish` | Build and publish to npm | `--tag <tag>` — npm dist-tag (e.g., `beta`, `next`) |
| `yarn twenty deploy` | Build and upload tarball to a server | `-r, --remote <name>` — target remote |
| `yarn twenty catalog-sync` | Trigger marketplace catalog sync on the server | `-r, --remote <name>` — target remote |
| `yarn twenty install` | Install a deployed app on a workspace | `-r, --remote <name>` — target remote |
| `yarn twenty dev` | Watch and sync local changes | Uses default remote |
| `yarn twenty remote add` | Add a server connection | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | List configured remotes | — |
| `yarn twenty remote switch` | Set default remote | — |
| `yarn twenty remote status` | Show connection status | — |
| `yarn twenty remote remove` | Remove a remote | — |
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: أنشئ أول تطبيق Twenty خلال دقائق.
---
<Warning>
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول ووظائف منطقية ومهارات ذكاء اصطناعي ومكونات واجهة مستخدم مخصصة — جميعها تُدار ككود.
**ما الذي يمكنك بناؤه:**
* كائنات مخصّصة، وحقول، وطرق عرض، وعناصر تنقّل لتشكيل نموذج بياناتك
* دوال منطقية يتم تشغيلها عبر مسارات HTTP، وجداول cron، أو أحداث قاعدة البيانات
* مكوّنات واجهة أمامية تُعرَض مباشرة داخل واجهة مستخدم Twenty
* مهارات توسّع قدرات وكلاء الذكاء الاصطناعي في Twenty
* انشر تطبيقاً عبر مساحات عمل متعددة
## المتطلبات الأساسية
* Node.js 24+
* Yarn 4
* Docker (أو مثيل Twenty محلي قيد التشغيل)
قبل أن تبدأ، تأكّد من تثبيت ما يلي على جهازك:
## البدء
* **Node.js 24+** — [نزّل من هنا](https://nodejs.org/)
* **Yarn 4** — يأتي مع Node.js عبر Corepack. قم بتمكينه عبر تشغيل `corepack enable`
* **Docker** — [نزّل من هنا](https://www.docker.com/products/docker-desktop/). مطلوب لتشغيل مثيل محلي من Twenty. غير مطلوب إذا كان لديك خادم Twenty قيد التشغيل بالفعل.
أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير:
## الخطوة 1: إنشاء هيكل تطبيقك
افتح الطرفية وشغّل:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> استخدم الخيار `--minimal` لتهيئة تثبيت مصغّر
سيُطلب منك إدخال اسم ووصف لتطبيقك. اضغط **Enter** لقبول الإعدادات الافتراضية.
من هنا يمكنك:
سيؤدي ذلك إلى إنشاء مجلد جديد باسم `my-twenty-app` يحتوي على كل ما تحتاجه.
<Note>
أداة إنشاء الهيكل تدعم الأعلام التالية:
* `--minimal` — إنشاء الهيكل للملفات الأساسية فقط، بدون أمثلة (افتراضي)
* `--exhaustive` — إنشاء الهيكل لجميع كيانات الأمثلة
* `--name <name>` — تعيين اسم التطبيق (يتخطى المطالبة)
* `--display-name <displayName>` — تعيين اسم العرض (يتخطى المطالبة)
* `--description <description>` — تعيين الوصف (يتخطى المطالبة)
* `--skip-local-instance` — تخطي مطالبة إعداد الخادم المحلي
</Note>
## الخطوة 2: إعداد مثيل محلي من Twenty
ستسأل أداة إنشاء الهيكل:
> **هل ترغب في إعداد مثيل محلي من Twenty؟**
* **اكتب `yes`** (موصى به) — سيؤدي ذلك إلى سحب صورة Docker `twenty-app-dev` وبدء تشغيل خادم Twenty محلي على المنفذ `2020`. تأكّد من أن Docker قيد التشغيل قبل المتابعة.
* **اكتب `no`** — اختر هذا إذا كان لديك خادم Twenty يعمل محليًا بالفعل.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="هل يجب بدء المثيل المحلي؟" />
</div>
## الخطوة 3: سجّل الدخول إلى مساحة العمل الخاصة بك
بعد ذلك، ستُفتح نافذة متصفح تعرض صفحة تسجيل الدخول الخاصة بـ Twenty. سجّل الدخول باستخدام حساب العرض التوضيحي المُجهَّز مسبقًا:
* **البريد الإلكتروني:** `tim@apple.dev`
* **كلمة المرور:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="شاشة تسجيل الدخول إلى Twenty" />
</div>
## الخطوة 4: تفويض التطبيق
بعد تسجيل الدخول، ستظهر لك شاشة تفويض. يتيح هذا لتطبيقك التفاعل مع مساحة العمل الخاصة بك.
انقر **Authorize** للمتابعة.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="شاشة تفويض واجهة الأوامر (CLI) الخاصة بـ Twenty" />
</div>
بمجرد منح التفويض، ستؤكّد الطرفية أن كل شيء قد تم إعداده.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="تم إنشاء هيكل التطبيق بنجاح" />
</div>
## الخطوة 5: ابدأ التطوير
انتقل إلى مجلد تطبيقك الجديد وابدأ خادم التطوير:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
يقوم هذا بمراقبة ملفات المصدر لديك، وإعادة البناء عند كل تغيير، ومزامنة تطبيقك تلقائيًا مع خادم Twenty المحلي. يفترض أن ترى لوحة حالة مباشرة في الطرفية.
## هيكل المشروع (مُنشأ بالقالب)
للحصول على مخرجات أكثر تفصيلاً (سجلات البناء، طلبات المزامنة، تتبعات الأخطاء)، استخدم العلم `--verbose`:
عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
<Warning>
وضع التطوير متاح فقط على مثيلات Twenty التي تعمل في وضع التطوير (`NODE_ENV=development`). المثيلات الإنتاجية ترفض طلبات مزامنة وضع التطوير. استخدم `yarn twenty deploy` للنشر إلى خوادم الإنتاج — اطّلع على [نشر التطبيقات](/l/ar/developers/extend/apps/publishing) للتفاصيل.
</Warning>
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="مخرجات الطرفية في وضع التطوير" />
</div>
## الخطوة 6: اعرض تطبيقك في Twenty
افتح [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) في متصفحك. انتقل إلى **Settings > Apps** واختر علامة التبويب **Developer**. يُفترض أن ترى تطبيقك مُدرجًا تحت **Your Apps**:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="قائمة &#x22;Your Apps&#x22; تعرض &#x22;My twenty app&#x22;" />
</div>
انقر على **My twenty app** لفتح **تسجيل التطبيق** الخاص به. التسجيل عبارة عن سجل على مستوى الخادم يصف تطبيقك — اسمه، والمعرّف الفريد، وبيانات اعتماد OAuth، والمصدر (محلي، npm، أو tarball). يُخزَّن على الخادم، وليس داخل أي مساحة عمل محددة. عند تثبيت تطبيق في مساحة عمل، ينشئ Twenty **تطبيقًا** بنطاق مساحة العمل يُشير مرة أخرى إلى هذا التسجيل. يمكن تثبيت تسجيل واحد عبر عدة مساحات عمل على الخادم نفسه.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="تفاصيل تسجيل التطبيق" />
</div>
انقر **View installed app** لعرض التطبيق المثبّت. تعرض علامة التبويب **About** الإصدار الحالي وخيارات الإدارة:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="التطبيق المثبّت — علامة تبويب About" />
</div>
انتقل إلى علامة التبويب **Content** لمشاهدة كل ما يقدمه تطبيقك — الكائنات، والحقول، ودوال المنطق، والوكلاء:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="التطبيق المثبّت — علامة تبويب Content" />
</div>
أنت جاهز تمامًا! حرّر أي ملف في `src/` وسيتم التقاط التغييرات تلقائيًا.
انتقل إلى [بناء التطبيقات](/l/ar/developers/extend/apps/building) للحصول على دليل مفصّل حول إنشاء الكائنات، ودوال المنطق، ومكونات الواجهة الأمامية، والمهارات، والمزيد.
---
## هيكل المشروع
تولّد أداة إنشاء الهيكل بنية الملفات التالية (مُبيّنة بوضع `--exhaustive` الذي يتضمن أمثلة لكل نوع من الكيانات):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`).
افتراضيًا (`--minimal`)، تُنشأ الملفات الأساسية فقط: `application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`. استخدم `--exhaustive` لتضمين جميع ملفات الأمثلة الموضّحة أعلاه.
بشكل عام:
### الملفات الرئيسية
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة.
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`.twenty/` و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
| ملف / مجلد | الغرض |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `package.json` | يصرّح باسم تطبيقك وإصداره واعتماداته. يتضمن نصًا برمجيًا باسم `twenty` بحيث يمكنك تشغيل `yarn twenty help` للاطلاع على جميع الأوامر. |
| `src/application-config.ts` | **مطلوب.** ملف الإعداد الرئيسي لتطبيقك. |
| `src/roles/` | يعرِّف الأدوار التي تتحكم بما يمكن لدوال المنطق الوصول إليه. |
| `src/logic-functions/` | دوال على جانب الخادم يتم تشغيلها عبر المسارات، وجداول cron، أو أحداث قاعدة البيانات. |
| `src/front-components/` | مكونات React تُعرَض داخل واجهة مستخدم Twenty. |
| `src/objects/` | تعريفات كائنات مخصّصة لتوسيع نموذج البيانات لديك. |
| `src/fields/` | حقول مخصّصة تُضاف إلى الكائنات الموجودة. |
| `src/views/` | تكوينات العروض المحفوظة. |
| `src/navigation-menu-items/` | روابط مخصّصة في شريط التنقل الجانبي. |
| `src/skills/` | مهارات توسّع قدرات وكلاء الذكاء الاصطناعي في Twenty. |
| `src/agents/` | وكلاء ذكاء اصطناعي مع موجهات مخصّصة. |
| `src/page-layouts/` | تخطيطات صفحات مخصّصة لعرض السجلات. |
| `src/__tests__/` | اختبارات تكامل (إعداد + اختبار مثال). |
| `public/` | أصول ثابتة (صور، خطوط) تُقدَّم مع تطبيقك. |
### اكتشاف الكيانات
## إدارة الريموتات
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
| دالة مساعدة | نوع الكيان |
| -------------------------------- | ---------------------------------------------- |
| `defineObject` | تعريفات كائنات مخصصة |
| `defineLogicFunction` | تعريفات الوظائف المنطقية |
| `definePreInstallLogicFunction` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) |
| `definePostInstallLogicFunction` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) |
| `defineFrontComponent` | تعريفات المكونات الواجهية |
| `defineRole` | تعريفات الأدوار |
| `defineField` | امتدادات الحقول للكائنات الموجودة |
| `defineView` | تعريفات العروض المحفوظة |
| `defineNavigationMenuItem` | تعريفات عناصر قائمة التنقل |
| `defineSkill` | تعريفات مهارات وكلاء الذكاء الاصطناعي |
<Note>
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
</Note>
مثال على كيان تم اكتشافه:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
* `yarn twenty dev` سيولّد تلقائياً `CoreApiClient` مضبوط الأنواع (لبيانات مساحة العمل عبر `/graphql`) داخل `node_modules/twenty-client-sdk/`. `MetadataApiClient` (لتهيئة مساحة العمل ورفع الملفات عبر `/metadata`) يأتي مُبنًى مسبقاً ومتاحاً على الفور. استوردْهما من `twenty-client-sdk/core` و`twenty-client-sdk/metadata` على الترتيب.
* `yarn twenty add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
## المصادقة
في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال:
* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك)
* مفتاح واجهة برمجة التطبيقات
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. يمكنك الاحتفاظ بملفات تعريف متعددة والتبديل بينها.
### إدارة مساحات العمل
**الريموت** هو خادم Twenty يتصل به تطبيقك. أثناء الإعداد، تُنشئ أداة إنشاء الهيكل واحدًا لك تلقائيًا. يمكنك إضافة ريموتات أخرى أو التبديل بينها في أي وقت.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. لا يزال بإمكانك تجاوزه مؤقتًا باستخدام `--workspace <name>`.
تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`.
## خادم التطوير المحلي (`yarn twenty server`)
يمكن لأداة سطر الأوامر (CLI) إدارة خادم Twenty محلي يعمل داخل Docker. هذا هو الخادم نفسه الذي يبدأ تلقائيًا عند إنشاء هيكل تطبيق باستخدام `create-twenty-app`، لكن يمكنك أيضًا إدارته يدويًا.
### بدء الخادم
```bash filename="Terminal"
yarn twenty server start
```
سيؤدي ذلك إلى سحب صورة Docker `twentycrm/twenty-app-dev:latest` (إن لم تكن موجودة بالفعل)، وإنشاء حاوية باسم `twenty-app-dev`، وبدء تشغيلها على المنفذ **2020**. تنتظر أداة CLI حتى يجتاز الخادم فحص السلامة قبل الإنهاء.
يتم إنشاء حجمين في Docker للاحتفاظ بالبيانات بين عمليات إعادة التشغيل:
* `twenty-app-dev-data` — قاعدة بيانات PostgreSQL
* `twenty-app-dev-storage` — تخزين ملفات
إذا كان المنفذ 2020 مستخدمًا بالفعل، يمكنك البدء على منفذ مختلف:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
تقوم أداة CLI تلقائيًا بتهيئة قيم `NODE_PORT` و`SERVER_URL` الداخلية في الحاوية لتطابق المنفذ المختار، بحيث تعمل دوال المنطق وOAuth وكل الشبكات الداخلية الأخرى بشكل صحيح.
بمجرد البدء، يُسجَّل الخادم تلقائيًا كـ `local` remote في إعدادات CLI لديك.
### التحقق من حالة الخادم
```bash filename="Terminal"
yarn twenty server status
```
يعرض ما إذا كان الخادم قيد التشغيل، وعنوان URL الخاص به، وبيانات اعتماد تسجيل الدخول الافتراضية (`tim@apple.dev` / `tim@apple.dev`).
### عرض سجلات الخادم
```bash filename="Terminal"
yarn twenty server logs
```
يبث سجلات الحاوية. استخدم `--lines` للتحكّم بعدد الأسطر الحديثة المراد عرضها:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### إيقاف الخادم
```bash filename="Terminal"
yarn twenty server stop
```
يوقف الحاوية. تُحفَظ بياناتك في أحجام Docker — وستُستأنف الحالة مع عملية `start` التالية من حيث توقفت.
### إعادة تعيين الخادم
```bash filename="Terminal"
yarn twenty server reset
```
يزيل الحاوية **و** يحذف كلا حجمي Docker، ممّا يمحو جميع البيانات. ستنشئ عملية `start` التالية مثيلًا جديدًا من البداية.
<Note>
يتطلّب الخادم أن يكون **Docker** قيد التشغيل. إذا ظهرت لك رسالة خطأ "Docker not running"، فتأكّد من تشغيل Docker Desktop (أو خادوم Docker).
</Note>
### مرجع الأوامر
| أمر | الوصف |
| -------------------------------------- | --------------------------------------------- |
| `yarn twenty server start` | بدء الخادم المحلي (يسحب الصورة إذا لزم الأمر) |
| `yarn twenty server start --port 3030` | ابدأ على منفذ مخصّص |
| `yarn twenty server stop` | إيقاف الخادم (مع الحفاظ على البيانات) |
| `yarn twenty server status` | عرض حالة الخادم، وعنوان URL، وبيانات الاعتماد |
| `yarn twenty server logs` | بث سجلات الخادم |
| `yarn twenty server logs --lines 100` | عرض آخر 100 سطر من السجلات |
| `yarn twenty server reset` | حذف جميع البيانات والبدء من جديد |
## التكامل المستمر (CI) باستخدام GitHub Actions
تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب.
سير العمل:
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` في أعلى سير العمل.
## إعداد يدوي (بدون المهيئ)
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك:
إذا كنت تفضّل إعداد الأمور بنفسك بدلًا من استخدام `create-twenty-app`، فيمكنك ذلك بخطوتين.
**1. أضِف `twenty-sdk` و`twenty-client-sdk` كاعتمادات:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
ثم أضف سكربتًا باسم `twenty`:
**2. أضِف نصًا برمجيًا باسم `twenty` إلى `package.json` لديك:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ yarn add -D twenty-sdk
}
```
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty dev`، `yarn twenty help`، إلخ.
يمكنك الآن تشغيل `yarn twenty dev`، و`yarn twenty help`، وجميع الأوامر الأخرى.
## كيفية استخدام مثيل محلي من Twenty
إذا كنت تقوم بتشغيل مثيل محلي من Twenty بالفعل (على سبيل المثال عبر `npx nx start twenty-server`)، فيمكنك الاتصال به بدلًا من استخدام Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
لا تثبّت `twenty-sdk` عالميًا. استخدمه دائمًا كاعتماد محلي للمشروع بحيث يتمكن كل مشروع من تثبيت إصداره الخاص.
</Note>
## استكشاف الأخطاء وإصلاحها
* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة برمجة التطبيقات وأن خادم Twenty قابل للوصول.
* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty dev` — فهو يولِّد العميل مضبوط الأنواع تلقائيًا.
* وضع التطوير لا يزامن: تأكد من أن `yarn twenty dev` قيد التشغيل وأن التغييرات غير متجاهلة في بيئتك.
إذا واجهت مشاكل:
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
* تأكّد من أن **Docker قيد التشغيل** قبل تشغيل أداة إنشاء الهيكل مع مثيل محلي.
* تأكّد من أنك تستخدم **Node.js 24+** (`node -v` للتحقق).
* تأكّد من **تمكين Corepack** (`corepack enable`) حتى يتوفر Yarn 4.
* جرّب حذف `node_modules` وتشغيل `yarn install` مرة أخرى إذا بدت الاعتمادات معطّلة.
ما زلت عالقًا؟ اطلب المساعدة على [خادم Twenty على Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,34 +4,76 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ
---
<Warning>
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
## نظرة عامة
بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه:
* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته.
* **نشر أرشيف tar** — ارفع تطبيقك مباشرةً إلى خادم Twenty محدد للاستخدام الداخلي أو الخاص.
* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته.
كلا المسارين يبدآن من نفس خطوة **build**.
## بناء تطبيقك
يقوم الأمر `build` بتجميع مصادر TypeScript الخاصة بك، وتحويل دوال المنطق ومكوّنات الواجهة الأمامية، وإنشاء ملف `manifest.json` يصف محتويات تطبيقك:
شغّل أمر build لتجميع تطبيقك وإنشاء ملف `manifest.json` جاهز للتوزيع:
```bash filename="Terminal"
yarn twenty build
```
يتم حفظ المخرجات في `.twenty/output/`. يحتوي هذا الدليل على كل ما يلزم للتوزيع: الكود المُجمَّع، والأصول، وملف manifest، ونسخة من `package.json` الخاص بك.
يقوم هذا بتجميع مصادر TypeScript، وتحويل دوال المنطق ومكوّنات الواجهة الأمامية، وكتابة كل شيء إلى `.twenty/output/`. أضِف `--tarball` لإنتاج حزمة `.tgz` أيضًا للتوزيع اليدوي أو لأمر deploy.
لإنشاء حزمة tarball بصيغة `.tgz` أيضًا (تُستخدم داخليًا بواسطة أمر النشر، أو للتوزيع اليدوي):
## النشر إلى خادم (tarball)
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty.
### المتطلبات الأساسية
قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`.
أضِف remote:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### النشر
بناء تطبيقك ورفعه إلى الخادم في خطوة واحدة:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### مشاركة تطبيق منشور
تطبيقات tarball لا تُدرَج في السوق العامة، لذا لن تكتشفها مساحات العمل الأخرى على الخادم نفسه عبر الاستعراض. لمشاركة تطبيق منشور:
1. اذهب إلى **الإعدادات > التطبيقات > التسجيلات** وافتح تطبيقك
2. في علامة التبويب **التوزيع**، انقر **نسخ رابط المشاركة**
3. شارك هذا الرابط مع المستخدمين في مساحات عمل أخرى — سيأخذهم مباشرةً إلى صفحة تثبيت التطبيق
يستخدم رابط المشاركة عنوان URL الأساسي للخادم (من دون أي نطاق فرعي لمساحة عمل)، لذا يعمل مع أي مساحة عمل على الخادم.
<Warning>
مشاركة التطبيقات الخاصة هي ميزة ضمن باقة Enterprise. اذهب إلى [الإعدادات > لوحة الإدارة > Enterprise](/settings/admin-panel#enterprise) لتمكينها.
</Warning>
### إدارة الإصدارات
لطرح تحديث:
1. ارفع قيمة الحقل `version` في ملف `package.json`
2. شغّل `yarn twenty deploy` (أو `yarn twenty deploy --remote production`)
3. سترى مساحات العمل التي ثبّتت التطبيق الترقية متاحة في إعداداتها
{/* TODO: add screenshot of the Upgrade button */}
## النشر على npm
يُتيح النشر على npm إمكانية العثور على تطبيقك في سوق Twenty. يمكن لأي مساحة عمل في Twenty استعراض تطبيقات السوق وتثبيتها وترقيتها مباشرةً من واجهة المستخدم.
@@ -39,41 +81,42 @@ yarn twenty build --tarball
### المتطلبات
* حساب على [npm](https://www.npmjs.com)
* الكلمة المفتاحية `twenty-app` **يجب** أن تُدرج في مصفوفة `keywords` في `package.json` الخاص بك
### إضافة الكلمة المفتاحية المطلوبة
يعثر سوق Twenty على التطبيقات من خلال البحث في سجل npm عن الحزم التي تحتوي على الكلمة المفتاحية `twenty-app`. أضِفها إلى `package.json` الخاص بك:
* الكلمة المفتاحية `twenty-app` في مصفوفة `keywords` في `package.json` (موجودة مسبقًا عند تهيئة المشروع باستخدام `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
يبحث السوق عن `keywords:twenty-app` في سجل npm. من دون هذه الكلمة المفتاحية، لن تظهر حزمتك في السوق حتى وإن كانت تحمل بادئة الاسم `twenty-app-`.
</Note>
### بيانات التعريف لسوق التطبيقات
### الخطوات
يدعم إعداد `defineApplication()` حقولًا اختيارية تتحكم في كيفية ظهور تطبيقك في السوق. استخدم `logoUrl` و`screenshots` للإشارة إلى الصور من مجلد `public/`:
1. **بناء تطبيقك:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **النشر على npm:**
اطّلع على [أكورديون defineApplication](/l/ar/developers/extend/apps/building#defineentity-functions) في صفحة بناء التطبيقات للاطلاع على القائمة الكاملة لحقول السوق (`author` و`category` و`aboutDescription` و`websiteUrl` و`termsUrl` وغيرها).
### النشر
```bash filename="Terminal"
yarn twenty publish
```
هذا يُشغِّل `npm publish` من دليل `.twenty/output/`.
للنشر تحت dist-tag معيّن (مثلًا: `beta` أو `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### كيف تعمل آلية الاكتشاف في السوق
يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**:
يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**.
1. يبحث عن جميع حزم npm التي تحتوي على الكلمة المفتاحية `keywords:twenty-app`
2. ولكل حزمة، يجلب ملف `manifest.json` من شبكة CDN الخاصة بـ npm
3. يتم استخراج بيانات التعريف الخاصة بالتطبيق (الاسم، الوصف، المؤلف، الشعار، لقطات الشاشة، الفئة) من ملف manifest وعرضها في السوق
بعد النشر، قد يستغرق ظهور تطبيقك في السوق ما يصل إلى ساعة واحدة. لتشغيل المزامنة فورًا بدلًا من انتظار التشغيل التالي كل ساعة:
يمكنك تشغيل المزامنة فورًا بدلًا من الانتظار:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
لاستهداف remote معيّن:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
تأتي بيانات التعريف المعروضة في السوق من استدعائك لـ `defineApplication()` في الشيفرة المصدرية لتطبيقك — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`.
تأتي بيانات التعريف المعروضة في السوق من إعداد `defineApplication()` — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`.
<Note>
إذا لم يحدد تطبيقك `aboutDescription` في `defineApplication()`، فسيستخدم السوق تلقائيًا ملف `README.md` الخاص بحزمتك من npm كمحتوى لصفحة حول. هذا يعني أنه يمكنك الاحتفاظ بملف README واحد لكل من npm وسوق Twenty. إذا كنت تريد وصفًا مختلفًا في السوق، فقم بتعيين `aboutDescription` بشكل صريح.
@@ -108,7 +143,7 @@ yarn twenty catalog-sync -r production
### النشر عبر CI
يتضمن المشروع المُولَّد سير عمل GitHub Actions يقوم بالنشر عند كل إصدار:
استخدم سير عمل GitHub Actions هذا للنشر تلقائيًا مع كل إصدار (يستخدم [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
بالنسبة لأنظمة CI الأخرى (GitLab CI، وCircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `yarn twenty build`، ثم `npm publish` من `.twenty/output`.
<Tip>
<Note>
**npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد.
</Tip>
## النشر إلى خادم (tarball)
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty.
### المتطلبات الأساسية
قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`.
أضِف remote:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
لخادم تطوير محلي:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
يمكنك أيضًا إجراء المصادقة باستخدام مفتاح API للبيئات غير التفاعلية:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
إدارة remotes الخاصة بك:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### النشر
بناء تطبيقك ورفعه إلى الخادم في خطوة واحدة:
```bash filename="Terminal"
yarn twenty deploy
```
يُنشئ هذا التطبيق باستخدام `--tarball`، ثم يرفع ملف tarball إلى الـ remote الافتراضي عبر رفع متعدد الأجزاء لـ GraphQL.
لنشره إلى remote معيّن:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### مشاركة تطبيق منشور
تطبيقات tarball لا تُدرَج في السوق العامة، لذا لن تكتشفها مساحات العمل الأخرى على الخادم نفسه عبر الاستعراض. لمشاركة تطبيق منشور:
1. اذهب إلى **الإعدادات > التطبيقات > التسجيلات** وافتح تطبيقك
2. في علامة التبويب **التوزيع**، انقر **نسخ رابط المشاركة**
3. شارك هذا الرابط مع المستخدمين في مساحات عمل أخرى — سيأخذهم مباشرةً إلى صفحة تثبيت التطبيق
يستخدم رابط المشاركة عنوان URL الأساسي للخادم (من دون أي نطاق فرعي لمساحة عمل)، لذا يعمل مع أي مساحة عمل على الخادم.
### إدارة الإصدارات
لطرح تحديث:
1. ارفع قيمة الحقل `version` في ملف `package.json`
2. شغّل `yarn twenty deploy` (أو `yarn twenty deploy -r production`)
3. سترى مساحات العمل التي ثبّتت التطبيق الترقية متاحة في إعداداتها
</Note>
## تثبيت التطبيقات
بعد نشر التطبيق (npm) أو نشره إلى الخادم (tarball)، تقوم مساحات العمل بتثبيته عبر واجهة المستخدم:
بعد نشر التطبيق (npm) أو نشره (tarball)، يمكن لمساحات العمل تثبيته عبر واجهة المستخدم.
اذهب إلى صفحة **الإعدادات > التطبيقات** في Twenty، حيث يمكن استعراض تطبيقات السوق والتطبيقات المنشورة عبر tarball وتثبيتها.
{/* TODO: add screenshot of the UI when the app is registered */}
يمكنك أيضًا تثبيت التطبيقات من سطر الأوامر:
```bash filename="Terminal"
yarn twenty install
```
أو من صفحة **الإعدادات > التطبيقات** في واجهة Twenty، حيث يمكن استعراض التطبيقات من السوق ومن النشر عبر tarball وتثبيتها.
## فئات توزيع التطبيقات
تُنظِّم Twenty التطبيقات في ثلاث فئات استنادًا إلى طريقة توزيعها:
| الفئة | كيف يعمل | مرئي في سوق Twenty؟ |
| ------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------- |
| **التطوير** | تطبيقات وضع التطوير المحلي التي تعمل عبر `yarn twenty dev`. تُستخدم للبناء والاختبار. | لا |
| **منشور (npm)** | تطبيقات منشورة على npm تحتوي على الكلمة المفتاحية `twenty-app`. مدرجة في سوق Twenty لتتمكن أي مساحة عمل من تثبيتها. | نعم |
| **داخلي (tarball)** | تطبيقات منشورة عبر tarball إلى خادم محدد. متاحة فقط لمساحات العمل على ذلك الخادم عبر رابط مشاركة. | لا |
<Tip>
ابدأ في وضع **التطوير** أثناء بناء تطبيقك. عندما يصبح جاهزًا، اختر **منشور** (npm) للتوزيع الواسع أو **داخلي** (tarball) للنشر الخاص.
</Tip>
## مرجع CLI
| أمر | الوصف | الأعلام الرئيسية |
| --------------------------- | ------------------------------------ | ----------------------------------------------------- |
| `yarn twenty build` | تجميع التطبيق وإنشاء manifest | `--tarball` — يقوم أيضًا بإنشاء حزمة `.tgz` |
| `yarn twenty publish` | بناء التطبيق ونشره إلى npm | `--tag <tag>` — وسم توزيع npm (مثلًا: `beta`، `next`) |
| `yarn twenty deploy` | بناء ورفع tarball إلى خادم | `-r, --remote <name>` — الـ remote المستهدف |
| `yarn twenty catalog-sync` | تشغيل مزامنة كتالوج السوق على الخادم | `-r, --remote <name>` — الـ remote المستهدف |
| `yarn twenty install` | تثبيت تطبيق منشور على مساحة عمل | `-r, --remote <name>` — الـ remote المستهدف |
| `yarn twenty dev` | مراقبة ومزامنة التغييرات المحلية | يستخدم الـ remote الافتراضي |
| `yarn twenty remote add` | إضافة اتصال بخادم | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | عرض الـ remotes المُكوَّنة | — |
| `yarn twenty remote switch` | تعيين الـ remote الافتراضي | — |
| `yarn twenty remote status` | عرض حالة الاتصال | — |
| `yarn twenty remote remove` | إزالة remote | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### لإرفاقه بكائن مختلف
استبدل `companyId` بالحقل المناسب:
استبدل `targetCompanyId` بالحقل المناسب:
| كائن | اسم الحقل |
| ---------- | -------------------- |
| الشركة | `companyId` |
| شخص | `personId` |
| الفرصة | `opportunityId` |
| كائن مخصّص | `yourCustomObjectId` |
| كائن | اسم الحقل |
| ---------- | -------------------------- |
| الشركة | `targetCompanyId` |
| شخص | `targetPersonId` |
| الفرصة | `targetOpportunityId` |
| كائن مخصّص | `targetYourCustomObjectId` |
حدّث كل من معلمة الوظيفة وكائن `variables.data` في عملية الـ mutation الخاصة بالمرفق.
File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: Vytvořte svou první aplikaci Twenty během několika minut.
---
<Warning>
Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí.
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
</Warning>
Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, AI schopnosti a komponenty uživatelského rozhraní — vše je spravováno jako kód.
**Co můžete vytvořit:**
* Vlastní objekty, pole, zobrazení a položky navigace pro utváření vašeho datového modelu
* Logické funkce spouštěné trasami HTTP, plánovačem cron nebo událostmi databáze
* Frontendové komponenty, které se vykreslují přímo uvnitř uživatelského rozhraní Twenty
* Dovednosti, které rozšiřují možnosti AI agentů Twenty
* Nasazení aplikace napříč více pracovními prostory
## Předpoklady
* Node.js 24+
* Yarn 4
* Docker (nebo běžící lokální instance Twenty)
Než začnete, ujistěte se, že máte ve svém počítači nainstalováno následující:
## Začínáme
* **Node.js 24+** — [Stáhnout zde](https://nodejs.org/)
* **Yarn 4** — Dodává se s Node.js prostřednictvím Corepacku. Povolte jej spuštěním `corepack enable`
* **Docker** — [Stáhnout zde](https://www.docker.com/products/docker-desktop/). Nutné pro spuštění lokální instance Twenty. Není potřeba, pokud už máte spuštěný server Twenty.
Vytvořte novou aplikaci pomocí oficiálního scaffolderu, poté se ověřte a začněte vyvíjet:
## Krok 1: Vytvořte kostru své aplikace
Otevřete terminál a spusťte:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Použijte volbu `--minimal` k vygenerování minimální instalace
Budete vyzváni k zadání názvu a popisu své aplikace. Stisknutím **Enter** přijmete výchozí hodnoty.
Odtud můžete:
Tím se vytvoří nová složka s názvem `my-twenty-app` se vším potřebným.
<Note>
Generátor kostry podporuje tyto přepínače:
* `--minimal` — vygeneruje pouze nezbytné soubory, bez příkladů (výchozí)
* `--exhaustive` — vygeneruje všechny ukázkové entity
* `--name <name>` — nastaví název aplikace (přeskočí výzvu)
* `--display-name <displayName>` — nastaví zobrazovaný název (přeskočí výzvu)
* `--description <description>` — nastaví popis (přeskočí výzvu)
* `--skip-local-instance` — přeskočí výzvu k nastavení lokálního serveru
</Note>
## Krok 2: Nastavte lokální instanci Twenty
Generátor kostry se zeptá:
> **Chcete nastavit lokální instanci Twenty?**
* **Zadejte `yes`** (doporučeno) — Stáhne image Dockeru `twenty-app-dev` a spustí lokální server Twenty na portu `2020`. Než budete pokračovat, ujistěte se, že Docker běží.
* **Zadejte `no`** — Zvolte, pokud už máte lokálně spuštěný server Twenty.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Spustit lokální instanci?" />
</div>
## Krok 3: Přihlaste se do svého pracovního prostoru
Poté se otevře okno prohlížeče se stránkou přihlášení do Twenty. Přihlaste se předpřipraveným demo účtem:
* **E-mail:** `tim@apple.dev`
* **Heslo:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Přihlašovací obrazovka Twenty" />
</div>
## Krok 4: Autorizujte aplikaci
Po přihlášení uvidíte autorizační obrazovku. Tím umožníte vaší aplikaci pracovat s vaším pracovním prostorem.
Pokračujte kliknutím na **Authorize**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Autorizační obrazovka Twenty CLI" />
</div>
Po autorizaci váš terminál potvrdí, že je vše nastaveno.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplikace byla úspěšně vygenerována" />
</div>
## Krok 5: Začněte vyvíjet
Přejděte do nové složky aplikace a spusťte vývojový server:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Sleduje zdrojové soubory, při každé změně znovu sestaví a automaticky synchronizuje vaši aplikaci s lokálním serverem Twenty. V terminálu byste měli vidět panel se stavem v reálném čase.
## Struktura projektu (vytvořená scaffolderem)
Pro podrobnější výstup (protokoly sestavení, požadavky na synchronizaci, stopy chyb) použijte přepínač `--verbose`:
Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* Zkopíruje minimální základní aplikaci do `my-twenty-app/`
* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4
* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI
* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, předinstalační a postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry
<Warning>
Vývojový režim je k dispozici pouze na instancích Twenty běžících v režimu development (`NODE_ENV=development`). Produkční instance odmítají požadavky na vývojovou synchronizaci. Pro nasazení na produkční servery použijte `yarn twenty deploy` — podrobnosti viz [Publikování aplikací](/l/cs/developers/extend/apps/publishing).
</Warning>
Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Výstup terminálu ve vývojovém režimu" />
</div>
## Krok 6: Zobrazte svou aplikaci v Twenty
Otevřete ve svém prohlížeči [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). Přejděte do **Settings > Apps** a vyberte kartu **Developer**. Vaše aplikace by měla být uvedena v části **Your Apps**:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Seznam Your Apps zobrazující My twenty app" />
</div>
Klikněte na **My twenty app** a otevřete její **registraci aplikace**. Registrace je záznam na úrovni serveru, který popisuje vaši aplikaci — její název, jedinečný identifikátor, přihlašovací údaje OAuth a zdroj (lokální, npm nebo tarball). Existuje na serveru, ne uvnitř žádného konkrétního pracovního prostoru. Když nainstalujete aplikaci do pracovního prostoru, Twenty vytvoří **aplikaci** v rozsahu pracovního prostoru, která odkazuje zpět na tuto registraci. Jedna registrace může být nainstalována ve více pracovních prostorech na stejném serveru.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Podrobnosti registrace aplikace" />
</div>
Klikněte na **View installed app**, abyste zobrazili nainstalovanou aplikaci. Karta **About** zobrazuje aktuální verzi a možnosti správy:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Nainstalovaná aplikace — karta About" />
</div>
Přepněte na kartu **Content**, abyste viděli vše, co vaše aplikace poskytuje — objekty, pole, logické funkce a agenty:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Nainstalovaná aplikace — karta Content" />
</div>
Vše je připraveno! Upravte libovolný soubor v `src/` a změny se automaticky projeví.
Přejděte na [Tvorba aplikací](/l/cs/developers/extend/apps/building) pro podrobný průvodce vytvářením objektů, logických funkcí, frontendových komponent, dovedností a dalšího.
---
## Struktura projektu
Generátor kostry vytvoří následující strukturu souborů (zobrazeno v režimu `--exhaustive`, který zahrnuje příklady pro každý typ entity):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`).
Ve výchozím nastavení (`--minimal`) se vytvoří pouze základní soubory: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`. Pro zahrnutí všech ukázkových souborů výše použijte `--exhaustive`.
V kostce:
### Klíčové soubory
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skript `twenty`, který deleguje na lokální `twenty` CLI. Spusťte `yarn twenty help` pro výpis všech dostupných příkazů.
* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, složky s coverage, soubory s logy a soubory `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
* **.oxlintrc.json** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu.
* **README.md**: Krátké README v kořeni aplikace se základními pokyny.
* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné.
* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód
| Soubor / Složka | Účel |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Definuje název, verzi a závislosti vaší aplikace. Obsahuje skript `twenty`, takže můžete spustit `yarn twenty help` a zobrazit všechny příkazy. |
| `src/application-config.ts` | **Povinné.** Hlavní konfigurační soubor vaší aplikace. |
| `src/roles/` | Definuje role, které určují, k čemu mají vaše logické funkce přístup. |
| `src/logic-functions/` | Serverové funkce spouštěné trasami, plánovačem cron nebo událostmi databáze. |
| `src/front-components/` | Komponenty Reactu, které se vykreslují uvnitř uživatelského rozhraní Twenty. |
| `src/objects/` | Vlastní definice objektů pro rozšíření vašeho datového modelu. |
| `src/fields/` | Vlastní pole přidaná k existujícím objektům. |
| `src/views/` | Konfigurace uložených zobrazení. |
| `src/navigation-menu-items/` | Vlastní odkazy v postranní navigaci. |
| `src/skills/` | Dovednosti, které rozšiřují možnosti AI agentů Twenty. |
| `src/agents/` | AI agenti s vlastními prompty. |
| `src/page-layouts/` | Vlastní rozvržení stránek pro zobrazení záznamů. |
| `src/__tests__/` | Integrační testy (nastavení + ukázkový test). |
| `public/` | Statická aktiva (obrázky, písma) poskytovaná s vaší aplikací. |
### Detekce entit
## Správa vzdálených serverů
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
| Pomocná funkce | Typ entity |
| -------------------------------- | --------------------------------------------------------- |
| `defineObject` | Definice vlastních objektů |
| `defineLogicFunction` | Definice logických funkcí |
| `definePreInstallLogicFunction` | Předinstalační logická funkce (spouští se před instalací) |
| `definePostInstallLogicFunction` | Postinstalační logická funkce (spouští se po instalaci) |
| `defineFrontComponent` | Definice frontendových komponent |
| `defineRole` | Definice rolí |
| `defineField` | Rozšíření polí u existujících objektů |
| `defineView` | Definice uložených zobrazení |
| `defineNavigationMenuItem` | Definice položek navigační nabídky |
| `defineSkill` | Definice dovedností agenta AI |
<Note>
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
</Note>
Příklad detekované entity:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Pozdější příkazy přidají další soubory a složky:
* `yarn twenty dev` automaticky vygeneruje typovaný `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) do `node_modules/twenty-client-sdk/`. `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`) je dodáván předpřipravený a je okamžitě k dispozici. Importujte je z `twenty-client-sdk/core` a `twenty-client-sdk/metadata` v uvedeném pořadí.
* `yarn twenty add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další.
## Ověření
Při prvním spuštění `yarn twenty auth:login` budete vyzváni k zadání:
* URL API (výchozí je http://localhost:3000 nebo váš aktuální profil pracovního prostoru)
* Klíč API
Vaše přihlašovací údaje se ukládají pro jednotlivé uživatele do `~/.twenty/config.json`. Můžete spravovat více profilů a přepínat mezi nimi.
### Správa pracovních prostorů
**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
Jakmile přepnete pracovní prostor pomocí `yarn twenty auth:switch`, všechny následující příkazy budou tento pracovní prostor používat jako výchozí. Můžete jej stále dočasně přepsat pomocí `--workspace <name>`.
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.
## Lokální vývojový server (`yarn twenty server`)
CLI může spravovat lokální server Twenty běžící v Dockeru. Jde o stejný server, který se spustí automaticky při vytvoření kostry aplikace pomocí `create-twenty-app`, ale můžete jej spravovat i ručně.
### Spuštění serveru
```bash filename="Terminal"
yarn twenty server start
```
Stáhne image Dockeru `twentycrm/twenty-app-dev:latest` (pokud již není k dispozici), vytvoří kontejner s názvem `twenty-app-dev` a spustí jej na portu **2020**. CLI čeká, dokud server neprojde kontrolou stavu, než vrátí řízení.
Vytvoří se dva svazky Dockeru pro zachování dat mezi restartováními:
* `twenty-app-dev-data` — databáze PostgreSQL
* `twenty-app-dev-storage` — úložiště souborů
Pokud je port 2020 již používán, můžete spustit na jiném portu:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI automaticky nakonfiguruje interní `NODE_PORT` a `SERVER_URL` kontejneru tak, aby odpovídaly zvolenému portu, takže logické funkce, OAuth a veškerá ostatní vnitřní síťová komunikace fungují správně.
Po spuštění je server automaticky zaregistrován jako `local` remote ve vaší konfiguraci CLI.
### Kontrola stavu serveru
```bash filename="Terminal"
yarn twenty server status
```
Zobrazí, zda server běží, jeho URL a výchozí přihlašovací údaje (`tim@apple.dev` / `tim@apple.dev`).
### Zobrazení protokolů serveru
```bash filename="Terminal"
yarn twenty server logs
```
Streamuje protokoly kontejneru. Pomocí `--lines` ovládnete, kolik posledních řádků se má zobrazit:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Zastavení serveru
```bash filename="Terminal"
yarn twenty server stop
```
Zastaví kontejner. Vaše data jsou zachována ve svazcích Dockeru — další `start` naváže tam, kde jste skončili.
### Resetování serveru
```bash filename="Terminal"
yarn twenty server reset
```
Odstraní kontejner **a** smaže oba svazky Dockeru, čímž vymaže všechna data. Další `start` vytvoří čistou instanci.
<Note>
Server vyžaduje, aby **Docker** běžel. Pokud vidíte chybu "Docker not running", ujistěte se, že je spuštěný Docker Desktop (nebo démon Dockeru).
</Note>
### Přehled příkazů
| Příkaz | Popis |
| -------------------------------------- | ------------------------------------------------------ |
| `yarn twenty server start` | Spustí lokální server (v případě potřeby stáhne image) |
| `yarn twenty server start --port 3030` | Spustí na vlastním portu |
| `yarn twenty server stop` | Zastaví server (zachová data) |
| `yarn twenty server status` | Zobrazí stav serveru, URL a přihlašovací údaje |
| `yarn twenty server logs` | Streamuje protokoly serveru |
| `yarn twenty server logs --lines 100` | Zobrazí posledních 100 řádků logu |
| `yarn twenty server reset` | Smaže všechna data a začne znovu |
## 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ů.
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í efemérní 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.
## Ruční nastavení (bez scaffolderu)
Ačkoli pro nejlepší začátky doporučujeme použít `create-twenty-app`, projekt můžete nastavit i ručně. Neinstalujte CLI globálně. Místo toho přidejte `twenty-sdk` jako lokální závislost a přidejte jeden skript do souboru package.json:
Pokud dáváte přednost vlastnímu nastavení místo použití `create-twenty-app`, můžete to udělat ve dvou krocích.
**1. Přidejte `twenty-sdk` a `twenty-client-sdk` jako závislosti:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Poté přidejte skript `twenty`:
**2. Přidejte skript `twenty` do svého `package.json`:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ Poté přidejte skript `twenty`:
}
```
Nyní můžete spouštět všechny příkazy přes `yarn twenty <command>`, např. `yarn twenty dev`, `yarn twenty help` atd.
Nyní můžete spouštět `yarn twenty dev`, `yarn twenty help` a všechny ostatní příkazy.
## Jak používat lokální instanci Twenty
Pokud již lokálně provozujete instanci Twenty (např. pomocí `npx nx start twenty-server`), můžete se k ní připojit namísto použití Dockeru:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Neinstalujte `twenty-sdk` globálně. Vždy jej používejte jako lokální závislost projektu, aby si každý projekt mohl připnout svou vlastní verzi.
</Note>
## Řešení potíží
* Chyby ověření: spusťte `yarn twenty auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění.
* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný.
* Typy nebo klient chybí nebo jsou zastaralé: restartujte `yarn twenty dev` — automaticky generuje typovaného klienta.
* Režim vývoje se nesynchronizuje: ujistěte se, že běží `yarn twenty dev` a že vaše prostředí změny neignoruje.
Pokud narazíte na potíže:
Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322
* Před spuštěním generátoru kostry s lokální instancí se ujistěte, že **Docker běží**.
* Ujistěte se, že používáte **Node.js 24+** (ověříte příkazem `node -v`).
* Ujistěte se, že je **Corepack povolen** (`corepack enable`), aby byl k dispozici Yarn 4.
* Zkuste smazat `node_modules` a znovu spustit `yarn install`, pokud se zdají závislosti poškozené.
Pořád se nedaří? Požádejte o pomoc na [Discordu Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,34 +4,76 @@ description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte i
---
<Warning>
Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí.
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
</Warning>
## Přehled
Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat:
* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat.
* **Nasaďte tarball** — nahrajte svou aplikaci přímo na konkrétní server Twenty pro interní nebo soukromé použití.
* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat.
Obě cesty začínají stejným krokem **build**.
## Sestavení vaší aplikace
Příkaz `build` zkompiluje vaše zdrojové soubory TypeScriptu, transpiluje logické funkce a frontendové komponenty a vygeneruje soubor `manifest.json`, který popisuje obsah vaší aplikace:
Spusťte příkaz build ke zkompilování své aplikace a k vygenerování souboru `manifest.json` připraveného k distribuci:
```bash filename="Terminal"
yarn twenty build
```
Výstup se zapisuje do `.twenty/output/`. Tento adresář obsahuje vše potřebné pro distribuci: zkompilovaný kód, statické soubory, manifest a kopii souboru `package.json`.
Tím se zkompilují zdrojové soubory TypeScriptu, transpilují logické funkce a frontendové komponenty a vše se zapíše do `.twenty/output/`. Přidejte `--tarball`, abyste také vytvořili balíček `.tgz` pro ruční distribuci nebo příkaz deploy.
Chcete-li také vytvořit tarball `.tgz` (používaný interně příkazem deploy nebo pro ruční distribuci):
## Nasazení na server (tarball)
U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty.
### Předpoklady
Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`.
Přidat vzdálený cíl:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Nasazení
Sestavte a nahrajte svou aplikaci na server v jednom kroku:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Sdílení nasazené aplikace
Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci:
1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci
2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení**
3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace
Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru.
<Warning>
Sdílení soukromých aplikací je funkce Enterprise. Přejděte do [Nastavení > Admin Panel > Enterprise](/settings/admin-panel#enterprise) a povolte ji.
</Warning>
### Správa verzí
Chcete-li vydat aktualizaci:
1. Zvyšte hodnotu pole `version` v souboru `package.json`
2. Spusťte `yarn twenty deploy` (nebo `yarn twenty deploy --remote production`)
3. Pracovní prostory, které mají aplikaci nainstalovanou, uvidí dostupnou aktualizaci ve svém nastavení
{/* TODO: add screenshot of the Upgrade button */}
## Publikování na npm
Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketplace Twenty. Jakýkoli pracovní prostor Twenty může procházet, instalovat a aktualizovat aplikace z Marketplace přímo z UI.
@@ -39,41 +81,42 @@ Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketpla
### Požadavky
* Účet na [npm](https://www.npmjs.com)
* Klíčové slovo `twenty-app` **musí** být uvedeno v poli `keywords` vašeho `package.json`
### Přidání požadovaného klíčového slova
Tržiště Twenty objevuje aplikace hledáním balíčků v registru npm s klíčovým slovem `twenty-app`. Přidejte jej do svého `package.json`:
* Klíčové slovo `twenty-app` ve vašem poli `keywords` v souboru `package.json` (již je zahrnuto, když založíte projekt pomocí `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
Tržiště vyhledává v registru npm výraz `keywords:twenty-app`. Bez tohoto klíčového slova se váš balíček v tržišti neobjeví, i když má jmennou předponu `twenty-app-`.
</Note>
### Metadata tržiště
### Postup
Konfigurace `defineApplication()` podporuje volitelná pole, která určují, jak se vaše aplikace zobrazuje v tržišti. Použijte `logoUrl` a `screenshots` k odkazování na obrázky ze složky `public/`:
1. **Sestavení vaší aplikace:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **Publikování na npm:**
Podívejte se na [sekci defineApplication](/l/cs/developers/extend/apps/building#defineentity-functions) na stránce Building Apps pro úplný seznam polí tržiště (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` atd.).
### Publikování
```bash filename="Terminal"
yarn twenty publish
```
Tímto se spustí `npm publish` z adresáře `.twenty/output/`.
Chcete-li publikovat pod konkrétním dist-tagem (např. `beta` nebo `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### Jak funguje objevování v tržišti
Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**:
Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**.
1. Vyhledá všechny balíčky na npm s klíčovým slovem `keywords:twenty-app`
2. Pro každý balíček stáhne `manifest.json` z CDN npm
3. Metadata aplikace (název, popis, autor, logo, snímky obrazovky, kategorie) se získají z manifestu a zobrazí se v tržišti
Po publikování se vaše aplikace může v tržišti objevit až za jednu hodinu. Chcete-li spustit synchronizaci okamžitě místo čekání na další hodinové spuštění:
Synchronizaci můžete spustit okamžitě místo čekání:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Chcete-li zacílit na konkrétní vzdálený cíl:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
Metadata zobrazená v tržišti pocházejí z volání `defineApplication()` ve zdrojovém kódu vaší aplikace — z polí jako `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` a `termsUrl`.
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`.
<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`.
@@ -108,7 +143,7 @@ Pokud vaše aplikace nedefinuje `aboutDescription` v `defineApplication()`, trž
### Publikování pomocí CI
Vygenerovaný projekt obsahuje pracovní postup GitHub Actions, který publikuje při každém vydání:
Použijte tento pracovní postup GitHub Actions k automatickému publikování při každém vydání (používá [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `yarn twenty build` a poté `npm publish` z `.twenty/output`.
<Tip>
<Note>
**npm provenance** je volitelné, ale doporučené. Publikování s `--provenance` přidá k vašemu záznamu na npm odznak důvěryhodnosti a umožní uživatelům ověřit, že balíček byl sestaven z konkrétního commitu ve veřejné CI pipeline. Pokyny k nastavení najdete v [dokumentaci k npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Tip>
## Nasazení na server (tarball)
U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty.
### Předpoklady
Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`.
Přidat vzdálený cíl:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
Pro lokální vývojový server:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
Pro neinteraktivní prostředí se můžete ověřit také pomocí klíče API:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Spravujte své vzdálené servery:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Nasazení
Sestavte a nahrajte svou aplikaci na server v jednom kroku:
```bash filename="Terminal"
yarn twenty deploy
```
Tímto se aplikace sestaví s `--tarball` a poté se tarball nahraje na výchozí vzdálený server prostřednictvím vícedílného (multipart) nahrávání GraphQL.
Chcete-li nasadit na konkrétní vzdálený server:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Sdílení nasazené aplikace
Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci:
1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci
2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení**
3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace
Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru.
### Správa verzí
Chcete-li vydat aktualizaci:
1. Zvyšte hodnotu pole `version` v souboru `package.json`
2. Spusťte `yarn twenty deploy` (nebo `yarn twenty deploy -r production`)
3. Pracovní prostory, které mají aplikaci nainstalovanou, uvidí dostupnou aktualizaci ve svém nastavení
</Note>
## Instalace aplikací
Jakmile je aplikace publikována (npm) nebo nasazena (tarball), pracovní prostory ji instalují prostřednictvím uživatelského rozhraní:
Jakmile je aplikace publikována (npm) nebo nasazena (tarball), mohou ji pracovní prostory nainstalovat prostřednictvím uživatelského rozhraní.
Přejděte na stránku **Nastavení > Aplikace** v Twenty, kde lze procházet a instalovat jak aplikace z tržiště, tak aplikace nasazené jako tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
Aplikace můžete nainstalovat také z příkazového řádku:
```bash filename="Terminal"
yarn twenty install
```
Nebo ze stránky **Nastavení > Aplikace** v rozhraní Twenty, kde lze procházet a instalovat jak aplikace z tržiště, tak aplikace nasazené jako tarball.
## Kategorie distribuce aplikací
Twenty organizuje aplikace do tří kategorií podle způsobu distribuce:
| Kategorie | Jak to funguje | Viditelné v Marketplace? |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| **Vývoj** | Aplikace v místním vývojářském režimu spuštěné přes `yarn twenty dev`. Slouží k sestavování a testování. | Ne |
| **Publikováno (npm)** | Aplikace publikované na npm s klíčovým slovem `twenty-app`. Uvedeny v Marketplace, aby je mohl kterýkoli pracovní prostor nainstalovat. | Ano |
| **Interní (tarball)** | Aplikace nasazené pomocí tarballu na konkrétní server. Dostupné pouze pro pracovní prostory na tomto serveru prostřednictvím odkazu ke sdílení. | Ne |
<Tip>
Začněte v režimu **Development** při sestavování své aplikace. Až bude připravena, zvolte **Published** (npm) pro širokou distribuci nebo **Internal** (tarball) pro soukromé nasazení.
</Tip>
## Reference CLI
| Příkaz | Popis | Klíčové přepínače |
| --------------------------- | ----------------------------------------------------- | --------------------------------------------------- |
| `yarn twenty build` | Sestaví aplikaci a vygeneruje manifest | `--tarball` — také vytvoří balíček `.tgz` |
| `yarn twenty publish` | Sestaví a publikuje na npm | `--tag <tag>` — npm dist-tag (např. `beta`, `next`) |
| `yarn twenty deploy` | Sestaví a nahraje tarball na server | `-r, --remote <name>` — cílový vzdálený repozitář |
| `yarn twenty catalog-sync` | Spustí synchronizaci katalogu tržiště na serveru | `-r, --remote <name>` — cílový vzdálený repozitář |
| `yarn twenty install` | Nainstaluje nasazenou aplikaci do pracovního prostoru | `-r, --remote <name>` — cílový vzdálený server |
| `yarn twenty dev` | Sleduje a synchronizuje lokální změny | Používá výchozí vzdálený server |
| `yarn twenty remote add` | Přidá připojení k serveru | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | Vypíše nakonfigurované vzdálené servery | — |
| `yarn twenty remote switch` | Nastaví výchozí vzdálený server | — |
| `yarn twenty remote status` | Zobrazí stav připojení | — |
| `yarn twenty remote remove` | Odebere vzdálený server | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### Chcete-li připojit k jinému objektu
Nahraďte `companyId` příslušným polem:
Nahraďte `targetCompanyId` příslušným polem:
| Objekt | Název pole |
| -------------- | -------------------- |
| Společnost | `companyId` |
| Osoba | `personId` |
| Příležitost | `opportunityId` |
| Vlastní objekt | `yourCustomObjectId` |
| Objekt | Název pole |
| -------------- | -------------------------- |
| Společnost | `targetCompanyId` |
| Osoba | `targetPersonId` |
| Příležitost | `targetOpportunityId` |
| Vlastní objekt | `targetYourCustomObjectId` |
Aktualizujte jak parametr funkce, tak objekt `variables.data` v mutaci přílohy.
File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App.
---
<Warning>
Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
</Warning>
Apps ermöglichen es Ihnen, Twenty mit benutzerdefinierten Objekten, Feldern, Logikfunktionen, KI-Fähigkeiten und UI-Komponenten zu erweitern — alles als Code verwaltet.
**Was Sie erstellen können:**
* Benutzerdefinierte Objekte, Felder, Ansichten und Navigationselemente, um Ihr Datenmodell zu gestalten
* Logikfunktionen, die durch HTTP-Routen, Cron-Zeitpläne oder Datenbankereignisse ausgelöst werden
* Frontend-Komponenten, die direkt innerhalb der Twenty-UI gerendert werden
* Skills, die die KI-Agenten von Twenty erweitern
* Eine App in mehreren Workspaces bereitstellen
## Voraussetzungen
* Node.js 24+
* Yarn 4
* Docker (oder eine lokal laufende Twenty-Instanz)
Bevor Sie beginnen, stellen Sie sicher, dass Folgendes auf Ihrem Rechner installiert ist:
## Erste Schritte
* **Node.js 24+** — [Hier herunterladen](https://nodejs.org/)
* **Yarn 4** — Wird mit Node.js über Corepack mitgeliefert. Aktivieren Sie es, indem Sie `corepack enable` ausführen
* **Docker** — [Hier herunterladen](https://www.docker.com/products/docker-desktop/). Erforderlich, um eine lokale Twenty-Instanz auszuführen. Nicht erforderlich, wenn bereits ein Twenty-Server läuft.
Erstellen Sie mit dem offiziellen Scaffolder eine neue App, authentifizieren Sie sich und beginnen Sie mit der Entwicklung:
## Schritt 1: App-Gerüst erstellen
Öffnen Sie ein Terminal und führen Sie Folgendes aus:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Verwenden Sie die Option `--minimal`, um eine minimale Installation zu erstellen
Sie werden aufgefordert, einen Namen und eine Beschreibung für Ihre App einzugeben. Drücken Sie **Enter**, um die Standardwerte zu übernehmen.
Von hier aus können Sie:
Dadurch wird ein neuer Ordner namens `my-twenty-app` mit allem erstellt, was Sie benötigen.
<Note>
Das Scaffolding-Tool unterstützt diese Flags:
* `--minimal` — erstellt nur die wesentlichen Dateien, keine Beispiele (Standard)
* `--exhaustive` — erstellt alle Beispiel-Entitäten
* `--name <name>` — legt den App-Namen fest (überspringt die Abfrage)
* `--display-name <displayName>` — legt den Anzeigenamen fest (überspringt die Abfrage)
* `--description <description>` — legt die Beschreibung fest (überspringt die Abfrage)
* `--skip-local-instance` — überspringt die Eingabeaufforderung zur Einrichtung des lokalen Servers
</Note>
## Schritt 2: Lokale Twenty-Instanz einrichten
Das Scaffolding-Tool fragt:
> **Möchten Sie eine lokale Twenty-Instanz einrichten?**
* **Geben Sie `yes` ein** (empfohlen) — Dadurch wird das Docker-Image `twenty-app-dev` heruntergeladen und ein lokaler Twenty-Server auf Port `2020` gestartet. Stellen Sie sicher, dass Docker läuft, bevor Sie fortfahren.
* **Geben Sie `no` ein** — Wählen Sie dies, wenn bereits ein Twenty-Server lokal läuft.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Soll die lokale Instanz gestartet werden?" />
</div>
## Schritt 3: Bei Ihrem Arbeitsbereich anmelden
Anschließend öffnet sich ein Browserfenster mit der Twenty-Anmeldeseite. Melden Sie sich mit dem vorab eingerichteten Demo-Konto an:
* **E-Mail:** `tim@apple.dev`
* **Passwort:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty-Anmeldebildschirm" />
</div>
## Schritt 4: Die App autorisieren
Nach der Anmeldung sehen Sie einen Autorisierungsbildschirm. Dadurch kann Ihre App mit Ihrem Arbeitsbereich interagieren.
Klicken Sie auf **Authorize**, um fortzufahren.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty-CLI-Autorisierungsbildschirm" />
</div>
Nach der Autorisierung bestätigt Ihr Terminal, dass alles eingerichtet ist.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App-Gerüst erfolgreich erstellt" />
</div>
## Schritt 5: Mit der Entwicklung beginnen
Wechseln Sie in Ihren neuen App-Ordner und starten Sie den Entwicklungsserver:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Dadurch werden Ihre Quelldateien überwacht, bei jeder Änderung neu gebaut und Ihre App automatisch mit dem lokalen Twenty-Server synchronisiert. In Ihrem Terminal sollte eine Live-Statusanzeige angezeigt werden.
## Projektstruktur (vom Scaffolder erzeugt)
Für ausführlichere Ausgaben (Build-Protokolle, Sync-Anfragen, Fehlerspuren) verwenden Sie das Flag `--verbose`:
Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der Scaffolder Folgendes:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* Kopiert eine minimale Basisanwendung nach `my-twenty-app/`
* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu
* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind
* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Pre-Installations- und Post-Installationsfunktionen) sowie Beispieldateien entsprechend dem Scaffolding-Modus
<Warning>
Der Dev-Modus ist nur auf Twenty-Instanzen verfügbar, die im Entwicklungsmodus laufen (`NODE_ENV=development`). Produktionsinstanzen lehnen Dev-Synchronisierungsanfragen ab. Verwenden Sie `yarn twenty deploy`, um auf Produktionsservern bereitzustellen — Details finden Sie unter [Apps veröffentlichen](/l/de/developers/extend/apps/publishing).
</Warning>
Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Terminalausgabe im Dev-Modus" />
</div>
## Schritt 6: Ihre App in Twenty ansehen
Öffnen Sie [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in Ihrem Browser. Navigieren Sie zu **Settings > Apps** und wählen Sie die Registerkarte **Developer**. Unter **Your Apps** sollte Ihre App aufgeführt sein:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Liste &#x22;Your Apps&#x22;, die &#x22;My twenty app&#x22; anzeigt" />
</div>
Klicken Sie auf **My twenty app**, um die **Anwendungsregistrierung** zu öffnen. Eine Registrierung ist ein Servereintrag, der Ihre App beschreibt — ihren Namen, den eindeutigen Bezeichner, OAuth-Zugangsdaten und die Quelle (lokal, npm oder Tarball). Sie befindet sich auf dem Server, nicht in einem bestimmten Arbeitsbereich. Wenn Sie eine App in einen Arbeitsbereich installieren, erstellt Twenty eine arbeitsbereichsbezogene Anwendung, die auf diese Registrierung verweist. Eine Registrierung kann in mehreren Arbeitsbereichen auf demselben Server installiert werden.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Details der Anwendungsregistrierung" />
</div>
Klicken Sie auf **View installed app**, um die installierte App anzuzeigen. Die Registerkarte **About** zeigt die aktuelle Version und Verwaltungsoptionen:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installierte App — Registerkarte About" />
</div>
Wechseln Sie zur Registerkarte **Content**, um alles zu sehen, was Ihre App bereitstellt — Objekte, Felder, Logikfunktionen und Agenten:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installierte App — Registerkarte Content" />
</div>
Alles erledigt! Bearbeiten Sie eine beliebige Datei in `src/`, und die Änderungen werden automatisch übernommen.
Wechseln Sie zu [Apps erstellen](/l/de/developers/extend/apps/building) für eine ausführliche Anleitung zum Erstellen von Objekten, Logikfunktionen, Frontend-Komponenten, Skills und mehr.
---
## Projektstruktur
Das Scaffolding-Tool erzeugt die folgende Verzeichnisstruktur (gezeigt im Modus `--exhaustive`, der Beispiele für jeden Entitätstyp enthält):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`).
Standardmäßig (`--minimal`) werden nur die Kerndateien erstellt: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`. Verwenden Sie `--exhaustive`, um alle oben gezeigten Beispieldateien einzuschließen.
Auf hoher Ebene:
### Wichtige Dateien
* **package.json**: Deklariert den App-Namen, die Version und die Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie ein `twenty`-Skript hinzu, das an die lokale `twenty`-CLI delegiert. Führen Sie `yarn twenty help` aus, um alle verfügbaren Befehle aufzulisten.
* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
* **.oxlintrc.json** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit.
* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen.
* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich.
* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren
| Datei / Ordner | Zweck |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `package.json` | Deklariert den App-Namen, die Version und Abhängigkeiten. Enthält ein `twenty`-Skript, sodass Sie `yarn twenty help` ausführen können, um alle Befehle anzuzeigen. |
| `src/application-config.ts` | **Erforderlich.** Die Hauptkonfigurationsdatei für Ihre App. |
| `src/roles/` | Definiert Rollen, die steuern, worauf Ihre Logikfunktionen zugreifen können. |
| `src/logic-functions/` | Serverseitige Funktionen, die durch Routen, Cron-Zeitpläne oder Datenbankereignisse ausgelöst werden. |
| `src/front-components/` | React-Komponenten, die innerhalb der Twenty-UI gerendert werden. |
| `src/objects/` | Benutzerdefinierte Objektdefinitionen zur Erweiterung Ihres Datenmodells. |
| `src/fields/` | Benutzerdefinierte Felder, die vorhandenen Objekten hinzugefügt werden. |
| `src/views/` | Konfigurationen gespeicherter Ansichten. |
| `src/navigation-menu-items/` | Benutzerdefinierte Links in der Seitenleisten-Navigation. |
| `src/skills/` | Skills, die die KI-Agenten von Twenty erweitern. |
| `src/agents/` | KI-Agenten mit benutzerdefinierten Prompts. |
| `src/page-layouts/` | Benutzerdefinierte Seitenlayouts für Datensatzansichten. |
| `src/__tests__/` | Integrationstests (Setup + Beispieltest). |
| `public/` | Statische Assets (Bilder, Schriftarten), die mit Ihrer App ausgeliefert werden. |
### Entitätserkennung
## Remotes verwalten
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
| Hilfsfunktion | Entitätstyp |
| -------------------------------- | ------------------------------------------------------------------------ |
| `defineObject` | Benutzerdefinierte Objektdefinitionen |
| `defineLogicFunction` | Definitionen von Logikfunktionen |
| `definePreInstallLogicFunction` | Pre-Installations-Logikfunktion (wird vor der Installation ausgeführt) |
| `definePostInstallLogicFunction` | Post-Installations-Logikfunktion (wird nach der Installation ausgeführt) |
| `defineFrontComponent` | Definitionen von Frontend-Komponenten |
| `defineRole` | Rollendefinitionen |
| `defineField` | Felderweiterungen für bestehende Objekte |
| `defineView` | Gespeicherte View-Definitionen |
| `defineNavigationMenuItem` | Definitionen von Navigationsmenüeinträgen |
| `defineSkill` | Skill-Definitionen für KI-Agenten |
<Note>
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
</Note>
Beispiel für eine erkannte Entität:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
* `yarn twenty dev` generiert den typisierten `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) automatisch in `node_modules/twenty-client-sdk/`. Der `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`) wird vorkompiliert ausgeliefert und ist sofort verfügbar. Importieren Sie sie jeweils aus `twenty-client-sdk/core` und `twenty-client-sdk/metadata`.
* `yarn twenty add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu.
## Authentifizierung
Wenn Sie `yarn twenty auth:login` zum ersten Mal ausführen, werden Sie nach Folgendem gefragt:
* API-URL (standardmäßig http://localhost:3000 oder Ihr aktuelles Workspace-Profil)
* API-Schlüssel
Ihre Anmeldedaten werden pro Benutzer in `~/.twenty/config.json` gespeichert. Sie können mehrere Profile verwalten und zwischen ihnen wechseln.
### Arbeitsbereiche verwalten
Ein **Remote** ist ein Twenty-Server, mit dem sich Ihre App verbindet. Während der Einrichtung erstellt das Scaffolding-Tool automatisch eines für Sie. Sie können jederzeit weitere Remotes hinzufügen oder zwischen ihnen wechseln.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
Sobald Sie mit `yarn twenty auth:switch` den Arbeitsbereich gewechselt haben, verwenden alle nachfolgenden Befehle standardmäßig diesen Arbeitsbereich. Sie können es weiterhin vorübergehend mit `--workspace <name>` überschreiben.
Ihre Anmeldedaten werden in `~/.twenty/config.json` gespeichert.
## Lokaler Entwicklungsserver (`yarn twenty server`)
Die CLI kann einen lokalen, in Docker laufenden Twenty-Server verwalten. Dies ist derselbe Server, der automatisch gestartet wird, wenn Sie mit `create-twenty-app` eine App aufsetzen, aber Sie können ihn auch manuell verwalten.
### Server starten
```bash filename="Terminal"
yarn twenty server start
```
Dadurch wird das Docker-Image `twentycrm/twenty-app-dev:latest` heruntergeladen (falls nicht bereits vorhanden), ein Container namens `twenty-app-dev` erstellt und auf Port **2020** gestartet. Die CLI wartet, bis der Server seinen Health-Check bestanden hat, bevor sie zurückkehrt.
Es werden zwei Docker-Volumes erstellt, um Daten zwischen Neustarts beizubehalten:
* `twenty-app-dev-data` — PostgreSQL-Datenbank
* `twenty-app-dev-storage` — Dateispeicher
Wenn Port 2020 bereits verwendet wird, können Sie auf einem anderen Port starten:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
Die CLI konfiguriert automatisch die internen Werte `NODE_PORT` und `SERVER_URL` des Containers passend zum gewählten Port, sodass Logikfunktionen, OAuth und alle anderen internen Netzwerkfunktionen korrekt arbeiten.
Nach dem Start wird der Server automatisch als `local`-Remote in Ihrer CLI-Konfiguration registriert.
### Serverstatus prüfen
```bash filename="Terminal"
yarn twenty server status
```
Zeigt an, ob der Server läuft, seine URL und die Standard-Anmeldedaten (`tim@apple.dev` / `tim@apple.dev`).
### Serverprotokolle anzeigen
```bash filename="Terminal"
yarn twenty server logs
```
Streamt die Containerprotokolle. Verwenden Sie `--lines`, um zu steuern, wie viele der letzten Zeilen angezeigt werden:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Server stoppen
```bash filename="Terminal"
yarn twenty server stop
```
Stoppt den Container. Ihre Daten bleiben in den Docker-Volumes erhalten — der nächste `start` macht dort weiter, wo Sie aufgehört haben.
### Server zurücksetzen
```bash filename="Terminal"
yarn twenty server reset
```
Entfernt den Container **und** löscht beide Docker-Volumes, wobei alle Daten gelöscht werden. Der nächste `start` erstellt eine frische Instanz.
<Note>
Der Server erfordert, dass **Docker** läuft. Wenn der Fehler "Docker not running" angezeigt wird, stellen Sie sicher, dass Docker Desktop (oder der Docker-Daemon) gestartet ist.
</Note>
### Befehlsreferenz
| Befehl | Beschreibung |
| -------------------------------------- | ----------------------------------------------------------- |
| `yarn twenty server start` | Lokalen Server starten (lädt das Image bei Bedarf herunter) |
| `yarn twenty server start --port 3030` | Auf einem benutzerdefinierten Port starten |
| `yarn twenty server stop` | Server stoppen (Daten bleiben erhalten) |
| `yarn twenty server status` | Serverstatus, URL und Anmeldedaten anzeigen |
| `yarn twenty server logs` | Serverprotokolle streamen |
| `yarn twenty server logs --lines 100` | Die letzten 100 Protokollzeilen anzeigen |
| `yarn twenty server reset` | Alle Daten löschen und neu starten |
## 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.
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.
## Manuelle Einrichtung (ohne Scaffolder)
Wir empfehlen zwar `create-twenty-app` für das beste Einstiegserlebnis, Sie können ein Projekt aber auch manuell einrichten. Installieren Sie die CLI nicht global. Fügen Sie stattdessen `twenty-sdk` als lokale Abhängigkeit hinzu und binden Sie ein einzelnes Skript in Ihrer package.json ein:
Wenn Sie die Einrichtung lieber selbst vornehmen möchten, anstatt `create-twenty-app` zu verwenden, können Sie dies in zwei Schritten tun.
**1. Fügen Sie `twenty-sdk` und `twenty-client-sdk` als Abhängigkeiten hinzu:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Fügen Sie dann ein `twenty`-Skript hinzu:
**2. Fügen Sie Ihrer `package.json` ein `twenty`-Skript hinzu:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ Fügen Sie dann ein `twenty`-Skript hinzu:
}
```
Jetzt können Sie alle Befehle über `yarn twenty <command>` ausführen, z. B. `yarn twenty dev`, `yarn twenty help` usw.
Sie können jetzt `yarn twenty dev`, `yarn twenty help` und alle anderen Befehle ausführen.
## So verwenden Sie eine lokale Twenty-Instanz
Wenn Sie bereits lokal eine Twenty-Instanz ausführen (z. B. über `npx nx start twenty-server`), können Sie sich damit verbinden, anstatt Docker zu verwenden:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Installieren Sie `twenty-sdk` nicht global. Verwenden Sie es immer als lokale Projektabhängigkeit, damit jedes Projekt seine eigene Version festlegen kann.
</Note>
## Fehlerbehebung
* Authentifizierungsfehler: Führen Sie `yarn twenty auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat.
* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist.
* Typen oder Client fehlen oder sind veraltet: Starten Sie `yarn twenty dev` neu — der typisierte Client wird automatisch generiert.
* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn twenty dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden.
Wenn Probleme auftreten:
Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322
* Stellen Sie sicher, dass **Docker läuft**, bevor Sie das Scaffolding-Tool mit einer lokalen Instanz starten.
* Stellen Sie sicher, dass Sie **Node.js 24+** verwenden (`node -v` zur Überprüfung).
* Stellen Sie sicher, dass **Corepack aktiviert ist** (`corepack enable`), damit Yarn 4 verfügbar ist.
* Versuchen Sie, `node_modules` zu löschen und `yarn install` erneut auszuführen, wenn Abhängigkeiten fehlerhaft erscheinen.
Hängen Sie immer noch fest? Bitten Sie im [Twenty-Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) um Hilfe.
@@ -4,34 +4,76 @@ description: Veröffentlichen Sie Ihre Twenty-App auf dem Twenty-Marktplatz oder
---
<Warning>
Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
</Warning>
## Übersicht
Sobald Ihre App [lokal gebaut und getestet](/l/de/developers/extend/apps/building) wurde, haben Sie zwei Möglichkeiten, sie zu verteilen:
* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann.
* **Einen Tarball bereitstellen** — Laden Sie Ihre App direkt auf einen bestimmten Twenty-Server für die interne oder private Nutzung hoch.
* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann.
Beide Pfade beginnen mit demselben **Build**-Schritt.
## Erstellen Ihrer App
Der Befehl `build` kompiliert Ihre TypeScript-Quelltexte, transpiliert Logikfunktionen und Frontend-Komponenten und erzeugt eine `manifest.json`, die die Inhalte Ihrer App beschreibt:
Führen Sie den Build-Befehl aus, um Ihre App zu kompilieren und eine distributionsfertige `manifest.json` zu erzeugen:
```bash filename="Terminal"
yarn twenty build
```
Die Ausgabe wird in `.twenty/output/` geschrieben. Dieses Verzeichnis enthält alles, was für die Verteilung benötigt wird: kompilierter Code, Assets, das Manifest und eine Kopie Ihrer `package.json`.
Dabei werden TypeScript-Quelltexte kompiliert, Logikfunktionen und Frontend-Komponenten transpiliert und alles in `.twenty/output/` geschrieben. Fügen Sie `--tarball` hinzu, um zusätzlich ein `.tgz`-Paket für die manuelle Verteilung oder den Deploy-Befehl zu erzeugen.
Um zusätzlich ein `.tgz`-Tarball zu erstellen (wird intern vom Deploy-Befehl verwendet oder für die manuelle Verteilung):
## Bereitstellung auf einem Server (Tarball)
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, ausschließlich für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einem Twenty-Server bereitstellen.
### Voraussetzungen
Bevor Sie bereitstellen, benötigen Sie ein konfiguriertes Remote, das auf den Zielserver zeigt. Remotes speichern die Server-URL und Anmeldeinformationen lokal in `~/.twenty/config.json`.
Ein Remote hinzufügen:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Bereitstellen
Bauen und laden Sie Ihre App in einem Schritt auf den Server hoch:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Eine bereitgestellte App freigeben
Tarball-Apps werden nicht im öffentlichen Marktplatz gelistet, daher entdecken andere Arbeitsbereiche auf demselben Server sie nicht durch Stöbern. So geben Sie eine bereitgestellte App frei:
1. Gehen Sie zu **Einstellungen > Anwendungen > Registrierungen** und öffnen Sie Ihre App
2. Klicken Sie im Tab **Distribution** auf **Freigabelink kopieren**
3. Teilen Sie diesen Link mit Nutzern in anderen Arbeitsbereichen — er führt sie direkt zur Installationsseite der App
Der Freigabelink verwendet die Basis-URL des Servers (ohne Workspace-Subdomain), sodass er für jeden Arbeitsbereich auf dem Server funktioniert.
<Warning>
Das Teilen privater Apps ist eine Enterprise-Funktion. Gehen Sie zu [Einstellungen > Admin-Panel > Enterprise](/settings/admin-panel#enterprise), um es zu aktivieren.
</Warning>
### Versionsverwaltung
So veröffentlichen Sie ein Update:
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
2. Führen Sie `yarn twenty deploy` aus (oder `yarn twenty deploy --remote production`)
3. Arbeitsbereiche, die die App installiert haben, sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
{/* TODO: add screenshot of the Upgrade button */}
## Auf npm veröffentlichen
Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Jeder Twenty-Arbeitsbereich kann Marktplatz-Apps direkt über die Benutzeroberfläche durchsuchen, installieren und aktualisieren.
@@ -39,41 +81,42 @@ Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Je
### Anforderungen
* Ein [npm](https://www.npmjs.com)-Konto
* Das Schlüsselwort `twenty-app` **muss** in Ihrem `package.json`-`keywords`-Array aufgeführt sein
### Das erforderliche Schlüsselwort hinzufügen
Der Twenty-Marktplatz entdeckt Apps, indem er die npm-Registry nach Paketen mit dem Schlüsselwort `twenty-app` durchsucht. Fügen Sie es zu Ihrer `package.json` hinzu:
* Das Schlüsselwort `twenty-app` in Ihrem `package.json`-Array `keywords` (bereits enthalten, wenn Sie mit `create-twenty-app` ein Gerüst erstellen)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
Der Marktplatz sucht in der npm-Registry nach `keywords:twenty-app`. Ohne dieses Schlüsselwort erscheint Ihr Paket nicht im Marktplatz, selbst wenn es das Namenspräfix `twenty-app-` hat.
</Note>
### Marktplatz-Metadaten
### Schritte
Die `defineApplication()`-Konfiguration unterstützt optionale Felder, die steuern, wie Ihre App im Marktplatz erscheint. Verwenden Sie `logoUrl` und `screenshots`, um Bilder aus dem Ordner `public/` zu referenzieren:
1. **Erstellen Ihrer App:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **Auf npm veröffentlichen:**
Siehe das [defineApplication-Akkordeon](/l/de/developers/extend/apps/building#defineentity-functions) auf der Seite Building Apps für die vollständige Liste der Marktplatzfelder (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` usw.).
### Veröffentlichen
```bash filename="Terminal"
yarn twenty publish
```
Dies führt `npm publish` aus dem Verzeichnis `.twenty/output/` aus.
Um unter einem bestimmten dist-tag zu veröffentlichen (z. B. `beta` oder `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### So funktioniert die Marktplatz-Erkennung
Der Twenty-Server synchronisiert seinen Marktplatzkatalog **stündlich** mit der npm-Registry:
Der Twenty-Server synchronisiert seinen Marktplatzkatalog **stündlich** aus der npm-Registry.
1. Er sucht nach allen npm-Paketen mit dem Schlüsselwort `keywords:twenty-app`
2. Für jedes Paket ruft er die `manifest.json` vom npm-CDN ab
3. Die Metadaten der App (Name, Beschreibung, Autor, Logo, Screenshots, Kategorie) werden aus dem Manifest extrahiert und im Marktplatz angezeigt
Nach der Veröffentlichung kann es bis zu einer Stunde dauern, bis Ihre App im Marktplatz erscheint. Um die Synchronisierung sofort auszulösen, statt auf den nächsten stündlichen Lauf zu warten:
Sie können die Synchronisierung sofort auslösen, anstatt zu warten:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Um ein bestimmtes Remote anzusteuern:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
Die im Marktplatz angezeigten Metadaten stammen aus Ihrem `defineApplication()`-Aufruf im Quellcode Ihrer App — Felder wie `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` und `termsUrl`.
Die im Marktplatz angezeigten Metadaten stammen aus Ihrer `defineApplication()`-Konfiguration — Felder wie `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` und `termsUrl`.
<Note>
Wenn deine App keine `aboutDescription` in `defineApplication()` definiert, verwendet der Marktplatz automatisch die `README.md` deines Pakets von npm als Inhalt der Über-uns-Seite. Das bedeutet, dass du eine einzige README sowohl für npm als auch für den Twenty-Marktplatz pflegen kannst. Wenn du im Marktplatz eine andere Beschreibung möchtest, setze `aboutDescription` explizit.
@@ -108,7 +143,7 @@ Wenn deine App keine `aboutDescription` in `defineApplication()` definiert, verw
### CI-Veröffentlichung
Das vorgefertigte Projekt enthält einen GitHub-Actions-Workflow, der bei jedem Release eine Veröffentlichung durchführt:
Verwenden Sie diesen GitHub-Actions-Workflow, um bei jedem Release automatisch zu veröffentlichen (verwendet [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `yarn twenty build` und anschließend `npm publish` aus `.twenty/output`.
<Tip>
<Note>
**npm-Provenance** ist optional, wird jedoch empfohlen. Das Veröffentlichen mit `--provenance` fügt Ihrem npm-Eintrag ein Vertrauensabzeichen hinzu, sodass Nutzer überprüfen können, dass das Paket aus einem bestimmten Commit in einer öffentlichen CI-Pipeline gebaut wurde. Siehe die [npm-Provenance-Dokumentation](https://docs.npmjs.com/generating-provenance-statements) für Einrichtungshinweise.
</Tip>
## Bereitstellung auf einem Server (Tarball)
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, ausschließlich für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einem Twenty-Server bereitstellen.
### Voraussetzungen
Bevor Sie bereitstellen, benötigen Sie ein konfiguriertes Remote, das auf den Zielserver zeigt. Remotes speichern die Server-URL und Anmeldeinformationen lokal in `~/.twenty/config.json`.
Ein Remote hinzufügen:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
Für einen lokalen Entwicklungsserver:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
Sie können sich in nicht interaktiven Umgebungen auch mit einem API-Schlüssel authentifizieren:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Ihre Remotes verwalten:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Bereitstellen
Bauen und laden Sie Ihre App in einem Schritt auf den Server hoch:
```bash filename="Terminal"
yarn twenty deploy
```
Dies baut die App mit `--tarball` und lädt anschließend den Tarball per GraphQL-Multipart-Upload auf das Standard-Remote hoch.
Um auf ein bestimmtes Remote bereitzustellen:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Eine bereitgestellte App freigeben
Tarball-Apps werden nicht im öffentlichen Marktplatz gelistet, daher entdecken andere Arbeitsbereiche auf demselben Server sie nicht durch Stöbern. So geben Sie eine bereitgestellte App frei:
1. Gehen Sie zu **Einstellungen > Anwendungen > Registrierungen** und öffnen Sie Ihre App
2. Klicken Sie im Tab **Distribution** auf **Freigabelink kopieren**
3. Teilen Sie diesen Link mit Nutzern in anderen Arbeitsbereichen — er führt sie direkt zur Installationsseite der App
Der Freigabelink verwendet die Basis-URL des Servers (ohne Workspace-Subdomain), sodass er für jeden Arbeitsbereich auf dem Server funktioniert.
### Versionsverwaltung
So veröffentlichen Sie ein Update:
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
2. Führen Sie `yarn twenty deploy` aus (oder `yarn twenty deploy -r production`)
3. Arbeitsbereiche, die die App installiert haben, sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
</Note>
## Apps installieren
Sobald eine App veröffentlicht (npm) oder bereitgestellt (Tarball) wurde, installieren Arbeitsbereiche sie über die Benutzeroberfläche:
Sobald eine App veröffentlicht (npm) oder bereitgestellt (Tarball) wurde, können Arbeitsbereiche sie über die Benutzeroberfläche installieren.
Gehen Sie zur Seite **Einstellungen > Anwendungen** in Twenty, auf der sowohl Marktplatz- als auch per Tarball bereitgestellte Apps durchsucht und installiert werden können.
{/* TODO: add screenshot of the UI when the app is registered */}
Sie können Apps auch über die Befehlszeile installieren:
```bash filename="Terminal"
yarn twenty install
```
Oder über die Seite **Einstellungen > Anwendungen** in der Twenty-Oberfläche, wo sowohl Marktplatz- als auch per Tarball bereitgestellte Apps durchsucht und installiert werden können.
## Kategorien der App-Verteilung
Twenty organisiert Apps in drei Kategorien, basierend auf ihrer Vertriebsart:
| Kategorie | Wie es funktioniert | Im Marktplatz sichtbar? |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| **Entwicklung** | Lokale Apps im Entwicklungsmodus, die über `yarn twenty dev` ausgeführt werden. Zum Erstellen und Testen verwendet. | Nein |
| **Veröffentlicht (npm)** | Auf npm veröffentlichte Apps mit dem Schlüsselwort `twenty-app`. Im Marktplatz gelistet, damit jeder Arbeitsbereich sie installieren kann. | Ja |
| **Intern (Tarball)** | Apps, die per Tarball auf einen bestimmten Server bereitgestellt werden. Nur für Arbeitsbereiche auf diesem Server per Freigabelink verfügbar. | Nein |
<Tip>
Beginnen Sie im **Entwicklungsmodus**, während Sie Ihre App erstellen. Wenn sie bereit ist, wählen Sie **Veröffentlicht** (npm) für die breite Verteilung oder **Intern** (Tarball) für die private Bereitstellung.
</Tip>
## CLI-Referenz
| Befehl | Beschreibung | Wichtige Flags |
| --------------------------- | --------------------------------------------------------------- | --------------------------------------------------- |
| `yarn twenty build` | App kompilieren und Manifest erzeugen | `--tarball` — zusätzlich ein `.tgz`-Paket erstellen |
| `yarn twenty publish` | Bauen und auf npm veröffentlichen | `--tag <tag>` — npm-dist-tag (z. B. `beta`, `next`) |
| `yarn twenty deploy` | Tarball bauen und auf einen Server hochladen | `-r, --remote <name>` — Ziel-Remote |
| `yarn twenty catalog-sync` | Synchronisierung des Marktplatzkatalogs auf dem Server auslösen | `-r, --remote <name>` — Ziel-Remote |
| `yarn twenty install` | Eine bereitgestellte App in einem Arbeitsbereich installieren | `-r, --remote <name>` — Ziel-Remote |
| `yarn twenty dev` | Lokale Änderungen beobachten und synchronisieren | Verwendet das Standard-Remote |
| `yarn twenty remote add` | Eine Serververbindung hinzufügen | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | Konfigurierte Remotes auflisten | — |
| `yarn twenty remote switch` | Standard-Remote festlegen | — |
| `yarn twenty remote status` | Verbindungsstatus anzeigen | — |
| `yarn twenty remote remove` | Ein Remote entfernen | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### An ein anderes Objekt anhängen
Ersetzen Sie `companyId` durch das entsprechende Feld:
Ersetzen Sie `targetCompanyId` durch das entsprechende Feld:
| Objekt | Feldname |
| -------------------------- | -------------------- |
| Unternehmen | `companyId` |
| Person | `personId` |
| Opportunity | `opportunityId` |
| Benutzerdefiniertes Objekt | `yourCustomObjectId` |
| Objekt | Feldname |
| -------------------------- | -------------------------- |
| Unternehmen | `targetCompanyId` |
| Person | `targetPersonId` |
| Opportunity | `targetOpportunityId` |
| Benutzerdefiniertes Objekt | `targetYourCustomObjectId` |
Aktualisieren Sie sowohl den Funktionsparameter als auch das Objekt `variables.data` in der Attachment-Mutation.
File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: Crea la tua prima app Twenty in pochi minuti.
---
<Warning>
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione.
</Warning>
Le app ti permettono di estendere Twenty con oggetti, campi, funzioni logiche, competenze IA e componenti UI personalizzati — il tutto gestito come codice.
**Cosa puoi creare:**
* Oggetti, campi, viste ed elementi di navigazione personalizzati per definire il tuo modello di dati
* Funzioni logiche attivate da route HTTP, pianificazioni cron o eventi del database
* Componenti front-end che vengono renderizzati direttamente all'interno della UI di Twenty
* Abilità che estendono gli agenti IA di Twenty
* Distribuisci un'app su più spazi di lavoro
## Prerequisiti
* Node.js 24+
* Yarn 4
* Docker (o un'istanza locale di Twenty in esecuzione)
Prima di iniziare, assicurati che quanto segue sia installato sul tuo computer:
## Per iniziare
* **Node.js 24+** — [Scarica qui](https://nodejs.org/)
* **Yarn 4** — Incluso con Node.js tramite Corepack. Abilitalo eseguendo `corepack enable`
* **Docker** — [Scarica qui](https://www.docker.com/products/docker-desktop/). Necessario per eseguire un'istanza locale di Twenty. Non necessario se hai già un server Twenty in esecuzione.
Crea una nuova app utilizzando lo scaffolder ufficiale, quindi autenticati e inizia a sviluppare:
## Passaggio 1: Crea lo scheletro della tua app
Apri un terminale ed esegui:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Usa l'opzione `--minimal` per creare un'installazione minima
Ti verrà chiesto di inserire un nome e una descrizione per la tua app. Premi **Invio** per accettare i valori predefiniti.
Da qui puoi:
Questo crea una nuova cartella chiamata `my-twenty-app` con tutto il necessario.
<Note>
Lo strumento di scaffolding supporta questi flag:
* `--minimal` — genera solo i file essenziali, senza esempi (predefinito)
* `--exhaustive` — genera tutte le entità di esempio
* `--name <name>` — imposta il nome dell'app (salta la richiesta)
* `--display-name <displayName>` — imposta il nome visualizzato (salta la richiesta)
* `--description <description>` — imposta la descrizione (salta la richiesta)
* `--skip-local-instance` — salta la richiesta di configurazione del server locale
</Note>
## Passaggio 2: Configura un'istanza locale di Twenty
Lo strumento di scaffolding chiederà:
> **Vuoi configurare un'istanza locale di Twenty?**
* **Digita `yes`** (consigliato) — Questo scarica l'immagine Docker `twenty-app-dev` e avvia un server Twenty locale sulla porta `2020`. Assicurati che Docker sia in esecuzione prima di continuare.
* **Digita `no`** — Sceglilo se hai già un server Twenty in esecuzione in locale.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Avviare l'istanza locale?" />
</div>
## Passaggio 3: Accedi al tuo spazio di lavoro
Successivamente si aprirà una finestra del browser con la pagina di accesso di Twenty. Accedi con l'account demo preconfigurato:
* **Email:** `tim@apple.dev`
* **Password:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Schermata di accesso di Twenty" />
</div>
## Passaggio 4: Autorizza l'app
Dopo l'accesso, vedrai una schermata di autorizzazione. Questo consente alla tua app di interagire con il tuo spazio di lavoro.
Fai clic su **Authorize** per continuare.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Schermata di autorizzazione della CLI di Twenty" />
</div>
Una volta autorizzato, il terminale confermerà che tutto è configurato.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App creata con successo" />
</div>
## Passaggio 5: Inizia a sviluppare
Entra nella nuova cartella della tua app e avvia il server di sviluppo:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
Vedi anche: le pagine di riferimento della CLI per [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Questo controlla i file sorgente, ricompila a ogni modifica e sincronizza automaticamente la tua app con il server Twenty locale. Dovresti vedere un pannello di stato in tempo reale nel terminale.
## Struttura del progetto (generata dallo scaffolder)
Per un output più dettagliato (log di build, richieste di sincronizzazione, tracce di errore), usa il flag `--verbose`:
Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* Copia un'applicazione base minimale in `my-twenty-app/`
* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4
* Crea file di configurazione e script collegati alla CLI `twenty`
* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding
<Warning>
La modalità di sviluppo è disponibile solo sulle istanze di Twenty in esecuzione in modalità sviluppo (`NODE_ENV=development`). Le istanze di produzione rifiutano le richieste di sincronizzazione in modalità sviluppo. Usa `yarn twenty deploy` per distribuire sui server di produzione — vedi [Pubblicazione delle app](/l/it/developers/extend/apps/publishing) per i dettagli.
</Warning>
Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Output del terminale in modalità sviluppo" />
</div>
## Passaggio 6: Visualizza la tua app in Twenty
Apri [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) nel browser. Vai su **Settings > Apps** e seleziona la scheda **Developer**. Dovresti vedere la tua app elencata in **Your Apps**:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Elenco Your Apps che mostra My twenty app" />
</div>
Fai clic su **My twenty app** per aprire la sua **registrazione dell'applicazione**. Una registrazione è un record a livello di server che descrive la tua app — il suo nome, identificatore univoco, credenziali OAuth e origine (locale, npm o tarball). Risiede sul server, non all'interno di uno spazio di lavoro specifico. Quando installi un'app in uno spazio di lavoro, Twenty crea un'**applicazione** con ambito dello spazio di lavoro che rimanda a questa registrazione. Una registrazione può essere installata in più spazi di lavoro sullo stesso server.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Dettagli della registrazione dell'applicazione" />
</div>
Fai clic su **View installed app** per vedere l'app installata. La scheda **About** mostra la versione corrente e le opzioni di gestione:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="App installata — scheda About" />
</div>
Passa alla scheda **Content** per vedere tutto ciò che la tua app fornisce — oggetti, campi, funzioni logiche e agenti:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="App installata — scheda Content" />
</div>
È tutto pronto! Modifica qualsiasi file in `src/` e le modifiche verranno rilevate automaticamente.
Vai a [Creare app](/l/it/developers/extend/apps/building) per una guida dettagliata sulla creazione di oggetti, funzioni logiche, componenti front-end, skill e altro.
---
## Struttura del progetto
Lo strumento di scaffolding genera la seguente struttura di file (mostrata con la modalità `--exhaustive`, che include esempi per ogni tipo di entità):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
Per impostazione predefinita (`--minimal`), vengono creati solo i file principali: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`. Usa `--exhaustive` per includere tutti i file di esempio mostrati sopra.
A livello generale:
### File principali
* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili.
* **.gitignore**: Ignora i file generati comuni come `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto.
* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto.
* **.oxlintrc.json** e **tsconfig.json**: Forniscono linting e configurazione TypeScript per i sorgenti TypeScript della tua app.
* **README.md**: Un breve README nella radice dell'app con istruzioni di base.
* **public/**: Una cartella per archiviare risorse pubbliche (immagini, font, file statici) che saranno servite con la tua applicazione. I file collocati qui vengono caricati durante la sincronizzazione e sono accessibili in fase di esecuzione.
* **src/**: Il luogo principale in cui definisci la tua applicazione come codice
| File / Cartella | Scopo |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Dichiara il nome, la versione e le dipendenze della tua app. Include uno script `twenty` così puoi eseguire `yarn twenty help` per vedere tutti i comandi. |
| `src/application-config.ts` | **Obbligatorio.** Il file di configurazione principale della tua app. |
| `src/roles/` | Definisce i ruoli che controllano a cosa possono accedere le tue funzioni logiche. |
| `src/logic-functions/` | Funzioni lato server attivate da route, pianificazioni cron o eventi del database. |
| `src/front-components/` | Componenti React che vengono renderizzati all'interno della UI di Twenty. |
| `src/objects/` | Definizioni di oggetti personalizzati per estendere il tuo modello dati. |
| `src/fields/` | Campi personalizzati aggiunti a oggetti esistenti. |
| `src/views/` | Configurazioni di viste salvate. |
| `src/navigation-menu-items/` | Link personalizzati nella navigazione laterale. |
| `src/skills/` | Abilità che estendono gli agenti IA di Twenty. |
| `src/agents/` | Agenti IA con prompt personalizzati. |
| `src/page-layouts/` | Layout di pagina personalizzati per le viste dei record. |
| `src/__tests__/` | Test di integrazione (setup + test di esempio). |
| `public/` | Asset statici (immagini, font) serviti insieme alla tua app. |
### Rilevamento delle entità
## Gestione dei remoti
L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define<Entity>({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`:
| Funzione helper | Tipo di entità |
| -------------------------------- | ------------------------------------------------------------------------------ |
| `defineObject` | Definizioni di oggetti personalizzati |
| `defineLogicFunction` | Definizioni di funzioni logiche |
| `definePreInstallLogicFunction` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) |
| `definePostInstallLogicFunction` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) |
| `defineFrontComponent` | Definizioni dei componenti front-end |
| `defineRole` | Definizioni di ruoli |
| `defineField` | Estensioni di campo per oggetti esistenti |
| `defineView` | Definizioni di viste salvate |
| `defineNavigationMenuItem` | Definizioni delle voci del menu di navigazione |
| `defineSkill` | Definizioni delle competenze degli agenti IA |
<Note>
**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define<Entity>({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito.
</Note>
Esempio di entità rilevata:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Comandi successivi aggiungeranno altri file e cartelle:
* `yarn twenty dev` genererà automaticamente il `CoreApiClient` tipizzato (per i dati dell'area di lavoro via `/graphql`) in `node_modules/twenty-client-sdk/`. Il `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file via `/metadata`) è fornito precompilato ed è disponibile immediatamente. Importali da `twenty-client-sdk/core` e `twenty-client-sdk/metadata` rispettivamente.
* `yarn twenty add` aggiungerà file di definizione delle entità sotto `src/` per i tuoi oggetti personalizzati, funzioni, componenti front-end, ruoli, competenze e altro ancora.
## Autenticazione
La prima volta che esegui `yarn twenty auth:login`, ti verranno richiesti:
* URL dell'API (predefinito a http://localhost:3000 o al profilo dello spazio di lavoro corrente)
* Chiave API
Le tue credenziali sono archiviate per utente in `~/.twenty/config.json`. Puoi mantenere più profili e passare da uno all'altro.
### Gestione delle aree di lavoro
Un **remoto** è un server Twenty a cui la tua app si connette. Durante la configurazione, lo strumento di scaffolding ne crea uno automaticamente per te. Puoi aggiungere altri remoti o passare da uno all'altro in qualsiasi momento.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
Una volta che hai cambiato area di lavoro con `yarn twenty auth:switch`, tutti i comandi successivi utilizzeranno quell'area di lavoro per impostazione predefinita. Puoi comunque sovrascriverla temporaneamente con `--workspace <name>`.
Le tue credenziali sono archiviate in `~/.twenty/config.json`.
## Server di sviluppo locale (`yarn twenty server`)
La CLI può gestire un server Twenty locale in esecuzione in Docker. Questo è lo stesso server avviato automaticamente quando crei lo scheletro di un'app con `create-twenty-app`, ma puoi anche gestirlo manualmente.
### Avvio del server
```bash filename="Terminal"
yarn twenty server start
```
Questo scarica l'immagine Docker `twentycrm/twenty-app-dev:latest` (se non è già presente), crea un container chiamato `twenty-app-dev` e lo avvia sulla porta **2020**. La CLI attende che il server superi il controllo di integrità prima di restituire il controllo.
Vengono creati due volumi Docker per mantenere i dati tra i riavvii:
* `twenty-app-dev-data` — database PostgreSQL
* `twenty-app-dev-storage` — archiviazione file
Se la porta 2020 è già in uso, puoi avviare su una porta diversa:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
La CLI configura automaticamente le variabili interne del container `NODE_PORT` e `SERVER_URL` per corrispondere alla porta scelta, in modo che funzioni logiche, OAuth e tutto il resto del networking interno funzionino correttamente.
Una volta avviato, il server viene registrato automaticamente come remoto `local` nella configurazione della CLI.
### Verifica dello stato del server
```bash filename="Terminal"
yarn twenty server status
```
Mostra se il server è in esecuzione, il suo URL e le credenziali di accesso predefinite (`tim@apple.dev` / `tim@apple.dev`).
### Visualizzazione dei log del server
```bash filename="Terminal"
yarn twenty server logs
```
Trasmette in streaming i log del container. Usa `--lines` per controllare quante righe recenti mostrare:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Arresto del server
```bash filename="Terminal"
yarn twenty server stop
```
Arresta il container. I tuoi dati vengono conservati nei volumi Docker — il prossimo `start` riprenderà da dove avevi lasciato.
### Reimpostazione del server
```bash filename="Terminal"
yarn twenty server reset
```
Rimuove il container **e** elimina entrambi i volumi Docker, cancellando tutti i dati. Il prossimo `start` crea un'istanza nuova.
<Note>
Il server richiede che **Docker** sia in esecuzione. Se vedi l'errore "Docker not running", assicurati che Docker Desktop (o il demone Docker) sia avviato.
</Note>
### Riferimento ai comandi
| Comando | Descrizione |
| -------------------------------------- | --------------------------------------------------------- |
| `yarn twenty server start` | Avvia il server locale (scarica l'immagine se necessario) |
| `yarn twenty server start --port 3030` | Avvia su una porta personalizzata |
| `yarn twenty server stop` | Arresta il server (conserva i dati) |
| `yarn twenty server status` | Mostra stato del server, URL e credenziali |
| `yarn twenty server logs` | Trasmetti in streaming i log del server |
| `yarn twenty server logs --lines 100` | Mostra le ultime 100 righe di log |
| `yarn twenty server reset` | Elimina tutti i dati e riparti da zero |
## CI con GitHub Actions
Lo strumento di scaffolding genera un workflow GitHub Actions pronto all'uso in `.github/workflows/ci.yml`. Esegue automaticamente i test di integrazione a ogni push su `main` e sulle pull request.
Il workflow:
1. Esegue il checkout del tuo codice
2. Avvia un server Twenty temporaneo utilizzando l'azione `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Installa le dipendenze con `yarn install --immutable`
4. Esegue `yarn test` con `TWENTY_API_URL` e `TWENTY_API_KEY` iniettati dagli output dell'azione
```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 }}
```
Non è necessario configurare alcun secret — l'azione `spawn-twenty-docker-image` avvia un server Twenty effimero direttamente nel runner e fornisce i dettagli di connessione. Il secret `GITHUB_TOKEN` è fornito automaticamente da GitHub.
Per fissare una versione specifica di Twenty invece di `latest`, modifica la variabile d'ambiente `TWENTY_VERSION` all'inizio del workflow.
## Configurazione manuale (senza lo scaffolder)
Sebbene consigliamo di utilizzare `create-twenty-app` per la migliore esperienza iniziale, puoi anche configurare un progetto manualmente. Non installare la CLI globalmente. Invece, aggiungi `twenty-sdk` come dipendenza locale e collega un unico script nel tuo package.json:
Se preferisci configurare tutto manualmente invece di usare `create-twenty-app`, puoi farlo in due passaggi.
**1. Aggiungi `twenty-sdk` e `twenty-client-sdk` come dipendenze:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Quindi aggiungi uno script `twenty`:
**2. Aggiungi uno script `twenty` al tuo `package.json`:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ Quindi aggiungi uno script `twenty`:
}
```
Ora puoi eseguire tutti i comandi tramite `yarn twenty <command>`, ad es. `yarn twenty dev`, `yarn twenty help`, ecc.
Ora puoi eseguire `yarn twenty dev`, `yarn twenty help` e tutti gli altri comandi.
## Come utilizzare un'istanza locale di Twenty
Se stai già eseguendo un'istanza di Twenty in locale (ad es. tramite `npx nx start twenty-server`), puoi connetterti ad essa invece di usare Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Non installare `twenty-sdk` globalmente. Usalo sempre come dipendenza locale del progetto, in modo che ogni progetto possa fissare la propria versione.
</Note>
## Risoluzione dei problemi
* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti.
* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile.
* Tipi o client mancanti/obsoleti: riavvia `yarn twenty dev` — genera automaticamente il client tipizzato.
* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente.
Se riscontri problemi:
Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
* Assicurati che **Docker sia in esecuzione** prima di avviare lo strumento di scaffolding con un'istanza locale.
* Assicurati di usare **Node.js 24+** (`node -v` per verificare).
* Assicurati che **Corepack sia abilitato** (`corepack enable`) in modo che Yarn 4 sia disponibile.
* Prova a eliminare `node_modules` ed eseguire di nuovo `yarn install` se le dipendenze sembrano danneggiate.
Ancora bloccato? Chiedi aiuto su [Discord di Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,34 +4,76 @@ description: Distribuisci la tua app Twenty nel marketplace oppure distribuiscil
---
<Warning>
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione.
</Warning>
## Panoramica
Una volta che la tua app è stata [compilata e testata localmente](/l/it/developers/extend/apps/building), hai due modalità per distribuirla:
* **Pubblica su npm** — elenca la tua app nel marketplace di Twenty affinché qualsiasi spazio di lavoro possa scoprirla e installarla.
* **Distribuisci un tarball** — carica la tua app direttamente su un server Twenty specifico per uso interno o privato.
* **Pubblica su npm** — elenca la tua app nel marketplace di Twenty affinché qualsiasi spazio di lavoro possa scoprirla e installarla.
Entrambi i percorsi partono dalla stessa fase di **build**.
## Compilazione della tua app
Il comando `build` compila i tuoi sorgenti TypeScript, transpila le funzioni di logica e i componenti front-end e genera un `manifest.json` che descrive i contenuti della tua app:
Esegui il comando di build per compilare la tua app e generare un `manifest.json` pronto per la distribuzione:
```bash filename="Terminal"
yarn twenty build
```
L'output viene scritto in `.twenty/output/`. Questa directory contiene tutto il necessario per la distribuzione: codice compilato, risorse, il manifest e una copia del tuo `package.json`.
Questo compila i sorgenti TypeScript, transpila le funzioni di logica e i componenti front-end e scrive tutto in `.twenty/output/`. Aggiungi `--tarball` per produrre anche un pacchetto `.tgz` per la distribuzione manuale o il comando di deploy.
Per creare anche un tarball `.tgz` (usato internamente dal comando di deploy o per la distribuzione manuale):
## Distribuzione su un server (tarball)
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo aziendali o build sperimentali — puoi distribuire un tarball direttamente su un server Twenty.
### Prerequisiti
Prima della distribuzione, ti serve un remote configurato che punti al server di destinazione. I remote memorizzano localmente l'URL del server e le credenziali di autenticazione in `~/.twenty/config.json`.
Aggiungi un remote:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Distribuzione
Compila e carica la tua app sul server in un solo passaggio:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Condivisione di un'app distribuita
Le app in formato tarball non sono elencate nel marketplace pubblico, quindi altri spazi di lavoro sullo stesso server non le troveranno navigando. Per condividere un'app distribuita:
1. Vai su **Impostazioni > Applicazioni > Registrazioni** e apri la tua app
2. Nella scheda **Distribuzione**, fai clic su **Copia link di condivisione**
3. Condividi questo link con utenti su altri spazi di lavoro — li porterà direttamente alla pagina di installazione dell'app
Il link di condivisione utilizza l'URL di base del server (senza alcun sottodominio dello spazio di lavoro) così funziona per qualsiasi spazio di lavoro sul server.
<Warning>
La condivisione delle app private è una funzionalità Enterprise. Vai a [Impostazioni > Pannello di amministrazione > Enterprise](/settings/admin-panel#enterprise) per abilitarla.
</Warning>
### Gestione delle versioni
Per rilasciare un aggiornamento:
1. Incrementa il campo `version` nel tuo `package.json`
2. Esegui `yarn twenty deploy` (oppure `yarn twenty deploy --remote production`)
3. Gli spazi di lavoro che hanno l'app installata vedranno l'aggiornamento disponibile nelle proprie impostazioni
{/* TODO: add screenshot of the Upgrade button */}
## Pubblicazione su npm
La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Qualsiasi spazio di lavoro Twenty può sfogliare, installare e aggiornare le app del marketplace direttamente dall'interfaccia utente.
@@ -39,41 +81,42 @@ La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Q
### Requisiti
* Un account [npm](https://www.npmjs.com)
* La parola chiave `twenty-app` **deve** essere elencata nell'array `keywords` del tuo `package.json`
### Aggiunta della parola chiave richiesta
Il marketplace di Twenty individua le app cercando nel registro npm i pacchetti con la parola chiave `twenty-app`. Aggiungila al tuo `package.json`:
* La parola chiave `twenty-app` nell'array `keywords` del tuo `package.json` (già inclusa quando inizializzi con `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
Il marketplace cerca `keywords:twenty-app` sul registro npm. Senza questa parola chiave, il tuo pacchetto non apparirà nel marketplace anche se ha il prefisso nel nome `twenty-app-`.
</Note>
### Metadati del marketplace
### Passaggi
La configurazione `defineApplication()` supporta campi opzionali che controllano come la tua app appare nel marketplace. Usa `logoUrl` e `screenshots` per fare riferimento alle immagini nella cartella `public/`:
1. **Compila la tua app:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **Pubblica su npm:**
Vedi l'[accordion defineApplication](/l/it/developers/extend/apps/building#defineentity-functions) nella pagina Building Apps per l'elenco completo dei campi del marketplace (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, ecc.).
### Pubblica
```bash filename="Terminal"
yarn twenty publish
```
Questo esegue `npm publish` dalla directory `.twenty/output/`.
Per pubblicare con un dist-tag specifico (ad es. `beta` o `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### Come funziona l'individuazione nel marketplace
Il server Twenty sincronizza il proprio catalogo del marketplace dal registro npm **ogni ora**:
Il server Twenty sincronizza il proprio catalogo del marketplace dal registro npm **ogni ora**.
1. Cerca tutti i pacchetti npm con `keywords:twenty-app`
2. Per ogni pacchetto, recupera il `manifest.json` dalla CDN di npm
3. I metadati dell'app (nome, descrizione, autore, logo, screenshot, categoria) vengono estratti dal manifest e visualizzati nel marketplace
Dopo la pubblicazione, la tua app può impiegare fino a un'ora per apparire nel marketplace. Per attivare subito la sincronizzazione invece di attendere la prossima esecuzione oraria:
Puoi attivare la sincronizzazione immediatamente invece di aspettare:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Per puntare a un remote specifico:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
I metadati mostrati nel marketplace provengono dalla chiamata a `defineApplication()` nel codice sorgente della tua app — campi come `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` e `termsUrl`.
I metadati visualizzati nel marketplace provengono dalla configurazione `defineApplication()` — campi come `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` e `termsUrl`.
<Note>
Se la tua app non definisce un `aboutDescription` in `defineApplication()`, il marketplace userà automaticamente il `README.md` del tuo pacchetto su npm come contenuto della pagina Informazioni. Questo significa che puoi mantenere un unico README sia per npm sia per il marketplace di Twenty. Se desideri una descrizione diversa nel marketplace, imposta esplicitamente `aboutDescription`.
@@ -108,7 +143,7 @@ Se la tua app non definisce un `aboutDescription` in `defineApplication()`, il m
### Pubblicazione con CI
Il progetto generato include un workflow di GitHub Actions che pubblica a ogni release:
Usa questo workflow di GitHub Actions per pubblicare automaticamente a ogni release (usa [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `yarn twenty build`, quindi `npm publish` da `.twenty/output`.
<Tip>
<Note>
**npm provenance** è opzionale ma consigliata. La pubblicazione con `--provenance` aggiunge un badge di attendibilità alla tua scheda npm, consentendo agli utenti di verificare che il pacchetto sia stato creato a partire da uno specifico commit in una pipeline CI pubblica. Consulta la [documentazione su npm provenance](https://docs.npmjs.com/generating-provenance-statements) per le istruzioni di configurazione.
</Tip>
## Distribuzione su un server (tarball)
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo aziendali o build sperimentali — puoi distribuire un tarball direttamente su un server Twenty.
### Prerequisiti
Prima della distribuzione, ti serve un remote configurato che punti al server di destinazione. I remote memorizzano localmente l'URL del server e le credenziali di autenticazione in `~/.twenty/config.json`.
Aggiungi un remote:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
Per un server di sviluppo locale:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
Puoi anche autenticarti con una chiave API per ambienti non interattivi:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Gestisci i tuoi remote:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Distribuzione
Compila e carica la tua app sul server in un solo passaggio:
```bash filename="Terminal"
yarn twenty deploy
```
Questo compila l'app con `--tarball`, quindi carica il tarball sul remote predefinito tramite un upload multipart GraphQL.
Per distribuire su un remote specifico:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Condivisione di un'app distribuita
Le app in formato tarball non sono elencate nel marketplace pubblico, quindi altri spazi di lavoro sullo stesso server non le troveranno navigando. Per condividere un'app distribuita:
1. Vai su **Impostazioni > Applicazioni > Registrazioni** e apri la tua app
2. Nella scheda **Distribuzione**, fai clic su **Copia link di condivisione**
3. Condividi questo link con utenti su altri spazi di lavoro — li porterà direttamente alla pagina di installazione dell'app
Il link di condivisione utilizza l'URL di base del server (senza alcun sottodominio dello spazio di lavoro) così funziona per qualsiasi spazio di lavoro sul server.
### Gestione delle versioni
Per rilasciare un aggiornamento:
1. Incrementa il campo `version` nel tuo `package.json`
2. Esegui `yarn twenty deploy` (oppure `yarn twenty deploy -r production`)
3. Gli spazi di lavoro che hanno l'app installata vedranno l'aggiornamento disponibile nelle proprie impostazioni
</Note>
## Installazione delle app
Una volta che un'app è stata pubblicata (npm) o distribuita (tarball), gli spazi di lavoro la installano tramite l'interfaccia utente:
Una volta che un'app è stata pubblicata (npm) o distribuita (tarball), gli spazi di lavoro possono installarla tramite l'interfaccia utente.
Vai alla pagina **Impostazioni > Applicazioni** in Twenty, dove è possibile sfogliare e installare sia le app del marketplace sia quelle distribuite tramite tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
Puoi anche installare le app dalla riga di comando:
```bash filename="Terminal"
yarn twenty install
```
Oppure dalla pagina **Impostazioni > Applicazioni** nell'interfaccia di Twenty, dove è possibile sfogliare e installare sia le app del marketplace sia quelle distribuite tramite tarball.
## Categorie di distribuzione delle app
Twenty organizza le app in tre categorie in base a come vengono distribuite:
| Categoria | Come funziona | Visibile nel marketplace? |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
| **Sviluppo** | App in modalità di sviluppo locale eseguite tramite `yarn twenty dev`. Usate per la compilazione e i test. | No |
| **Pubblicate (npm)** | App pubblicate su npm con la parola chiave `twenty-app`. Elencate nel marketplace per l'installazione da parte di qualsiasi spazio di lavoro. | Sì |
| **Interne (tarball)** | App distribuite tramite tarball su un server specifico. Disponibili solo per gli spazi di lavoro su quel server tramite un link di condivisione. | No |
<Tip>
Inizia in modalità **Sviluppo** mentre crei la tua app. Quando è pronta, scegli **Pubblicata** (npm) per un'ampia distribuzione oppure **Interna** (tarball) per una distribuzione privata.
</Tip>
## Riferimento CLI
| Comando | Descrizione | Flag principali |
| --------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
| `yarn twenty build` | Compila l'app e genera il manifest | `--tarball` — crea anche un pacchetto `.tgz` |
| `yarn twenty publish` | Compila e pubblica su npm | `--tag <tag>` — dist-tag npm (ad es. `beta`, `next`) |
| `yarn twenty deploy` | Compila e carica un tarball su un server | `-r, --remote <name>` — remote di destinazione |
| `yarn twenty catalog-sync` | Attiva la sincronizzazione del catalogo del marketplace sul server | `-r, --remote <name>` — remote di destinazione |
| `yarn twenty install` | Installa un'app distribuita su uno spazio di lavoro | `-r, --remote <name>` — remote di destinazione |
| `yarn twenty dev` | Osserva e sincronizza le modifiche locali | Usa il remote predefinito |
| `yarn twenty remote add` | Aggiungi una connessione al server | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | Elenca i remote configurati | — |
| `yarn twenty remote switch` | Imposta il remote predefinito | — |
| `yarn twenty remote status` | Mostra lo stato della connessione | — |
| `yarn twenty remote remove` | Rimuovi un remote | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### Per allegare a un oggetto diverso
Sostituisci `companyId` con il campo appropriato:
Sostituisci `targetCompanyId` con il campo appropriato:
| Oggetto | Nome del campo |
| ---------------------- | -------------------- |
| Azienda | `companyId` |
| Persona | `personId` |
| Opportunità | `opportunityId` |
| Oggetto personalizzato | `yourCustomObjectId` |
| Oggetto | Nome del campo |
| ---------------------- | -------------------------- |
| Azienda | `targetCompanyId` |
| Persona | `targetPersonId` |
| Opportunità | `targetOpportunityId` |
| Oggetto personalizzato | `targetYourCustomObjectId` |
Aggiorna sia il parametro della funzione sia l'oggetto `variables.data` nella mutation dell'allegato.
File diff suppressed because it is too large Load Diff
@@ -9,79 +9,137 @@ Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ain
Os apps permitem que você estenda o Twenty com objetos, campos, funções de lógica, habilidades de IA e componentes de UI personalizados — tudo gerenciado como código.
**O que você pode fazer hoje:**
* Defina objetos e campos personalizados como código (modelo de dados gerenciado)
* Crie funções de lógica com gatilhos personalizados (rotas HTTP, cron, eventos de banco de dados)
* Defina habilidades para agentes de IA
* Crie componentes de front-end que renderizam dentro da UI do Twenty
* Implemente o mesmo aplicativo em vários espaços de trabalho
## Pré-requisitos
* Node.js 24+ e Yarn 4
* Docker (para o servidor de desenvolvimento local do Twenty)
Antes de começar, verifique se o seguinte está instalado na sua máquina:
## Primeiros passos
* **Node.js 24+** — [Baixe aqui](https://nodejs.org/)
* **Yarn 4** — Vem com o Node.js via Corepack. Ative-o executando `corepack enable`
* **Docker** — [Baixe aqui](https://www.docker.com/products/docker-desktop/). Necessário para executar uma instância local do Twenty. Não é necessário se você já tiver um servidor Twenty em execução.
Crie um novo aplicativo usando o gerador oficial, depois autentique-se e comece a desenvolver:
## Passo 1: Gere o scaffold do seu aplicativo
Abra um terminal e execute:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
Será solicitado que você informe um nome e uma descrição para o seu aplicativo. Pressione **Enter** para aceitar os valores padrão.
Isso cria uma nova pasta chamada `my-twenty-app` com tudo de que você precisa.
<Note>
O gerador de scaffold oferece suporte a estas flags:
* `--minimal` — gera apenas os arquivos essenciais, sem exemplos (padrão)
* `--exhaustive` — gera todas as entidades de exemplo
* `--name <name>` — define o nome do aplicativo (pula o prompt)
* `--display-name <displayName>` — define o nome de exibição (pula o prompt)
* `--description <description>` — define a descrição (pula o prompt)
* `--skip-local-instance` — ignora o prompt de configuração do servidor local
</Note>
## Passo 2: Configure uma instância local do Twenty
O gerador de scaffold perguntará:
> **Você gostaria de configurar uma instância local do Twenty?**
* **Digite `yes`** (recomendado) — Isso baixa a imagem Docker `twenty-app-dev` e inicia um servidor Twenty local na porta `2020`. Certifique-se de que o Docker esteja em execução antes de continuar.
* **Digite `no`** — Escolha esta opção se você já tiver um servidor Twenty em execução localmente.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Deve iniciar instância local?" />
</div>
## Passo 3: Faça login no seu espaço de trabalho
Em seguida, uma janela do navegador será aberta com a página de login do Twenty. Faça login com a conta de demonstração pré-configurada:
* **E-mail:** `tim@apple.dev`
* **Senha:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Tela de login do Twenty" />
</div>
## Passo 4: Autorize o aplicativo
Após fazer login, você verá uma tela de autorização. Isso permite que seu aplicativo interaja com seu espaço de trabalho.
Clique em **Authorize** para continuar.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Tela de autorização da CLI do Twenty" />
</div>
Depois de autorizado, seu terminal confirmará que tudo está configurado.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Scaffold do aplicativo criado com sucesso" />
</div>
## Passo 5: Comece a desenvolver
Entre na nova pasta do seu aplicativo e inicie o servidor de desenvolvimento:
```bash filename="Terminal"
cd my-twenty-app
yarn twenty dev
```
O gerador de estrutura oferece suporte a dois modos para controlar quais arquivos de exemplo são incluídos:
Isso observa seus arquivos-fonte, recompila a cada alteração e sincroniza seu aplicativo com o servidor Twenty local automaticamente. Você deverá ver um painel de status em tempo real no seu terminal.
Para uma saída mais detalhada (logs de build, solicitações de sincronização, rastros de erro), use a flag `--verbose`:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
yarn twenty dev --verbose
```
A partir daqui você pode:
<Warning>
O modo de desenvolvimento só está disponível em instâncias do Twenty em modo de desenvolvimento (`NODE_ENV=development`). Instâncias de produção rejeitam solicitações de sincronização de desenvolvimento. Use `yarn twenty deploy` para fazer o deploy em servidores de produção — veja [Publicando aplicativos](/l/pt/developers/extend/apps/publishing) para detalhes.
</Warning>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Saída do terminal no modo de desenvolvimento" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Passo 6: Veja seu aplicativo no Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
Abra [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) no seu navegador. Navegue até **Settings > Apps** e selecione a aba **Developer**. Você deverá ver seu aplicativo listado em **Your Apps**:
# Execute the pre-install function
yarn twenty function:execute --preInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Lista Your Apps exibindo My twenty app" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
Clique em **My twenty app** para abrir o seu **registro do aplicativo**. Um registro é um registro em nível de servidor que descreve seu aplicativo — seu nome, identificador exclusivo, credenciais OAuth e origem (local, npm ou tarball). Ele reside no servidor, não dentro de nenhum espaço de trabalho específico. Quando você instala um aplicativo em um espaço de trabalho, o Twenty cria uma **aplicação** com escopo do espaço de trabalho que aponta para esse registro. Um registro pode ser instalado em vários espaços de trabalho no mesmo servidor.
# Uninstall the application from the current workspace
yarn twenty uninstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Detalhes do registro do aplicativo" />
</div>
# Display commands' help
yarn twenty help
```
Clique em **View installed app** para ver o aplicativo instalado. A aba **About** mostra a versão atual e as opções de gerenciamento:
Veja também: as páginas de referência da CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicativo instalado — aba About" />
</div>
## Estrutura do projeto (com scaffold)
Altere para a aba **Content** para ver tudo o que seu aplicativo oferece — objetos, campos, funções de lógica e agentes:
Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicativo instalado — aba Content" />
</div>
* Copia um aplicativo base mínimo para `my-twenty-app/`
* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4
* Cria arquivos de configuração e scripts conectados à CLI `twenty`
* Gera arquivos principais (configuração da aplicação, papel padrão para funções de lógica, funções de pré-instalação e pós-instalação) além de arquivos de exemplo com base no modo de geração de estrutura
Tudo pronto! Edite qualquer arquivo em `src/` e as alterações serão detectadas automaticamente.
Um app recém-criado com o modo padrão `--exhaustive` fica assim:
Acesse [Criando aplicativos](/l/pt/developers/extend/apps/building) para um guia detalhado sobre criação de objetos, funções de lógica, componentes de front-end, habilidades e mais.
---
## Estrutura do projeto
O gerador de scaffold cria a seguinte estrutura de arquivos (mostrada com o modo `--exhaustive`, que inclui exemplos para cada tipo de entidade):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -94,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
Com `--minimal`, apenas os arquivos principais são criados (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
Por padrão (`--minimal`), apenas os arquivos principais são criados: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`. Use `--exhaustive` para incluir todos os arquivos de exemplo mostrados acima.
Em alto nível:
### Arquivos principais
* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis.
* **.gitignore**: Ignora artefatos comuns como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, pastas de cobertura, arquivos de log e arquivos `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloqueiam e configuram a ferramenta Yarn 4 usada pelo projeto.
* **.nvmrc**: Fixa a versão do Node.js esperada pelo projeto.
* **.oxlintrc.json** e **tsconfig.json**: Fornecem lint e configuração do TypeScript para os fontes TypeScript do seu aplicativo.
* **README.md**: Um README curto na raiz do aplicativo com instruções básicas.
* **public/**: Uma pasta para armazenar recursos públicos (imagens, fontes, arquivos estáticos) que serão servidos com sua aplicação. Os arquivos colocados aqui são enviados durante a sincronização e ficam acessíveis em tempo de execução.
* **src/**: O local principal onde você define seu aplicativo como código
| Arquivo / Pasta | Finalidade |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Declara o nome, a versão e as dependências do seu aplicativo. Inclui um script `twenty` para que você possa executar `yarn twenty help` e ver todos os comandos. |
| `src/application-config.ts` | **Obrigatório.** O principal arquivo de configuração do seu aplicativo. |
| `src/roles/` | Define papéis que controlam o que suas funções de lógica podem acessar. |
| `src/logic-functions/` | Funções do lado do servidor acionadas por rotas, agendamentos do cron ou eventos de banco de dados. |
| `src/front-components/` | Componentes React que renderizam dentro da interface do Twenty. |
| `src/objects/` | Definições de objetos personalizados para estender seu modelo de dados. |
| `src/fields/` | Campos personalizados adicionados a objetos existentes. |
| `src/views/` | Configurações de visualizações salvas. |
| `src/navigation-menu-items/` | Links personalizados na navegação da barra lateral. |
| `src/skills/` | Habilidades que estendem os agentes de IA do Twenty. |
| `src/agents/` | Agentes de IA com prompts personalizados. |
| `src/page-layouts/` | Layouts de página personalizados para visualizações de registros. |
| `src/__tests__/` | Testes de integração (configuração + teste de exemplo). |
| `public/` | Recursos estáticos (imagens, fontes) servidos com seu aplicativo. |
### Detecção de entidades
## Gerenciando remotos
O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define<Entity>({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`:
| Função utilitária | Tipo de entidade |
| -------------------------------- | -------------------------------------------------------------------- |
| `defineObject` | Definições de objetos personalizados |
| `defineLogicFunction` | Definições de funções de lógica |
| `definePreInstallLogicFunction` | Função de lógica de pré-instalação (é executada antes da instalação) |
| `definePostInstallLogicFunction` | Função de lógica de pós-instalação (é executada após a instalação) |
| `defineFrontComponent` | Definições de componentes de front-end |
| `defineRole` | Definições de papéis |
| `defineField` | Extensões de campos para objetos existentes |
| `defineView` | Definições de visualizações salvas |
| `defineNavigationMenuItem` | Definições de itens do menu de navegação |
| `defineSkill` | Definições de habilidades de agente de IA |
<Note>
**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define<Entity>({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito.
</Note>
Exemplo de uma entidade detectada:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Comandos posteriores adicionarão mais arquivos e pastas:
* `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros.
## Autenticação
Na primeira vez que você executar `yarn twenty auth:login`, será solicitado o seguinte:
* URL da API (padrão: http://localhost:3000 ou o perfil do seu espaço de trabalho atual)
* Chave de API
Suas credenciais são armazenadas por usuário em `~/.twenty/config.json`. Você pode manter vários perfis e alternar entre eles.
### Gerenciando espaços de trabalho
Um **remoto** é um servidor Twenty ao qual seu aplicativo se conecta. Durante a configuração, o gerador de scaffold cria um para você automaticamente. Você pode adicionar mais remotos ou alternar entre eles a qualquer momento.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
Depois que você alternar os espaços de trabalho com `yarn twenty auth:switch`, todos os comandos subsequentes usarão esse espaço de trabalho por padrão. Você ainda pode substituí-lo temporariamente com `--workspace <name>`.
Suas credenciais são armazenadas em `~/.twenty/config.json`.
## Servidor de desenvolvimento local (`yarn twenty server`)
A CLI pode gerenciar um servidor Twenty local em execução no Docker. Este é o mesmo servidor iniciado automaticamente quando você cria o scaffold de um aplicativo com `create-twenty-app`, mas você também pode gerenciá-lo manualmente.
### Iniciando o servidor
```bash filename="Terminal"
yarn twenty server start
```
Isso baixa a imagem Docker `twentycrm/twenty-app-dev:latest` (se ainda não estiver presente), cria um contêiner chamado `twenty-app-dev` e o inicia na porta **2020**. A CLI aguarda até que o servidor passe na verificação de integridade antes de retornar.
Dois volumes do Docker são criados para persistir os dados entre reinicializações:
* `twenty-app-dev-data` — banco de dados PostgreSQL
* `twenty-app-dev-storage` — armazenamento de arquivos
Se a porta 2020 já estiver em uso, você pode iniciar em uma porta diferente:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
A CLI configura automaticamente as variáveis internas do contêiner `NODE_PORT` e `SERVER_URL` para corresponderem à porta escolhida, para que as funções de lógica, o OAuth e toda a comunicação interna de rede funcionem corretamente.
Depois de iniciado, o servidor é registrado automaticamente como o remoto `local` na configuração da sua CLI.
### Verificando o status do servidor
```bash filename="Terminal"
yarn twenty server status
```
Exibe se o servidor está em execução, sua URL e as credenciais de login padrão (`tim@apple.dev` / `tim@apple.dev`).
### Visualizando os logs do servidor
```bash filename="Terminal"
yarn twenty server logs
```
Transmite os logs do contêiner. Use `--lines` para controlar quantas linhas recentes mostrar:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Parando o servidor
```bash filename="Terminal"
yarn twenty server stop
```
Interrompe o contêiner. Seus dados são preservados nos volumes do Docker — o próximo `start` continua de onde você parou.
### Redefinindo o servidor
```bash filename="Terminal"
yarn twenty server reset
```
Remove o contêiner **e** exclui os dois volumes do Docker, apagando todos os dados. O próximo `start` cria uma instância nova.
<Note>
O servidor requer que o **Docker** esteja em execução. Se você vir um erro "Docker not running", certifique-se de que o Docker Desktop (ou o daemon do Docker) esteja iniciado.
</Note>
### Referência de comandos
| Comando | Descrição |
| -------------------------------------- | ------------------------------------------------------ |
| `yarn twenty server start` | Inicia o servidor local (baixa a imagem se necessário) |
| `yarn twenty server start --port 3030` | Iniciar em uma porta personalizada |
| `yarn twenty server stop` | Interrompe o servidor (preserva os dados) |
| `yarn twenty server status` | Mostra o status do servidor, a URL e as credenciais |
| `yarn twenty server logs` | Transmite os logs do servidor |
| `yarn twenty server logs --lines 100` | Mostra as últimas 100 linhas de log |
| `yarn twenty server reset` | Exclui todos os dados e inicia do zero |
## CI com GitHub Actions
O gerador de scaffold cria um workflow do GitHub Actions pronto para uso em `.github/workflows/ci.yml`. Ele executa seus testes de integração automaticamente a cada push para `main` e em pull requests.
O workflow:
1. Faz checkout do seu código
2. Inicializa um servidor Twenty temporário usando a ação `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instala as dependências com `yarn install --immutable`
4. Executa `yarn test` com `TWENTY_API_URL` e `TWENTY_API_KEY` injetados a partir das saídas da ação
```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 }}
```
Você não precisa configurar nenhum segredo — a ação `spawn-twenty-docker-image` inicia um servidor Twenty efêmero diretamente no runner e fornece os detalhes de conexão. O segredo `GITHUB_TOKEN` é fornecido automaticamente pelo GitHub.
Para fixar uma versão específica do Twenty em vez de `latest`, altere a variável de ambiente `TWENTY_VERSION` no topo do workflow.
## Configuração manual (sem o gerador)
Embora recomendemos usar `create-twenty-app` para a melhor experiência inicial, você também pode configurar um projeto manualmente. Não instale a CLI globalmente. Em vez disso, adicione `twenty-sdk` como uma dependência local e configure um único script no seu package.json:
Se preferir configurar tudo por conta própria em vez de usar `create-twenty-app`, você pode fazer isso em duas etapas.
**1. Adicione `twenty-sdk` e `twenty-client-sdk` como dependências:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Em seguida, adicione um script `twenty`:
**2. Adicione um script `twenty` ao seu `package.json`:**
```json filename="package.json"
{
@@ -221,25 +393,19 @@ Em seguida, adicione um script `twenty`:
}
```
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty dev`, `yarn twenty help`, etc.
Agora você pode executar `yarn twenty dev`, `yarn twenty help` e todos os outros comandos.
## Como usar uma instância local do Twenty
Se você já estiver executando uma instância do Twenty localmente (por exemplo, via `npx nx start twenty-server`), você pode conectar-se a ela em vez de usar o Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Não instale o `twenty-sdk` globalmente. Use-o sempre como uma dependência local do projeto para que cada projeto possa fixar sua própria versão.
</Note>
## Resolução de Problemas
* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias.
* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível.
* Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client.
* Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment.
Se você tiver problemas:
Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
* Certifique-se de que o **Docker está em execução** antes de iniciar o scaffolder com uma instância local.
* Certifique-se de que está usando **Node.js 24+** (`node -v` para verificar).
* Certifique-se de que o **Corepack está ativado** (`corepack enable`) para que o Yarn 4 esteja disponível.
* Tente excluir `node_modules` e executar `yarn install` novamente se as dependências parecerem corrompidas.
Ainda com dificuldades? Peça ajuda no [Discord da Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,15 +4,75 @@ description: Distribua seu aplicativo Twenty no Marketplace ou implante-o intern
---
<Warning>
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
</Warning>
## Visão Geral
Depois que seu aplicativo estiver [compilado e testado localmente](/l/pt/developers/extend/apps/building), você tem dois caminhos para distribuí-lo:
* **Implantar um tarball** — envie seu aplicativo diretamente para um servidor Twenty específico para uso interno ou privado.
* **Publicar no npm** — liste seu aplicativo no Marketplace da Twenty para que qualquer espaço de trabalho possa descobrir e instalar.
* **Enviar um tarball** — implante seu aplicativo em um servidor Twenty específico para uso interno sem torná-lo público.
Ambos os caminhos começam na mesma etapa de **build**.
## Compilando seu app
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
## Implantando em um servidor (tarball)
Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode implantar um tarball diretamente em um servidor Twenty.
### Pré-requisitos
Antes de implantar, você precisa de um remote configurado apontando para o servidor de destino. Os remotes armazenam a URL do servidor e as credenciais de autenticação localmente em `~/.twenty/config.json`.
Adicionar um remote:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Implantando
Compile e envie seu aplicativo para o servidor em uma única etapa:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Compartilhando um aplicativo implantado
Aplicativos em tarball não são listados no marketplace público, então outros espaços de trabalho no mesmo servidor não os descobrirão ao navegar. Para compartilhar um aplicativo implantado:
1. Vá para **Configurações > Aplicações > Registros** e abra seu aplicativo
2. Na guia **Distribuição**, clique em **Copiar link de compartilhamento**
3. Compartilhe esse link com usuários de outros espaços de trabalho — ele os leva diretamente para a página de instalação do aplicativo
O link de compartilhamento usa a URL base do servidor (sem qualquer subdomínio de espaço de trabalho), para funcionar em qualquer espaço de trabalho no servidor.
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### Gerenciamento de versões
Para lançar uma atualização:
1. Atualize o campo `version` no seu `package.json`
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Os espaços de trabalho que têm o aplicativo instalado verão a atualização disponível em suas configurações
{/* TODO: add screenshot of the Upgrade button */}
## Publicação no npm
@@ -21,29 +81,69 @@ Publicar no npm torna seu aplicativo descobrível no Marketplace da Twenty. Qual
### Requisitos
* Uma conta no [npm](https://www.npmjs.com)
* O nome do seu pacote **deve** usar o prefixo `twenty-app-` (por exemplo, `twenty-app-postcard-sender`)
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
### Etapas
1. **Compile seu aplicativo** — a CLI compila seus códigos-fonte TypeScript e gera o manifesto do aplicativo:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Publicar no npm** — envie o pacote compilado para o registro do npm:
### Metadados do Marketplace
```bash filename="Terminal"
npx twenty publish
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
### Descoberta automática
See the [defineApplication accordion](/l/pt/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
Pacotes com o prefixo `twenty-app-` são detectados automaticamente pelo catálogo do Marketplace da Twenty. Depois de publicado, seu aplicativo aparece no Marketplace em poucos minutos — sem necessidade de registro manual ou aprovação.
### Publish
```bash filename="Terminal"
yarn twenty publish
```
Para publicar sob uma dist-tag específica (por exemplo, `beta` ou `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### Como funciona a descoberta no marketplace
O servidor Twenty sincroniza seu catálogo do marketplace a partir do registro do npm **a cada hora**.
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
Se o seu aplicativo não definir um `aboutDescription` em `defineApplication()`, o marketplace usará automaticamente o `README.md` do seu pacote no npm como conteúdo da página Sobre. Isso significa que você pode manter um único README tanto para o npm quanto para o marketplace da Twenty. Se quiser uma descrição diferente no marketplace, defina explicitamente `aboutDescription`.
</Note>
### Publicação via CI
O projeto gerado inclui um workflow do GitHub Actions que publica a cada lançamento. Ele executa `app:build` e depois `npm publish --provenance` a partir da saída do build:
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -68,52 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `npx twenty build` e depois `npm publish` a partir de `.twenty/output`.
<Tip>
**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração.
</Tip>
## Distribuição interna
Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode enviar um tarball diretamente para um servidor Twenty.
### Enviar um tarball
Compile seu aplicativo e implante-o em um servidor específico em uma única etapa:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Qualquer espaço de trabalho nesse servidor pode então instalar e atualizar o aplicativo na página de configurações de **Aplicativos**.
### Gerenciamento de versões
Para lançar uma atualização:
1. Atualize o campo `version` no seu `package.json`
2. Envie um novo tarball com `npx twenty publish --server <server-url>`
3. Os espaços de trabalho nesse servidor verão a atualização disponível nas suas configurações
Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `yarn twenty build` e, em seguida, `npm publish` a partir de `.twenty/output`.
<Note>
Aplicativos internos ficam restritos ao servidor para o qual são enviados. Eles não aparecem no Marketplace público e não podem ser instalados por espaços de trabalho em outros servidores.
**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração.
</Note>
## Categorias de aplicativos
## Instalando aplicativos
A Twenty organiza os aplicativos em três categorias com base em como são distribuídos:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
| Categoria | Como Funciona | Visível no Marketplace? |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| **Desenvolvimento** | Aplicativos em modo de desenvolvimento local executados via `yarn twenty dev`. Usados para compilação e testes. | Não |
| **Publicado** | Aplicativos publicados no npm com o prefixo `twenty-app-`. Listados no Marketplace para que qualquer espaço de trabalho possa instalar. | Sim |
| **Interno** | Aplicativos implantados via tarball em um servidor específico. Disponível apenas para espaços de trabalho nesse servidor. | Não |
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
<Tip>
Comece no modo de **Desenvolvimento** enquanto cria seu aplicativo. Quando estiver pronto, escolha **Publicado** (npm) para ampla distribuição ou **Interno** (tarball) para implantação privada.
</Tip>
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
```bash filename="Terminal"
yarn twenty install
```
File diff suppressed because it is too large Load Diff
@@ -297,6 +297,16 @@ yarn command:prod cron:workflow:automated-cron-trigger
**Modo somente ambiente:** Se você definir `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, adicione estas variáveis ao seu arquivo `.env`.
</Warning>
## Armazenamento S3
<Warning>
Por padrão, o Twenty armazena os arquivos enviados no sistema de arquivos local. Para implantações em produção, use o S3 ou um serviço compatível com S3 (MinIO, DigitalOcean Spaces, etc.) para garantir que os arquivos persistam entre reinicializações do contêiner e possam escalar em várias instâncias de servidor.
</Warning>
Defina `STORAGE_TYPE=S_3` e configure as variáveis `STORAGE_S3_*` pelo painel de administração ou `.env`. Veja a [referência de config-variables.ts](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts) para a lista completa de variáveis do S3.
Ao usar o S3 com recursos dependentes de CORS (por exemplo, downloads de arquivos no navegador), verifique se o seu bucket permite a origem do seu frontend do Twenty na sua configuração de CORS.
## Funções lógicas e interpretador de código
O Twenty oferece suporte a funções lógicas para fluxos de trabalho e ao interpretador de código para análise de dados com IA. Ambos executam código fornecido pelo usuário e exigem configuração explícita por motivos de segurança.
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### Para anexar a um objeto diferente
Substitua `companyId` pelo campo apropriado:
Substitua `targetCompanyId` pelo campo apropriado:
| Objeto | Nome do Campo |
| -------------------- | -------------------- |
| Empresa | `companyId` |
| Pessoa | `personId` |
| Oportunidade | `opportunityId` |
| Objeto personalizado | `yourCustomObjectId` |
| Objeto | Nome do Campo |
| -------------------- | -------------------------- |
| Empresa | `targetCompanyId` |
| Pessoa | `targetPersonId` |
| Oportunidade | `targetOpportunityId` |
| Objeto personalizado | `targetYourCustomObjectId` |
Atualize tanto o parâmetro da função como o objeto `variables.data` na mutação de anexo.
File diff suppressed because it is too large Load Diff
@@ -9,68 +9,137 @@ Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, d
Aplicațiile vă permit să extindeți Twenty cu obiecte personalizate, câmpuri, funcții logice, abilități IA și componente UI — toate gestionate ca cod.
**Ce puteți construi:**
* Obiecte, câmpuri, vizualizări și elemente de navigare personalizate pentru a defini modelul dumneavoastră de date
* Funcții logice declanșate de rute HTTP, programări cron sau evenimente din baza de date
* Componente front-end care se afișează direct în interfața Twenty
* Abilități care extind capabilitățile agenților AI ai Twenty
* Implementați o aplicație în mai multe spații de lucru
## Cerințe
* Node.js 24+
* Yarn 4
* Docker (sau o instanță Twenty locală în execuție)
Înainte de a începe, asigurați-vă că următoarele sunt instalate pe calculatorul dvs.:
## Începeți
* **Node.js 24+** — [Descărcați aici](https://nodejs.org/)
* **Yarn 4** — Vine împreună cu Node.js prin Corepack. Activați-l rulând `corepack enable`
* **Docker** — [Descărcați aici](https://www.docker.com/products/docker-desktop/). Necesar pentru a rula o instanță Twenty locală. Nu este necesar dacă aveți deja un server Twenty care rulează.
Creați o aplicație nouă folosind generatorul oficial, apoi autentificați-vă și începeți să dezvoltați:
## Pasul 1: Creați scheletul aplicației
Deschideți un terminal și rulați:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Folosiți opțiunea `--minimal` pentru a genera o instalare minimă
Vi se va cere să introduceți un nume și o descriere pentru aplicația dvs. Apăsați **Enter** pentru a accepta valorile implicite.
De aici puteți:
Aceasta creează un folder nou numit `my-twenty-app` cu tot ce aveți nevoie.
<Note>
Generatorul de schelet acceptă următoarele opțiuni:
* `--minimal` — generează doar fișierele esențiale, fără exemple (implicit)
* `--exhaustive` — generează toate entitățile de exemplu
* `--name <name>` — setează numele aplicației (omite solicitarea)
* `--display-name <displayName>` — setează numele afișat (omite solicitarea)
* `--description <description>` — setează descrierea (omite solicitarea)
* `--skip-local-instance` — omite solicitarea de configurare a serverului local
</Note>
## Pasul 2: Configurați o instanță Twenty locală
Generatorul de schelet va întreba:
> **Doriți să configurați o instanță Twenty locală?**
* **Tastați `yes`** (recomandat) — Aceasta descarcă imaginea Docker `twenty-app-dev` și pornește un server Twenty local pe portul `2020`. Asigurați-vă că Docker rulează înainte de a continua.
* **Tastați `no`** — Alegeți această opțiune dacă aveți deja un server Twenty care rulează local.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Porniți instanța locală?" />
</div>
## Pasul 3: Autentificați-vă în spațiul dvs. de lucru
În continuare, se va deschide o fereastră de browser cu pagina de autentificare Twenty. Autentificați-vă cu contul demo preconfigurat:
* **E-mail:** `tim@apple.dev`
* **Parolă:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Ecranul de autentificare Twenty" />
</div>
## Pasul 4: Autorizați aplicația
După autentificare, veți vedea un ecran de autorizare. Acest lucru permite aplicației dvs. să interacționeze cu spațiul dvs. de lucru.
Faceți clic pe **Authorize** pentru a continua.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Ecranul de autorizare Twenty CLI" />
</div>
După autorizare, terminalul va confirma că totul este configurat.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplicația a fost creată cu succes" />
</div>
## Pasul 5: Începeți dezvoltarea
Intrați în noul folder al aplicației și porniți serverul de dezvoltare:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Acesta monitorizează fișierele sursă, reconstruiește la fiecare modificare și sincronizează automat aplicația cu serverul Twenty local. Ar trebui să vedeți în terminal un panou de stare în timp real.
## Structura proiectului (generată)
Pentru un output mai detaliat (jurnale de build, cereri de sincronizare, urme ale erorilor), folosiți opțiunea `--verbose`:
Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* Copiază o aplicație de bază minimală în `my-twenty-app/`
* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4
* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty`
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului.
<Warning>
Modul de dezvoltare este disponibil doar pe instanțele Twenty care rulează în modul development (`NODE_ENV=development`). Instanțele de producție resping cererile de sincronizare pentru dezvoltare. Folosiți `yarn twenty deploy` pentru a implementa pe serverele de producție — vedeți [Publicarea aplicațiilor](/l/ro/developers/extend/apps/publishing) pentru detalii.
</Warning>
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Ieșirea terminalului în modul de dezvoltare" />
</div>
## Pasul 6: Vedeți aplicația în Twenty
Deschideți [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) în browser. Navigați la **Settings > Apps** și selectați fila **Developer**. Ar trebui să vedeți aplicația listată la **Your Apps**:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Lista Your Apps care afișează My twenty app" />
</div>
Faceți clic pe **My twenty app** pentru a deschide **înregistrarea aplicației**. O înregistrare este un element la nivel de server care descrie aplicația — numele, identificatorul unic, acreditările OAuth și sursa (locală, npm sau arhivă tar). Aceasta există pe server, nu în interiorul unui spațiu de lucru anume. Când instalați o aplicație într-un spațiu de lucru, Twenty creează o aplicație la nivelul spațiului de lucru care face referire la această înregistrare. O singură înregistrare poate fi instalată în mai multe spații de lucru pe același server.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Detalii despre înregistrarea aplicației" />
</div>
Faceți clic pe **View installed app** pentru a vedea aplicația instalată. Fila **About** afișează versiunea curentă și opțiunile de administrare:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicație instalată — fila About" />
</div>
Comutați la fila **Content** pentru a vedea tot ceea ce oferă aplicația — obiecte, câmpuri, funcții logice și agenți:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicație instalată — fila Content" />
</div>
Totul este gata! Editați orice fișier din `src/`, iar modificările vor fi preluate automat.
Accesați [Construirea aplicațiilor](/l/ro/developers/extend/apps/building) pentru un ghid detaliat despre crearea de obiecte, funcții logice, componente front-end, abilități și altele.
---
## Structura proiectului
Generatorul de schelet generează următoarea structură de fișiere (afișată cu modul `--exhaustive`, care include exemple pentru fiecare tip de entitate):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`).
În mod implicit (`--minimal`), sunt create doar fișierele de bază: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`. Folosiți `--exhaustive` pentru a include toate fișierele de exemplu prezentate mai sus.
Pe scurt:
### Fișiere cheie
* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulați `yarn twenty help` pentru a lista toate comenzile disponibile.
* **.gitignore**: Ignoră artefacte comune precum `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect.
* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect.
* **.oxlintrc.json** și **tsconfig.json**: Oferă linting și configurație TypeScript pentru fișierele TypeScript ale aplicației.
* **README.md**: Un README scurt în rădăcina aplicației, cu instrucțiuni de bază.
* **public/**: Un folder pentru stocarea resurselor publice (imagini, fonturi, fișiere statice) care vor fi servite împreună cu aplicia dvs. Fișierele plasate aici sunt încărcate în timpul sincronizării și sunt accesibile la rulare.
* **src/**: Locul principal unde vă definiți aplicația sub formă de cod
| Fișier / Folder | Scop |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Declară numele aplicației, versiunea și dependențele. Include un script `twenty` astfel încât să puteți rula `yarn twenty help` pentru a vedea toate comenzile. |
| `src/application-config.ts` | **Necesar.** Fișierul principal de configurare pentru aplicație. |
| `src/roles/` | Definește roluri care controlează la ce pot avea acces funcțiile logice. |
| `src/logic-functions/` | Funcții pe server declanșate de rute, programări cron sau evenimente din baza de date. |
| `src/front-components/` | Componente React care se afișează în interfața Twenty. |
| `src/objects/` | Definiții de obiecte personalizate pentru a extinde modelul de date. |
| `src/fields/` | Câmpuri personalizate adăugate obiectelor existente. |
| `src/views/` | Configurații pentru vizualizări salvate. |
| `src/navigation-menu-items/` | Linkuri personalizate în bara laterală de navigare. |
| `src/skills/` | Abilități care extind capabilitățile agenților AI ai Twenty. |
| `src/agents/` | Agenți AI cu prompturi personalizate. |
| `src/page-layouts/` | Machete de pagină personalizate pentru vizualizările de înregistrare. |
| `src/__tests__/` | Teste de integrare (configurare + test exemplu). |
| `public/` | Resurse statice (imagini, fonturi) servite împreună cu aplicația. |
### Detectarea entităților
## Gestionarea remote-urilor
SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define<Entity>({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`:
| Funcție ajutătoare | Tipul entității |
| -------------------------------- | -------------------------------------------------------------- |
| `defineObject` | Definiții de obiecte personalizate |
| `defineLogicFunction` | Definiții de funcții de logică |
| `definePreInstallLogicFunction` | Funcție logică de pre-instalare (rulează înainte de instalare) |
| `definePostInstallLogicFunction` | Funcție logică post-instalare (rulează după instalare) |
| `defineFrontComponent` | Definiții ale componentelor de interfață |
| `defineRole` | Definiții de rol |
| `defineField` | Extensii de câmp pentru obiectele existente |
| `defineView` | Definiții pentru vizualizări salvate |
| `defineNavigationMenuItem` | Definiții pentru elemente de meniu de navigare |
| `defineSkill` | Definiții ale abilităților agentului IA |
<Note>
**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define<Entity>({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință.
</Note>
Exemplu de entitate detectată:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Comenzile ulterioare vor adăuga mai multe fișiere și foldere:
* `yarn twenty dev` va genera automat `CoreApiClient` tipizat (pentru datele spațiului de lucru prin `/graphql`) în `node_modules/twenty-client-sdk/`. `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`) este livrat preconstruit și este disponibil imediat. Importați-le din `twenty-client-sdk/core` și `twenty-client-sdk/metadata`, respectiv.
* `yarn twenty add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele personalizate, funcțiile, componentele front-end, rolurile, abilitățile și altele.
## Autentificare
Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita:
* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent)
* Cheie API
Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele.
### Gestionarea spațiilor de lucru
Un „remote” este un server Twenty la care se conectează aplicația. În timpul configurării, generatorul de schelet creează automat unul pentru dvs. Puteți adăuga mai multe remote-uri sau comuta între ele oricând.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
După ce ați schimbat spațiul de lucru cu `yarn twenty auth:switch`, toate comenzile ulterioare vor folosi implicit acel spațiu de lucru. Îl puteți totuși suprascrie temporar cu `--workspace <name>`.
Acreditările dvs. sunt stocate în `~/.twenty/config.json`.
## Server local de dezvoltare (`yarn twenty server`)
CLI-ul poate gestiona un server Twenty local care rulează în Docker. Acesta este același server pornit automat când creați scheletul unei aplicații cu `create-twenty-app`, dar îl puteți gestiona și manual.
### Pornirea serverului
```bash filename="Terminal"
yarn twenty server start
```
Aceasta descarcă imaginea Docker `twentycrm/twenty-app-dev:latest` (dacă nu este deja prezentă), creează un container numit `twenty-app-dev` și îl pornește pe portul **2020**. CLI-ul așteaptă până când serverul trece verificarea de integritate înainte de a reveni.
Sunt create două volume Docker pentru a păstra datele între reporniri:
* `twenty-app-dev-data` — bază de date PostgreSQL
* `twenty-app-dev-storage` — stocare fișiere
Dacă portul 2020 este deja utilizat, puteți porni pe un alt port:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI-ul configurează automat `NODE_PORT` și `SERVER_URL` interne ale containerului pentru a se potrivi cu portul ales, astfel încât funcțiile logice, OAuth și toată rețeaua internă să funcționeze corect.
După pornire, serverul este înregistrat automat ca remote `local` în configurația CLI.
### Verificarea stării serverului
```bash filename="Terminal"
yarn twenty server status
```
Afișează dacă serverul rulează, URL-ul său și acreditările implicite de autentificare (`tim@apple.dev` / `tim@apple.dev`).
### Vizualizarea jurnalelor serverului
```bash filename="Terminal"
yarn twenty server logs
```
Transmite în flux jurnalele containerului. Folosiți `--lines` pentru a controla câte linii recente să fie afișate:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Oprirea serverului
```bash filename="Terminal"
yarn twenty server stop
```
Oprește containerul. Datele dvs. sunt păstrate în volumele Docker — următoarea comandă `start` reia de unde ați rămas.
### Resetarea serverului
```bash filename="Terminal"
yarn twenty server reset
```
Elimină containerul **și** șterge ambele volume Docker, ștergând toate datele. Următoarea comandă `start` creează o instanță nouă.
<Note>
Serverul necesită ca **Docker** să ruleze. Dacă vedeți eroarea "Docker not running", asigurați-vă că Docker Desktop (sau demonul Docker) este pornit.
</Note>
### Referință pentru comenzi
| Comandă | Descriere |
| -------------------------------------- | ------------------------------------------------------------- |
| `yarn twenty server start` | Pornește serverul local (descarcă imaginea dacă este necesar) |
| `yarn twenty server start --port 3030` | Pornește pe un port personalizat |
| `yarn twenty server stop` | Oprește serverul (păstrează datele) |
| `yarn twenty server status` | Afișează starea serverului, URL-ul și acreditările |
| `yarn twenty server logs` | Transmite în flux jurnalele serverului |
| `yarn twenty server logs --lines 100` | Afișează ultimele 100 de linii de jurnal |
| `yarn twenty server reset` | Șterge toate datele și pornește de la zero |
## CI cu GitHub Actions
Scaffolderul generează un workflow GitHub Actions gata de utilizare în `.github/workflows/ci.yml`. Rulează automat testele de integrare la fiecare push pe `main` și la pull request-uri.
Workflow-ul:
1. Preia codul
2. Pornește un server Twenty temporar folosind acțiunea `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instalează dependențele cu `yarn install --immutable`
4. Rulează `yarn test` cu `TWENTY_API_URL` și `TWENTY_API_KEY` injectate din rezultatele acțiunii
```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 }}
```
Nu trebuie să configurați niciun secret — acțiunea `spawn-twenty-docker-image` pornește un server Twenty efemer direct în runner și oferă detaliile de conectare. Secretul `GITHUB_TOKEN` este furnizat automat de GitHub.
Pentru a fixa o versiune Twenty specifică în loc de `latest`, modificați variabila de mediu `TWENTY_VERSION` din partea de sus a workflow-ului.
## Configurare manuală (fără generator)
Deși recomandăm utilizarea `create-twenty-app` pentru cea mai bună experiență de început, puteți configura și un proiect manual. Nu instalați CLI-ul global. În schimb, adăugați `twenty-sdk` ca dependență locală și conectați un singur script în package.json-ul dvs.:
Dacă preferați să configurați totul manual în loc să folosiți `create-twenty-app`, o puteți face în doi pași.
**1. Adăugați `twenty-sdk` și `twenty-client-sdk` ca dependențe:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Apoi adăugați un script `twenty`:
**2. Adăugați un script `twenty` în `package.json`:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ Apoi adăugați un script `twenty`:
}
```
Acum poți rula toate comenzile prin `yarn twenty <command>`, de ex. `yarn twenty dev`, `yarn twenty help`, etc.
Acum puteți rula `yarn twenty dev`, `yarn twenty help` și toate celelalte comenzi.
## Cum să folosești o instanță Twenty locală
Dacă rulezi deja local o instanță Twenty (de exemplu prin `npx nx start twenty-server`), te poți conecta la ea în loc să folosești Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Nu instalați `twenty-sdk` global. Folosiți-l întotdeauna ca dependență locală de proiect, astfel încât fiecare proiect să își poată fixa propria versiune.
</Note>
## Depanare
* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare.
* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil.
* Tipuri sau client lipsă/învechite: repornește `yarn twenty dev` — acesta generează automat clientul tipizat.
* Modul dev nu sincronizează: asigură-te că `yarn twenty dev` rulează și că modificările nu sunt ignorate de mediul tău.
Dacă întâmpinați probleme:
Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
* Asigurați-vă că Docker rulează înainte de a porni scaffolderul cu o instanță locală.
* Asigurați-vă că folosiți **Node.js 24+** (`node -v` pentru verificare).
* Asigurați-vă că **Corepack este activat** (`corepack enable`) astfel încât Yarn 4 să fie disponibil.
* Încercați să ștergeți `node_modules` și să rulați din nou `yarn install` dacă dependențele par deteriorate.
Încă aveți probleme? Cereți ajutor pe [Discordul Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,34 +4,76 @@ description: Distribuie aplicația ta Twenty în marketplace sau implementeaz-o
---
<Warning>
Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare.
Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare.
</Warning>
## Prezentare generală
După ce aplicația ta este [construită și testată local](/l/ro/developers/extend/apps/building), ai două căi pentru distribuire:
* **Publică pe npm** — listează aplicația ta în marketplace-ul Twenty pentru ca orice spațiu de lucru să o poată descoperi și instala.
* **Implementați o arhivă tar** — încărcați aplicația direct pe un server Twenty anume pentru uz intern sau privat.
* **Publică pe npm** — listează aplicația ta în marketplace-ul Twenty pentru ca orice spațiu de lucru să o poată descoperi și instala.
Ambele căi pornesc din aceeași etapă de **build**.
## Construirea aplicației
Comanda `build` compilează sursele TypeScript, transpilează funcțiile de logică și componentele de front-end și generează un `manifest.json` care descrie conținutul aplicației:
Rulează comanda `build` pentru a compila aplicația și a genera un `manifest.json` pregătit pentru distribuire:
```bash filename="Terminal"
yarn twenty build
```
Rezultatul este scris în `.twenty/output/`. Acest director conține tot ce este necesar pentru distribuție: cod compilat, resurse, manifestul și o copie a fișierului tău `package.json`.
Aceasta compilează sursele TypeScript, transpilează funcțiile de logică și componentele de front-end și scrie totul în `.twenty/output/`. Adaugă `--tarball` pentru a produce și un pachet `.tgz` pentru distribuire manuală sau pentru comanda de deploy.
Pentru a crea și un pachet `.tgz` (folosit intern de comanda de implementare sau pentru distribuire manuală):
## Implementare pe un server (tarball)
Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți implementa un tarball direct pe un server Twenty.
### Cerințe
Înainte de implementare, ai nevoie de un remote configurat care să indice serverul țintă. Remote-urile stochează local URL-ul serverului și credențialele de autentificare în `~/.twenty/config.json`.
Adaugă un remote:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Implementare
Construiește și încarcă aplicația ta pe server într-un singur pas:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Partajarea unei aplicații implementate
Aplicațiile tarball nu sunt listate în marketplace-ul public, astfel încât alte spații de lucru de pe același server nu le vor descoperi prin navigare. Pentru a partaja o aplicație implementată:
1. Mergi la **Setări > Aplicații > Înregistrări** și deschide aplicația ta
2. În fila **Distribuție**, fă clic pe **Copiază linkul de partajare**
3. Partajează acest link cu utilizatori din alte spații de lucru — îi duce direct la pagina de instalare a aplicației
Linkul de partajare folosește URL-ul de bază al serverului (fără niciun subdomeniu de spațiu de lucru), astfel încât funcționează pentru orice spațiu de lucru de pe server.
<Warning>
Partajarea aplicațiilor private este o funcționalitate Enterprise. Mergi la [Setări > Panou de administrare > Enterprise](/settings/admin-panel#enterprise) pentru a o activa.
</Warning>
### Gestionarea versiunilor
Pentru a lansa o actualizare:
1. Actualizează câmpul `version` din `package.json`
2. Rulează `yarn twenty deploy` (sau `yarn twenty deploy --remote production`)
3. Spațiile de lucru care au aplicația instalată vor vedea actualizarea disponibilă în setările lor
{/* TODO: add screenshot of the Upgrade button */}
## Publicarea pe npm
Publicarea pe npm face ca aplicația ta să poată fi descoperită în marketplace-ul Twenty. Orice spațiu de lucru Twenty poate răsfoi, instala și actualiza aplicațiile din marketplace direct din interfață.
@@ -39,41 +81,42 @@ Publicarea pe npm face ca aplicația ta să poată fi descoperită în marketpla
### Cerințe
* Un cont [npm](https://www.npmjs.com)
* Cuvântul cheie `twenty-app` trebuie să fie listat în array-ul `keywords` din `package.json`-ul tău
### Adăugarea cuvântului cheie necesar
Marketplace-ul Twenty descoperă aplicații căutând în registrul npm pachete cu cuvântul cheie `twenty-app`. Adaugă-l în `package.json`-ul tău:
* Cuvântul cheie `twenty-app` din array-ul `keywords` al fișierului `package.json` (deja inclus când inițializezi proiectul cu `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
Marketplace-ul caută `keywords:twenty-app` în registrul npm. Fără acest cuvânt cheie, pachetul tău nu va apărea în marketplace chiar dacă are prefixul de nume `twenty-app-`.
</Note>
### Metadate pentru marketplace
### Pași
Configurația `defineApplication()` acceptă câmpuri opționale care controlează modul în care aplicația ta apare în marketplace. Folosește `logoUrl` și `screenshots` pentru a face referire la imaginile din folderul `public/`:
1. **Construiește-ți aplicația:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **Publică pe npm:**
Vezi [acordeonul defineApplication](/l/ro/developers/extend/apps/building#defineentity-functions) din pagina Building Apps pentru lista completă de câmpuri ale marketplace-ului (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publicare
```bash filename="Terminal"
yarn twenty publish
```
Aceasta rulează `npm publish` din directorul `.twenty/output/`.
Pentru a publica sub un dist-tag specific (de ex., `beta` sau `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### Cum funcționează descoperirea în marketplace
Serverul Twenty sincronizează catalogul marketplace-ului din registrul npm la fiecare oră:
Serverul Twenty sincronizează catalogul marketplace-ului din registrul npm **la fiecare oră**.
1. Caută toate pachetele npm cu cuvântul cheie `keywords:twenty-app`
2. Pentru fiecare pachet, preia `manifest.json` din CDN-ul npm
3. Metadatele aplicației (nume, descriere, autor, logo, capturi de ecran, categorie) sunt extrase din manifest și afișate în marketplace
După publicare, poate dura până la o oră ca aplicația ta să apară în marketplace. Pentru a declanșa sincronizarea imediat, în loc să aștepți următoarea rulare orară:
Poți declanșa sincronizarea imediat, în loc să aștepți:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Pentru a viza un remote specific:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
Metadatele afișate în marketplace provin din apelul tău `defineApplication()` din codul sursă al aplicației — câmpuri precum `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` și `termsUrl`.
Metadatele afișate în marketplace provin din configurația `defineApplication()` — câmpuri precum `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` și `termsUrl`.
<Note>
Dacă aplicația ta nu definește un `aboutDescription` în `defineApplication()`, piața va folosi automat fișierul `README.md` al pachetului tău de pe npm drept conținut pentru pagina Despre. Acest lucru înseamnă că poți menține un singur README atât pentru npm, cât și pentru piața Twenty. Dacă vrei o descriere diferită în piață, setează explicit `aboutDescription`.
@@ -108,7 +143,7 @@ Dacă aplicația ta nu definește un `aboutDescription` în `defineApplication()
### Publicare CI
Proiectul generat include un workflow GitHub Actions care publică la fiecare lansare:
Folosește acest workflow GitHub Actions pentru a publica automat la fiecare release (folosește [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Pentru alte sisteme CI (GitLab CI, CircleCI etc.), se aplică aceleași trei comenzi: `yarn install`, `yarn twenty build`, apoi `npm publish` din `.twenty/output`.
<Tip>
<Note>
**npm provenance** este opțională, dar recomandată. Publicarea cu `--provenance` adaugă un badge de încredere la listarea ta în npm, permițând utilizatorilor să verifice că pachetul a fost construit dintr-un commit specific într-un pipeline CI public. Vezi [documentația npm provenance](https://docs.npmjs.com/generating-provenance-statements) pentru instrucțiuni de configurare.
</Tip>
## Implementare pe un server (tarball)
Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți implementa un tarball direct pe un server Twenty.
### Cerințe
Înainte de implementare, ai nevoie de un remote configurat care să indice serverul țintă. Remote-urile stochează local URL-ul serverului și credențialele de autentificare în `~/.twenty/config.json`.
Adaugă un remote:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
Pentru un server de dezvoltare local:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
Te poți autentifica și cu o cheie API pentru medii neinteractive:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Gestionează-ți remote-urile:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Implementare
Construiește și încarcă aplicația ta pe server într-un singur pas:
```bash filename="Terminal"
yarn twenty deploy
```
Aceasta construiește aplicația cu `--tarball`, apoi încarcă tarball-ul către remote-ul implicit printr-o încărcare multipart GraphQL.
Pentru a implementa către un remote specific:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Partajarea unei aplicații implementate
Aplicațiile tarball nu sunt listate în marketplace-ul public, astfel încât alte spații de lucru de pe același server nu le vor descoperi prin navigare. Pentru a partaja o aplicație implementată:
1. Mergi la **Setări > Aplicații > Înregistrări** și deschide aplicația ta
2. În fila **Distribuție**, fă clic pe **Copiază linkul de partajare**
3. Partajează acest link cu utilizatori din alte spații de lucru — îi duce direct la pagina de instalare a aplicației
Linkul de partajare folosește URL-ul de bază al serverului (fără niciun subdomeniu de spațiu de lucru), astfel încât funcționează pentru orice spațiu de lucru de pe server.
### Gestionarea versiunilor
Pentru a lansa o actualizare:
1. Actualizează câmpul `version` din `package.json`
2. Rulează `yarn twenty deploy` (sau `yarn twenty deploy -r production`)
3. Spațiile de lucru care au aplicația instalată vor vedea actualizarea disponibilă în setările lor
</Note>
## Instalarea aplicațiilor
După ce o aplicație este publicată (npm) sau implementată (tarball), spațiile de lucru o instalează prin interfața utilizatorului (UI):
După ce o aplicație este publicată (npm) sau implementată (tarball), spațiile de lucru o pot instala prin interfața utilizatorului (UI).
Mergi la pagina **Setări > Aplicații** din Twenty, unde pot fi parcurse și instalate atât aplicațiile din marketplace, cât și cele implementate prin tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
Poți instala aplicații și din linia de comandă:
```bash filename="Terminal"
yarn twenty install
```
Sau din pagina **Setări > Aplicații** din Twenty UI, unde pot fi navigate și instalate atât aplicațiile din marketplace, cât și cele implementate prin tarball.
## Categorii de distribuție a aplicațiilor
Twenty organizează aplicațiile în trei categorii, în funcție de modul în care sunt distribuite:
| Categorie | Cum funcționează | Vizibilă în marketplace? |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| **Dezvoltare** | Aplicații în modul de dezvoltare local, rulate prin `yarn twenty dev`. Folosite pentru construire și testare. | Nu |
| **Publicat (npm)** | Aplicații publicate pe npm cu cuvântul cheie `twenty-app`. Listate în marketplace pentru ca orice spațiu de lucru să le poată instala. | Da |
| **Intern (tarball)** | Aplicații implementate prin tarball pe un server specific. Disponibile doar pentru spațiile de lucru de pe acel server printr-un link de partajare. | Nu |
<Tip>
Pornește în modul **Dezvoltare** în timp ce îți construiești aplicația. Când este gata, alege **Publicat** (npm) pentru distribuire largă sau **Intern** (tarball) pentru implementare privată.
</Tip>
## Referință CLI
| Comandă | Descriere | Opțiuni cheie |
| --------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- |
| `yarn twenty build` | Compilează aplicația și generează manifestul | `--tarball` — creează și un pachet `.tgz` |
| `yarn twenty publish` | Construiește și publică pe npm | `--tag <tag>` — dist-tag npm (de ex., `beta`, `next`) |
| `yarn twenty deploy` | Construiește și încarcă un tarball pe un server | `-r, --remote <name>` — remote țintă |
| `yarn twenty catalog-sync` | Declanșează sincronizarea catalogului marketplace-ului pe server | `-r, --remote <name>` — remote țintă |
| `yarn twenty install` | Instalează o aplicație implementată pe un spațiu de lucru | `-r, --remote <name>` — remote țintă |
| `yarn twenty dev` | Monitorizează și sincronizează modificările locale | Folosește remote-ul implicit |
| `yarn twenty remote add` | Adaugă o conexiune la server | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | Listează remote-urile configurate | — |
| `yarn twenty remote switch` | Setează remote-ul implicit | — |
| `yarn twenty remote status` | Afișează starea conexiunii | — |
| `yarn twenty remote remove` | Elimină un remote | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### Pentru a atașa la un alt obiect
Înlocuiți `companyId` cu câmpul corespunzător:
Înlocuiți `targetCompanyId` cu câmpul corespunzător:
| Obiect | Nume câmp |
| ------------------- | -------------------- |
| Companie | `companyId` |
| Persoană | `personId` |
| Oportunitate | `opportunityId` |
| Obiect personalizat | `yourCustomObjectId` |
| Obiect | Nume câmp |
| ------------------- | -------------------------- |
| Companie | `targetCompanyId` |
| Persoană | `targetPersonId` |
| Oportunitate | `targetOpportunityId` |
| Obiect personalizat | `targetYourCustomObjectId` |
Actualizați atât parametrul funcției, cât și obiectul `variables.data` în mutația de atașare.
File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: Создайте своё первое приложение Twenty
---
<Warning>
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.
Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
</Warning>
Приложения позволяют расширять Twenty с помощью пользовательских объектов, полей, логических функций, навыков ИИ и UI-компонентов — всё это управляется как код.
**Что вы можете создать:**
* Пользовательские объекты, поля, представления и элементы навигации для формирования вашей модели данных
* Логические функции, запускаемые маршрутами HTTP, расписаниями cron или событиями базы данных
* Фронтенд-компоненты, которые непосредственно отображаются внутри интерфейса Twenty
* Навыки, расширяющие возможности ИИ-агентов Twenty
* Разверните приложение в нескольких рабочих пространствах
## Требования
* Node.js 24+
* Yarn 4
* Docker (или запущенный локальный экземпляр Twenty)
Прежде чем начать, убедитесь, что на вашем компьютере установлено следующее:
## Начало работы
* **Node.js 24+** — [Скачать здесь](https://nodejs.org/)
* **Yarn 4** — Поставляется вместе с Node.js через Corepack. Включите его, выполнив `corepack enable`
* **Docker** — [Скачать здесь](https://www.docker.com/products/docker-desktop/). Требуется для запуска локального экземпляра Twenty. Не требуется, если у вас уже запущен сервер Twenty.
Создайте новое приложение с помощью официального генератора, затем выполните аутентификацию и начните разработку:
## Шаг 1: Сгенерируйте каркас приложения
Откройте терминал и выполните:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Используйте параметр `--minimal`, чтобы создать минимальную установку
Вам будет предложено ввести имя и описание вашего приложения. Нажмите **Enter**, чтобы принять значения по умолчанию.
Отсюда вы можете:
Будет создана новая папка `my-twenty-app` со всем необходимым.
<Note>
Генератор поддерживает следующие флаги:
* `--minimal` — сгенерировать только основные файлы, без примеров (по умолчанию)
* `--exhaustive` — сгенерировать все примеры сущностей
* `--name <name>` — задать имя приложения (пропускает запрос)
* `--display-name <displayName>` — задать отображаемое имя (пропускает запрос)
* `--description <description>` — задать описание (пропускает запрос)
* `--skip-local-instance` — пропустить запрос на настройку локального сервера
</Note>
## Шаг 2: Настройте локальный экземпляр Twenty
Скэффолдер спросит:
> **Хотите настроить локальный экземпляр Twenty?**
* **Введите `yes`** (рекомендуется) — это скачает Docker-образ `twenty-app-dev` и запустит локальный сервер Twenty на порту `2020`. Перед продолжением убедитесь, что Docker запущен.
* **Введите `no`** — выберите это, если у вас уже запущен локальный сервер Twenty.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Запустить локальный экземпляр?" />
</div>
## Шаг 3: Войдите в своё рабочее пространство
Затем откроется окно браузера со страницей входа в Twenty. Войдите, используя предварительно созданную демонстрационную учётную запись:
* **Электронная почта:** `tim@apple.dev`
* **Пароль:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Экран входа в Twenty" />
</div>
## Шаг 4: Авторизуйте приложение
После входа вы увидите экран авторизации. Это позволит вашему приложению взаимодействовать с вашим рабочим пространством.
Нажмите **Authorize**, чтобы продолжить.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Экран авторизации Twenty CLI" />
</div>
После авторизации в терминале появится подтверждение, что всё настроено.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Каркас приложения успешно создан" />
</div>
## Шаг 5: Начните разработку
Перейдите в папку вашего нового приложения и запустите сервер разработки:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
Смотрите также: страницы справки CLI для [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) и [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Он отслеживает исходные файлы, пересобирает при каждом изменении и автоматически синхронизирует ваше приложение с локальным сервером Twenty. В терминале должна появиться панель текущего статуса.
## Структура проекта (сгенерированного)
Для более подробного вывода (журналы сборки, запросы синхронизации, трассировки ошибок) используйте флаг `--verbose`:
Когда вы запускаете `npx create-twenty-app@latest my-twenty-app`, генератор:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* Копирует минимальное базовое приложение в `my-twenty-app/`
* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4
* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty`
* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, предустановочную и послеустановочную функции), а также примерные файлы в зависимости от выбранного режима создания каркаса
<Warning>
Режим разработки доступен только на экземплярах Twenty, запущенных в режиме разработки (`NODE_ENV=development`). Экземпляры в продакшене отклоняют запросы синхронизации из режима разработки. Используйте `yarn twenty deploy` для развёртывания на продакшен-серверах — подробности см. в разделе [Публикация приложений](/l/ru/developers/extend/apps/publishing).
</Warning>
Сгенерированное с помощью каркаса приложение с режимом по умолчанию `--exhaustive` выглядит так:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Вывод терминала в режиме разработки" />
</div>
## Шаг 6: Посмотрите своё приложение в Twenty
Откройте [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) в браузере. Перейдите в **Settings > Apps** и выберите вкладку **Developer**. Вы должны увидеть своё приложение в разделе **Your Apps**:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Список Your Apps с приложением My twenty app" />
</div>
Нажмите **My twenty app**, чтобы открыть его **регистрацию приложения**. Регистрация — это запись на уровне сервера, описывающая ваше приложение: его имя, уникальный идентификатор, учётные данные OAuth и источник (локальный, npm или tarball). Она хранится на сервере, а не внутри какого-либо конкретного рабочего пространства. Когда вы устанавливаете приложение в рабочее пространство, Twenty создаёт привязанное к рабочему пространству **приложение**, которое ссылается на эту регистрацию. Одну и ту же регистрацию можно установить в нескольких рабочих пространствах на одном сервере.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Сведения о регистрации приложения" />
</div>
Нажмите **View installed app**, чтобы посмотреть установленное приложение. Вкладка **About** показывает текущую версию и параметры управления:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Установленное приложение — вкладка About" />
</div>
Переключитесь на вкладку **Content**, чтобы увидеть всё, что предоставляет ваше приложение: объекты, поля, логические функции и агенты:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Установленное приложение — вкладка Content" />
</div>
Готово! Отредактируйте любой файл в `src/`, и изменения будут подхвачены автоматически.
Перейдите к разделу [Создание приложений](/l/ru/developers/extend/apps/building) за подробным руководством по созданию объектов, логических функций, фронтенд-компонентов, навыков и многого другого.
---
## Структура проекта
Скэффолдер генерирует следующую структуру файлов (показано в режиме `--exhaustive`, который включает примеры для каждого типа сущностей):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`).
По умолчанию (`--minimal`) создаются только основные файлы: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`. Используйте `--exhaustive`, чтобы включить все показанные выше файлы-примеры.
В общих чертах:
### Ключевые файлы
* **package.json**: Объявляет имя приложения, версию, движки (Node 24+, Yarn 4) и добавляет `twenty-sdk`, а также скрипт `twenty`, который делегирует выполнение локальному CLI `twenty`. Выполните `yarn twenty help`, чтобы вывести список всех доступных команд.
* **.gitignore**: Игнорирует распространённые артефакты, такие как `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, каталоги coverage, файлы журналов и файлы `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Фиксируют и настраивают используемый в проекте инструментарий Yarn 4.
* **.nvmrc**: Фиксирует версию Node.js, ожидаемую проектом.
* **.oxlintrc.json** и **tsconfig.json**: Обеспечивают линтинг и конфигурацию TypeScript для исходников вашего приложения на TypeScript.
* **README.md**: Короткий README в корне приложения с базовыми инструкциями.
* **public/**: Папка для хранения общедоступных ресурсов (изображений, шрифтов, статических файлов), которые будут отдаваться вашим приложением. Файлы, размещённые здесь, загружаются во время синхронизации и доступны во время выполнения.
* **src/**: Основное место, где вы определяете приложение как код
| Файл / Папка | Назначение |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Содержит имя, версию и зависимости вашего приложения. Содержит скрипт `twenty`, чтобы вы могли выполнить `yarn twenty help` и увидеть все команды. |
| `src/application-config.ts` | **Обязательно.** Основной файл конфигурации для вашего приложения. |
| `src/roles/` | Определяет роли, которые контролируют доступ логических функций. |
| `src/logic-functions/` | Серверные функции, запускаемые маршрутами, расписаниями cron или событиями базы данных. |
| `src/front-components/` | Компоненты React, которые отображаются внутри интерфейса Twenty. |
| `src/objects/` | Пользовательские определения объектов для расширения вашей модели данных. |
| `src/fields/` | Пользовательские поля, добавляемые к существующим объектам. |
| `src/views/` | Конфигурации сохранённых представлений. |
| `src/navigation-menu-items/` | Пользовательские ссылки в боковой навигации. |
| `src/skills/` | Навыки, расширяющие возможности ИИ-агентов Twenty. |
| `src/agents/` | ИИ-агенты с пользовательскими промптами. |
| `src/page-layouts/` | Пользовательские макеты страниц для представлений записей. |
| `src/__tests__/` | Интеграционные тесты (настройка + пример теста). |
| `public/` | Статические ресурсы (изображения, шрифты), обслуживаемые вместе с вашим приложением. |
### Обнаружение сущностей
## Управление удалёнными серверами
SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов **`export default define<Entity>({...})`**. Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из `twenty-sdk`:
| Вспомогательная функция | Тип сущности |
| -------------------------------- | ------------------------------------------------------------------ |
| `defineObject` | Определения пользовательских объектов |
| `defineLogicFunction` | Определения логических функций |
| `definePreInstallLogicFunction` | Предустановочная логическая функция (запускается до установки) |
| `definePostInstallLogicFunction` | Послеустановочная логическая функция (запускается после установки) |
| `defineFrontComponent` | Определения компонентов фронтенда |
| `defineRole` | Определения ролей |
| `defineField` | Расширения полей для существующих объектов |
| `defineView` | Определения сохранённых представлений |
| `defineNavigationMenuItem` | Определения пунктов меню навигации |
| `defineSkill` | Определения навыков агента ИИ |
<Note>
**Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define<Entity>({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование.
</Note>
Пример обнаруженной сущности:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Позднее команды добавят больше файлов и папок:
* `yarn twenty dev` автоматически сгенерирует типизированный `CoreApiClient` (для данных рабочего пространства через `/graphql`) в `node_modules/twenty-client-sdk/`. `MetadataApiClient` (для конфигурации рабочего пространства и загрузки файлов через `/metadata`) поставляется в предсобранном виде и доступен сразу. Импортируйте их из `twenty-client-sdk/core` и `twenty-client-sdk/metadata` соответственно.
* `yarn twenty add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов, ролей, навыков и многого другого.
## Аутентификация
При первом запуске `yarn twenty auth:login` вам будет предложено указать:
* URL API (по умолчанию http://localhost:3000 или текущий профиль рабочего пространства)
* Ключ API
Ваши учётные данные хранятся для каждого пользователя в `~/.twenty/config.json`. Вы можете хранить несколько профилей и переключаться между ними.
### Управление рабочими пространствами
Remote — это сервер Twenty, к которому подключается ваше приложение. Во время настройки скэффолдер автоматически создаст его для вас. Вы можете в любой момент добавлять новые remotes или переключаться между ними.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
После переключения рабочего пространства с помощью `yarn twenty auth:switch` все последующие команды по умолчанию будут использовать это рабочее пространство. Вы по-прежнему можете временно переопределить это с помощью `--workspace <name>`.
Ваши учётные данные хранятся в `~/.twenty/config.json`.
## Локальный сервер разработки (`yarn twenty server`)
CLI может управлять локальным сервером Twenty, запущенным в Docker. Это тот же сервер, который автоматически запускается при создании каркаса приложения с помощью `create-twenty-app`, но им можно управлять и вручную.
### Запуск сервера
```bash filename="Terminal"
yarn twenty server start
```
Эта команда скачивает Docker-образ `twentycrm/twenty-app-dev:latest` (если его ещё нет), создаёт контейнер с именем `twenty-app-dev` и запускает его на порту **2020**. CLI ждёт, пока сервер пройдёт проверку работоспособности, прежде чем вернуть управление.
Создаются два тома Docker для сохранения данных между перезапусками:
* `twenty-app-dev-data` — база данных PostgreSQL
* `twenty-app-dev-storage` — файловое хранилище
Если порт 2020 уже используется, вы можете запустить на другом порту:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI автоматически настраивает внутренние `NODE_PORT` и `SERVER_URL` контейнера в соответствии с выбранным портом, чтобы логические функции, OAuth и прочие внутренние сетевые взаимодействия работали корректно.
После запуска сервер автоматически регистрируется как remote `local` в конфигурации вашего CLI.
### Проверка состояния сервера
```bash filename="Terminal"
yarn twenty server status
```
Показывает, запущен ли сервер, его URL и учётные данные по умолчанию (`tim@apple.dev` / `tim@apple.dev`).
### Просмотр журналов сервера
```bash filename="Terminal"
yarn twenty server logs
```
Выводит журналы контейнера в потоковом режиме. Используйте `--lines`, чтобы задать, сколько последних строк показывать:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Остановка сервера
```bash filename="Terminal"
yarn twenty server stop
```
Останавливает контейнер. Ваши данные сохраняются в томах Docker — следующий `start` продолжит с того места, где вы остановились.
### Сброс сервера
```bash filename="Terminal"
yarn twenty server reset
```
Удаляет контейнер и оба тома Docker, полностью стирая все данные. Следующий `start` создаст новый чистый экземпляр.
<Note>
Для работы сервера необходимо, чтобы **Docker** был запущен. Если вы видите ошибку "Docker not running", убедитесь, что запущен Docker Desktop (или демон Docker).
</Note>
### Справочник команд
| Команда | Описание |
| -------------------------------------- | -------------------------------------------------------------- |
| `yarn twenty server start` | Запустить локальный сервер (при необходимости скачивает образ) |
| `yarn twenty server start --port 3030` | Запустить на пользовательском порту |
| `yarn twenty server stop` | Остановить сервер (данные сохраняются) |
| `yarn twenty server status` | Показать состояние сервера, URL и учётные данные |
| `yarn twenty server logs` | Потоковый вывод журналов сервера |
| `yarn twenty server logs --lines 100` | Показать последние 100 строк журнала |
| `yarn twenty server reset` | Удалить все данные и начать с чистого листа |
## CI с GitHub Actions
Скэффолдер генерирует готовый к использованию workflow GitHub Actions в `.github/workflows/ci.yml`. Он автоматически запускает ваши интеграционные тесты при каждом пуше в `main` и в pull request'ах.
Рабочий процесс:
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` в начале workflow.
## Ручная настройка (без генератора)
Хотя мы рекомендуем использовать `create-twenty-app` для наилучшего старта, вы также можете настроить проект вручную. Не устанавливайте CLI глобально. Вместо этого добавьте `twenty-sdk` как локальную зависимость и настройте один скрипт в вашем package.json:
Если вы предпочитаете настроить всё самостоятельно, не используя `create-twenty-app`, это можно сделать в два шага.
**1. Добавьте `twenty-sdk` и `twenty-client-sdk` в зависимости:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Затем добавьте скрипт `twenty`:
**2. Добавьте скрипт `twenty` в ваш `package.json`:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ yarn add -D twenty-sdk
}
```
Теперь вы можете запускать все команды через `yarn twenty <command>`, например, `yarn twenty dev`, `yarn twenty help` и т. д.
Теперь вы можете запускать `yarn twenty dev`, `yarn twenty help` и все остальные команды.
## Как использовать локальный экземпляр Twenty
Если у вас уже запущен локально экземпляр Twenty (например, через `npx nx start twenty-server`), вы можете подключиться к нему вместо использования Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
Не устанавливайте `twenty-sdk` глобально. Всегда используйте его как локальную зависимость проекта, чтобы каждый проект мог закреплять свою версию.
</Note>
## Устранение неполадок
* Ошибки аутентификации: выполните `yarn twenty auth:login` и убедитесь, что у вашего ключа API есть необходимые права.
* Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty.
* Типы или клиент отсутствуют/устарели: перезапустите `yarn twenty dev` — он автоматически генерирует типизированный клиент.
* Режим разработки не синхронизируется: убедитесь, что запущен `yarn twenty dev`, и что ваша среда не игнорирует изменения.
Если столкнётесь с проблемами:
Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
* Перед запуском генератора с локальным экземпляром убедитесь, что **Docker запущен**.
* Убедитесь, что используете **Node.js 24+** (`node -v` для проверки).
* Убедитесь, что **Corepack включён** (`corepack enable`), чтобы Yarn 4 был доступен.
* Если зависимости, похоже, повреждены, попробуйте удалить `node_modules` и снова выполнить `yarn install`.
Все ещё не получается? Попросите помощи на [Discord-сервере Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -4,34 +4,76 @@ description: Распространяйте своё приложение Twenty
---
<Warning>
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.
Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
</Warning>
## Обзор
После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/building), у вас есть два пути для его распространения:
* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить.
* **Разверните tar-архив** — загрузите своё приложение напрямую на конкретный сервер Twenty для внутреннего или частного использования.
* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить.
Оба пути начинаются с одного и того же шага **build**.
## Сборка вашего приложения
Команда `build` компилирует ваши исходники TypeScript, транспилирует функции логики и фронтенд-компоненты и генерирует `manifest.json`, который описывает содержимое вашего приложения:
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
Выходные данные записываются в `.twenty/output/`. Этот каталог содержит всё необходимое для распространения: скомпилированный код, ресурсы, манифест и копию вашего `package.json`.
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
Чтобы также создать tarball `.tgz` (который внутренне используется командой deploy или для ручного распространения):
## Развертывание на сервер (tarball)
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty.
### Требования
Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`.
Добавьте remote:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Развертывание
Соберите и загрузите ваше приложение на сервер в одном шаге:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Общий доступ к развернутому приложению
Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Чтобы поделиться развернутым приложением:
1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение
2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа**
3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения
Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере.
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### Управление версиями
Чтобы выпустить обновление:
1. Обновите значение поля `version` в файле `package.json`
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Рабочие пространства, в которых установлено приложение, увидят доступное обновление в своих настройках
{/* TODO: add screenshot of the Upgrade button */}
## Публикация в npm
Публикация в npm делает ваше приложение видимым в маркетплейсе Twenty. Любое рабочее пространство Twenty может просматривать, устанавливать и обновлять приложения из маркетплейса непосредственно из интерфейса.
@@ -39,41 +81,42 @@ yarn twenty build --tarball
### Требования
* Учётная запись [npm](https://www.npmjs.com)
* Ключевое слово `twenty-app` **обязательно** должно быть указано в массиве `keywords` вашего `package.json`
### Добавление обязательного ключевого слова
Маркетплейс Twenty находит приложения, ища в реестре npm пакеты с ключевым словом `twenty-app`. Добавьте его в ваш `package.json`:
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
Маркетплейс ищет в реестре npm по `keywords:twenty-app`. Без этого ключевого слова ваш пакет не появится в маркетплейсе, даже если в его имени есть префикс `twenty-app-`.
</Note>
### Метаданные маркетплейса
### Шаги
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
1. **Сборка вашего приложения:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **Публикация в npm:**
See the [defineApplication accordion](/l/ru/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publish
```bash filename="Terminal"
yarn twenty publish
```
Это выполняет `npm publish` из каталога `.twenty/output/`.
Чтобы опубликовать с определённым dist-tag (например, `beta` или `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### Как работает обнаружение приложений в маркетплейсе
Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**:
Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**.
1. Он ищет все пакеты npm с ключевым словом `keywords:twenty-app`
2. Для каждого пакета он извлекает `manifest.json` с CDN npm
3. Метаданные приложения (name, description, author, logo, screenshots, category) извлекаются из манифеста и отображаются в маркетплейсе
После публикации может пройти до одного часа, прежде чем ваше приложение появится в маркетплейсе. Чтобы запустить синхронизацию немедленно, не дожидаясь следующего почасового запуска:
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Чтобы указать конкретный remote:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
Метаданные, отображаемые в маркетплейсе, берутся из вызова `defineApplication()` в исходном коде вашего приложения — из таких полей, как `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` и `termsUrl`.
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
Если ваше приложение не определяет `aboutDescription` в `defineApplication()`, маркетплейс автоматически использует `README.md` вашего пакета из npm в качестве содержимого страницы «О приложении». Это означает, что вы можете поддерживать единый README как для npm, так и для маркетплейса Twenty. Если вы хотите другое описание в маркетплейсе, явно задайте `aboutDescription`.
@@ -108,7 +143,7 @@ yarn twenty catalog-sync -r production
### Публикация через CI
Сгенерированный шаблоном проект включает рабочий процесс GitHub Actions, который выполняет публикацию при каждом релизе:
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `yarn twenty build`, затем `npm publish` из `.twenty/output`.
<Tip>
<Note>
**npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Tip>
## Развертывание на сервер (tarball)
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty.
### Требования
Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`.
Добавьте remote:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
Для локального сервера разработки:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
Вы также можете аутентифицироваться с помощью API-ключа в неинтерактивных средах:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Управляйте remotes:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Развертывание
Соберите и загрузите ваше приложение на сервер в одном шаге:
```bash filename="Terminal"
yarn twenty deploy
```
Это собирает приложение с флагом `--tarball`, затем загружает tarball на remote по умолчанию через GraphQL multipart upload.
Чтобы развернуть на конкретный remote:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Общий доступ к развернутому приложению
Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Чтобы поделиться развернутым приложением:
1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение
2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа**
3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения
Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере.
### Управление версиями
Чтобы выпустить обновление:
1. Обновите значение поля `version` в файле `package.json`
2. Выполните `yarn twenty deploy` (или `yarn twenty deploy -r production`)
3. Рабочие пространства, в которых установлено приложение, увидят доступное обновление в своих настройках
</Note>
## Установка приложений
После публикации приложения (npm) или его развертывания (tarball) рабочие пространства устанавливают его через интерфейс:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
```bash filename="Terminal"
yarn twenty install
```
Или со страницы **Настройки > Приложения** в интерфейсе Twenty, где можно просматривать и устанавливать как приложения из маркетплейса, так и развернутые через tarball.
## Категории распространения приложений
Twenty группирует приложения в три категории в зависимости от способа их распространения:
| Категория | Как это работает | Отображается в маркетплейсе? |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| **Разработка** | Локальные приложения в режиме разработки, запущенные через `yarn twenty dev`. Используются для сборки и тестирования. | Нет |
| **Опубликовано (npm)** | Приложения, опубликованные в npm с ключевым словом `twenty-app`. Отображаются в маркетплейсе, доступные для установки любому рабочему пространству. | Да |
| **Внутренние (tarball)** | Приложения, развернутые через tarball на конкретном сервере. Доступны только рабочим пространствам на этом сервере по ссылке общего доступа. | Нет |
<Tip>
Начните в режиме **Разработка** во время создания приложения. Когда будет готово, выберите **Опубликовано** (npm) для широкого распространения или **Внутренний** (tarball) для приватного развертывания.
</Tip>
## Справочник по CLI
| Команда | Описание | Основные флаги |
| --------------------------- | -------------------------------------------------------- | ------------------------------------------------------- |
| `yarn twenty build` | Скомпилировать приложение и сгенерировать манифест | `--tarball` — также создать пакет `.tgz` |
| `yarn twenty publish` | Собрать и опубликовать в npm | `--tag <tag>` — dist-tag npm (например, `beta`, `next`) |
| `yarn twenty deploy` | Собрать и загрузить tarball на сервер | `-r, --remote <name>` — целевой удалённый репозиторий |
| `yarn twenty catalog-sync` | Запустить на сервере синхронизацию каталога маркетплейса | `-r, --remote <name>` — целевой удалённый репозиторий |
| `yarn twenty install` | Установить развернутое приложение в рабочем пространстве | `-r, --remote <name>` — целевой remote |
| `yarn twenty dev` | Отслеживать и синхронизировать локальные изменения | Использует remote по умолчанию |
| `yarn twenty remote add` | Добавить подключение к серверу | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | Показать настроенные remotes | — |
| `yarn twenty remote switch` | Установить remote по умолчанию | — |
| `yarn twenty remote status` | Показать статус подключения | — |
| `yarn twenty remote remove` | Удалить remote | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### Чтобы прикреплять к другому объекту
Замените `companyId` на соответствующее поле:
Замените `targetCompanyId` на соответствующее поле:
| Объект | Имя поля |
| ----------------------- | -------------------- |
| Компания | `companyId` |
| Контакт | `personId` |
| Сделка | `opportunityId` |
| Пользовательский объект | `yourCustomObjectId` |
| Объект | Имя поля |
| ----------------------- | -------------------------- |
| Компания | `targetCompanyId` |
| Контакт | `targetPersonId` |
| Сделка | `targetOpportunityId` |
| Пользовательский объект | `targetYourCustomObjectId` |
Обновите и параметр функции, и объект `variables.data` в мутации вложения.
File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: İlk Twenty uygulamanızı dakikalar içinde oluşturun.
---
<Warning>
Uygulamalar şu anda alfa testinde. Özellik işlevsel ancak hâlâ gelişmekte.
Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte.
</Warning>
Uygulamalar, Twenty'yi özel nesneler, alanlar, mantık işlevleri, Yapay Zeka yetenekleri ve UI bileşenleriyle genişletmenizi sağlar — tümü kod olarak yönetilir.
**Oluşturabilecekleriniz:**
* Veri modelinizi şekillendirmek için özel nesneler, alanlar, görünümler ve gezinti öğeleri
* HTTP rotaları, cron zamanlamaları veya veritabanı olayları tarafından tetiklenen mantık işlevleri
* Twenty'nin UI'si içinde doğrudan görüntülenen ön uç bileşenleri
* Twenty'nin yapay zeka ajanlarını genişleten beceriler
* Bir uygulamayı birden çok çalışma alanına dağıtın
## Ön Gereksinimler
* Node.js 24+
* Yarn 4
* Docker (veya çalışan yerel bir Twenty örneği)
Başlamadan önce, makinenizde aşağıdakilerin kurulu olduğundan emin olun:
## Başlarken
* **Node.js 24+** — [Buradan indirin](https://nodejs.org/)
* **Yarn 4** — Corepack aracılığıyla Node.js ile birlikte gelir. `corepack enable` komutunu çalıştırarak etkinleştirin
* **Docker** — [Buradan indirin](https://www.docker.com/products/docker-desktop/). Yerel bir Twenty örneğini çalıştırmak için gereklidir. Zaten çalışan bir Twenty sunucunuz varsa gerekmez.
Resmi iskelet oluşturucu aracını kullanarak yeni bir uygulama oluşturun, ardından kimlik doğrulaması yapıp geliştirmeye başlayın:
## Adım 1: Uygulamanızın iskeletini oluşturun
Bir terminal açın ve şunu çalıştırın:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> Minimal bir kurulum iskeleti oluşturmak için `--minimal` seçeneğini kullanın
Uygulamanız için bir ad ve açıklama girmeniz istenecektir. Varsayılanları kabul etmek için **Enter** tuşuna basın.
Buradan şunları yapabilirsiniz:
Bu, `my-twenty-app` adlı, ihtiyacınız olan her şeyi içeren yeni bir klasör oluşturur.
<Note>
İskelet oluşturucu şu bayrakları destekler:
* `--minimal` — yalnızca temel dosyaların iskeletini oluşturur, örnek yok (varsayılan)
* `--exhaustive` — tüm örnek varlıkların iskeletini oluşturur
* `--name <name>` — uygulama adını ayarlar (istemi atlar)
* `--display-name <displayName>` — görünen adı ayarlar (istemi atlar)
* `--description <description>` — açıklamayı ayarlar (istemi atlar)
* `--skip-local-instance` — yerel sunucu kurulum istemini atlar
</Note>
## Adım 2: Yerel bir Twenty örneği kurun
İskelet oluşturucu şunu soracaktır:
> **Yerel bir Twenty örneği kurmak ister misiniz?**
* **`yes` yazın** (önerilir) — Bu, `twenty-app-dev` Docker imajını çeker ve `2020` portunda yerel bir Twenty sunucusu başlatır. Devam etmeden önce Docker'ın çalıştığından emin olun.
* **`no` yazın** — Yerelde zaten çalışan bir Twenty sunucunuz varsa bunu seçin.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Yerel örnek başlatılsın mı?" />
</div>
## Adım 3: Çalışma alanınıza giriş yapın
Ardından, Twenty oturum açma sayfasıyla bir tarayıcı penceresi açılacaktır. Önceden eklenmiş demo hesabıyla oturum açın:
* **E-posta:** `tim@apple.dev`
* **Parola:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty oturum açma ekranı" />
</div>
## Adım 4: Uygulamayı yetkilendirin
Oturum açtıktan sonra bir yetkilendirme ekranı göreceksiniz. Bu, uygulamanızın çalışma alanınızla etkileşim kurmasını sağlar.
Devam etmek için **Authorize**'a tıklayın.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI yetkilendirme ekranı" />
</div>
Yetkilendirildikten sonra terminaliniz her şeyin kurulduğunu onaylayacaktır.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Uygulama iskeleti başarıyla oluşturuldu" />
</div>
## Adım 5: Geliştirmeye başlayın
Yeni uygulama klasörünüze gidin ve geliştirme sunucusunu başlatın:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
Ayrıca bkz.: [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) ve [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) için CLI başvuru sayfaları.
Bu, kaynak dosyalarınızı izler, her değişiklikte yeniden derler ve uygulamanızı yerel Twenty sunucusuyla otomatik olarak eşitler. Terminalinizde canlı bir durum paneli görmelisiniz.
## Proje yapısı (şablondan oluşturulmuş)
Daha ayrıntılı çıktı (derleme günlükleri, eşitleme istekleri, hata izleri) için `--verbose` bayrağını kullanın:
`npx create-twenty-app@latest my-twenty-app` komutunu çalıştırdığınızda iskelet oluşturucu şunları yapar:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* Minimal bir temel uygulamayı `my-twenty-app/` içine kopyalar
* Yerel bir `twenty-sdk` bağımlılığı ve Yarn 4 yapılandırması ekler
* `twenty` CLI ile bağlantılı yapılandırma dosyaları ve betikler oluşturur
* İskelet oluşturma moduna bağlı olarak çekirdek dosyaları (uygulama yapılandırması, varsayılan işlev rolü, kurulum öncesi ve kurulum sonrası işlevler) ile örnek dosyaları üretir
<Warning>
Geliştirme modu yalnızca geliştirme ortamında (`NODE_ENV=development`) çalışan Twenty örneklerinde kullanılabilir. Üretim örnekleri geliştirme eşitleme isteklerini reddeder. Üretim sunucularına dağıtmak için `yarn twenty deploy` komutunu kullanın — ayrıntılar için [Uygulamaları Yayınlama](/l/tr/developers/extend/apps/publishing) bölümüne bakın.
</Warning>
Varsayılan `--exhaustive` moduyla yeni oluşturulmuş bir uygulama şu şekilde görünür:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Geliştirme modu terminal çıktısı" />
</div>
## Adım 6: Uygulamanızı Twenty'de görün
Tarayıcınızda [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) adresini açın. **Settings > Apps** bölümüne gidin ve **Developer** sekmesini seçin. **Your Apps** altında uygulamanızın listelendiğini görmelisiniz:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps listesinde My twenty app gösteriliyor" />
</div>
**My twenty app** üzerine tıklayarak **uygulama kaydını** açın. Kayıt, uygulamanızı tanımlayan sunucu düzeyinde bir kayıttır — adını, benzersiz tanımlayıcısını, OAuth kimlik bilgilerini ve kaynağını (yerel, npm veya tarball). Belirli bir çalışma alanının içinde değil, sunucuda bulunur. Bir uygulamayı bir çalışma alanına kurduğunuzda, Twenty bu kayda işaret eden, çalışma alanı kapsamında bir **uygulama** oluşturur. Tek bir kayıt, aynı sunucudaki birden çok çalışma alanına kurulabilir.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Uygulama kaydı ayrıntıları" />
</div>
Yüklü uygulamayı görmek için **View installed app**'e tıklayın. **About** sekmesi, geçerli sürümü ve yönetim seçeneklerini gösterir:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Yüklü uygulama — About sekmesi" />
</div>
Uygulamanızın sağladığı her şeyi — nesneler, alanlar, mantık işlevleri ve ajanlar — görmek için **Content** sekmesine geçin:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Yüklü uygulama — Content sekmesi" />
</div>
Her şey hazır! `src/` içindeki herhangi bir dosyayı düzenleyin; değişiklikler otomatik olarak alınacaktır.
Nesneler, mantık işlevleri, ön bileşenler, beceriler ve daha fazlasını oluşturma hakkında ayrıntılı bir kılavuz için [Uygulama Oluşturma](/l/tr/developers/extend/apps/building) bölümüne göz atın.
---
## Proje yapısı
İskelet oluşturucu aşağıdaki dosya yapısını üretir (`--exhaustive` modunda gösterilmiştir; her varlık türü için örnekler içerir):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`).
Varsayılan olarak (`--minimal`), yalnızca çekirdek dosyalar oluşturulur: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`. Yukarıda gösterilen tüm örnek dosyaları dahil etmek için `--exhaustive` kullanın.
Genel hatlarıyla:
### Temel dosyalar
* **package.json**: Uygulama adını, sürümünü, motorları (Node 24+, Yarn 4) bildirir ve `twenty-sdk` ile yerel `twenty` CLI'sine yetki devreden bir `twenty` betiği ekler. Tüm mevcut komutları listelemek için `yarn twenty help` komutunu çalıştırın.
* **.gitignore**: `node_modules`, `.yarn`, `.twenty/`, `dist/`, `build/`, kapsam klasörleri, günlük dosyaları ve `.env*` dosyaları gibi yaygın artifaktları yok sayar.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Proje tarafından kullanılan Yarn 4 araç zincirini kilitler ve yapılandırır.
* **.nvmrc**: Projenin beklediği Node.js sürümünü sabitler.
* **.oxlintrc.json** ve **tsconfig.json**: Uygulamanızın TypeScript kaynakları için linting ve TypeScript yapılandırması sağlar.
* **README.md**: Uygulama kökünde temel talimatların yer aldığı kısa bir README.
* **public/**: Uygulamanızla birlikte sunulacak genel varlıkları (görseller, yazı tipleri, statik dosyalar) depolamak için bir klasör. Buraya yerleştirilen dosyalar senkronizasyon sırasında yüklenir ve çalışma zamanında erişilebilir olur.
* **src/**: Uygulamanızı kod olarak tanımladığınız ana yer
| Dosya / Klasör | Amaç |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Uygulamanızın adını, sürümünü ve bağımlılıklarını bildirir. Tüm komutları görmek için `yarn twenty help` çalıştırabilmeniz amacıyla bir `twenty` betiği içerir. |
| `src/application-config.ts` | **Gerekli.** Uygulamanızın ana yapılandırma dosyası. |
| `src/roles/` | Mantık işlevlerinizin neye erişebileceğini kontrol eden rolleri tanımlar. |
| `src/logic-functions/` | Rotalar, cron zamanlamaları veya veritabanı olayları tarafından tetiklenen sunucu tarafı işlevler. |
| `src/front-components/` | Twenty'nin UI'si içinde görüntülenen React bileşenleri. |
| `src/objects/` | Veri modelinizi genişletmek için özel nesne tanımları. |
| `src/fields/` | Mevcut nesnelere eklenen özel alanlar. |
| `src/views/` | Kaydedilmiş görünüm yapılandırmaları. |
| `src/navigation-menu-items/` | Kenar çubuğu gezintisinde özel bağlantılar. |
| `src/skills/` | Twenty'nin yapay zeka ajanlarının yeteneklerini genişleten beceriler. |
| `src/agents/` | Özel istemlere sahip yapay zekâ ajanları. |
| `src/page-layouts/` | Kayıt görünümleri için özel sayfa düzenleri. |
| `src/__tests__/` | Entegrasyon testleri (kurulum + örnek test). |
| `public/` | Uygulamanızla birlikte sunulan statik varlıklar (görüntüler, yazı tipleri). |
### Varlık algılama
## Uzakları yönetme
SDK, TypeScript dosyalarınızı **`export default define<Entity>({...})`** çağrılarını arayarak ayrıştırıp varlıkları algılar. Her varlık türünün, `twenty-sdk` tarafından dışa aktarılan karşılık gelen bir yardımcı fonksiyonu vardır:
| Yardımcı fonksiyon | Varlık türü |
| -------------------------------- | -------------------------------------------------------- |
| `defineObject` | Özel nesne tanımları |
| `defineLogicFunction` | Mantık işlevi tanımları |
| `definePreInstallLogicFunction` | Kurulum öncesi mantık işlevi (kurulumdan önce çalışır) |
| `definePostInstallLogicFunction` | Kurulum sonrası mantık işlevi (kurulumdan sonra çalışır) |
| `defineFrontComponent` | Ön bileşen tanımları |
| `defineRole` | Rol tanımları |
| `defineField` | Mevcut nesneler için alan genişletmeleri |
| `defineView` | Kaydedilmiş görünüm tanımları |
| `defineNavigationMenuItem` | Gezinme menüsü öğesi tanımları |
| `defineSkill` | Yapay zekâ ajanı yetenek tanımları |
<Note>
**Dosya adlandırma esnektir.** Varlık algılama AST tabanlıdır — SDK, kaynak dosyalarınızı `export default define<Entity>({...})` desenini bulmak için tarar. Dosyalarınızı ve klasörlerinizi dilediğiniz gibi düzenleyebilirsiniz. Varlık türüne göre gruplama (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca kod organizasyonu için bir gelenektir.
</Note>
Algılanan bir varlığa örnek:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
İlerideki komutlar daha fazla dosya ve klasör ekleyecektir:
* `yarn twenty dev`, türlendirilmiş `CoreApiClient`'i (çalışma alanı verileri için `/graphql` aracılığıyla) `node_modules/twenty-client-sdk/` içine otomatik olarak oluşturur. `MetadataApiClient` (çalışma alanı yapılandırması ve dosya yüklemeleri için `/metadata` aracılığıyla) önceden derlenmiş olarak gelir ve hemen kullanılabilir. Bunları sırasıyla `twenty-client-sdk/core` ve `twenty-client-sdk/metadata` içinden içe aktarın.
* `yarn twenty add`, özel nesneleriniz, fonksiyonlarınız, ön bileşenleriniz, rolleriniz, yetenekleriniz ve daha fazlası için `src/` altında varlık tanım dosyaları ekler.
## Kimlik Doğrulama
`yarn twenty auth:login` komutunu ilk kez çalıştırdığınızda, sizden şunlar istenir:
* API URLsi (varsayılan: http://localhost:3000 veya mevcut çalışma alanı profiliniz)
* API anahtarı
Kimlik bilgileriniz kullanıcı başına `~/.twenty/config.json` içinde saklanır. Birden fazla profili yönetebilir ve aralarında geçiş yapabilirsiniz.
### Çalışma alanlarını yönetme
Bir **uzak**, uygulamanızın bağlandığı Twenty sunucusudur. Kurulum sırasında iskelet oluşturucu sizin için otomatik olarak bir tane oluşturur. Dilediğiniz zaman daha fazla uzak ekleyebilir veya aralarında geçiş yapabilirsiniz.
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
`yarn twenty auth:switch` ile çalışma alanlarını değiştirdikten sonra, sonraki tüm komutlar varsayılan olarak o çalışma alanını kullanacaktır. Yine de bunu geçici olarak `--workspace <name>` ile geçersiz kılabilirsiniz.
Kimlik bilgileriniz `~/.twenty/config.json` içinde saklanır.
## Yerel geliştirme sunucusu (`yarn twenty server`)
CLI, Docker'da çalışan yerel bir Twenty sunucusunu yönetebilir. Bu, `create-twenty-app` ile bir uygulamanın iskeletini oluşturduğunuzda otomatik olarak başlatılan sunucunun aynısıdır; ancak bunu el ile de yönetebilirsiniz.
### Sunucuyu Başlatma
```bash filename="Terminal"
yarn twenty server start
```
Bu, `twentycrm/twenty-app-dev:latest` Docker imajını (zaten mevcut değilse) çeker, `twenty-app-dev` adlı bir konteyner oluşturur ve **2020** portunda başlatır. CLI, dönmeden önce sunucunun sağlık kontrolünü geçmesini bekler.
Yeniden başlatmalar arasında verileri kalıcı kılmak için iki Docker birimi oluşturulur:
* `twenty-app-dev-data` — PostgreSQL veritabanı
* `twenty-app-dev-storage` — dosya depolama
2020 portu zaten kullanımda ise farklı bir portta başlatabilirsiniz:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI, seçilen porta uyacak şekilde konteynerin dahili `NODE_PORT` ve `SERVER_URL` ayarlarını otomatik olarak yapılandırır; böylece mantık işlevleri, OAuth ve diğer tüm dahili ağ işlemleri doğru şekilde çalışır.
Başlatıldığında, sunucu CLI yapılandırmanızda `local` uzak olarak otomatik olarak kaydedilir.
### Sunucu durumunu kontrol etme
```bash filename="Terminal"
yarn twenty server status
```
Sunucunun çalışıp çalışmadığını, URL'sini ve varsayılan oturum açma kimlik bilgilerini (`tim@apple.dev` / `tim@apple.dev`) gösterir.
### Sunucu günlüklerini görüntüleme
```bash filename="Terminal"
yarn twenty server logs
```
Konteyner günlüklerini akış olarak iletir. Kaç satırın gösterileceğini denetlemek için `--lines` kullanın:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Sunucuyu durdurma
```bash filename="Terminal"
yarn twenty server stop
```
Konteyneri durdurur. Verileriniz Docker birimlerinde korunur — bir sonraki `start`, kaldığınız yerden devam eder.
### Sunucuyu sıfırlama
```bash filename="Terminal"
yarn twenty server reset
```
Konteyneri kaldırır ve her iki Docker birimini de silerek tüm verileri temizler. Sonraki `start`, yeni bir örnek oluşturur.
<Note>
Sunucunun çalışması için **Docker**'ın çalışıyor olması gerekir. "Docker not running" hatası görürseniz Docker Desktop'ın (veya Docker daemon'ının) başlatıldığından emin olun.
</Note>
### Komut başvurusu
| Komut | Açıklama |
| -------------------------------------- | ------------------------------------------------------ |
| `yarn twenty server start` | Yerel sunucuyu başlatır (gerekirse imajı çeker) |
| `yarn twenty server start --port 3030` | Özel bir portta başlatır |
| `yarn twenty server stop` | Sunucuyu durdurur (verileri korur) |
| `yarn twenty server status` | Sunucu durumunu, URL'yi ve kimlik bilgilerini gösterir |
| `yarn twenty server logs` | Sunucu günlüklerini akış olarak iletir |
| `yarn twenty server logs --lines 100` | Son 100 günlük satırını gösterir |
| `yarn twenty server reset` | Tüm verileri siler ve sıfırdan başlatır |
## GitHub Actions ile CI
İskelet oluşturucu, `.github/workflows/ci.yml` konumunda kullanıma hazır bir GitHub Actions iş akışı üretir. Entegrasyon testlerinizi `main` dalına yapılan her itmede ve çekme isteklerinde otomatik olarak çalıştırır.
İş akışı:
1. Kodunuzu depodan çıkarır
2. `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` eylemini kullanarak geçici bir Twenty sunucusu başlatır
3. `yarn install --immutable` ile bağımlılıkları kurar
4. Eylem çıktılarından enjekte edilen `TWENTY_API_URL` ve `TWENTY_API_KEY` ile `yarn test` çalıştırır
```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 }}
```
Herhangi bir gizli değişken yapılandırmanız gerekmez — `spawn-twenty-docker-image` eylemi, koşucu içinde doğrudan geçici bir Twenty sunucusu başlatır ve bağlantı ayrıntılarını çıktı olarak verir. `GITHUB_TOKEN` gizli değişkeni GitHub tarafından otomatik olarak sağlanır.
`latest` yerine belirli bir Twenty sürümünü sabitlemek için iş akışının başındaki `TWENTY_VERSION` ortam değişkenini değiştirin.
## Manuel kurulum (iskelet oluşturucu olmadan)
En iyi başlangıç deneyimi için `create-twenty-app` kullanmanızı önersek de, bir projeyi manuel olarak da kurabilirsiniz. CLI'yi global olarak kurmayın. Bunun yerine `twenty-sdk`'yi yerel bir bağımlılık olarak ekleyin ve package.json içinde tek bir betik tanımlayın:
`create-twenty-app` kullanmak yerine her şeyi kendiniz ayarlamayı tercih ederseniz, bunu iki adımda yapabilirsiniz.
**1. `twenty-sdk` ve `twenty-client-sdk` paketlerini bağımlılık olarak ekleyin:**
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
Ardından bir `twenty` betiği ekleyin:
**2. `package.json` dosyanıza bir `twenty` betiği ekleyin:**
```json filename="package.json"
{
@@ -210,25 +393,19 @@ Ardından bir `twenty` betiği ekleyin:
}
```
Artık tüm komutları `yarn twenty <command>` üzerinden çalıştırabilirsiniz; örn. `yarn twenty dev`, `yarn twenty help` vb.
Artık `yarn twenty dev`, `yarn twenty help` ve diğer tüm komutları çalıştırabilirsiniz.
## Yerel bir Twenty örneği nasıl kullanılır?
Zaten yerel olarak bir Twenty örneği çalıştırıyorsanız (örneğin `npx nx start twenty-server` ile), Docker kullanmak yerine ona bağlanabilirsiniz:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
`twenty-sdk`'yi global olarak kurmayın. Her projenin kendi sürümünü sabitleyebilmesi için onu her zaman yerel bir proje bağımlılığı olarak kullanın.
</Note>
## Sorun Giderme
* Kimlik doğrulama hataları: `yarn twenty auth:login` çalıştırın ve API anahtarınızın gerekli izinlere sahip olduğundan emin olun.
* Sunucuya bağlanılamıyor: API URLsini ve Twenty sunucusunun erişilebilir olduğunu doğrulayın.
* Türler veya istemci eksik/eski: `yarn twenty dev` komutunu yeniden çalıştırın — tiplendirilmiş istemciyi otomatik olarak oluşturur.
* Geliştirme modu eşitlenmiyor: `yarn twenty dev`'in çalıştığından ve değişikliklerin ortamınız tarafından yok sayılmadığından emin olun.
Sorunlarla karşılaşırsanız:
Discord Yardım Kanalı: https://discord.com/channels/1130383047699738754/1130386664812982322
* Yerel bir örnekle scaffolder'ı başlatmadan önce **Docker'ın çalıştığından** emin olun.
* **Node.js 24+** kullandığınızdan emin olun (kontrol etmek için `node -v`).
* Yarn 4'ün kullanılabilir olması için **Corepack'in etkinleştirildiğinden** emin olun (`corepack enable`).
* Bağımlılıklar bozuk görünüyorsa `node_modules` dizinini silip `yarn install` komutunu yeniden çalıştırmayı deneyin.
Hâlâ takıldınız mı? Yardım için [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) üzerinden yardım isteyin.
@@ -4,34 +4,76 @@ description: Twenty uygulamanızı pazaryerine sunun ya da dahili olarak dağıt
---
<Warning>
Uygulamalar şu anda alfa testinde. Özellik işlevsel ancak hâlâ gelişmekte.
Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte.
</Warning>
## Genel Bakış
Uygulamanız [yerelde derlenip test edildikten sonra](/l/tr/developers/extend/apps/building), dağıtım için iki yolunuz vardır:
* **npmye yayımlama** — uygulamanızı Twenty pazaryerinde listeleyin; böylece herhangi bir çalışma alanı keşfedip yükleyebilir.
* **Bir tar arşivi dağıtın** — uygulamanızı dahili veya özel kullanım için doğrudan belirli bir Twenty sunucusuna yükleyin.
* **npmye yayımlama** — uygulamanızı Twenty pazaryerinde listeleyin; böylece herhangi bir çalışma alanı keşfedip yükleyebilir.
Her iki yol da aynı **build** adımından başlar.
## Uygulamanızı derleme
`build` komutu TypeScript kaynaklarınızı derler, mantık işlevlerini ve ön uç bileşenlerini transpile eder ve uygulamanızın içeriğini açıklayan bir `manifest.json` üretir:
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
Çıktı `.twenty/output/` dizinine yazılır. Bu dizin, dağıtım için gereken her şeyi içerir: derlenmiş kod, varlıklar, manifest ve `package.json` dosyanızın bir kopyası.
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
Ayrıca bir `.tgz` tarball oluşturmak için (deploy komutu tarafından dahili olarak kullanılır veya el ile dağıtım için):
## Sunucuya dağıtım (tarball)
Genel kullanıma açık olmasını istemediğiniz uygulamalar — sahipli araçlar, yalnızca kurumsal entegrasyonlar veya deneysel derlemeler — için bir tarball’ı doğrudan bir Twenty sunucusuna dağıtabilirsiniz.
### Ön Gereksinimler
Dağıtmadan önce, hedef sunucuyu işaret eden yapılandırılmış bir remotea ihtiyacınız vardır. Remotelar sunucu URLsini ve kimlik doğrulama bilgilerini yerel olarak `~/.twenty/config.json` içinde saklar.
Bir remote ekleyin:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Dağıtım
Uygulamanızı tek adımda derleyip sunucuya yükleyin:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Dağıtılmış bir uygulamayı paylaşma
Tarball uygulamaları genel pazar yerinde listelenmez; bu nedenle aynı sunucudaki diğer çalışma alanları gezinerek onları keşfedemez. Dağıtılmış bir uygulamayı paylaşmak için:
1. **Ayarlar > Uygulamalar > Kayıtlar** bölümüne gidin ve uygulamanızı açın
2. **Dağıtım** sekmesinde, **Paylaşım bağlantısını kopyala**ya tıklayın
3. Bu bağlantıyı diğer çalışma alanlarındaki kullanıcılarla paylaşın — onları doğrudan uygulamanın yükleme sayfasına götürür
Paylaşım bağlantısı, sunucunun temel URLsini (herhangi bir çalışma alanı alt alan adı olmadan) kullanır; böylece sunucudaki herhangi bir çalışma alanı için çalışır.
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### Sürüm yönetimi
Bir güncelleme yayımlamak için:
1. `package.json` içindeki `version` alanını artırın
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Uygulamayı kurmuş olan çalışma alanları, ayarlarında mevcut güncellemeyi görecektir
{/* TODO: add screenshot of the Upgrade button */}
## npmye yayımlama
npmye yayımlamak, uygulamanızın Twenty pazaryerinde keşfedilebilir olmasını sağlar. Herhangi bir Twenty çalışma alanı, pazaryeri uygulamalarına doğrudan arayüzden göz atabilir, yükleyebilir ve güncelleyebilir.
@@ -39,41 +81,42 @@ npmye yayımlamak, uygulamanızın Twenty pazaryerinde keşfedilebilir olmas
### Gereksinimler
* Bir [npm](https://www.npmjs.com) hesabı
* `twenty-app` anahtar kelimesi `package.json` dosyanızdaki `keywords` dizisinde **mutlaka** listelenmelidir
### Gerekli anahtar kelimeyi ekleme
Twenty pazar yeri, npm kayıt defterinde `twenty-app` anahtar kelimesine sahip paketleri arayarak uygulamaları keşfeder. Bunu `package.json` dosyanıza ekleyin:
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
Pazar yeri, npm kayıt defterinde `keywords:twenty-app` araması yapar. Bu anahtar kelime olmadan, adında `twenty-app-` öneki bulunsa bile paketiniz pazar yerinde görünmez.
</Note>
### Pazaryeri meta verileri
### Adımlar
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
1. **Uygulamanızı derleyin:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **npmye yayımlayın:**
See the [defineApplication accordion](/l/tr/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publish
```bash filename="Terminal"
yarn twenty publish
```
Bu, `.twenty/output/` dizininden `npm publish` komutunu çalıştırır.
Belirli bir dist-tag altında yayımlamak için (ör. `beta` veya `next`):
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### Pazar yerinde keşif nasıl çalışır
Twenty sunucusu pazar yeri kataloğunu npm kayıt defterinden **her saat** eşitler:
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
1. `keywords:twenty-app` anahtar kelimesine sahip tüm npm paketlerini arar
2. Her paket için `manifest.json` dosyasını npm CDNinden getirir
3. Uygulamanın meta verileri (ad, açıklama, yazar, logo, ekran görüntüleri, kategori) manifest dosyasından çıkarılır ve pazar yerinde görüntülenir
Yayımladıktan sonra, uygulamanızın pazar yerinde görünmesi bir saate kadar sürebilir. Bir sonraki saatlik çalışmayı beklemek yerine eşitlemeyi hemen tetiklemek için:
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Belirli bir remoteu hedeflemek için:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
Pazar yerinde gösterilen meta veriler, uygulamanızın kaynak kodundaki `defineApplication()` çağrısından gelir — `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` ve `termsUrl` gibi alanlar.
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
Uygulamanız `defineApplication()` içinde bir `aboutDescription` tanımlamıyorsa, pazaryeri, hakkında sayfasının içeriği olarak paketinizin npm'deki `README.md` dosyasını otomatik olarak kullanır. Bu, hem npm hem de Twenty pazaryeri için tek bir README dosyası kullanabileceğiniz anlamına gelir. Pazaryerinde farklı bir açıklama istiyorsanız, `aboutDescription` değerini açıkça ayarlayın.
@@ -108,7 +143,7 @@ Uygulamanız `defineApplication()` içinde bir `aboutDescription` tanımlamıyor
### CI üzerinden yayımlama
İskelet proje, her sürümde yayımlayan bir GitHub Actions iş akışını içerir:
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Diğer CI sistemleri (GitLab CI, CircleCI, vb.) için de aynı üç komut geçerlidir: `yarn install`, `yarn twenty build` ve ardından `.twenty/output` dizininden `npm publish`.
<Tip>
<Note>
**npm provenance** isteğe bağlıdır ancak önerilir. `--provenance` ile yayımlamak, npm listenize bir güven rozeti ekler ve kullanıcıların paketin herkese açık bir CI ardışık düzenindeki belirli bir committen oluşturulduğunu doğrulamasını sağlar. Kurulum talimatları için [npm provenance belgelerine](https://docs.npmjs.com/generating-provenance-statements) bakın.
</Tip>
## Sunucuya dağıtım (tarball)
Genel kullanıma açık olmasını istemediğiniz uygulamalar — sahipli araçlar, yalnızca kurumsal entegrasyonlar veya deneysel derlemeler — için bir tarball’ı doğrudan bir Twenty sunucusuna dağıtabilirsiniz.
### Ön Gereksinimler
Dağıtmadan önce, hedef sunucuyu işaret eden yapılandırılmış bir remotea ihtiyacınız vardır. Remotelar sunucu URLsini ve kimlik doğrulama bilgilerini yerel olarak `~/.twenty/config.json` içinde saklar.
Bir remote ekleyin:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
Yerel bir geliştirme sunucusu için:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
Etkileşimli olmayan ortamlar için bir API anahtarıyla da kimlik doğrulayabilirsiniz:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
Remotelarınızı yönetin:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### Dağıtım
Uygulamanızı tek adımda derleyip sunucuya yükleyin:
```bash filename="Terminal"
yarn twenty deploy
```
Bu, uygulamayı `--tarball` ile derler ve ardından tarball’ı varsayılan remotea GraphQL çok parçalı yükleme ile yükler.
Belirli bir remotea dağıtmak için:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### Dağıtılmış bir uygulamayı paylaşma
Tarball uygulamaları genel pazar yerinde listelenmez; bu nedenle aynı sunucudaki diğer çalışma alanları gezinerek onları keşfedemez. Dağıtılmış bir uygulamayı paylaşmak için:
1. **Ayarlar > Uygulamalar > Kayıtlar** bölümüne gidin ve uygulamanızı açın
2. **Dağıtım** sekmesinde, **Paylaşım bağlantısını kopyala**ya tıklayın
3. Bu bağlantıyı diğer çalışma alanlarındaki kullanıcılarla paylaşın — onları doğrudan uygulamanın yükleme sayfasına götürür
Paylaşım bağlantısı, sunucunun temel URLsini (herhangi bir çalışma alanı alt alan adı olmadan) kullanır; böylece sunucudaki herhangi bir çalışma alanı için çalışır.
### Sürüm yönetimi
Bir güncelleme yayımlamak için:
1. `package.json` içindeki `version` alanını artırın
2. `yarn twenty deploy` (veya `yarn twenty deploy -r production`) komutunu çalıştırın
3. Uygulamayı kurmuş olan çalışma alanları, ayarlarında mevcut güncellemeyi görecektir
</Note>
## Uygulamaları yükleme
Bir uygulama yayımlandığında (npm) veya dağıtıldığında (tarball), çalışma alanları onu kullanıcı arayüzü (UI) aracılığıyla yükler:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
```bash filename="Terminal"
yarn twenty install
```
Veya Twenty kullanıcı arayüzündeki **Ayarlar > Uygulamalar** sayfasından; burada hem pazar yerindeki hem de tarball ile dağıtılmış uygulamalar görüntülenip yüklenebilir.
## Uygulama dağıtım kategorileri
Twenty, uygulamaları nasıl dağıtıldıklarına göre üç kategoriye ayırır:
| Kategori | Nasıl Çalışır | Pazaryerinde görünür mü? |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| **Geliştirme** | `yarn twenty dev` ile çalışan yerel geliştirme modu uygulamaları. Derleme ve test için kullanılır. | Hayır |
| **Yayımlanmış (npm)** | `twenty-app` anahtar kelimesiyle npmye yayımlanan uygulamalar. Herhangi bir çalışma alanının yükleyebilmesi için pazaryerinde listelenir. | Evet |
| **Dahili (tarball)** | Bir tarball aracılığıyla belirli bir sunucuya dağıtılan uygulamalar. Yalnızca o sunucudaki çalışma alanları için bir paylaşım bağlantısı aracılığıyla kullanılabilir. | Hayır |
<Tip>
Uygulamanızı geliştirirken **Geliştirme** modunda başlayın. Hazır olduğunda, geniş dağıtım için **Yayımlanmış** (npm) ya da özel dağıtım için **Dahili** (tarball) seçeneğini tercih edin.
</Tip>
## CLI başvurusu
| Komut | Açıklama | Temel bayraklar |
| --------------------------- | --------------------------------------------------- | -------------------------------------------------- |
| `yarn twenty build` | Uygulamayı derleyin ve manifest oluşturun | `--tarball` — ayrıca bir `.tgz` paket oluşturur |
| `yarn twenty publish` | Derleyin ve npmye yayımlayın | `--tag <tag>` — npm dist-tag (örn. `beta`, `next`) |
| `yarn twenty deploy` | Derleyin ve tarball’ı bir sunucuya yükleyin | `-r, --remote <name>` — hedef remote |
| `yarn twenty catalog-sync` | Sunucuda pazar yeri katalog eşitlemesini tetikleyin | `-r, --remote <name>` — hedef remote |
| `yarn twenty install` | Dağıtılmış bir uygulamayı bir çalışma alanına kurun | `-r, --remote <name>` — hedef remote |
| `yarn twenty dev` | Yerel değişiklikleri izleyin ve eşitleyin | Varsayılan remoteu kullanır |
| `yarn twenty remote add` | Bir sunucu bağlantısı ekleyin | `--url`, `--token`, `--as`, `--local`, `--port` |
| `yarn twenty remote list` | Yapılandırılmış remoteları listeleyin | — |
| `yarn twenty remote switch` | Varsayılan remoteu ayarlayın | — |
| `yarn twenty remote status` | Bağlantı durumunu gösterin | — |
| `yarn twenty remote remove` | Bir remoteu kaldırın | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### Farklı bir nesneye eklemek için
`companyId` değerini uygun alanla değiştirin:
`targetCompanyId` değerini uygun alanla değiştirin:
| Nesne | Alan Adı |
| ---------- | -------------------- |
| Şirket | `companyId` |
| Kişi | `personId` |
| Fırsat | `opportunityId` |
| Özel Nesne | `yourCustomObjectId` |
| Nesne | Alan Adı |
| ---------- | -------------------------- |
| Şirket | `targetCompanyId` |
| Kişi | `targetPersonId` |
| Fırsat | `targetOpportunityId` |
| Özel Nesne | `targetYourCustomObjectId` |
Ek oluşturma mutasyonunda hem işlev parametresini hem de `variables.data` nesnesini güncelleyin.
File diff suppressed because it is too large Load Diff
@@ -4,73 +4,142 @@ description: 几分钟内创建你的第一个 Twenty 应用。
---
<Warning>
应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。
应用目前处于 Alpha 阶段。 该功能可用,但仍在演进中。
</Warning>
应用可通过自定义对象、字段、逻辑函数、AI 技能和 UI 组件来扩展 Twenty——全部以代码进行管理。
**你可以构建的内容:**
* 自定义对象、字段、视图和导航项,以塑造你的数据模型
* 由 HTTP 路由、cron 调度或数据库事件触发的逻辑函数
* 在 Twenty 的 UI 中直接渲染的前端组件
* 用于扩展 Twenty 的 AI 代理的技能
* 将同一个应用部署到多个工作空间
## 先决条件
* Node.js 24+
* Yarn 4
* Docker (或正在运行的本地 Twenty 实例)
在开始之前,请确保你的机器已安装以下内容:
## 开始使用
* **Node.js 24+** — [在此下载](https://nodejs.org/)
* **Yarn 4** — 通过 Corepack 随 Node.js 提供。 通过运行 `corepack enable` 启用它
* **Docker** — [在此下载](https://www.docker.com/products/docker-desktop/)。 运行本地 Twenty 实例所必需。 如果你已经有一个正在运行的 Twenty 服务器,则不需要。
使用官方脚手架创建一个新应用,然后进行身份验证并开始开发:
## 步骤 1:为你的应用创建脚手架
打开终端并运行:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
> 使用 `--minimal` 选项生成最简安装脚手架
系统会提示你为应用输入名称和描述。 按下 **Enter** 接受默认值。
从这里您可以:
这会创建一个名为 `my-twenty-app` 的新文件夹,其中包含你所需的一切。
<Note>
脚手架工具支持以下标志:
* `--minimal` — 仅创建必要文件,不包含示例(默认)
* `--exhaustive` — 创建所有示例实体
* `--name <name>` — 设置应用名称(跳过提示)
* `--display-name <displayName>` — 设置显示名称(跳过提示)
* `--description <description>` — 设置描述(跳过提示)
* `--skip-local-instance` — 跳过本地服务器设置提示
</Note>
## 步骤 2:设置本地 Twenty 实例
脚手架工具会询问:
> **是否要设置本地 Twenty 实例?**
* **输入 `yes`**(推荐)— 这将拉取 `twenty-app-dev` Docker 镜像,并在端口 `2020` 上启动本地 Twenty 服务器。 继续之前,请确保 Docker 正在运行。
* **输入 `no`** — 如果你已经有一个在本地运行的 Twenty 服务器,请选择此项。
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="是否启动本地实例?" />
</div>
## 步骤 3:登录你的工作区
接下来,将打开一个浏览器窗口,显示 Twenty 登录页面。 使用预置的演示账户登录:
* **邮箱:** `tim@apple.dev`
* **密码:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty 登录界面" />
</div>
## 步骤 4:授权该应用
登录后,你会看到一个授权界面。 这使你的应用可以与工作区交互。
点击 **授权** 继续。
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI 授权界面" />
</div>
授权后,你的终端会确认一切已就绪。
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="应用脚手架创建成功" />
</div>
## 步骤 5:开始开发
进入你的新应用文件夹并启动开发服务器:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty add
# Watch your application's function logs
yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
yarn twenty uninstall
# Display commands' help
yarn twenty help
cd my-twenty-app
yarn twenty dev
```
另请参阅:[create-twenty-app](https://www.npmjs.com/package/create-twenty-app) 和 [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) 的 CLI 参考页面
它会监听你的源文件,每次更改都会重建,并自动将你的应用同步到本地 Twenty 服务器。 你应当在终端中看到一个实时状态面板
## 项目结构(脚手架生成)
如需更详细的输出(构建日志、同步请求、错误跟踪),请使用 `--verbose` 标志:
当你运行 `npx create-twenty-app@latest my-twenty-app` 时,脚手架将:
```bash filename="Terminal"
yarn twenty dev --verbose
```
* 将一个最小的基础应用复制到 `my-twenty-app/` 中
* 添加本地 `twenty-sdk` 依赖和 Yarn 4 配置
* 创建与 `twenty` CLI 关联的配置文件和脚本
* 生成核心文件(应用配置、默认函数角色、安装前/安装后函数),并基于脚手架模式生成示例文件
<Warning>
开发模式仅适用于以开发模式运行的 Twenty 实例(`NODE_ENV=development`)。 生产实例会拒绝开发同步请求。 使用 `yarn twenty deploy` 部署到生产服务器——详见[发布应用](/l/zh/developers/extend/apps/publishing)。
</Warning>
使用默认 `--exhaustive` 模式新搭建的应用如下所示:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="开发模式终端输出" />
</div>
## 步骤 6:在 Twenty 中查看你的应用
在浏览器中打开 [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer)。 前往 **设置 > 应用**,并选择 **开发者** 选项卡。 你应当在 **你的应用** 下看到你的应用:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="“你的应用”列表显示 My twenty app" />
</div>
点击 **My twenty app** 打开其 **应用注册**。 注册项是一个服务器级记录,用于描述你的应用——其名称、唯一标识符、OAuth 凭据以及来源(本地、npm 或 tarball)。 它位于服务器上,而不在任何特定工作区内。 当你将应用安装到工作区时,Twenty 会创建一个工作区范围的 **应用**,指向该注册项。 同一服务器上的多个工作区可以安装同一个注册项。
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="应用注册详情" />
</div>
点击 **查看已安装的应用** 以查看已安装的应用。 **关于** 选项卡显示当前版本和管理选项:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="已安装的应用 — “关于”选项卡" />
</div>
切换到 **内容** 选项卡,以查看你的应用提供的全部内容——对象、字段、逻辑函数和智能体:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="已安装的应用 — “内容”选项卡" />
</div>
一切就绪! 编辑 `src/` 中的任意文件,更改会被自动检测到。
前往[构建应用](/l/zh/developers/extend/apps/building),查看关于创建对象、逻辑函数、前端组件、技能等的详细指南。
---
## 项目结构
脚手架工具会生成以下文件结构(以 `--exhaustive` 模式展示,其中包含每种实体类型的示例):
```text filename="my-twenty-app/"
my-twenty-app/
@@ -83,124 +152,238 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
public/ # Public assets folder (images, fonts, etc.)
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
```
使用 `--minimal` 时,只会创建核心文件`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`
默认情况下(`--minimal`),仅创建核心文件`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`。 使用 `--exhaustive` 可包含上面展示的所有示例文件
总体来说:
### 关键文件
* **package.json**:声明应用名称、版本、引擎(Node 24+、Yarn 4),并添加 `twenty-sdk` 以及一个 `twenty` 脚本,该脚本会委托给本地的 `twenty` CLI。 运行 `yarn twenty help` 以列出所有可用命令。
* **.gitignore**:忽略常见产物,如 `node_modules`、`.yarn`、`.twenty/`、`dist/`、`build/`、覆盖率文件夹、日志文件以及 `.env*` 文件。
* **yarn.lock**、**.yarnrc.yml**、**.yarn/**:锁定并配置项目使用的 Yarn 4 工具链。
* **.nvmrc**:固定项目期望的 Node.js 版本。
* **.oxlintrc.json** 和 **tsconfig.json**:为应用的 TypeScript 源码提供 Lint 与 TypeScript 配置。
* **README.md**:应用根目录中的简短 README,包含基本说明。
* **public/**: 一个用于存储公共资源(图像、字体、静态文件)的文件夹,这些资源将随你的应用程序一起提供。 放置在此处的文件会在同步期间上传,并可在运行时访问。
* **src/**:你以代码形式定义应用的主要位置
| 文件 / 文件夹 | 目的 |
| ---------------------------- | ------------------------------------------------------------------ |
| `package.json` | 声明应用的名称、版本和依赖。 包含一个 `twenty` 脚本,因此你可以运行 `yarn twenty help` 查看所有命令。 |
| `src/application-config.ts` | **必需。** 应用的主配置文件。 |
| `src/roles/` | 定义角色,用于控制逻辑函数的访问权限。 |
| `src/logic-functions/` | 由路由、cron 调度或数据库事件触发的服务端函数。 |
| `src/front-components/` | 在 Twenty 的 UI 中渲染的 React 组件。 |
| `src/objects/` | 用于扩展数据模型的自定义对象定义。 |
| `src/fields/` | 添加到现有对象的自定义字段。 |
| `src/views/` | 已保存的视图配置。 |
| `src/navigation-menu-items/` | 侧边栏导航中的自定义链接。 |
| `src/skills/` | 用于扩展 Twenty 的 AI 代理的技能. |
| `src/agents/` | 具有自定义提示词的 AI 智能体。 |
| `src/page-layouts/` | 记录视图的自定义页面布局。 |
| `src/__tests__/` | 集成测试(设置 + 示例测试)。 |
| `public/` | 随应用一起提供的静态资源(图像、字体)。 |
### 实体检测
## 管理远程
该 SDK 通过在你的 TypeScript 文件中解析 **`export default define<Entity>({...})`** 调用来检测实体。 每种实体类型都有一个从 `twenty-sdk` 导出的对应辅助函数:
| 辅助函数 | 实体类型 |
| -------------------------------- | ---------------- |
| `defineObject` | 自定义对象定义 |
| `defineLogicFunction` | 逻辑函数定义 |
| `definePreInstallLogicFunction` | 安装前逻辑函数(在安装之前运行) |
| `definePostInstallLogicFunction` | 安装后逻辑函数(在安装之后运行) |
| `defineFrontComponent` | 前端组件定义 |
| `defineRole` | 角色定义 |
| `defineField` | 现有对象的字段扩展 |
| `defineView` | 已保存的视图定义 |
| `defineNavigationMenuItem` | 导航菜单项定义 |
| `defineSkill` | AI 代理技能定义 |
<Note>
**文件命名是灵活的。** 实体检测基于 AST — SDK 会扫描你的源文件以查找 `export default define<Entity>({...})` 模式。 你可以按照自己的喜好组织文件和文件夹。 按实体类型分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。
</Note>
已检测实体的示例:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
后续命令将添加更多文件和文件夹:
* `yarn twenty dev` 会自动生成类型化的 `CoreApiClient`(通过 `/graphql` 获取工作区数据),并写入 `node_modules/twenty-client-sdk/`。 `MetadataApiClient`(通过 `/metadata` 处理工作区配置和文件上传)为预构建版本,可立即使用。 分别从 `twenty-client-sdk/core` 和 `twenty-client-sdk/metadata` 导入它们。
* `yarn twenty add` 会在 `src/` 下为你的自定义对象、函数、前端组件、角色、技能等添加实体定义文件。
## 身份验证
首次运行 `yarn twenty auth:login` 时,你将被提示输入:
* API URL(默认为 http://localhost:3000 或你当前的工作空间配置)
* API 密钥
你的凭据按用户存储在 `~/.twenty/config.json` 中。 你可以维护多个配置文件并在它们之间切换。
### 管理工作空间
“远程”是指你的应用连接到的 Twenty 服务器。 在设置期间,脚手架工具会为你自动创建一个。 你可以随时添加更多远程或在它们之间切换。
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# List all configured workspaces
yarn twenty auth:list
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# Switch the default workspace (interactive)
yarn twenty auth:switch
# List all configured remotes
yarn twenty remote list
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
# Switch the active remote
yarn twenty remote switch <name>
```
使用 `yarn twenty auth:switch` 切换工作空间后,后续所有命令将默认使用该工作空间。 你仍可通过 `--workspace <name>` 临时覆盖
你的凭据存储在 `~/.twenty/config.json` 中
## 本地开发服务器(`yarn twenty server`
CLI 可以管理在 Docker 中运行的本地 Twenty 服务器。 这与使用 `create-twenty-app` 搭建应用时自动启动的服务器相同,但你也可以手动管理它。
### 启动服务器
```bash filename="Terminal"
yarn twenty server start
```
这将拉取 `twentycrm/twenty-app-dev:latest` Docker 镜像(如果尚未存在),创建名为 `twenty-app-dev` 的容器,并在端口 **2020** 上启动它。 CLI 会等待服务器通过健康检查后再返回。
会创建两个 Docker 卷,以在重启之间持久化数据:
* `twenty-app-dev-data` — PostgreSQL 数据库
* `twenty-app-dev-storage` — 文件存储
如果端口 2020 已被占用,你可以在其他端口上启动:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI 会自动配置容器内部的 `NODE_PORT` 和 `SERVER_URL` 以匹配所选端口,从而使逻辑函数、OAuth 以及所有其他内部网络正常工作。
启动后,该服务器会在你的 CLI 配置中自动注册为 `local` 远程。
### 检查服务器状态
```bash filename="Terminal"
yarn twenty server status
```
显示服务器是否在运行、其 URL,以及默认登录凭据(`tim@apple.dev` / `tim@apple.dev`)。
### 查看服务器日志
```bash filename="Terminal"
yarn twenty server logs
```
持续输出容器日志。 使用 `--lines` 控制显示的最近日志行数:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### 停止服务器
```bash filename="Terminal"
yarn twenty server stop
```
停止容器。 你的数据会保存在 Docker 卷中——下次 `start` 会从上次中断处继续。
### 重置服务器
```bash filename="Terminal"
yarn twenty server reset
```
移除容器并删除这两个 Docker 卷,清除所有数据。 下一次 `start` 会创建一个全新实例。
<Note>
服务器需要 Docker 处于运行状态。 如果看到 "Docker not running" 错误,请确保 Docker Desktop(或 Docker 守护进程)已启动。
</Note>
### 命令参考
| 命令 | 描述 |
| -------------------------------------- | --------------- |
| `yarn twenty server start` | 启动本地服务器(按需拉取镜像) |
| `yarn twenty server start --port 3030` | 在自定义端口启动 |
| `yarn twenty server stop` | 停止服务器(保留数据) |
| `yarn twenty server status` | 显示服务器状态、URL 和凭据 |
| `yarn twenty server logs` | 流式输出服务器日志 |
| `yarn twenty server logs --lines 100` | 显示最近 100 行日志 |
| `yarn twenty server reset` | 删除所有数据并全新开始 |
## 使用 GitHub Actions 进行 CI
脚手架工具会在 `.github/workflows/ci.yml` 生成一个开箱即用的 GitHub Actions 工作流。 它会在每次向 `main` 推送以及拉取请求上自动运行你的集成测试。
工作流:
1. 检出你的代码
2. 使用 `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` 动作启动一个临时的 Twenty 服务器
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 会自动提供 `GITHUB_TOKEN` 机密。
若要固定为特定的 Twenty 版本而不是 `latest`,请在工作流顶部修改 `TWENTY_VERSION` 环境变量。
## 手动设置(不使用脚手架)
虽然我们建议使用 `create-twenty-app` 以获得最佳的上手体验,但你也可以手动设置项目。 不要全局安装 CLI。 相反,请将 `twenty-sdk` 添加为本地依赖,并在你的 package.json 中配置一个脚本:
如果你不想使用 `create-twenty-app`,而是自行完成设置,可以分两步进行。
\*\*1. 将 `twenty-sdk` 和 `twenty-client-sdk` 添加为依赖项:
```bash filename="Terminal"
yarn add -D twenty-sdk
yarn add twenty-sdk twenty-client-sdk
```
然后添加一个 `twenty` 脚本:
\*\*2. 在你的 `package.json` 中添加一个 `twenty` 脚本:
```json filename="package.json"
{
@@ -210,25 +393,19 @@ yarn add -D twenty-sdk
}
```
现在你可以通过 `yarn twenty <command>` 运行所有命令,例如 `yarn twenty dev`、`yarn twenty help`
现在你可以运行 `yarn twenty dev`、`yarn twenty help` 以及所有其他命令
## 如何使用本地 Twenty 实例
如果你已经在本地运行一个 Twenty 实例(例如通过 `npx nx start twenty-server`),你可以连接到它,而不是使用 Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
<Note>
不要全局安装 `twenty-sdk`。 始终将其作为本地项目依赖使用,以便每个项目都能固定其自己的版本。
</Note>
## 故障排除
* 身份验证错误:运行 `yarn twenty auth:login`,并确保你的 API 密钥具有所需权限。
* 无法连接到服务器:请验证 API URL,并确保 Twenty 服务器可达。
* 类型或客户端缺失/过期:重启 `yarn twenty dev` — 它会自动生成类型化客户端。
* 开发模式未同步:确保 `yarn twenty dev` 正在运行,并且你的环境不会忽略变更。
如果遇到问题:
Discord 帮助频道:https://discord.com/channels/1130383047699738754/1130386664812982322
* 在使用本地实例启动脚手架工具之前,请确保**Docker 已在运行**。
* 请确保使用 **Node.js 24+**(运行 `node -v` 进行检查)。
* 请确保**已启用 Corepack**`corepack enable`),以便可使用 Yarn 4。
* 如果依赖似乎有问题,尝试删除 `node_modules` 并重新运行 `yarn install`。
仍然遇到问题? 在 [Twenty 的 Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) 上寻求帮助。
@@ -4,34 +4,76 @@ description: 将你的 Twenty 应用分发到应用市场,或进行内部部
---
<Warning>
应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。
应用目前处于 Alpha 阶段。 该功能可用,但仍在演进中。
</Warning>
## 概览
一旦你的应用已[在本地构建并完成测试](/l/zh/developers/extend/apps/building),你可以通过两种方式进行分发:
* **发布到 npm** — 将你的应用在 Twenty 应用市场上架,供任何工作区发现并安装。
* **部署 tar 包** — 直接将你的应用上传到特定的 Twenty 服务器,以供内部或私有使用。
* **发布到 npm** — 将你的应用在 Twenty 应用市场上架,供任何工作区发现并安装。
两种路径都从同一个**构建**步骤开始。
## 构建你的应用
`build` 命令会编译你的 TypeScript 源码,转译逻辑函数和前端组件,并生成一个描述你应用内容的 `manifest.json`
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
输出将写入 `.twenty/output/`。 此目录包含分发所需的一切:已编译的代码、资源、清单,以及你的 `package.json` 副本。
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
要同时创建一个 `.tgz` 压缩包(由部署命令在内部使用,或用于手动分发):
## 部署到服务器(tar 包)
对于你不希望公开的应用(专有工具、仅供企业使用的集成或实验性构建),你可以将 tar 包直接部署到某台 Twenty 服务器。
### 先决条件
在部署之前,你需要配置一个指向目标服务器的远程。 远程会将服务器 URL 和身份验证凭据本地存储在 `~/.twenty/config.json` 中。
添加远程:
```bash filename="Terminal"
yarn twenty build --tarball
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### 部署
一步构建并将你的应用上传到服务器:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### 共享已部署的应用
通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 要共享已部署的应用:
1. 前往 **Settings > Applications > Registrations** 并打开你的应用
2. 在 **Distribution** 选项卡中,点击 **Copy share link**
3. 将此链接分享给其他工作区的用户 — 它会将他们直接带到该应用的安装页面
该分享链接使用服务器的基础 URL(不包含任何工作区子域),因此适用于该服务器上的任意工作区。
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### 版本管理
要发布更新:
1. 更新 `package.json` 中的 `version` 字段
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. 已安装该应用的工作区会在其设置中看到可用的升级
{/* TODO: add screenshot of the Upgrade button */}
## 发布到 npm
发布到 npm 可让你的应用在 Twenty 应用市场中被发现。 任何 Twenty 工作区都可以直接通过 UI 浏览、安装和升级应用市场中的应用。
@@ -39,41 +81,42 @@ yarn twenty build --tarball
### 要求
* 一个 [npm](https://www.npmjs.com) 账户
* 在你的 `package.json` `keywords` 数组中**必须**包含 `twenty-app` 关键字
### 添加所需关键字
Twenty 市场通过在 npm 注册表中搜索带有 `twenty-app` 关键字的包来发现应用。 将其添加到你的 `package.json`
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"],
...
"keywords": ["twenty-app"]
}
```
<Note>
该市场会在 npm 注册表中搜索 `keywords:twenty-app`。 没有此关键字,即使包名带有 `twenty-app-` 前缀,你的包也不会出现在市场中。
</Note>
### 应用市场元数据
### 步骤
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
1. **构建你的应用:**
```bash filename="Terminal"
yarn twenty build
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
2. **发布到 npm**
See the [defineApplication accordion](/l/zh/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publish
```bash filename="Terminal"
yarn twenty publish
```
这会在 `.twenty/output/` 目录下运行 `npm publish`。
要在特定的 dist-tag(例如 `beta` 或 `next`)下发布:
```bash filename="Terminal"
@@ -82,25 +125,17 @@ yarn twenty publish --tag beta
### 应用市场的发现机制如何运作
Twenty 服务器会**每小时**从 npm 注册表同步其市场目录:
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
1. 它会搜索所有带有 `keywords:twenty-app` 关键字的 npm 包
2. 对于每个包,它会从 npm CDN 获取 `manifest.json`
3. 应用的元数据(名称、描述、作者、徽标、屏幕截图、类别)将从清单中提取,并显示在市场中
发布后,你的应用最多可能需要一小时才会出现在市场中。 要立即触发同步,而无需等待下一次每小时同步:
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
要指定特定的远程:
```bash filename="Terminal"
yarn twenty catalog-sync -r production
```
市场中显示的元数据来自你在应用源代码中调用的 `defineApplication()` —— 诸如 `displayName`、`description`、`author`、`category`、`logoUrl`、`screenshots`、`aboutDescription`、`websiteUrl` 和 `termsUrl` 等字段。
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
如果您的应用未在 `defineApplication()` 中定义 `aboutDescription`,市场将自动使用 npm 上您的软件包的 `README.md` 作为关于页面内容。 这意味着您可以为 npm 和 Twenty 市场维护同一个 README。 如果您希望在市场中使用不同的描述,请显式设置 `aboutDescription`。
@@ -108,7 +143,7 @@ yarn twenty catalog-sync -r production
### CI 发布
脚手架项目包含一个 GitHub Actions 工作流,会在每次发版时自动发布:
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -133,121 +168,24 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
对于其他 CI 系统(GitLab CI、CircleCI 等),同样适用以下三条命令:`yarn install`、`yarn twenty build`,然后在 `.twenty/output` 目录下执行 `npm publish`。
<Tip>
<Note>
**npm provenance** 可选,但建议启用。 使用 `--provenance` 发布会在你的 npm 列表中添加可信徽章,使用户可以验证该包是由公共 CI 流水线中的特定提交构建的。 有关设置说明,请参见 [npm provenance 文档](https://docs.npmjs.com/generating-provenance-statements)。
</Tip>
## 部署到服务器(tar 包)
对于你不希望公开的应用(专有工具、仅供企业使用的集成或实验性构建),你可以将 tar 包直接部署到某台 Twenty 服务器。
### 先决条件
在部署之前,你需要配置一个指向目标服务器的远程。 远程会将服务器 URL 和身份验证凭据本地存储在 `~/.twenty/config.json` 中。
添加远程:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --as production
```
对于本地开发服务器:
```bash filename="Terminal"
yarn twenty remote add --local --as local
```
对于非交互式环境,你也可以使用 API 密钥进行身份验证:
```bash filename="Terminal"
yarn twenty remote add --url https://your-twenty-server.com --token <api-key> --as production
```
管理你的远程:
```bash filename="Terminal"
yarn twenty remote list # List all configured remotes
yarn twenty remote switch prod # Set the default remote
yarn twenty remote status # Show active remote and auth status
yarn twenty remote remove old # Remove a remote
```
### 部署
一步构建并将你的应用上传到服务器:
```bash filename="Terminal"
yarn twenty deploy
```
这会使用 `--tarball` 构建应用,然后通过 GraphQL 多部分上传将该 tar 包上传到默认远程。
部署到特定远程:
```bash filename="Terminal"
yarn twenty deploy -r production
```
### 共享已部署的应用
通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 要共享已部署的应用:
1. 前往 **Settings > Applications > Registrations** 并打开你的应用
2. 在 **Distribution** 选项卡中,点击 **Copy share link**
3. 将此链接分享给其他工作区的用户 — 它会将他们直接带到该应用的安装页面
该分享链接使用服务器的基础 URL(不包含任何工作区子域),因此适用于该服务器上的任意工作区。
### 版本管理
要发布更新:
1. 更新 `package.json` 中的 `version` 字段
2. 运行 `yarn twenty deploy`(或 `yarn twenty deploy -r production`
3. 已安装该应用的工作区会在其设置中看到可用的升级
</Note>
## 安装应用
一旦应用已发布(npm)或已部署(tar 包),各工作区即可通过 UI 进行安装:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
```bash filename="Terminal"
yarn twenty install
```
或者在 Twenty UI 的 **Settings > Applications** 页面中浏览并安装来自市场或通过 tar 包部署的应用。
## 应用分发类别
Twenty 会根据分发方式将应用归为三类:
| 类别 | 工作原理 | 在应用市场中可见? |
| ------------- | -------------------------------------------------- | --------- |
| **开发** | 通过 `yarn twenty dev` 运行的本地开发模式应用。 用于构建和测试。 | 否 |
| **已发布(npm** | 发布到 npm 且包含 `twenty-app` 关键字的应用。 在应用市场上架,供任何工作区安装。 | 是 |
| **内部(tar 包)** | 通过 tar 包部署到特定服务器的应用。 仅通过分享链接对该服务器上的工作区可用。 | 否 |
<Tip>
在构建你的应用时,从**开发**模式开始。 准备就绪后,选择用于广泛分发的**已发布**(npm),或用于私有部署的**内部**(tar 包)。
</Tip>
## CLI 参考
| 命令 | 描述 | 关键选项 |
| --------------------------- | ---------------- | ------------------------------------------- |
| `yarn twenty build` | 编译应用并生成清单 | `--tarball` — 同时创建一个 `.tgz` 包 |
| `yarn twenty publish` | 构建并发布到 npm | `--tag <tag>` — npm 分发标签(例如 `beta`、`next` |
| `yarn twenty deploy` | 构建并将 tar 包上传到服务器 | `-r, --remote <name>` — 目标远程 |
| `yarn twenty catalog-sync` | 在服务器上触发市场目录同步 | `-r, --remote <name>` — 目标远程 |
| `yarn twenty install` | 在某个工作区安装已部署的应用 | `-r, --remote <name>` — 目标远程 |
| `yarn twenty dev` | 监听并同步本地更改 | 使用默认远程 |
| `yarn twenty remote add` | 添加服务器连接 | `--url``--token``--as``--local``--port` |
| `yarn twenty remote list` | 列出已配置的远程 | — |
| `yarn twenty remote switch` | 设置默认远程 | — |
| `yarn twenty remote status` | 显示连接状态 | — |
| `yarn twenty remote remove` | 移除远程 | — |
File diff suppressed because it is too large Load Diff
@@ -55,11 +55,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -79,11 +80,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -91,12 +121,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -108,15 +138,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -124,7 +154,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -135,8 +165,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -156,14 +191,14 @@ export const main = async (
#### 要附加到其他对象
将 `companyId` 替换为相应的字段:
将 `targetCompanyId` 替换为相应的字段:
| 对象 | 字段名称 |
| ----- | -------------------- |
| 公司 | `companyId` |
| 人员 | `personId` |
| 机会 | `opportunityId` |
| 自定义对象 | `yourCustomObjectId` |
| 对象 | 字段名称 |
| ----- | -------------------------- |
| 公司 | `targetCompanyId` |
| 人员 | `targetPersonId` |
| 机会 | `targetOpportunityId` |
| 自定义对象 | `targetYourCustomObjectId` |
同时更新函数参数以及附件的 mutation 中的 `variables.data` 对象。
@@ -54,11 +54,12 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoint
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const graphqlEndpoint = 'https://api.twenty.com/graphql';
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -78,11 +79,40 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
// Step 2: Upload the file via GraphQL multipart upload
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
@@ -90,12 +120,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fileFolder: 'Attachment' },
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(graphqlEndpoint, {
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -107,15 +137,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const filePath = uploadResult.data?.uploadFile?.path;
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!filePath) {
throw new Error('No file path returned from upload');
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 3: Create the attachment linked to the company
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateAttachment($data: AttachmentCreateInput!) {
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -123,7 +153,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(graphqlEndpoint, {
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -134,8 +164,13 @@ export const main = async (
variables: {
data: {
name: filename,
fullPath: filePath,
companyId,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
@@ -155,14 +190,14 @@ export const main = async (
#### To attach to a different object
Replace `companyId` with the appropriate field:
Replace `targetCompanyId` with the appropriate field:
| Object | Field Name |
|--------|------------|
| Company | `companyId` |
| Person | `personId` |
| Opportunity | `opportunityId` |
| Custom Object | `yourCustomObjectId` |
| Company | `targetCompanyId` |
| Person | `targetPersonId` |
| Opportunity | `targetOpportunityId` |
| Custom Object | `targetYourCustomObjectId` |
Update both the function parameter and the `variables.data` object in the attachment mutation.
@@ -1,6 +1,7 @@
import { FrontComponentErrorEffect } from '@/remote/components/FrontComponentErrorEffect';
import { FrontComponentHostCommunicationApiEffect } from '@/remote/components/FrontComponentHostCommunicationApiEffect';
import { FrontComponentInitializeHostCommunicationApiEffect } from '@/remote/components/FrontComponentInitializeHostCommunicationApiEffect';
import { FrontComponentUpdateContextEffect } from '@/remote/components/FrontComponentUpdateContextEffect';
import { FrontComponentUpdateHostCommunicationApiEffect } from '@/remote/components/FrontComponentUpdateHostCommunicationApiEffect';
import { type FrontComponentHostCommunicationApi } from '@/types/FrontComponentHostCommunicationApi';
import { type SdkClientUrls } from '@/types/HostToWorkerRenderContext';
import { type WorkerExports } from '@/types/WorkerExports';
@@ -55,7 +56,6 @@ export const FrontComponentRenderer = ({
apiUrl={apiUrl}
sdkClientUrls={sdkClientUrls}
frontComponentId={executionContext.frontComponentId}
frontComponentHostCommunicationApi={frontComponentHostCommunicationApi}
setReceiver={setReceiver}
setThread={setThread}
setError={setError}
@@ -63,7 +63,6 @@ export const FrontComponentRenderer = ({
);
}, [
componentUrl,
frontComponentHostCommunicationApi,
setError,
setReceiver,
setThread,
@@ -102,7 +101,13 @@ export const FrontComponentRenderer = ({
{isDefined(thread) && (
<>
<FrontComponentHostCommunicationApiEffect thread={thread} />
<FrontComponentUpdateHostCommunicationApiEffect
thread={thread}
frontComponentHostCommunicationApi={
frontComponentHostCommunicationApi
}
/>
<FrontComponentInitializeHostCommunicationApiEffect thread={thread} />
<FrontComponentUpdateContextEffect
thread={thread}
executionContext={executionContext}
@@ -1,8 +1,9 @@
export { FrontComponentRenderer } from './host/components/FrontComponentRenderer';
export { componentRegistry } from './host/generated/host-component-registry';
export { FrontComponentErrorEffect } from './remote/components/FrontComponentErrorEffect';
export { FrontComponentHostCommunicationApiEffect } from './remote/components/FrontComponentHostCommunicationApiEffect';
export { FrontComponentInitializeHostCommunicationApiEffect } from './remote/components/FrontComponentInitializeHostCommunicationApiEffect';
export { FrontComponentUpdateContextEffect } from './remote/components/FrontComponentUpdateContextEffect';
export { FrontComponentUpdateHostCommunicationApiEffect } from './remote/components/FrontComponentUpdateHostCommunicationApiEffect';
export { FrontComponentWorkerEffect } from './remote/components/FrontComponentWorkerEffect';
export {
HtmlA,
@@ -3,13 +3,13 @@ import { type WorkerExports } from '@/types/WorkerExports';
import { type ThreadWebWorker } from '@quilted/threads';
import { useEffect } from 'react';
type FrontComponentHostCommunicationApiEffectProps = {
type FrontComponentInitializeHostCommunicationApiEffectProps = {
thread: ThreadWebWorker<WorkerExports, FrontComponentHostCommunicationApi>;
};
export const FrontComponentHostCommunicationApiEffect = ({
export const FrontComponentInitializeHostCommunicationApiEffect = ({
thread,
}: FrontComponentHostCommunicationApiEffectProps) => {
}: FrontComponentInitializeHostCommunicationApiEffectProps) => {
useEffect(() => {
thread.imports.initializeHostCommunicationApi().catch((error) => {
console.error('Failed to initialize host communication API:', error);
@@ -0,0 +1,20 @@
import { type FrontComponentHostCommunicationApi } from '@/types/FrontComponentHostCommunicationApi';
import { type WorkerExports } from '@/types/WorkerExports';
import { type ThreadWebWorker } from '@quilted/threads';
import { useEffect } from 'react';
type FrontComponentUpdateHostCommunicationApiEffectProps = {
thread: ThreadWebWorker<WorkerExports, FrontComponentHostCommunicationApi>;
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
};
export const FrontComponentUpdateHostCommunicationApiEffect = ({
thread,
frontComponentHostCommunicationApi,
}: FrontComponentUpdateHostCommunicationApiEffectProps) => {
useEffect(() => {
Object.assign(thread.exports, frontComponentHostCommunicationApi);
}, [thread, frontComponentHostCommunicationApi]);
return null;
};
@@ -1,8 +1,8 @@
import { ThreadWebWorker, release, retain } from '@quilted/threads';
import { RemoteReceiver } from '@remote-dom/core/receivers';
import { useEffect, useRef } from 'react';
import { type ConfirmationModalCaller } from 'twenty-shared/types';
import { type CommandConfirmationModalResult } from 'twenty-sdk';
import { type ConfirmationModalCaller } from 'twenty-shared/types';
import { type FrontComponentHostCommunicationApi } from '../../types/FrontComponentHostCommunicationApi';
import { type SdkClientUrls } from '../../types/HostToWorkerRenderContext';
import { type WorkerExports } from '../../types/WorkerExports';
@@ -17,13 +17,26 @@ type CommandMenuItemConfirmationModalResultBrowserEventDetail = {
confirmationResult: CommandConfirmationModalResult;
};
const noopAsync = async () => {};
const HOST_COMMUNICATION_API_NOOP_INITIALIZATION: FrontComponentHostCommunicationApi =
{
navigate: noopAsync,
requestAccessTokenRefresh: async () => '',
openSidePanelPage: noopAsync,
openCommandConfirmationModal: noopAsync,
unmountFrontComponent: noopAsync,
enqueueSnackbar: noopAsync,
closeSidePanel: noopAsync,
updateProgress: noopAsync,
};
type FrontComponentWorkerEffectProps = {
componentUrl: string;
applicationAccessToken?: string;
apiUrl?: string;
sdkClientUrls?: SdkClientUrls;
frontComponentId: string;
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
setReceiver: React.Dispatch<React.SetStateAction<RemoteReceiver | null>>;
setThread: React.Dispatch<
React.SetStateAction<ThreadWebWorker<
@@ -40,7 +53,6 @@ export const FrontComponentWorkerEffect = ({
apiUrl,
sdkClientUrls,
frontComponentId,
frontComponentHostCommunicationApi,
setReceiver,
setThread,
setError,
@@ -68,7 +80,7 @@ export const FrontComponentWorkerEffect = ({
WorkerExports,
FrontComponentHostCommunicationApi
>(worker, {
exports: frontComponentHostCommunicationApi,
exports: { ...HOST_COMMUNICATION_API_NOOP_INITIALIZATION },
});
const handleCommandMenuItemConfirmationModalResultBrowserEvent = (
@@ -135,7 +147,6 @@ export const FrontComponentWorkerEffect = ({
setError,
setReceiver,
setThread,
frontComponentHostCommunicationApi,
]);
return null;
-1
View File
@@ -29,7 +29,6 @@
"workerDirectory": "public"
},
"dependencies": {
"@ai-sdk/react": "3.0.99",
"@apollo/client": "^4.0.0",
"@blocknote/mantine": "^0.47.1",
"@blocknote/react": "^0.47.1",

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