Compare commits

...

116 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
Baptiste Devessier 2612145436 Fix sdk tests (#19172) 2026-03-31 15:08:21 +00:00
github-actions[bot] 4c97642258 i18n - translations (#19171)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 16:33:53 +02:00
nitin 08f019e9c9 [AI] More tab new design (#19165)
closes
https://discord.com/channels/1130383047699738754/1480981616666087687

<img width="1596" height="1318" alt="CleanShot 2026-03-31 at 18 06 37"
src="https://github.com/user-attachments/assets/0d87610e-e4ce-4f3d-8047-97d242f230c5"
/>
2026-03-31 16:26:40 +02:00
Marie 888fa271f0 [Apps SDK] Fix rich app link in documentation (#19007)
- Fix link to rich app for LLMS
- Add example of extension of existing object in rich app (post card
app)
2026-03-31 13:35:57 +00:00
github-actions[bot] bb5c64952c i18n - translations (#19169)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 15:29:13 +02:00
github-actions[bot] 6dedb35a1f i18n - translations (#19168)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 15:22:30 +02:00
Weiko 436333f110 Add see records link to data model (#19163)
## Context
Add a link to the INDEX view page of an object to list all its records
directly from the data model page of the object.

<img width="556" height="460" alt="Screenshot 2026-03-31 at 14 04 20"
src="https://github.com/user-attachments/assets/3daa9ee5-ad09-42b5-a406-088ce8c53be4"
/>
2026-03-31 13:12:43 +00:00
Abdul Rahman ec283b8f2d Fix dropping workspace nav items at the bottom of the list (#18989)
https://github.com/user-attachments/assets/64abd955-892e-48c8-bf7b-d4d8645cf33e

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-03-31 13:06:31 +00:00
github-actions[bot] c3b969ab74 i18n - translations (#19166)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 14:55:02 +02:00
Weiko 287fe90ce9 Add link to workspace members index view page from the settings (#19164)
## Context
Fixes https://github.com/twentyhq/core-team-issues/issues/2039

<img width="608" height="519" alt="Screenshot 2026-03-31 at 14 28 00"
src="https://github.com/user-attachments/assets/939e47c6-84c2-471c-8da5-b76b161f086a"
/>
2026-03-31 12:39:22 +00:00
Thomas Trompette d10c0a6439 Fix: composite update events not received (#19053)
Database batch events (update / insert / soft-delete / hard-delete) were
building recordsBefore / recordsAfter after formatResult ran twice: once
inside WorkspaceSelectQueryBuilder.getMany() / getOne(), and again in
the CUD query builders. On the second pass, already-shaped composite
fields (e.g. emails) went through formatFieldMetadataValue and kept the
same object references as the live TypeORM row, so recordsBefore could
change when the entity was updated—breaking workflow triggers and diffs.
2026-03-31 11:58:12 +00:00
BugIsGod 176e81cd76 fix: clear navigation stack immediately in goBackFromSidePanel (#19153)
Fixes: #19152 

## Summary
goBackFromSidePanel returned early without writing the new (empty) stack
to the store, leaving stale navigation state. And it caused
openRecordInSidePanel to think the record was already open and skip
reopening.
<img width="587" height="405" alt="image"
src="https://github.com/user-attachments/assets/eb36f4c6-43ca-4c08-891a-c592ff673837"
/>

## Before


https://github.com/user-attachments/assets/03d1e24a-6d1e-4efc-93c1-72be0bc31a89


## After
When close the side panel with Escape, now it can be opened by clicking
arrow button again.




https://github.com/user-attachments/assets/ae700d41-4f81-4d1a-af9a-f97f31f489a6

---------

Co-authored-by: Devessier <baptiste@devessier.fr>
2026-03-31 11:29:03 +00:00
Charles Bochet 1ce4da5b67 Fix upgrade commands 2 (#19157) 2026-03-31 12:47:01 +02:00
github-actions[bot] ef66d6b337 i18n - translations (#19158)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 12:23:19 +02:00
BugIsGod bcca5d0002 fix: show disabled file chip when file no longer exists in timeline (#19045)
Fixes: #18943 
Follow-up pr: #19001

## Summary:                                                             
- Timeline activity shows file upload history, but deleted files had no
signed URL and were still rendered as clickable — clicking did nothing
- grab the fileId from properties.diff.after, look it up in the current
record's files field: if present, use live signed URL; if absent, mark
as deleted
- Deleted file chips show line-through label, not-allowed cursor, and
"File no longer exists" tooltip on hover
  


https://github.com/user-attachments/assets/5df6a675-0003-4fd1-ad57-a07e4338923f

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-03-31 10:07:19 +00:00
Weiko e0630b8653 Fix TransactionNotStartedError (#19155)
## Context
Checked in the codebase where we are trying to rollback a non-active
transaction.


packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
seemed to be the only place where it happens.

We could enforce this with a lint rule in the future 🤔
2026-03-31 10:02:50 +00:00
Paul Rastoin 61a27984e8 0.8.0.canary.7 bump (#19150)
Already published on npm
2026-03-31 09:50:28 +02:00
github-actions[bot] fd21d0c6ca i18n - docs translations (#19142)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 00:23:38 +02:00
github-actions[bot] 8d539f0e49 i18n - docs translations (#19139)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 22:30:14 +02:00
github-actions[bot] a6cecdbd49 i18n - docs translations (#19137)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 20:38:05 +02:00
Raphaël Bosi 383935d0d9 Fix widgets drag handles (#19133)
- Remove drag handles and cursor resize for the placeholder
- Fix the drag handles no longer appearing on hover and always present
drag handle for north and south

This was due to the linaria migration: adding a minus sign or px after a
css variable isn't working, we have to use calc.

## Before


https://github.com/user-attachments/assets/cba398ab-92c8-4924-ba3c-1a28fb4fd163

## After


https://github.com/user-attachments/assets/aec675cd-b809-434d-a8a7-edb12ce37364
2026-03-30 17:07:49 +00:00
Paul Rastoin 37908114fc [SDK] Extract twenty-front-component-renderer outside of twenty-sdk ( 2.8MB ) (#19021)
Followup https://github.com/twentyhq/twenty/pull/19010

## Dependency diagram

```
┌─────────────────────┐
│     twenty-front    │
│   (React frontend)  │
└─────────┬───────────┘
          │ imports runtime:
          │   FrontComponentRenderer
          │   FrontComponentRendererWithSdkClient
          │   useFrontComponentExecutionContext
          ▼
┌──────────────────────────────────┐         ┌─────────────────────────┐
│ twenty-front-component-renderer  │────────▶│       twenty-sdk        │
│   (remote-dom host + worker)     │         │  (app developer SDK)    │
│                                  │         │                         │
│  imports from twenty-sdk:        │         │  Public API:            │
│   • types only:                  │         │   defineFrontComponent  │
│     FrontComponentExecutionContext│         │   navigate, closeSide…  │
│     NavigateFunction             │         │   useFrontComponent…    │
│     CloseSidePanelFunction       │         │   Command components    │
│     CommandConfirmation…         │         │   conditional avail.    │
│     OpenCommandConfirmation…     │         │                         │
│     EnqueueSnackbarFunction      │         │  Internal only:         │
│     etc.                         │         │   frontComponentHost…   │
│                                  │         │   front-component-build │
│  owns locally:                   │         │   esbuild plugins       │
│   • ALLOWED_HTML_ELEMENTS        │         │                         │
│   • EVENT_TO_REACT               │         └────────────┬────────────┘
│   • HTML_TAG_TO_CUSTOM_ELEMENT…  │                      │
│   • SerializedEventData          │                      │ types
│   • PropertySchema               │                      ▼
│   • frontComponentHostComm…      │         ┌─────────────────────────┐
│     (local ref to globalThis)    │         │     twenty-shared       │
│   • setFrontComponentExecution…  │         │  (common types/utils)   │
│     (local impl, same keys)      │         │   AppPath, SidePanelP…  │
│                                  │         │   EnqueueSnackbarParams │
└──────────────────────────────────┘         │   isDefined, …          │
          │                                  └─────────────────────────┘
          │ also depends on
          ▼
    twenty-shared (types)
    @remote-dom/* (runtime)
    @quilted/threads (runtime)
    react (runtime)
```

**Key points:**

- **`twenty-front`** depends on the renderer, **not** on `twenty-sdk`
directly (for rendering)
- **`twenty-front-component-renderer`** depends on `twenty-sdk` for
**types only** (function signatures, `FrontComponentExecutionContext`).
The runtime bridge (`frontComponentHostCommunicationApi`) is shared via
`globalThis` keys, not module imports
- **`twenty-sdk`** has no dependency on the renderer — clean one-way
dependency
- The renderer owns all remote-dom infrastructure (element schemas,
event mappings, custom element tags) that was previously leaking through
the SDK's public API
- The SDK's `./build` entry point was removed entirely (unused)
2026-03-30 17:06:06 +00:00
github-actions[bot] 369ae2862f i18n - docs translations (#19135)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 18:40:35 +02:00
github-actions[bot] 5bde41ebbb i18n - translations (#19134)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 18:30:38 +02:00
Félix Malfait 8fa3962e1c feat: add resumable stream support for agent chat (#19107)
## Overview
Add resumable stream support for agent chat to allow clients to
reconnect and resume streaming responses if the connection is
interrupted (e.g., during page refresh).

## Changes

### Backend (Twenty Server)
- Add `activeStreamId` column to `AgentChatThreadEntity` to track
ongoing streams
- Create `AgentChatResumableStreamService` to manage Redis-backed
resumable streams using the `resumable-stream` library with ioredis
- Extend `AgentChatController` with:
  - `GET /:threadId/stream` endpoint to resume an existing stream
  - `DELETE /:threadId/stream` endpoint to stop an active stream
- Update `AgentChatStreamingService` to store streams in Redis and track
active stream IDs
- Add `resumable-stream@^2.2.12` dependency to package.json

### Frontend (Twenty Front)
- Update `useAgentChat` hook to:
- Use a persistent transport with `prepareReconnectToStreamRequest` for
resumable streams
  - Export `resumeStream` function from useChat
  - Add `handleStop` callback to clear active stream on DELETE endpoint
- Use thread ID as stable message ID instead of including message count
- Add stream resumption logic in `AgentChatAiSdkStreamEffect` component
to automatically call `resumeStream()` when switching threads

## Database Migration
New migration `1774003611071-add-active-stream-id-to-agent-chat-thread`
adds the `activeStreamId` column to store the current resumable stream
identifier.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 18:19:25 +02:00
Weiko ca00e8dece Fix databaseSchema migration (#19132) 2026-03-30 18:01:19 +02:00
Thomas des Francs 2263e14394 clean paddings in favorites section (#19112)
## Summary
- hide the Favorites section wrapper when there are no top-level
favorites and no folder creation in progress
- remove the extra top padding from the Favorites list so the section
title-to-content gap matches other navigation sections
- prevent the empty animated container from adding stray space in the
navbar

## screens

Before
<img width="926" height="820" alt="CleanShot 2026-03-30 at 12 02 11@2x"
src="https://github.com/user-attachments/assets/eac53f86-1108-42ac-986f-328327fbe947"
/>

<img width="1602" height="1168" alt="CleanShot 2026-03-30 at 12 02
29@2x"
src="https://github.com/user-attachments/assets/6249c7f9-9ac5-46c8-a709-dfc1904b37c7"
/>

(arrows = paddings too big)

After
<img width="1270" height="1074" alt="CleanShot 2026-03-30 at 12 01
24@2x"
src="https://github.com/user-attachments/assets/16e4155a-5591-4387-9675-f5c7be58ce32"
/>
2026-03-30 15:37:35 +00:00
Etienne ecf8161d0e Fix - Remove signFileUrl method (#19121)
`signFileUrl` is the old way to resolve file url. Replaced by
`signFileByIdUrl` which needs `FileFolder` and `fileId`.

Fix also avatar for person and workspaceMember. To be continued with
imageIdentifier refactor.


Fix : 
- https://discord.com/channels/1130383047699738754/1486723854910099576
- https://discord.com/channels/1130383047699738754/1484566445584285870
- https://discord.com/channels/1130383047699738754/1487124612889313420

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-30 15:14:54 +00:00
Etienne 3d1c53ec9d Gql direct execution - Improvements (#18972)
#### Direct Execution  __typename & null backfill
##### __typename filling
The direct execution path now correctly derives __typename at every
level of the GraphQL response:
Connection types — CompanyConnection, CompanyEdge, Company, PageInfo
GroupBy types — TaskGroupByConnection (was incorrectly producing
TaskConnection)
Composite fields — Links, FullName, Currency, etc. handled by a
dedicated formatter (was inheriting the parent object's typename)
Previously, __typename was derived from the object's universal
identifier (a UUID), producing broken values like
20202020B3744779A56180086Cb2E17FConnection.
##### Null backfill
Selected fields missing from the resolver result are backfilled with
null, matching the standard Yoga schema behavior.
##### Integration test
A new test runs the same findMany query (with __typename at all
structural levels) through both paths — standard Yoga schema and direct
execution — and asserts identical output via toStrictEqual.
2026-03-30 17:20:25 +02:00
github-actions[bot] 2a5aab0c44 i18n - translations (#19129)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 17:19:21 +02:00
martmull 8985dfbc5d Improve apps (#19120)
fixes
https://discord.com/channels/1130383047699738754/1488094970241089586
2026-03-30 15:03:23 +00:00
github-actions[bot] 40abe1e6d0 i18n - docs translations (#19128)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 16:49:57 +02:00
Raphaël Bosi 6c128a35dd Fix cropped calendar event name (#19126)
## Before
<img width="3024" height="1480" alt="CleanShot 2026-03-30 at 16 17 13
2@2x"
src="https://github.com/user-attachments/assets/0ec774a6-5e28-440a-93d5-1e91ea9c4c5a"
/>

## After
<img width="3024" height="1482" alt="CleanShot 2026-03-30 at 16 16
56@2x"
src="https://github.com/user-attachments/assets/0a15a6a4-be6f-4e22-b00b-548df9e73e2d"
/>
2026-03-30 14:35:39 +00:00
Paul Rastoin c0086646fd [APP] Stricter API assertions (#19114)
# Introduction

Improved the ci assertions to be sure the default logic function worked
fully

## what's next
About to introduce a s3 + lambda e2e test to cover the lambda driver too
in addition of the local driver
2026-03-30 14:18:45 +00:00
Charles Bochet 08b041633a chore: remove 1-19 upgrade commands and navigation menu item feature flags (#19083)
## Summary
- Deletes the entire `1-19` upgrade version command directory (7 files,
~1500 lines) and removes all references from the upgrade module and
command runner.
- Removes `IS_NAVIGATION_MENU_ITEM_ENABLED` and
`IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED` feature flags from the enum,
seed data, generated schema files, and test mocks. These flags had no
remaining feature-gated code — navigation menu items are now always
enabled.
2026-03-30 14:15:46 +00:00
neo773 2fccd29ec6 messaging cleanup (#19124) 2026-03-30 16:21:00 +02:00
github-actions[bot] 30bdc24bf8 i18n - translations (#19125)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 16:08:36 +02:00
Etienne d2cf05f4f4 Fix - Not shared message on record index (#19103)
quality-feedbacks :
https://discord.com/channels/1130383047699738754/1486711185436053514/1486711185436053514
<img width="1000" height="500" alt="Screenshot 2026-03-30 at 09 42 48"
src="https://github.com/user-attachments/assets/fe9027fe-0056-4fec-af51-5b39bb467bb5"
/>
<img width="1000" height="500" alt="Screenshot 2026-03-30 at 09 42 34"
src="https://github.com/user-attachments/assets/8cd05ea0-7eb2-4490-b87e-3a7dcd57add2"
/>

---------

Co-authored-by: Weiko <corentin@twenty.com>
2026-03-30 13:53:35 +00:00
Raphaël Bosi 696a202bb9 Fix navigation menu edition (#19115)
# Description

Fix "Already in navbar" false positive in the sidebar object picker:
`getObjectMetadataIdsInDraft` was collecting `objectMetadataId` from all
navigation menu item types (OBJECT, VIEW, RECORD), so having a pinned
view or record for an object type would incorrectly block re-adding that
object. Now only OBJECT-type items contribute to the duplicate check.

# Video QA

## Before


https://github.com/user-attachments/assets/7b853a55-6d7a-444c-92ee-bf6a75d9927c

## After


https://github.com/user-attachments/assets/65e651e2-9df8-45fd-872d-00782d6b6490

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-30 13:50:52 +00:00
Raphaël Bosi b8374f5531 Fix export view (#19117)
# PR Description

**Export view** command sent { id: { in: [] } } to the server, which
rejects empty arrays. It now falls back to the view's filters instead
when there is no selection, which is the intended behavior.

# Video QA

## Before


https://github.com/user-attachments/assets/3e6263f9-9c66-4f67-a81e-e0571652147d

## After


https://github.com/user-attachments/assets/977a366d-1936-457f-96a1-a5d4fb8ac98e
2026-03-30 13:07:24 +00:00
Charles Bochet 1109b89cd7 fix: use Redis metadata version for GraphQL response cache key (#19111)
## Summary

- Fix stale `ObjectMetadataItems` GraphQL response cache after field
creation by using `request.workspaceMetadataVersion` (sourced from
Redis) instead of `workspace.metadataVersion` (from the potentially
stale CoreEntityCacheService)
- Make the E2E kanban view test selector more robust with a regex match

## Root Cause

The `useCachedMetadata` GraphQL plugin keys cached responses using
`workspace.metadataVersion` from the `CoreEntityCacheService`. When a
field is created:

1. The migration runner increments `metadataVersion` in DB and Redis
2. But the `CoreEntityCacheService` for `WorkspaceEntity` is **not**
invalidated
3. So `request.workspace.metadataVersion` still has the old version
4. The cache key resolves to the old cached response
5. The frontend gets stale metadata without the newly created field

This breaks E2E tests (and likely affects users) - after creating a
custom field, the metadata isn't visible until the workspace entity
cache refreshes.

## Fix

Use `request.workspaceMetadataVersion` (populated from Redis by the
middleware, always up-to-date) as the primary version for cache keys,
falling back to the entity cache version.

## Test plan

- [ ] E2E `create-kanban-view` tests should pass (creating a Select
field and immediately using it in a Kanban view)
- [ ] Verify `ObjectMetadataItems` returns fresh data after field
creation (no stale cache)


Made with [Cursor](https://cursor.com)
2026-03-30 15:01:54 +02:00
github-actions[bot] 107914b437 i18n - docs translations (#19119)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 14:39:38 +02:00
Raphaël Bosi cb85a1b5a3 Fix record name hover (#19109)
## Before


https://github.com/user-attachments/assets/1972f3ac-17b2-4531-a64e-22f2672de71c

## After


https://github.com/user-attachments/assets/0fffdac6-0fcb-4446-9b7f-1791ec08e2df
2026-03-30 13:15:02 +02:00
neo773 ef8789fad6 fix: convert empty parentFolderId to null in messageFolder core dual-write (#19110) 2026-03-30 13:11:38 +02:00
Raphaël Bosi ee479001a1 Fix workspace dropdown (#19106)
## Before


https://github.com/user-attachments/assets/2ebc682d-f868-4a8b-8a1a-fe304222652e

## After


https://github.com/user-attachments/assets/e6fddab2-b5a1-4941-b60f-f45ad2bfaadd
2026-03-30 12:56:23 +02:00
github-actions[bot] 6af7e32c54 i18n - docs translations (#19113)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 12:45:45 +02:00
Abdullah. d246b16063 More parts of the new website. (#19094)
This PR brings in a few more components and some refactor of the
existing components for the website due to design requirements.
2026-03-30 12:00:32 +02:00
github-actions[bot] 0b2f435b3e i18n - translations (#19108)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-30 11:00:52 +02:00
martmull 039a04bc2f Fix warning about ../../tsconfig.base.json not found (#19105)
as title
2026-03-30 10:56:59 +02:00
martmull fe1377f18b Provide applicatiion assets (#18973)
- improve backend
- improve frontend

<img width="1293" height="824" alt="image"
src="https://github.com/user-attachments/assets/7a4633f1-85cd-4126-b058-dbeae6ba2218"
/>
2026-03-30 10:53:31 +02:00
1362 changed files with 61224 additions and 58274 deletions
+26 -23
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"
@@ -86,6 +75,8 @@ jobs:
run: |
echo "Attempting to merge main into current branch..."
git config user.email "ci@twenty.com"
git config user.name "CI"
git fetch origin main
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
@@ -99,8 +90,8 @@ jobs:
echo "❌ Merge failed due to conflicts"
echo "⚠️ Falling back to comparing current branch against main without merge"
# Abort the failed merge
git merge --abort
# Abort the failed merge (may not exist if merge never started)
git merge --abort 2>/dev/null || true
echo "merged=false" >> $GITHUB_OUTPUT
echo "BRANCH_STATE=conflicts" >> $GITHUB_ENV
@@ -402,11 +393,22 @@ jobs:
# Clean up temp directory
rm -rf /tmp/current-branch-files
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Validate downloaded schema files
id: validate-schemas
run: |
valid=true
for file in main-schema-introspection.json current-schema-introspection.json \
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
main-rest-api.json current-rest-api.json \
main-rest-metadata-api.json current-rest-metadata-api.json; do
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing schema file: $file"
valid=false
fi
done
echo "valid=$valid" >> $GITHUB_OUTPUT
- name: Install OpenAPI Diff Tool
run: |
@@ -414,6 +416,7 @@ jobs:
echo "Using OpenAPITools/openapi-diff via Docker"
- name: Generate GraphQL Schema Diff Reports
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== INSTALLING GRAPHQL INSPECTOR CLI ==="
npm install -g @graphql-inspector/cli
@@ -424,7 +427,6 @@ jobs:
echo "Checking GraphQL schema for changes..."
if graphql-inspector diff main-schema-introspection.json current-schema-introspection.json >/dev/null 2>&1; then
echo "✅ No changes in GraphQL schema"
# Don't create a diff file for no changes
else
echo "⚠️ Changes detected in GraphQL schema, generating report..."
echo "# GraphQL Schema Changes" > graphql-schema-diff.md
@@ -442,7 +444,6 @@ jobs:
echo "Checking GraphQL metadata schema for changes..."
if graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json >/dev/null 2>&1; then
echo "✅ No changes in GraphQL metadata schema"
# Don't create a diff file for no changes
else
echo "⚠️ Changes detected in GraphQL metadata schema, generating report..."
echo "# GraphQL Metadata Schema Changes" > graphql-metadata-diff.md
@@ -461,6 +462,7 @@ jobs:
ls -la *-diff.md 2>/dev/null || echo "No diff files generated (no changes detected)"
- name: Check REST API Breaking Changes
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== CHECKING REST API FOR BREAKING CHANGES ==="
@@ -529,6 +531,7 @@ jobs:
fi
- name: Check REST Metadata API Breaking Changes
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== CHECKING REST METADATA API FOR BREAKING CHANGES ==="
+5 -13
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: |
@@ -188,7 +180,7 @@ jobs:
cd /tmp/e2e-test-workspace/test-app
EXEC_OUTPUT=$(npx --no-install twenty exec --functionName create-hello-world-company)
echo "$EXEC_OUTPUT"
echo "$EXEC_OUTPUT" | grep -q "Created company"
echo "$EXEC_OUTPUT" | grep -q 'Created company.*Hello World.*with id'
- name: Run scaffolded app integration test
env:
@@ -0,0 +1,114 @@
name: CI Front Component Renderer
on:
pull_request:
merge_group:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
yarn.lock
packages/twenty-front-component-renderer/**
packages/twenty-sdk/**
packages/twenty-shared/**
!packages/twenty-sdk/package.json
renderer-task:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
matrix:
task: [build, typecheck, lint]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run ${{ matrix.task }}
run: npx nx ${{ matrix.task }} twenty-front-component-renderer
renderer-sb-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build storybook
run: npx nx storybook:build twenty-front-component-renderer
- name: Upload storybook build
uses: actions/upload-artifact@v4
with:
name: storybook-twenty-front-component-renderer
path: packages/twenty-front-component-renderer/storybook-static
retention-days: 1
renderer-sb-test:
timeout-minutes: 30
runs-on: ubuntu-latest
needs: renderer-sb-build
env:
STORYBOOK_URL: http://localhost:6008
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build dependencies
run: npx nx build twenty-sdk
- name: Download storybook build
uses: actions/download-artifact@v4
with:
name: storybook-twenty-front-component-renderer
path: packages/twenty-front-component-renderer/storybook-static
- name: Install Playwright
run: |
cd packages/twenty-front-component-renderer
npx playwright install
- name: Serve storybook & run tests
run: |
npx http-server packages/twenty-front-component-renderer/storybook-static --port 6008 --silent &
timeout 30 bash -c 'until curl -sf http://localhost:6008 > /dev/null 2>&1; do sleep 1; done'
npx nx storybook:test twenty-front-component-renderer
ci-front-component-renderer-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs:
[
changed-files-check,
renderer-task,
renderer-sb-build,
renderer-sb-test,
]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+2 -1
View File
@@ -25,6 +25,7 @@ jobs:
package.json
yarn.lock
packages/twenty-front/**
packages/twenty-front-component-renderer/**
packages/twenty-ui/**
packages/twenty-shared/**
packages/twenty-sdk/**
@@ -93,7 +94,7 @@ jobs:
run: |
npx nx build twenty-shared
npx nx build twenty-ui
npx nx build twenty-sdk
npx nx build twenty-front-component-renderer
- name: Download storybook build
uses: actions/download-artifact@v4
with:
+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:
+4 -15
View File
@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
task: [lint, typecheck, test:unit, test:integration]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
@@ -41,9 +41,6 @@ jobs:
uses: ./.github/actions/yarn-install
- name: Build
run: npx nx build twenty-sdk
- name: Install Playwright
if: contains(matrix.task, 'storybook')
run: npx playwright install chromium
- name: Run ${{ matrix.task }} task
uses: ./.github/actions/nx-affected
with:
@@ -56,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: >-
@@ -74,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:
+8 -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",
@@ -208,6 +209,7 @@
"packages/twenty-e2e-testing",
"packages/twenty-shared",
"packages/twenty-sdk",
"packages/twenty-front-component-renderer",
"packages/twenty-client-sdk",
"packages/twenty-apps",
"packages/twenty-cli",
+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). 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.5",
"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,
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
## UUID requirement
@@ -10,6 +10,7 @@ import * as path from 'path';
import { basename } from 'path';
import {
authLoginOAuth,
detectLocalServer,
serverStart,
type ServerStartResult,
} from 'twenty-sdk/cli';
@@ -38,7 +39,7 @@ export class CreateAppCommand {
try {
const exampleOptions = this.resolveExampleOptions(
options.mode ?? 'exhaustive',
options.mode ?? 'minimal',
);
await this.validateDirectory(appDirectory);
@@ -62,15 +63,19 @@ export class CreateAppCommand {
let serverResult: ServerStartResult | undefined;
if (!options.skipLocalInstance) {
const startResult = await serverStart({
onProgress: (message: string) => console.log(chalk.gray(message)),
});
const shouldStartServer = await this.shouldStartServer();
if (startResult.success) {
serverResult = startResult.data;
await this.connectToLocal(serverResult.url);
} else {
console.log(chalk.yellow(`\n${startResult.error.message}`));
if (shouldStartServer) {
const startResult = await serverStart({
onProgress: (message: string) => console.log(chalk.gray(message)),
});
if (startResult.success) {
serverResult = startResult.data;
await this.promptConnectToLocal(serverResult.url);
} else {
console.log(chalk.yellow(`\n${startResult.error.message}`));
}
}
}
@@ -158,7 +163,7 @@ export class CreateAppCommand {
includeExampleNavigationMenuItem: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleIntegrationTest: false,
includeExampleIntegrationTest: true,
};
}
@@ -201,7 +206,46 @@ export class CreateAppCommand {
);
}
private async connectToLocal(serverUrl: string): Promise<void> {
private async shouldStartServer(): Promise<boolean> {
const existingServerUrl = await detectLocalServer();
if (existingServerUrl) {
return true;
}
const { startDocker } = await inquirer.prompt([
{
type: 'confirm',
name: 'startDocker',
message:
'No running Twenty instance found. Would you like to start one using Docker?',
default: true,
},
]);
return startDocker;
}
private async promptConnectToLocal(serverUrl: string): Promise<void> {
const { shouldAuthenticate } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldAuthenticate',
message: `Would you like to authenticate to the local Twenty instance (${serverUrl})?`,
default: true,
},
]);
if (!shouldAuthenticate) {
console.log(
chalk.gray(
'Authentication skipped. Run `yarn twenty remote add` manually.',
),
);
return;
}
try {
const result = await authLoginOAuth({
apiUrl: serverUrl,
@@ -211,14 +255,14 @@ export class CreateAppCommand {
if (!result.success) {
console.log(
chalk.yellow(
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
'Authentication failed. Run `yarn twenty remote add` manually.',
),
);
}
} catch {
console.log(
chalk.yellow(
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
'Authentication failed. Run `yarn twenty remote add` manually.',
),
);
}
@@ -236,7 +280,7 @@ export class CreateAppCommand {
if (!serverResult) {
console.log(
chalk.gray(
'- yarn twenty remote add --local # Authenticate with Twenty',
'- yarn twenty remote add # Authenticate with Twenty',
),
);
}
@@ -425,8 +425,12 @@ const handler = async (): Promise<{ message: string }> => {
},
});
if (!createCompany?.id || !createCompany?.name) {
throw new Error('Failed to create company: missing id or name in response');
}
return {
message: \`Created company "\${createCompany?.name}" with id \${createCompany?.id}\`,
message: \`Created company "\${createCompany.name}" with id \${createCompany.id}\`,
};
};
@@ -794,6 +798,7 @@ const createPackageJson = async ({
npm: 'please-use-yarn',
yarn: '>=4.0.2',
},
keywords: ['twenty-app'],
packageManager: 'yarn@4.9.2',
scripts,
devDependencies,
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
@@ -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,22 +9,13 @@
},
"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 ."
},
"dependencies": {
"twenty-sdk": "latest"
"twenty-sdk": "latest",
"twenty-client-sdk": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
@@ -0,0 +1,18 @@
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
// Field on existing company object
export default defineField({
universalIdentifier: 'f922fdb8-10a9-4f11-a1d0-992a779f6dff',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
type: FieldType.BOOLEAN,
name: 'canReceivePostcards',
label: 'Can Receive Postcards',
description: 'Whether the company can receive postcards',
icon: 'IconMailbox',
defaultValue: false,
});
@@ -6,6 +6,9 @@ export const RECIPIENT_UNIVERSAL_IDENTIFIER =
export const RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER =
'd2a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
export const RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER =
'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
export default defineObject({
universalIdentifier: RECIPIENT_UNIVERSAL_IDENTIFIER,
nameSingular: 'recipient',
@@ -23,7 +26,7 @@ export default defineObject({
icon: 'IconMail',
},
{
universalIdentifier: 'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
universalIdentifier: RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.ADDRESS,
label: 'Mailing Address',
name: 'mailingAddress',
@@ -1,13 +1,16 @@
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
import {
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card-recipient.object';
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
export const ALL_POST_CARD_RECIPIENTS_VIEW_ID =
'b1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d';
export const POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER =
'fd959c6f-3465-4a3a-b7ad-3f4004fffc9a';
export default defineView({
universalIdentifier: ALL_POST_CARD_RECIPIENTS_VIEW_ID,
name: 'All Post Card Recipients',
@@ -17,7 +20,8 @@ export default defineView({
position: 2,
fields: [
{
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
universalIdentifier:
POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
@@ -1,9 +1,10 @@
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
import {
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER,
RECIPIENT_UNIVERSAL_IDENTIFIER,
} from '../objects/recipient.object';
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
export const ALL_RECIPIENTS_VIEW_ID = 'b1a2b3c4-0002-4a7b-8c9d-0e1f2a3b4c5d';
@@ -23,5 +24,13 @@ export default defineView({
isVisible: true,
size: 200,
},
{
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
fieldMetadataUniversalIdentifier:
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
position: 1,
isVisible: true,
size: 150,
},
],
});
+1 -1
View File
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
## UUID requirement
+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
@@ -20,8 +20,8 @@
"@types/react": "^18.2.0",
"oxlint": "^0.16.0",
"react": "^18.2.0",
"twenty-client-sdk": "portal:../../twenty-client-sdk",
"twenty-sdk": "portal:../../twenty-sdk",
"twenty-client-sdk": "0.8.0-canary.5",
"twenty-sdk": "0.8.0-canary.5",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^3.1.1"
@@ -16,8 +16,12 @@ const handler = async (): Promise<{ message: string }> => {
},
});
if (!createCompany?.id || !createCompany?.name) {
throw new Error('Failed to create company: missing id or name in response');
}
return {
message: `Created company "${createCompany?.name}" with id ${createCompany?.id}`,
message: `Created company "${createCompany.name}" with id ${createCompany.id}`,
};
};
@@ -14,7 +14,8 @@ export default defineObject({
labelPlural: 'Example items',
description: 'A sample custom object',
icon: 'IconBox',
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
labelIdentifierFieldMetadataUniversalIdentifier:
NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
+123 -107
View File
@@ -5,13 +5,13 @@ __metadata:
version: 8
cacheKey: 10c0
"@alcalzone/ansi-tokenize@npm:^0.1.3":
version: 0.1.3
resolution: "@alcalzone/ansi-tokenize@npm:0.1.3"
"@alcalzone/ansi-tokenize@npm:^0.2.4":
version: 0.2.5
resolution: "@alcalzone/ansi-tokenize@npm:0.2.5"
dependencies:
ansi-styles: "npm:^6.2.1"
is-fullwidth-code-point: "npm:^4.0.0"
checksum: 10c0/b88c5708271bb64ce132fc80dac8d5b87fc1699bf3abfdf10ecae40dbb56ab82460818f5746ecdd9870a00be9854e039d5cb3a121b808d0ff6f5bc7d0146cb38
is-fullwidth-code-point: "npm:^5.0.0"
checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4
languageName: node
linkType: hard
@@ -2655,7 +2655,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-escapes@npm:^7.0.0":
"ansi-escapes@npm:^7.3.0":
version: 7.3.0
resolution: "ansi-escapes@npm:7.3.0"
dependencies:
@@ -2717,7 +2717,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.2.1":
"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3":
version: 6.2.3
resolution: "ansi-styles@npm:6.2.3"
checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868
@@ -2948,7 +2948,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^5.3.0":
"chalk@npm:^5.3.0, chalk@npm:^5.6.0":
version: 5.6.2
resolution: "chalk@npm:5.6.2"
checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976
@@ -3020,13 +3020,13 @@ __metadata:
languageName: node
linkType: hard
"cli-truncate@npm:^4.0.0":
version: 4.0.0
resolution: "cli-truncate@npm:4.0.0"
"cli-truncate@npm:^5.1.1":
version: 5.2.0
resolution: "cli-truncate@npm:5.2.0"
dependencies:
slice-ansi: "npm:^5.0.0"
string-width: "npm:^7.0.0"
checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c
slice-ansi: "npm:^8.0.0"
string-width: "npm:^8.2.0"
checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471
languageName: node
linkType: hard
@@ -3306,7 +3306,7 @@ __metadata:
languageName: node
linkType: hard
"es-toolkit@npm:^1.22.0":
"es-toolkit@npm:^1.39.10":
version: 1.45.1
resolution: "es-toolkit@npm:1.45.1"
dependenciesMeta:
@@ -3727,7 +3727,7 @@ __metadata:
languageName: node
linkType: hard
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1":
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0":
version: 1.5.0
resolution: "get-east-asian-width@npm:1.5.0"
checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167
@@ -3906,8 +3906,8 @@ __metadata:
"@types/react": "npm:^18.2.0"
oxlint: "npm:^0.16.0"
react: "npm:^18.2.0"
twenty-client-sdk: "portal:../../twenty-client-sdk"
twenty-sdk: "portal:../../twenty-sdk"
twenty-client-sdk: "npm:0.8.0-canary.5"
twenty-sdk: "npm:0.8.0-canary.5"
typescript: "npm:^5.9.3"
vite-tsconfig-paths: "npm:^4.2.1"
vitest: "npm:^3.1.1"
@@ -4030,44 +4030,45 @@ __metadata:
languageName: node
linkType: hard
"ink@npm:^5.1.1":
version: 5.2.1
resolution: "ink@npm:5.2.1"
"ink@npm:^6.8.0":
version: 6.8.0
resolution: "ink@npm:6.8.0"
dependencies:
"@alcalzone/ansi-tokenize": "npm:^0.1.3"
ansi-escapes: "npm:^7.0.0"
"@alcalzone/ansi-tokenize": "npm:^0.2.4"
ansi-escapes: "npm:^7.3.0"
ansi-styles: "npm:^6.2.1"
auto-bind: "npm:^5.0.1"
chalk: "npm:^5.3.0"
chalk: "npm:^5.6.0"
cli-boxes: "npm:^3.0.0"
cli-cursor: "npm:^4.0.0"
cli-truncate: "npm:^4.0.0"
cli-truncate: "npm:^5.1.1"
code-excerpt: "npm:^4.0.0"
es-toolkit: "npm:^1.22.0"
es-toolkit: "npm:^1.39.10"
indent-string: "npm:^5.0.0"
is-in-ci: "npm:^1.0.0"
is-in-ci: "npm:^2.0.0"
patch-console: "npm:^2.0.0"
react-reconciler: "npm:^0.29.0"
scheduler: "npm:^0.23.0"
react-reconciler: "npm:^0.33.0"
scheduler: "npm:^0.27.0"
signal-exit: "npm:^3.0.7"
slice-ansi: "npm:^7.1.0"
slice-ansi: "npm:^8.0.0"
stack-utils: "npm:^2.0.6"
string-width: "npm:^7.2.0"
type-fest: "npm:^4.27.0"
widest-line: "npm:^5.0.0"
string-width: "npm:^8.1.1"
terminal-size: "npm:^4.0.1"
type-fest: "npm:^5.4.1"
widest-line: "npm:^6.0.0"
wrap-ansi: "npm:^9.0.0"
ws: "npm:^8.18.0"
yoga-layout: "npm:~3.2.1"
peerDependencies:
"@types/react": ">=18.0.0"
react: ">=18.0.0"
react-devtools-core: ^4.19.1
"@types/react": ">=19.0.0"
react: ">=19.0.0"
react-devtools-core: ">=6.1.2"
peerDependenciesMeta:
"@types/react":
optional: true
react-devtools-core:
optional: true
checksum: 10c0/0e2607712783353fbe99438ca4220db3d5874c7ae1a07e2b232f7539489b13a9db929b3046eedeccf318a3ffa222a572287dbe2307c848b8e7f6ec4bba39e550
checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff
languageName: node
linkType: hard
@@ -4140,14 +4141,7 @@ __metadata:
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^4.0.0":
version: 4.0.0
resolution: "is-fullwidth-code-point@npm:4.0.0"
checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^5.0.0":
"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0":
version: 5.1.0
resolution: "is-fullwidth-code-point@npm:5.1.0"
dependencies:
@@ -4165,12 +4159,12 @@ __metadata:
languageName: node
linkType: hard
"is-in-ci@npm:^1.0.0":
version: 1.0.0
resolution: "is-in-ci@npm:1.0.0"
"is-in-ci@npm:^2.0.0":
version: 2.0.0
resolution: "is-in-ci@npm:2.0.0"
bin:
is-in-ci: cli.js
checksum: 10c0/98f9cec4c35aece4cf731abf35ebf28359a9b0324fac810da05b842515d9ccb33b8999c1d9a679f0362e1a4df3292065c38d7f86327b1387fa667bc0150f4898
checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb
languageName: node
linkType: hard
@@ -5014,15 +5008,14 @@ __metadata:
languageName: node
linkType: hard
"react-dom@npm:^18.2.0":
version: 18.3.1
resolution: "react-dom@npm:18.3.1"
"react-dom@npm:^19.0.0":
version: 19.2.4
resolution: "react-dom@npm:19.2.4"
dependencies:
loose-envify: "npm:^1.1.0"
scheduler: "npm:^0.23.2"
scheduler: "npm:^0.27.0"
peerDependencies:
react: ^18.3.1
checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85
react: ^19.2.4
checksum: 10c0/f0c63f1794dedb154136d4d0f59af00b41907f4859571c155940296808f4b94bf9c0c20633db75b5b2112ec13d8d7dd4f9bf57362ed48782f317b11d05a44f35
languageName: node
linkType: hard
@@ -5033,15 +5026,14 @@ __metadata:
languageName: node
linkType: hard
"react-reconciler@npm:^0.29.0":
version: 0.29.2
resolution: "react-reconciler@npm:0.29.2"
"react-reconciler@npm:^0.33.0":
version: 0.33.0
resolution: "react-reconciler@npm:0.33.0"
dependencies:
loose-envify: "npm:^1.1.0"
scheduler: "npm:^0.23.2"
scheduler: "npm:^0.27.0"
peerDependencies:
react: ^18.3.1
checksum: 10c0/94f48ddc348a974256cf13c859f5a94efdb0cd72e04c51b1a4d5c72a8b960ccd35df2196057ee6a4cbcb26145e12b01e3f9ba3b183fddb901414db36a07cbf43
react: ^19.2.0
checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a
languageName: node
linkType: hard
@@ -5054,6 +5046,13 @@ __metadata:
languageName: node
linkType: hard
"react@npm:^19.0.0":
version: 19.2.4
resolution: "react@npm:19.2.4"
checksum: 10c0/cd2c9ff67a720799cc3b38a516009986f7fc4cb8d3e15716c6211cf098d1357ee3e348ab05ad0600042bbb0fd888530ba92e329198c92eafa0994f5213396596
languageName: node
linkType: hard
"readdirp@npm:^4.0.1":
version: 4.1.2
resolution: "readdirp@npm:4.1.2"
@@ -5298,12 +5297,10 @@ __metadata:
languageName: node
linkType: hard
"scheduler@npm:^0.23.0, scheduler@npm:^0.23.2":
version: 0.23.2
resolution: "scheduler@npm:0.23.2"
dependencies:
loose-envify: "npm:^1.1.0"
checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
"scheduler@npm:^0.27.0":
version: 0.27.0
resolution: "scheduler@npm:0.27.0"
checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
languageName: node
linkType: hard
@@ -5406,23 +5403,13 @@ __metadata:
languageName: node
linkType: hard
"slice-ansi@npm:^5.0.0":
version: 5.0.0
resolution: "slice-ansi@npm:5.0.0"
"slice-ansi@npm:^8.0.0":
version: 8.0.0
resolution: "slice-ansi@npm:8.0.0"
dependencies:
ansi-styles: "npm:^6.0.0"
is-fullwidth-code-point: "npm:^4.0.0"
checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f
languageName: node
linkType: hard
"slice-ansi@npm:^7.1.0":
version: 7.1.2
resolution: "slice-ansi@npm:7.1.2"
dependencies:
ansi-styles: "npm:^6.2.1"
is-fullwidth-code-point: "npm:^5.0.0"
checksum: 10c0/36742f2eb0c03e2e81a38ed14d13a64f7b732fe38c3faf96cce0599788a345011e840db35f1430ca606ea3f8db2abeb92a8d25c2753a819e3babaa10c2e289a2
ansi-styles: "npm:^6.2.3"
is-fullwidth-code-point: "npm:^5.1.0"
checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a
languageName: node
linkType: hard
@@ -5532,7 +5519,7 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^7.0.0, string-width@npm:^7.2.0":
"string-width@npm:^7.0.0":
version: 7.2.0
resolution: "string-width@npm:7.2.0"
dependencies:
@@ -5543,6 +5530,16 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0":
version: 8.2.0
resolution: "string-width@npm:8.2.0"
dependencies:
get-east-asian-width: "npm:^1.5.0"
strip-ansi: "npm:^7.1.2"
checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02
languageName: node
linkType: hard
"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1":
version: 3.0.1
resolution: "strip-ansi@npm:3.0.1"
@@ -5570,7 +5567,7 @@ __metadata:
languageName: node
linkType: hard
"strip-ansi@npm:^7.1.0":
"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2":
version: 7.2.0
resolution: "strip-ansi@npm:7.2.0"
dependencies:
@@ -5640,6 +5637,13 @@ __metadata:
languageName: node
linkType: hard
"tagged-tag@npm:^1.0.0":
version: 1.0.0
resolution: "tagged-tag@npm:1.0.0"
checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1
languageName: node
linkType: hard
"tar@npm:^7.5.4":
version: 7.5.11
resolution: "tar@npm:7.5.11"
@@ -5653,6 +5657,13 @@ __metadata:
languageName: node
linkType: hard
"terminal-size@npm:^4.0.1":
version: 4.0.1
resolution: "terminal-size@npm:4.0.1"
checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8
languageName: node
linkType: hard
"tinybench@npm:^2.9.0":
version: 2.9.0
resolution: "tinybench@npm:2.9.0"
@@ -5751,20 +5762,21 @@ __metadata:
languageName: node
linkType: hard
"twenty-client-sdk@portal:../../twenty-client-sdk::locator=hello-world%40workspace%3A.":
version: 0.0.0-use.local
resolution: "twenty-client-sdk@portal:../../twenty-client-sdk::locator=hello-world%40workspace%3A."
"twenty-client-sdk@npm:0.8.0-canary.5":
version: 0.8.0-canary.5
resolution: "twenty-client-sdk@npm:0.8.0-canary.5"
dependencies:
"@genql/cli": "npm:^3.0.3"
"@genql/runtime": "npm:^2.10.0"
esbuild: "npm:^0.25.0"
graphql: "npm:^16.8.1"
checksum: 10c0/754b92b732c8e03c9779f6557324710afe4c07f7e6efbe766bd8ff3b6dd8a00c0d9f3fecce2098d68ef9556cafa722b2189e490473e420fdb9cf30c56beb3619
languageName: node
linkType: soft
linkType: hard
"twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A.":
version: 0.0.0-use.local
resolution: "twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A."
"twenty-sdk@npm:0.8.0-canary.5":
version: 0.8.0-canary.5
resolution: "twenty-sdk@npm:0.8.0-canary.5"
dependencies:
"@chakra-ui/react": "npm:^3.33.0"
"@emotion/react": "npm:^11.14.0"
@@ -5782,13 +5794,14 @@ __metadata:
esbuild: "npm:^0.25.0"
graphql: "npm:^16.8.1"
graphql-sse: "npm:^2.5.4"
ink: "npm:^5.1.1"
ink: "npm:^6.8.0"
inquirer: "npm:^10.0.0"
jsonc-parser: "npm:^3.2.0"
preact: "npm:^10.28.3"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
react: "npm:^19.0.0"
react-dom: "npm:^19.0.0"
tinyglobby: "npm:^0.2.15"
twenty-client-sdk: "npm:0.8.0-canary.5"
typescript: "npm:^5.9.2"
uuid: "npm:^13.0.0"
vite: "npm:^7.0.0"
@@ -5796,8 +5809,9 @@ __metadata:
zod: "npm:^4.1.11"
bin:
twenty: dist/cli.cjs
checksum: 10c0/47d0970c88f59c092e8eca34dca38bf88d9d52678997349068ca8a8e5cebed2ea71dbd4e8b7299f40b99b5419d4cecd53b31f821b65959267e8d97eb91d2ddde
languageName: node
linkType: soft
linkType: hard
"type-fest@npm:^0.21.3":
version: 0.21.3
@@ -5806,10 +5820,12 @@ __metadata:
languageName: node
linkType: hard
"type-fest@npm:^4.27.0":
version: 4.41.0
resolution: "type-fest@npm:4.41.0"
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
"type-fest@npm:^5.4.1":
version: 5.5.0
resolution: "type-fest@npm:5.5.0"
dependencies:
tagged-tag: "npm:^1.0.0"
checksum: 10c0/60bf79a8df45abf99490e3204eceb5cf7f915413f8a69fb578c75cab37ddcb7d29ee21f185f0e1617323ac0b2a441e001b8dc691e220d0b087e9c29ea205538c
languageName: node
linkType: hard
@@ -6123,12 +6139,12 @@ __metadata:
languageName: node
linkType: hard
"widest-line@npm:^5.0.0":
version: 5.0.0
resolution: "widest-line@npm:5.0.0"
"widest-line@npm:^6.0.0":
version: 6.0.0
resolution: "widest-line@npm:6.0.0"
dependencies:
string-width: "npm:^7.0.0"
checksum: 10c0/6bd6cca8cda502ef50e05353fd25de0df8c704ffc43ada7e0a9cf9a5d4f4e12520485d80e0b77cec8a21f6c3909042fcf732aa9281e5dbb98cc9384a138b2578
string-width: "npm:^8.1.0"
checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7
languageName: node
linkType: hard
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
## Common Pitfalls
@@ -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.5",
"version": "0.8.0-canary.8",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -38,9 +38,6 @@ type ApplicationRegistration {
id: UUID!
universalIdentifier: String!
name: String!
description: String
logoUrl: String
author: String
oAuthClientId: String!
oAuthRedirectUris: [String!]!
oAuthScopes: [String!]!
@@ -48,8 +45,6 @@ type ApplicationRegistration {
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
websiteUrl: String
termsUrl: String
isListed: Boolean!
isFeatured: Boolean!
createdAt: DateTime!
@@ -1725,25 +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_NAVIGATION_MENU_ITEM_ENABLED
IS_NAVIGATION_MENU_ITEM_EDITING_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!
@@ -1772,6 +1770,8 @@ type ClientConfig {
calendarBookingPageId: String
isCloudflareIntegrationEnabled: Boolean!
isClickHouseConfigured: Boolean!
isWorkspaceSchemaDDLLocked: Boolean!
maintenance: ClientConfigMaintenanceMode
}
type UsageBreakdownItem {
@@ -1968,6 +1968,12 @@ type AdminPanelHealthServiceData {
queues: [AdminPanelWorkerQueueHealth!]
}
type MaintenanceMode {
startAt: DateTime!
endAt: DateTime!
link: String
}
type ModelsDevModelSuggestion {
modelId: String!
name: String!
@@ -2274,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!
@@ -2296,11 +2329,6 @@ type AuthorizeApp {
redirectUrl: String!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type AuthTokenPair {
accessOrWorkspaceAgnosticToken: AuthToken!
refreshToken: AuthToken!
@@ -2409,11 +2437,6 @@ type WorkspaceMigration {
actions: JSON!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type File {
id: UUID!
path: String!
@@ -2421,91 +2444,30 @@ type File {
createdAt: DateTime!
}
type MarketplaceAppField {
name: String!
type: String!
label: String!
description: String
icon: String
objectUniversalIdentifier: String
universalIdentifier: String
}
type MarketplaceAppObject {
universalIdentifier: String!
nameSingular: String!
namePlural: String!
labelSingular: String!
labelPlural: String!
description: String
icon: String
fields: [MarketplaceAppField!]!
}
type MarketplaceAppLogicFunction {
name: String!
description: String
timeoutSeconds: Int
}
type MarketplaceAppFrontComponent {
name: String!
description: String
}
type MarketplaceAppRoleObjectPermission {
objectUniversalIdentifier: String!
canReadObjectRecords: Boolean
canUpdateObjectRecords: Boolean
canSoftDeleteObjectRecords: Boolean
canDestroyObjectRecords: Boolean
}
type MarketplaceAppRoleFieldPermission {
objectUniversalIdentifier: String!
fieldUniversalIdentifier: String!
canReadFieldValue: Boolean
canUpdateFieldValue: Boolean
}
type MarketplaceAppDefaultRole {
id: String!
label: String!
description: String
canReadAllObjectRecords: Boolean!
canUpdateAllObjectRecords: Boolean!
canSoftDeleteAllObjectRecords: Boolean!
canDestroyAllObjectRecords: Boolean!
canUpdateAllSettings: Boolean!
canAccessAllTools: Boolean!
objectPermissions: [MarketplaceAppRoleObjectPermission!]!
fieldPermissions: [MarketplaceAppRoleFieldPermission!]!
permissionFlags: [String!]!
}
type MarketplaceApp {
id: String!
name: String!
description: String!
icon: String!
version: String!
author: String!
category: String!
logo: String
screenshots: [String!]!
aboutDescription: String!
providers: [String!]!
websiteUrl: String
termsUrl: String
objects: [MarketplaceAppObject!]!
fields: [MarketplaceAppField!]!
logicFunctions: [MarketplaceAppLogicFunction!]!
frontComponents: [MarketplaceAppFrontComponent!]!
defaultRole: MarketplaceAppDefaultRole
sourcePackage: String
isFeatured: Boolean!
}
type MarketplaceAppDetail {
universalIdentifier: String!
id: String!
name: String!
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
isListed: Boolean!
isFeatured: Boolean!
manifest: JSON
}
type PublicDomain {
id: UUID!
domain: String!
@@ -2594,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
@@ -2635,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
@@ -2691,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 {
@@ -2855,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!
}
@@ -2873,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!
@@ -3220,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
@@ -3249,6 +3217,7 @@ type Query {
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariable!]!
applicationRegistrationTarballUrl(id: String!): String
getApplicationShareLink(id: String!): String!
currentUser: User!
currentWorkspace: Workspace!
getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData!
@@ -3269,12 +3238,13 @@ type Query {
getModelsDevProviders: [ModelsDevProviderSuggestion!]!
getModelsDevSuggestions(providerType: String!): [ModelsDevModelSuggestion!]!
getAdminAiUsageByWorkspace(periodStart: DateTime, periodEnd: DateTime): [UsageBreakdownItem!]!
getMaintenanceMode: MaintenanceMode
getUsageAnalytics(input: UsageAnalyticsInput): UsageAnalytics!
getPostgresCredentials: PostgresCredentials
findManyPublicDomains: [PublicDomain!]!
getEmailingDomains: [EmailingDomain!]!
findManyMarketplaceApps: [MarketplaceApp!]!
findOneMarketplaceApp(universalIdentifier: String!): MarketplaceApp!
findMarketplaceAppDetail(universalIdentifier: String!): MarketplaceAppDetail!
findManyApplications: [Application!]!
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
}
@@ -3519,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!
@@ -3586,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!
@@ -3596,6 +3571,7 @@ type Mutation {
verifyEmailingDomain(id: String!): EmailingDomain!
createOneAppToken(input: CreateOneAppTokenInput!): AppToken!
installMarketplaceApp(universalIdentifier: String!, version: String): Boolean!
syncMarketplaceCatalog: Boolean!
installApplication(appRegistrationId: String!, version: String): Boolean!
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
uninstallApplication(universalIdentifier: String!): Boolean!
@@ -3832,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!
}
@@ -4441,14 +4422,9 @@ input GetAuthorizationUrlForSSOInput {
input CreateApplicationRegistrationInput {
name: String!
description: String
logoUrl: String
author: String
universalIdentifier: String
oAuthRedirectUris: [String!]
oAuthScopes: [String!]
websiteUrl: String
termsUrl: String
}
input UpdateApplicationRegistrationInput {
@@ -4458,13 +4434,8 @@ input UpdateApplicationRegistrationInput {
input UpdateApplicationRegistrationPayload {
name: String
description: String
logoUrl: String
author: String
oAuthRedirectUris: [String!]
oAuthScopes: [String!]
websiteUrl: String
termsUrl: String
isListed: Boolean
}
@@ -4612,6 +4583,7 @@ enum FileFolder {
type Subscription {
onEventSubscription(eventStreamId: String!): EventSubscription
logicFunctionLogs(input: LogicFunctionLogsInput!): LogicFunctionLogs!
onAgentChatEvent(threadId: UUID!): AgentChatEvent!
}
input LogicFunctionLogsInput {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+14 -1
View File
@@ -1,6 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2018",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
@@ -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"
+7 -5
View File
@@ -16,6 +16,7 @@ COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
COPY ./packages/twenty-front-component-renderer/package.json /app/packages/twenty-front-component-renderer/
COPY ./packages/twenty-sdk/package.json /app/packages/twenty-sdk/
COPY ./packages/twenty-client-sdk/package.json /app/packages/twenty-client-sdk/
@@ -51,6 +52,7 @@ FROM common-deps AS twenty-front-build
ARG REACT_APP_SERVER_BASE_URL
COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-front-component-renderer /app/packages/twenty-front-component-renderer
COPY ./packages/twenty-ui /app/packages/twenty-ui
COPY ./packages/twenty-shared /app/packages/twenty-shared
COPY ./packages/twenty-sdk /app/packages/twenty-sdk
@@ -193,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
@@ -209,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,83 +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 do today:**
- Define custom objects and fields as code (managed data model)
- Build logic functions with custom triggers (HTTP routes, cron, database events)
- Define skills for AI agents
- Build front components that render inside Twenty's UI
- Deploy the same app across multiple workspaces
## Prerequisites
- Node.js 24+ and Yarn 4
- Docker (for the local Twenty dev server)
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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults.
This creates a new folder called `my-twenty-app` with everything you need.
<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"
cd my-twenty-app
yarn twenty dev
```
The scaffolder supports two modes for controlling which example files are included:
This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal.
For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
yarn twenty dev --verbose
```
From here you can:
<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>
```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="Dev mode terminal output" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Step 6: See your app in Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**:
# Execute the pre-install function
yarn twenty function:execute --preInstall
<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>
# Execute the post-install function
yarn twenty function:execute --postInstall
Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server.
# Uninstall the application from the current workspace
yarn twenty uninstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Application registration details" />
</div>
# Display commands' help
yarn twenty help
```
Click **View installed app** to see the installed app. The **About** tab shows the current version and management options:
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).
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installed app — About tab" />
</div>
## Project structure (scaffolded)
Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents:
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installed app — Content tab" />
</div>
- 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
You are all set! Edit any file in `src/` and the changes will be picked up automatically.
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
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/
@@ -93,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`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
- **.nvmrc**: Pins the Node.js version expected by the project.
- **.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 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` 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"
{
@@ -219,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,15 +4,75 @@ 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:
- **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.
- **Push a tarball** — deploy your app to a specific Twenty server for internal use without making it publicly available.
Both paths start from the same **build** step.
## Building your 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.
## 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 --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
@@ -21,29 +81,69 @@ Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twe
### Requirements
- An [npm](https://www.npmjs.com) account
- Your package name **must** use the `twenty-app-` prefix (e.g., `twenty-app-postcard-sender`)
- The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
### Steps
1. **Build your app** — the CLI compiles your TypeScript sources and generates the application manifest:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Publish to npm** — push the built package to the npm registry:
### Marketplace metadata
```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',
],
});
```
### Auto-discovery
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.).
Packages with the `twenty-app-` prefix are automatically discovered by the Twenty marketplace catalog. Once published, your app appears in the marketplace within a few minutes — no manual registration or approval required.
### Publish
```bash filename="Terminal"
yarn twenty publish
```
To publish under a specific dist-tag (e.g., `beta` or `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### How marketplace discovery works
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
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>
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`.
</Note>
### CI publishing
The scaffolded project includes a GitHub Actions workflow that publishes on every release. It runs `app:build`, then `npm publish --provenance` from the build output:
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 }}
```
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `npx twenty build`, then `npm publish` from `.twenty/output`.
<Tip>
**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>
## Internal distribution
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can push a tarball directly to a Twenty server.
### Push a tarball
Build your app and deploy it to a specific server in one step:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Any workspace on that server can then install and upgrade the app from the **Applications** settings page.
### Version management
To release an update:
1. Bump the `version` field in your `package.json`
2. Push a new tarball with `npx twenty publish --server <server-url>`
3. Workspaces on that server will see the upgrade available in their settings
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`.
<Note>
Internal apps are scoped to the server they're pushed to. They won't appear in the public marketplace and can't be installed by workspaces on other servers.
**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.
</Note>
## App categories
## Installing apps
Twenty organizes apps into three categories based on how they're distributed:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
| Category | How it works | Visible in marketplace? |
|----------|-------------|------------------------|
| **Development** | Local dev mode apps running via `yarn twenty dev`. Used for building and testing. | No |
| **Published** | Apps published to npm with the `twenty-app-` prefix. Listed in the marketplace for any workspace to install. | Yes |
| **Internal** | Apps deployed via tarball to a specific server. Available only to workspaces on that server. | No |
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
<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>
{/* 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
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,84 +4,142 @@ description: أنشئ أول تطبيق Twenty خلال دقائق.
---
<Warning>
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول ووظائف منطقية ومهارات ذكاء اصطناعي ومكونات واجهة مستخدم مخصصة — جميعها تُدار ككود.
**ما الذي يمكنك فعله اليوم:**
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
* أنشئ وظائف منطقية مع مشغلات مخصصة (مسارات HTTP، cron، أحداث قاعدة البيانات)
* حدد المهارات لوكلاء الذكاء الاصطناعي
* أنشئ مكونات واجهية تُعرَض داخل واجهة مستخدم 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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
سيُطلب منك إدخال اسم ووصف لتطبيقك. اضغط **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"
cd my-twenty-app
yarn twenty dev
```
يدعم المُهيئ وضعين للتحكم في ملفات الأمثلة التي سيتم تضمينها:
يقوم هذا بمراقبة ملفات المصدر لديك، وإعادة البناء عند كل تغيير، ومزامنة تطبيقك تلقائيًا مع خادم Twenty المحلي. يفترض أن ترى لوحة حالة مباشرة في الطرفية.
للحصول على مخرجات أكثر تفصيلاً (سجلات البناء، طلبات المزامنة، تتبعات الأخطاء)، استخدم العلم `--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
```
من هنا يمكنك:
<Warning>
وضع التطوير متاح فقط على مثيلات Twenty التي تعمل في وضع التطوير (`NODE_ENV=development`). المثيلات الإنتاجية ترفض طلبات مزامنة وضع التطوير. استخدم `yarn twenty deploy` للنشر إلى خوادم الإنتاج — اطّلع على [نشر التطبيقات](/l/ar/developers/extend/apps/publishing) للتفاصيل.
</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="مخرجات الطرفية في وضع التطوير" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## الخطوة 6: اعرض تطبيقك في Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
افتح [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) في متصفحك. انتقل إلى **Settings > Apps** واختر علامة التبويب **Developer**. يُفترض أن ترى تطبيقك مُدرجًا تحت **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="قائمة &#x22;Your Apps&#x22; تعرض &#x22;My twenty app&#x22;" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
انقر على **My twenty app** لفتح **تسجيل التطبيق** الخاص به. التسجيل عبارة عن سجل على مستوى الخادم يصف تطبيقك — اسمه، والمعرّف الفريد، وبيانات اعتماد OAuth، والمصدر (محلي، npm، أو tarball). يُخزَّن على الخادم، وليس داخل أي مساحة عمل محددة. عند تثبيت تطبيق في مساحة عمل، ينشئ Twenty **تطبيقًا** بنطاق مساحة العمل يُشير مرة أخرى إلى هذا التسجيل. يمكن تثبيت تسجيل واحد عبر عدة مساحات عمل على الخادم نفسه.
# 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="تفاصيل تسجيل التطبيق" />
</div>
# Display commands' help
yarn twenty help
```
انقر **View installed app** لعرض التطبيق المثبّت. تعرض علامة التبويب **About** الإصدار الحالي وخيارات الإدارة:
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[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="التطبيق المثبّت — علامة تبويب About" />
</div>
## هيكل المشروع (مُنشأ بالقالب)
انتقل إلى علامة التبويب **Content** لمشاهدة كل ما يقدمه تطبيقك — الكائنات، والحقول، ودوال المنطق، والوكلاء:
عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="التطبيق المثبّت — علامة تبويب Content" />
</div>
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
أنت جاهز تمامًا! حرّر أي ملف في `src/` وسيتم التقاط التغييرات تلقائيًا.
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
انتقل إلى [بناء التطبيقات](/l/ar/developers/extend/apps/building) للحصول على دليل مفصّل حول إنشاء الكائنات، ودوال المنطق، ومكونات الواجهة الأمامية، والمهارات، والمزيد.
---
## هيكل المشروع
تولّد أداة إنشاء الهيكل بنية الملفات التالية (مُبيّنة بوضع `--exhaustive` الذي يتضمن أمثلة لكل نوع من الكيانات):
```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
```
مع `--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` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
| ملف / مجلد | الغرض |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `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` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/generated`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`).
* `yarn twenty entity: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"
{
@@ -221,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,15 +4,75 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ
---
<Warning>
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
## نظرة عامة
بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه:
* **نشر أرشيف tar** — ارفع تطبيقك مباشرةً إلى خادم Twenty محدد للاستخدام الداخلي أو الخاص.
* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته.
* **إرسال tarball** — انشر تطبيقك إلى خادم Twenty معيّن للاستخدام الداخلي من دون جعله متاحًا للعامة.
كلا المسارين يبدآن من نفس خطوة **build**.
## بناء تطبيقك
شغّل أمر build لتجميع تطبيقك وإنشاء ملف `manifest.json` جاهز للتوزيع:
```bash filename="Terminal"
yarn twenty build
```
يقوم هذا بتجميع مصادر TypeScript، وتحويل دوال المنطق ومكوّنات الواجهة الأمامية، وكتابة كل شيء إلى `.twenty/output/`. أضِف `--tarball` لإنتاج حزمة `.tgz` أيضًا للتوزيع اليدوي أو لأمر deploy.
## النشر إلى خادم (tarball)
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty.
### المتطلبات الأساسية
قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`.
أضِف remote:
```bash filename="Terminal"
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
@@ -21,29 +81,69 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ
### المتطلبات
* حساب على [npm](https://www.npmjs.com)
* يجب أن يستخدم اسم الحزمة البادئة `twenty-app-` (مثلًا، `twenty-app-postcard-sender`)
* الكلمة المفتاحية `twenty-app` في مصفوفة `keywords` في `package.json` (موجودة مسبقًا عند تهيئة المشروع باستخدام `create-twenty-app`)
### الخطوات
1. **قم ببناء تطبيقك** — تقوم أداة CLI بتجميع مصادر TypeScript الخاصة بك وإنشاء ملف بيان التطبيق:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **النشر على npm** — ادفع الحزمة المبنية إلى سجل npm:
### بيانات التعريف لسوق التطبيقات
```bash filename="Terminal"
npx twenty publish
يدعم إعداد `defineApplication()` حقولًا اختيارية تتحكم في كيفية ظهور تطبيقك في السوق. استخدم `logoUrl` و`screenshots` للإشارة إلى الصور من مجلد `public/`:
```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',
],
});
```
### الاكتشاف التلقائي
اطّلع على [أكورديون defineApplication](/l/ar/developers/extend/apps/building#defineentity-functions) في صفحة بناء التطبيقات للاطلاع على القائمة الكاملة لحقول السوق (`author` و`category` و`aboutDescription` و`websiteUrl` و`termsUrl` وغيرها).
تُكتشف الحِزم التي تحمل البادئة `twenty-app-` تلقائيًا بواسطة فهرس سوق Twenty. بعد نشره، سيظهر تطبيقك في السوق خلال بضع دقائق — من دون الحاجة إلى تسجيل يدوي أو موافقة يدوية.
### النشر
```bash filename="Terminal"
yarn twenty publish
```
للنشر تحت dist-tag معيّن (مثلًا: `beta` أو `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### كيف تعمل آلية الاكتشاف في السوق
يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**.
يمكنك تشغيل المزامنة فورًا بدلًا من الانتظار:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
تأتي بيانات التعريف المعروضة في السوق من إعداد `defineApplication()` — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`.
<Note>
إذا لم يحدد تطبيقك `aboutDescription` في `defineApplication()`، فسيستخدم السوق تلقائيًا ملف `README.md` الخاص بحزمتك من npm كمحتوى لصفحة حول. هذا يعني أنه يمكنك الاحتفاظ بملف README واحد لكل من npm وسوق Twenty. إذا كنت تريد وصفًا مختلفًا في السوق، فقم بتعيين `aboutDescription` بشكل صريح.
</Note>
### النشر عبر CI
يتضمن المشروع المُولَّد سير عمل GitHub Actions يقوم بالنشر عند كل إصدار. يشغِّل `app:build`، ثم ينفِّذ `npm publish --provenance` من مخرجات البناء:
استخدم سير عمل GitHub Actions هذا للنشر تلقائيًا مع كل إصدار (يستخدم [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 }}
```
بالنسبة لأنظمة CI الأخرى (GitLab CI، CircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `npx twenty build`، ثم `npm publish` من `.twenty/output`.
<Tip>
**npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد.
</Tip>
## التوزيع الداخلي
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك إرسال tarball مباشرةً إلى خادم Twenty.
### إرسال tarball
قم ببناء تطبيقك وانشره إلى خادم محدد في خطوة واحدة:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
يمكن لأي مساحة عمل على ذلك الخادم بعدها تثبيت التطبيق وترقيته من صفحة الإعدادات **التطبيقات**.
### إدارة الإصدارات
لطرح تحديث:
1. ارفع قيمة الحقل `version` في ملف `package.json`
2. أرسل tarball جديدًا باستخدام `npx twenty publish --server <server-url>`
3. سترى مساحات العمل على ذلك الخادم الترقية متاحة في إعداداتها
بالنسبة لأنظمة CI الأخرى (GitLab CI، وCircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `yarn twenty build`، ثم `npm publish` من `.twenty/output`.
<Note>
التطبيقات الداخلية مقتصرة على الخادم الذي تُرسل إليه. لن تظهر في السوق العام ولا يمكن لمساحات العمل على خوادم أخرى تثبيتها.
**npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد.
</Note>
## فئات التطبيقات
## تثبيت التطبيقات
تُنظِّم Twenty التطبيقات في ثلاث فئات استنادًا إلى طريقة توزيعها:
بعد نشر التطبيق (npm) أو نشره (tarball)، يمكن لمساحات العمل تثبيته عبر واجهة المستخدم.
| الفئة | كيف يعمل | مرئي في سوق Twenty؟ |
| ----------- | ---------------------------------------------------------------------------------------------------- | ------------------- |
| **التطوير** | تطبيقات وضع التطوير المحلي التي تعمل عبر `yarn twenty dev`. تُستخدم للبناء والاختبار. | لا |
| **منشور** | تطبيقات منشورة على npm مع البادئة `twenty-app-`. مدرجة في سوق Twenty لتتمكن أي مساحة عمل من تثبيتها. | نعم |
| **داخلي** | تطبيقات منشورة عبر tarball إلى خادم محدد. متاحة فقط لمساحات العمل على ذلك الخادم. | لا |
اذهب إلى صفحة **الإعدادات > التطبيقات** في Twenty، حيث يمكن استعراض تطبيقات السوق والتطبيقات المنشورة عبر tarball وتثبيتها.
<Tip>
ابدأ في وضع **التطوير** أثناء بناء تطبيقك. عندما يصبح جاهزًا، اختر **منشور** (npm) للتوزيع الواسع أو **داخلي** (tarball) للنشر الخاص.
</Tip>
{/* TODO: add screenshot of the UI when the app is registered */}
يمكنك أيضًا تثبيت التطبيقات من سطر الأوامر:
```bash filename="Terminal"
yarn twenty install
```
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,84 +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 dělat už dnes:**
* Definujte vlastní objekty a pole jako kód (spravovaný datový model)
* Vytvářejte logické funkce s vlastními spouštěči (HTTP routy, cron, databázové události)
* Definujte dovednosti agentů AI
* Vytvářejte frontendové komponenty, které se vykreslují uvnitř uživatelského rozhraní Twenty
* Nasazujte stejnou aplikaci do více pracovních prostorů
## Předpoklady
* Node.js 24+ a Yarn 4
* Docker (pro místní vývojový server 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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
Budete vyzváni k zadání názvu a popisu své aplikace. Stisknutím **Enter** přijmete výchozí hodnoty.
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"
cd my-twenty-app
yarn twenty dev
```
Nástroj pro generování kostry podporuje dva režimy pro řízení toho, které ukázkové soubory jsou zahrnuty:
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.
Pro podrobnější výstup (protokoly sestavení, požadavky na synchronizaci, stopy chyb) použijte přepínač `--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
```
Odtud můžete:
<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>
```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="Výstup terminálu ve vývojovém režimu" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Krok 6: Zobrazte svou aplikaci v Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
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**:
# 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="Seznam Your Apps zobrazující My twenty app" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
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.
# 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="Podrobnosti registrace aplikace" />
</div>
# Display commands' help
yarn twenty help
```
Klikněte na **View installed app**, abyste zobrazili nainstalovanou aplikaci. Karta **About** zobrazuje aktuální verzi a možnosti správy:
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).
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Nainstalovaná aplikace — karta About" />
</div>
## Struktura projektu (vytvořená scaffolderem)
Přepněte na kartu **Content**, abyste viděli vše, co vaše aplikace poskytuje — objekty, pole, logické funkce a agenty:
Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Nainstalovaná aplikace — karta Content" />
</div>
* 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
Vše je připraveno! Upravte libovolný soubor v `src/` a změny se automaticky projeví.
Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto:
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/
@@ -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
```
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`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
* **.oxlintrc.json** 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 dva typované API klienty v `node_modules/twenty-sdk/generated`: `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) a `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`).
* `yarn twenty entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další.
## Ověření
Při prvním spuštění `yarn twenty auth:login` budete vyzváni k zadání:
* URL API (výchozí je http://localhost:3000 nebo váš aktuální profil pracovního prostoru)
* Klíč API
Vaše přihlašovací údaje se ukládají pro jednotlivé uživatele do `~/.twenty/config.json`. Můžete spravovat více profilů a přepínat mezi nimi.
### Správa pracovních prostorů
**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"
{
@@ -221,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,15 +4,75 @@ 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:
* **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.
* **Odeslat tarball** — nasaďte svou aplikaci na konkrétní server Twenty pro interní použití, aniž by byla veřejně dostupná.
Obě cesty začínají stejným krokem **build**.
## Sestavení 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
```
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.
## 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 --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
@@ -21,29 +81,69 @@ Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketpla
### Požadavky
* Účet na [npm](https://www.npmjs.com)
* Název vašeho balíčku **musí** používat předponu `twenty-app-` (např. `twenty-app-postcard-sender`)
* 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`)
### Postup
1. **Sestavte svou aplikaci** — CLI zkompiluje vaše zdrojové soubory TypeScript a vygeneruje manifest aplikace:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Publikujte na npm** — odešlete sestavený balíček do registru npm:
### Metadata tržiště
```bash filename="Terminal"
npx twenty publish
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/`:
```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',
],
});
```
### Automatické rozpoznání
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.).
Balíčky s předponou `twenty-app-` jsou automaticky rozpoznávány katalogem Marketplace Twenty. Po publikování se vaše aplikace během několika minut objeví v Marketplace — nevyžaduje žádnou ruční registraci ani schvalování.
### Publikování
```bash filename="Terminal"
yarn twenty publish
```
Chcete-li publikovat pod konkrétním dist-tagem (např. `beta` nebo `next`):
```bash filename="Terminal"
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**.
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
```
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`.
</Note>
### Publikování pomocí CI
Vygenerovaný projekt obsahuje pracovní postup GitHub Actions, který publikuje při každém vydání. Spouští `app:build`, a poté `npm publish --provenance` z výstupu buildu:
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
@@ -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 }}
```
Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `npx twenty build` a poté `npm publish` z `.twenty/output`.
<Tip>
**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>
## Interní distribuce
Pro aplikace, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete odeslat tarball přímo na server Twenty.
### Odeslat tarball
Sestavte svou aplikaci a nasaďte ji na konkrétní server v jednom kroku:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Jakýkoli pracovní prostor na tomto serveru pak může aplikaci instalovat a aktualizovat ze stránky nastavení **Applications**.
### Správa verzí
Chcete-li vydat aktualizaci:
1. Zvyšte hodnotu pole `version` v souboru `package.json`
2. Odešlete nový tarball pomocí `npx twenty publish --server <server-url>`
3. Pracovní prostory na tomto serveru uvidí dostupnou aktualizaci ve svém nastavení
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`.
<Note>
Interní aplikace jsou omezené na server, na který jsou odeslány. Nezobrazí se ve veřejném Marketplace a nelze je instalovat v pracovních prostorech na jiných serverech.
**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).
</Note>
## Kategorie aplikací
## Instalace aplikací
Twenty organizuje aplikace do tří kategorií podle způsobu distribuce:
Jakmile je aplikace publikována (npm) nebo nasazena (tarball), mohou ji pracovní prostory nainstalovat prostřednictvím uživatelského rozhraní.
| 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** | Aplikace publikované na npm s předponou `twenty-app-`. Uvedeny v Marketplace, aby je mohl kterýkoli pracovní prostor nainstalovat. | Ano |
| **Interní** | Aplikace nasazené pomocí tarballu na konkrétní server. Dostupné pouze pro pracovní prostory na tomto serveru. | Ne |
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.
<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>
{/* 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
```
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,84 +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 heute tun können:**
* Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell)
* Erstellen Sie Logikfunktionen mit benutzerdefinierten Triggern (HTTP-Routen, cron, Datenbankereignisse)
* Fähigkeiten für KI-Agenten definieren
* Erstellen Sie Frontend-Komponenten, die in der Twenty-UI gerendert werden
* Dieselbe App in mehreren Workspaces bereitstellen
## Voraussetzungen
* Node.js 24+ und Yarn 4
* Docker (für den lokalen Twenty-Dev-Server)
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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
Sie werden aufgefordert, einen Namen und eine Beschreibung für Ihre App einzugeben. Drücken Sie **Enter**, um die Standardwerte zu übernehmen.
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"
cd my-twenty-app
yarn twenty dev
```
Das Scaffolding-Tool unterstützt zwei Modi, um zu steuern, welche Beispieldateien enthalten sind:
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.
Für ausführlichere Ausgaben (Build-Protokolle, Sync-Anfragen, Fehlerspuren) verwenden Sie das 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
```
Von hier aus können Sie:
<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>
```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="Terminalausgabe im Dev-Modus" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Schritt 6: Ihre App in Twenty ansehen
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
Ö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:
# 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="Liste &#x22;Your Apps&#x22;, die &#x22;My twenty app&#x22; anzeigt" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
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.
# 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="Details der Anwendungsregistrierung" />
</div>
# Display commands' help
yarn twenty help
```
Klicken Sie auf **View installed app**, um die installierte App anzuzeigen. Die Registerkarte **About** zeigt die aktuelle Version und Verwaltungsoptionen:
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).
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installierte App — Registerkarte About" />
</div>
## Projektstruktur (vom Scaffolder erzeugt)
Wechseln Sie zur Registerkarte **Content**, um alles zu sehen, was Ihre App bereitstellt — Objekte, Felder, Logikfunktionen und Agenten:
Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der Scaffolder Folgendes:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installierte App — Registerkarte Content" />
</div>
* 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
Alles erledigt! Bearbeiten Sie eine beliebige Datei in `src/`, und die Änderungen werden automatisch übernommen.
Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus:
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/
@@ -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
```
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`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
* **.oxlintrc.json** 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 automatisch zwei typisierte API-Clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) und `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`).
* `yarn twenty entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu.
## Authentifizierung
Wenn Sie `yarn twenty auth:login` zum ersten Mal ausführen, werden Sie nach Folgendem gefragt:
* API-URL (standardmäßig http://localhost:3000 oder Ihr aktuelles Workspace-Profil)
* API-Schlüssel
Ihre Anmeldedaten werden pro Benutzer in `~/.twenty/config.json` gespeichert. Sie können mehrere Profile verwalten und zwischen ihnen wechseln.
### Arbeitsbereiche verwalten
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"
{
@@ -221,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,15 +4,75 @@ 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:
* **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.
* **Einen Tarball pushen** — stellen Sie Ihre App auf einem bestimmten Twenty-Server für die interne Nutzung bereit, ohne sie öffentlich verfügbar zu machen.
Beide Pfade beginnen mit demselben **Build**-Schritt.
## Erstellen Ihrer App
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
```
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.
## 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 --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
@@ -21,29 +81,69 @@ Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Je
### Anforderungen
* Ein [npm](https://www.npmjs.com)-Konto
* Ihr Paketname **muss** das Präfix `twenty-app-` verwenden (z. B. `twenty-app-postcard-sender`)
* Das Schlüsselwort `twenty-app` in Ihrem `package.json`-Array `keywords` (bereits enthalten, wenn Sie mit `create-twenty-app` ein Gerüst erstellen)
### Schritte
1. **App erstellen** — die CLI kompiliert Ihre TypeScript-Quellen und erzeugt das Anwendungsmanifest:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Auf npm veröffentlichen** — pushen Sie das gebaute Paket in die npm-Registry:
### Marktplatz-Metadaten
```bash filename="Terminal"
npx twenty publish
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:
```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',
],
});
```
### Automatische Erkennung
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.).
Pakete mit dem Präfix `twenty-app-` werden vom Twenty-Marktplatzkatalog automatisch erkannt. Nach der Veröffentlichung erscheint Ihre App innerhalb weniger Minuten im Marktplatz — keine manuelle Registrierung oder Genehmigung erforderlich.
### Veröffentlichen
```bash filename="Terminal"
yarn twenty publish
```
Um unter einem bestimmten dist-tag zu veröffentlichen (z. B. `beta` oder `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### So funktioniert die Marktplatz-Erkennung
Der Twenty-Server synchronisiert seinen Marktplatzkatalog **stündlich** aus der npm-Registry.
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
```
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.
</Note>
### CI-Veröffentlichung
Das vorgefertigte Projekt enthält einen GitHub-Actions-Workflow, der bei jedem Release eine Veröffentlichung durchführt. Er führt `app:build` aus und danach `npm publish --provenance` aus dem Build-Output:
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
@@ -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 }}
```
Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `npx twenty build` und anschließend `npm publish` aus `.twenty/output`.
<Tip>
**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>
## Interne Verteilung
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, nur für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einen Twenty-Server pushen.
### Einen Tarball pushen
Erstellen Sie Ihre App und stellen Sie sie in einem Schritt auf einem bestimmten Server bereit:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Jeder Arbeitsbereich auf diesem Server kann die App anschließend über die Seite **Applications** in den Einstellungen installieren und aktualisieren.
### Versionsverwaltung
So veröffentlichen Sie ein Update:
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
2. Pushen Sie einen neuen Tarball mit `npx twenty publish --server <server-url>`
3. Arbeitsbereiche auf diesem Server sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
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`.
<Note>
Interne Apps sind auf den Server beschränkt, auf den sie gepusht werden. Sie erscheinen nicht im öffentlichen Marktplatz und können von Arbeitsbereichen auf anderen Servern nicht installiert werden.
**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.
</Note>
## App-Kategorien
## Apps installieren
Twenty organisiert Apps in drei Kategorien, basierend auf ihrer Vertriebsart:
Sobald eine App veröffentlicht (npm) oder bereitgestellt (Tarball) wurde, können Arbeitsbereiche sie über die Benutzeroberfläche installieren.
| 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** | Auf npm veröffentlichte Apps mit dem Präfix `twenty-app-`. Im Marktplatz gelistet, damit jeder Arbeitsbereich sie installieren kann. | Ja |
| **Intern** | Apps, die per Tarball auf einen bestimmten Server bereitgestellt werden. Nur für Arbeitsbereiche auf diesem Server verfügbar. | Nein |
Gehen Sie zur Seite **Einstellungen > Anwendungen** in Twenty, auf der sowohl Marktplatz- als auch per Tarball bereitgestellte Apps durchsucht und installiert werden können.
<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>
{/* 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
```
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,84 +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 fare oggi:**
* Definisci oggetti e campi personalizzati come codice (modello dati gestito)
* Crea funzioni logiche con trigger personalizzati (route HTTP, cron, eventi del database)
* Definisci le abilità per gli agenti IA
* Crea componenti front-end che vengono renderizzati all'interno della UI di Twenty
* Distribuisci la stessa app su più spazi di lavoro
## Prerequisiti
* Node.js 24+ e Yarn 4
* Docker (per il server di sviluppo locale di Twenty)
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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
Ti verrà chiesto di inserire un nome e una descrizione per la tua app. Premi **Invio** per accettare i valori predefiniti.
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"
cd my-twenty-app
yarn twenty dev
```
Lo strumento di scaffolding supporta due modalità per controllare quali file di esempio vengono inclusi:
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.
Per un output più dettagliato (log di build, richieste di sincronizzazione, tracce di errore), usa il 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
```
Da qui puoi:
<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>
```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="Output del terminale in modalità sviluppo" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Passaggio 6: Visualizza la tua app in Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
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**:
# 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="Elenco Your Apps che mostra My twenty app" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
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.
# 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="Dettagli della registrazione dell'applicazione" />
</div>
# Display commands' help
yarn twenty help
```
Fai clic su **View installed app** per vedere l'app installata. La scheda **About** mostra la versione corrente e le opzioni di gestione:
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).
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="App installata — scheda About" />
</div>
## Struttura del progetto (generata dallo scaffolder)
Passa alla scheda **Content** per vedere tutto ciò che la tua app fornisce — oggetti, campi, funzioni logiche e agenti:
Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="App installata — scheda Content" />
</div>
* 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
È tutto pronto! Modifica qualsiasi file in `src/` e le modifiche verranno rilevate automaticamente.
Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così:
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/
@@ -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
```
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`, `generated/` (client tipizzato), `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto.
* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto.
* **.oxlintrc.json** 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 due client API tipizzati in `node_modules/twenty-sdk/generated`: `CoreApiClient` (per i dati dell'area di lavoro tramite `/graphql`) e `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file tramite `/metadata`).
* `yarn twenty entity:add` aggiungerà file di definizione delle entità sotto `src/` per i tuoi oggetti, 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"
{
@@ -221,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,15 +4,75 @@ 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:
* **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.
* **Esegui il push di un tarball** — distribuisci la tua app su un server Twenty specifico per uso interno senza renderla pubblicamente disponibile.
Entrambi i percorsi partono dalla stessa fase di **build**.
## Compilazione 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
```
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.
## 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 --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
@@ -21,29 +81,69 @@ La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Q
### Requisiti
* Un account [npm](https://www.npmjs.com)
* Il nome del tuo pacchetto **deve** usare il prefisso `twenty-app-` (es. `twenty-app-postcard-sender`)
* La parola chiave `twenty-app` nell'array `keywords` del tuo `package.json` (già inclusa quando inizializzi con `create-twenty-app`)
### Passaggi
1. **Compila la tua app** — la CLI compila i tuoi sorgenti TypeScript e genera il manifest dell'applicazione:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Pubblica su npm** — esegui il push del pacchetto compilato al registro npm:
### Metadati del marketplace
```bash filename="Terminal"
npx twenty publish
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/`:
```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',
],
});
```
### Rilevamento automatico
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.).
I pacchetti con il prefisso `twenty-app-` vengono rilevati automaticamente dal catalogo del marketplace di Twenty. Una volta pubblicata, la tua app compare nel marketplace entro pochi minuti — non è richiesta alcuna registrazione o approvazione manuale.
### Pubblica
```bash filename="Terminal"
yarn twenty publish
```
Per pubblicare con un dist-tag specifico (ad es. `beta` o `next`):
```bash filename="Terminal"
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**.
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
```
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`.
</Note>
### Pubblicazione con CI
Il progetto generato include un workflow di GitHub Actions che pubblica a ogni release. Esegue `app:build`, quindi `npm publish --provenance` dall'output di build:
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
@@ -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 }}
```
Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `npx twenty build`, quindi `npm publish` da `.twenty/output`.
<Tip>
**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 interna
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo per l'azienda o build sperimentali — puoi eseguire il push di un tarball direttamente su un server Twenty.
### Push di un tarball
Compila la tua app e distribuiscila su un server specifico in un solo passaggio:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Qualsiasi spazio di lavoro su quel server può quindi installare e aggiornare l'app dalla pagina delle impostazioni **Applicazioni**.
### Gestione delle versioni
Per rilasciare un aggiornamento:
1. Incrementa il campo `version` nel tuo `package.json`
2. Esegui il push di un nuovo tarball con `npx twenty publish --server <server-url>`
3. Gli spazi di lavoro su quel server vedranno l'aggiornamento disponibile nelle proprie impostazioni
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`.
<Note>
Le app interne sono limitate al server su cui vengono caricate. Non compariranno nel marketplace pubblico e non potranno essere installate dagli spazi di lavoro su altri server.
**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.
</Note>
## Categorie di app
## Installazione delle app
Twenty organizza le app in tre categorie in base a come vengono distribuite:
Una volta che un'app è stata pubblicata (npm) o distribuita (tarball), gli spazi di lavoro possono installarla tramite l'interfaccia utente.
| 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 |
| **Pubblicata** | App pubblicate su npm con il prefisso `twenty-app-`. Elencate nel marketplace per l'installazione da parte di qualsiasi spazio di lavoro. | Sì |
| **Interna** | App distribuite tramite tarball su un server specifico. Disponibili solo per gli spazi di lavoro su quel server. | No |
Vai alla pagina **Impostazioni > Applicazioni** in Twenty, dove è possibile sfogliare e installare sia le app del marketplace sia quelle distribuite tramite tarball.
<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>
{/* 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
```
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,79 +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 face astăzi:**
* Definiți obiecte și câmpuri personalizate sub formă de cod (model de date gestionat)
* Construiți funcții logice cu declanșatoare personalizate (rute HTTP, cron, evenimente ale bazei de date)
* Definiți abilități pentru agenți de IA
* Construiți componente front-end care se afișează în interfața Twenty
* Implementați aceeași aplicație în mai multe spații de lucru
## Cerințe
* Node.js 24+ și Yarn 4
* Docker (pentru serverul local de dezvoltare Twenty)
Î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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
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.
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"
cd my-twenty-app
yarn twenty dev
```
Generatorul de schelet acceptă două moduri pentru a controla ce fișiere de exemplu sunt incluse:
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.
Pentru un output mai detaliat (jurnale de build, cereri de sincronizare, urme ale erorilor), folosiți opțiunea `--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
```
De aici puteți:
<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>
```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="Ieșirea terminalului în modul de dezvoltare" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Pasul 6: Vedeți aplicația în Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
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**:
# 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 care afișează My twenty app" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
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.
# 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="Detalii despre înregistrarea aplicației" />
</div>
# Display commands' help
yarn twenty help
```
Faceți clic pe **View installed app** pentru a vedea aplicația instalată. Fila **About** afișează versiunea curentă și opțiunile de administrare:
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).
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicație instalată — fila About" />
</div>
## Structura proiectului (generată)
Comutați la fila **Content** pentru a vedea tot ceea ce oferă aplicația — obiecte, câmpuri, funcții logice și agenți:
Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicație instalată — fila Content" />
</div>
* 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.
Totul este gata! Editați orice fișier din `src/`, iar modificările vor fi preluate automat.
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
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/
@@ -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
```
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`, `generated/` (client tipizat), `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect.
* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect.
* **.oxlintrc.json** ș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 doi clienți API tipizați în `node_modules/twenty-sdk/generated`: `CoreApiClient` (pentru datele spațiului de lucru prin `/graphql`) și `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`).
* `yarn twenty entity:add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele, funcțiile, componentele front-end, rolurile, abilitățile și altele.
## Autentificare
Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita:
* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent)
* Cheie API
Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele.
### Gestionarea spațiilor de lucru
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"
{
@@ -221,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,15 +4,75 @@ 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:
* **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.
* **Trimite un tarball** — implementează aplicația ta pe un server Twenty specific pentru utilizare internă, fără a o face disponibilă public.
Ambele căi pornesc din aceeași etapă de **build**.
## Construirea 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
```
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.
## 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 --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
@@ -21,29 +81,69 @@ Publicarea pe npm face ca aplicația ta să poată fi descoperită în marketpla
### Cerințe
* Un cont [npm](https://www.npmjs.com)
* Numele pachetului tău **trebuie** să folosească prefixul `twenty-app-` (de ex., `twenty-app-postcard-sender`)
* 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`)
### Pași
1. **Construiește-ți aplicația** — CLI compilează sursele TypeScript și generează manifestul aplicației:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Publică pe npm** — publică pachetul construit în registrul npm:
### Metadate pentru marketplace
```bash filename="Terminal"
npx twenty publish
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/`:
```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',
],
});
```
### Descoperire automată
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.).
Pachetele cu prefixul `twenty-app-` sunt descoperite automat de catalogul marketplace-ului Twenty. După publicare, aplicația ta apare în marketplace în câteva minute — fără înregistrare manuală sau aprobare necesară.
### Publicare
```bash filename="Terminal"
yarn twenty publish
```
Pentru a publica sub un dist-tag specific (de ex., `beta` sau `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### Cum funcționează descoperirea în marketplace
Serverul Twenty sincronizează catalogul marketplace-ului din registrul npm **la fiecare oră**.
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
```
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`.
</Note>
### Publicare CI
Proiectul generat include un workflow GitHub Actions care publică la fiecare lansare. Acesta rulează `app:build`, apoi `npm publish --provenance` din rezultatul build-ului:
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
@@ -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 }}
```
Pentru alte sisteme CI (GitLab CI, CircleCI etc.), se aplică aceleași trei comenzi: `yarn install`, `npx twenty build`, apoi `npm publish` din `.twenty/output`.
<Tip>
**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>
## Distribuire internă
Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți trimite un tarball direct pe un server Twenty.
### Trimite un tarball
Construiește-ți aplicația și implementeaz-o pe un server specific, într-un singur pas:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Orice spațiu de lucru de pe acel server poate apoi instala și actualiza aplicația din pagina de setări **Applications**.
### Gestionarea versiunilor
Pentru a lansa o actualizare:
1. Actualizează câmpul `version` din `package.json`
2. Trimite un tarball nou cu `npx twenty publish --server <server-url>`
3. Spațiile de lucru de pe acel server vor vedea actualizarea disponibilă în setările lor
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`.
<Note>
Aplicațiile interne sunt limitate la serverul pe care sunt trimise. Acestea nu vor apărea în marketplace-ul public și nu pot fi instalate de spațiile de lucru de pe alte servere.
**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.
</Note>
## Categorii de aplicații
## Instalarea aplicațiilor
Twenty organizează aplicațiile în trei categorii, în funcție de modul în care sunt distribuite:
După ce o aplicație este publicată (npm) sau implementată (tarball), spațiile de lucru o pot instala prin interfața utilizatorului (UI).
| 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** | Aplicații publicate pe npm cu prefixul `twenty-app-`. Listate în marketplace pentru ca orice spațiu de lucru să le poată instala. | Da |
| **Intern** | Aplicații implementate prin tarball pe un server specific. Disponibile doar pentru spațiile de lucru de pe acel server. | Nu |
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.
<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>
{/* 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
```
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,84 +4,142 @@ description: Создайте своё первое приложение Twenty
---
<Warning>
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.
Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
</Warning>
Приложения позволяют расширять Twenty с помощью пользовательских объектов, полей, логических функций, навыков ИИ и UI-компонентов — всё это управляется как код.
**Что вы можете делать уже сегодня:**
* Определяйте пользовательские объекты и поля в виде кода (управляемая модель данных)
* Создавайте логические функции с пользовательскими триггерами (HTTP-маршруты, cron, события базы данных)
* Определяйте навыки для ИИ-агентов
* Создавайте фронтенд-компоненты, которые отображаются внутри интерфейса 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
cd my-twenty-app
```
# Start dev mode: automatically syncs local changes to your workspace
Вам будет предложено ввести имя и описание вашего приложения. Нажмите **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"
cd my-twenty-app
yarn twenty dev
```
Генератор каркаса поддерживает два режима для управления тем, какие файлы-примеры включаются:
Он отслеживает исходные файлы, пересобирает при каждом изменении и автоматически синхронизирует ваше приложение с локальным сервером Twenty. В терминале должна появиться панель текущего статуса.
Для более подробного вывода (журналы сборки, запросы синхронизации, трассировки ошибок) используйте флаг `--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
```
Отсюда вы можете:
<Warning>
Режим разработки доступен только на экземплярах Twenty, запущенных в режиме разработки (`NODE_ENV=development`). Экземпляры в продакшене отклоняют запросы синхронизации из режима разработки. Используйте `yarn twenty deploy` для развёртывания на продакшен-серверах — подробности см. в разделе [Публикация приложений](/l/ru/developers/extend/apps/publishing).
</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="Вывод терминала в режиме разработки" />
</div>
# Watch your application's function logs
yarn twenty function:logs
## Шаг 6: Посмотрите своё приложение в Twenty
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
Откройте [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) в браузере. Перейдите в **Settings > Apps** и выберите вкладку **Developer**. Вы должны увидеть своё приложение в разделе **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="Список Your Apps с приложением My twenty app" />
</div>
# Execute the post-install function
yarn twenty function:execute --postInstall
Нажмите **My twenty app**, чтобы открыть его **регистрацию приложения**. Регистрация — это запись на уровне сервера, описывающая ваше приложение: его имя, уникальный идентификатор, учётные данные OAuth и источник (локальный, npm или tarball). Она хранится на сервере, а не внутри какого-либо конкретного рабочего пространства. Когда вы устанавливаете приложение в рабочее пространство, Twenty создаёт привязанное к рабочему пространству **приложение**, которое ссылается на эту регистрацию. Одну и ту же регистрацию можно установить в нескольких рабочих пространствах на одном сервере.
# 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="Сведения о регистрации приложения" />
</div>
# Display commands' help
yarn twenty help
```
Нажмите **View installed app**, чтобы посмотреть установленное приложение. Вкладка **About** показывает текущую версию и параметры управления:
Смотрите также: страницы справки CLI для [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) и [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="Установленное приложение — вкладка About" />
</div>
## Структура проекта (сгенерированного)
Переключитесь на вкладку **Content**, чтобы увидеть всё, что предоставляет ваше приложение: объекты, поля, логические функции и агенты:
Когда вы запускаете `npx create-twenty-app@latest my-twenty-app`, генератор:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Установленное приложение — вкладка Content" />
</div>
* Копирует минимальное базовое приложение в `my-twenty-app/`
* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4
* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty`
* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, предустановочную и послеустановочную функции), а также примерные файлы в зависимости от выбранного режима создания каркаса
Готово! Отредактируйте любой файл в `src/`, и изменения будут подхвачены автоматически.
Сгенерированное с помощью каркаса приложение с режимом по умолчанию `--exhaustive` выглядит так:
Перейдите к разделу [Создание приложений](/l/ru/developers/extend/apps/building) за подробным руководством по созданию объектов, логических функций, фронтенд-компонентов, навыков и многого другого.
---
## Структура проекта
Скэффолдер генерирует следующую структуру файлов (показано в режиме `--exhaustive`, который включает примеры для каждого типа сущностей):
```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
```
С `--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`, `generated/` (типизированный клиент), `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` автоматически сгенерирует два типизированных клиента API в `node_modules/twenty-sdk/generated`: `CoreApiClient` (для данных рабочего пространства через `/graphql`) и `MetadataApiClient` (для конфигурации рабочего пространства и загрузки файлов через `/metadata`).
* `yarn twenty entity: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"
{
@@ -221,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,15 +4,75 @@ description: Распространяйте своё приложение Twenty
---
<Warning>
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.
Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
</Warning>
## Обзор
После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/building), у вас есть два пути для его распространения:
* **Разверните tar-архив** — загрузите своё приложение напрямую на конкретный сервер Twenty для внутреннего или частного использования.
* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить.
* **Отправить tarball** — разверните приложение на конкретном сервере Twenty для внутреннего использования, не делая его общедоступным.
Оба пути начинаются с одного и того же шага **build**.
## Сборка вашего приложения
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.
## Развертывание на сервер (tarball)
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty.
### Требования
Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`.
Добавьте remote:
```bash filename="Terminal"
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
@@ -21,29 +81,69 @@ description: Распространяйте своё приложение Twenty
### Требования
* Учётная запись [npm](https://www.npmjs.com)
* Название вашего пакета **должно** использовать префикс `twenty-app-` (например, `twenty-app-postcard-sender`)
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
### Шаги
1. **Соберите приложение** — CLI компилирует исходные файлы TypeScript и генерирует манифест приложения:
```bash filename="Terminal"
yarn twenty build
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
2. **Опубликуйте в npm** — отправьте собранный пакет в реестр npm:
### Метаданные маркетплейса
```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',
],
});
```
### Автоматическое обнаружение
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.).
Пакеты с префиксом `twenty-app-` автоматически обнаруживаются каталогом маркетплейса Twenty. После публикации ваше приложение появится в маркетплейсе в течение нескольких минут — ручная регистрация или одобрение не требуются.
### Publish
```bash filename="Terminal"
yarn twenty publish
```
Чтобы опубликовать с определённым dist-tag (например, `beta` или `next`):
```bash filename="Terminal"
yarn twenty publish --tag beta
```
### Как работает обнаружение приложений в маркетплейсе
Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**.
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>
Если ваше приложение не определяет `aboutDescription` в `defineApplication()`, маркетплейс автоматически использует `README.md` вашего пакета из npm в качестве содержимого страницы «О приложении». Это означает, что вы можете поддерживать единый README как для npm, так и для маркетплейса Twenty. Если вы хотите другое описание в маркетплейсе, явно задайте `aboutDescription`.
</Note>
### Публикация через CI
Сгенерированный шаблоном проект включает workflow GitHub Actions, который выполняет публикацию при каждом релизе. Он запускает `app:build`, затем `npm publish --provenance` из результатов сборки:
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 }}
```
Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `npx twenty build`, затем `npm publish` из `.twenty/output`.
<Tip>
**npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Tip>
## Внутреннее распространение
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятия или экспериментальные сборки — вы можете отправить tarball напрямую на сервер Twenty.
### Отправка tarball
Соберите приложение и разверните его на конкретном сервере одним шагом:
```bash filename="Terminal"
npx twenty publish --server <server-url>
```
Любое рабочее пространство на этом сервере сможет установить и обновить приложение на странице настроек **Applications**.
### Управление версиями
Чтобы выпустить обновление:
1. Обновите значение поля `version` в файле `package.json`
2. Отправьте новый tarball командой `npx twenty publish --server <server-url>`
3. Рабочие пространства на этом сервере увидят доступное обновление в своих настройках
Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `yarn twenty build`, затем `npm publish` из `.twenty/output`.
<Note>
Внутренние приложения ограничены сервером, на который они отправлены. Они не появятся в публичном маркетплейсе и не могут быть установлены рабочими пространствами на других серверах.
**npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Note>
## Категории приложений
## Установка приложений
Twenty группирует приложения в три категории в зависимости от способа их распространения:
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
| Категория | Как это работает | Отображается в маркетплейсе? |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| **Разработка** | Локальные приложения в режиме разработки, запущенные через `yarn twenty dev`. Используются для сборки и тестирования. | Нет |
| **Опубликовано** | Приложения, опубликованные в npm с префиксом `twenty-app-`. Отображаются в маркетплейсе, доступные для установки любому рабочему пространству. | Да |
| **Внутренний** | Приложения, развернутые через tarball на конкретном сервере. Доступны только рабочим пространствам на этом сервере. | Нет |
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
<Tip>
Начните в режиме **Разработка** во время создания приложения. Когда будет готово, выберите **Опубликовано** (npm) для широкого распространения или **Внутренний** (tarball) для приватного развертывания.
</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

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