--- title: Publishing icon: "upload" description: Distribute your Twenty app to the marketplace or deploy it internally. --- ## Overview Once your app is [built and tested locally](/developers/extend/apps/getting-started/concepts), 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. 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 dev: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 publish 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 --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 app:publish --private # To deploy to a specific remote: # yarn twenty app:publish --private --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. ### Version management When updating an already deployed tarball app, the server requires the `version` in `package.json` to be **strictly higher** (per [semver](https://semver.org) ordering) than the currently deployed version. Re-deploying the same version, or pushing a lower one, is rejected before the tarball is stored — you'll see a `VERSION_ALREADY_EXISTS` error from the CLI. To release an update: 1. Bump the `version` field in your `package.json` (e.g. `1.2.3` → `1.2.4`, `1.3.0`, or `2.0.0`) 2. Run `yarn twenty app:publish --private` (or `yarn twenty app:publish --private --remote production`) 3. Workspaces that have the app installed and enabled auto-upgrade for it (in the app's Settings tab) are upgraded automatically in the background; the others will see the upgrade available in their settings Pre-release tags work as expected: bumping `1.0.0-rc.1` → `1.0.0-rc.2` is allowed, and a final release like `1.0.0` is correctly recognized as higher than `1.0.0-rc.5`. The version in `package.json` must itself be a valid semver string. {/* TODO: add screenshot of the Upgrade button */} ### Server version compatibility If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`: ```json filename="package.json" { "name": "twenty-my-app", "version": "1.0.0", "engines": { "node": "^24.5.0", "twenty": ">=2.3.0" } } ``` The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns: | Range | Meaning | |-------|---------| | `>=2.3.0` | Any server from 2.3.0 onward | | `>=2.3.0 <3.0.0` | 2.3.0 or later, but below the next major | | `^2.3.0` | Same as `>=2.3.0 <3.0.0` | **What happens at deploy and install time:** - If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version. - If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps). - If the server has no `APP_VERSION` configured, the check is skipped. The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility. ## Automated CI/CD (scaffolded workflows) Apps generated with `create-twenty-app` ship with three GitHub Actions workflows out of the box, under `.github/workflows/`. CI runs with no setup, CD requires a single secret, and publishing to npm requires a one-time npm trusted-publisher setup. ### CI — `ci.yml` Runs integration tests on every push to `main` and every pull request. **What it does:** 1. Checks out your app's source. 2. Spawns an isolated Twenty test instance using the `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` composite action (the CI equivalent of `yarn twenty docker:start --test`). 3. Enables Corepack, sets up Node.js from your `.nvmrc`, and installs dependencies with `yarn install --immutable`. 4. Runs `yarn test`, passing `TWENTY_API_URL` and `TWENTY_API_KEY` from the spawned instance so your tests can talk to a real server. **Config knobs:** - `TWENTY_VERSION` (env, defaults to `latest`) — pin the Twenty server version used in CI by editing this in `ci.yml`. - Concurrency is grouped by `github.ref` and cancels in-progress runs on new pushes. No secrets are required — the test instance is ephemeral and lives only for the duration of the job. ### CD — `cd.yml` Deploys your app to a configured Twenty server on every push to `main`, and optionally from a pull request when the `deploy` label is applied. **What it does:** 1. Checks out the PR head (for labeled PRs) or the pushed commit. 2. Runs `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — the CI equivalent of `yarn twenty app:publish --private`. 3. Runs `twentyhq/twenty/.github/actions/install-twenty-app@main` so the newly deployed version is installed into the target workspace. **Required configuration:** | Setting | Where | Purpose | |---------|-------|---------| | `TWENTY_DEPLOY_URL` | `env` in `cd.yml` (defaults to `http://localhost:3000`) | The Twenty server to deploy to. Change this to your real server URL before first use. | | `TWENTY_DEPLOY_API_KEY` | GitHub repo **Settings → Secrets and variables → Actions** | API key with deploy permission on the target server. | The default `TWENTY_DEPLOY_URL` of `http://localhost:3000` is a placeholder — it will not reach anything from a GitHub-hosted runner. Update it to your server's public URL (or use a self-hosted runner with network access) before enabling CD. **Triggering a preview deploy from a PR:** Add the `deploy` label to a pull request. The `if:` guard in `cd.yml` will run the job for that PR using the PR's head commit, letting you validate a change on the target server before merging. ### Publish — `publish.yml` Publishes your app to npm with provenance when you push a version tag (e.g. `v1.0.0`), or when you run the workflow manually from the Actions tab. **What it does:** 1. Checks out your app, sets up Node.js, and updates npm (trusted publishing requires npm 11.5.1 or later). 2. Runs `yarn twenty app:publish`, which builds the app and publishes `.twenty/output` to npm. In CI it automatically adds `--provenance` and `--access public`, so no flags are needed in the workflow. **One-time setup:** On npmjs.com open your package > **Settings → Trusted Publisher** and register this repository with the `publish.yml` workflow (see the [npm trusted publishing docs](https://docs.npmjs.com/trusted-publishers)). Publishing with provenance certifies which GitHub repository built the package, which is also how you claim ownership of your app in a Twenty marketplace. npm only accepts provenance from **public** source repositories. If you publish from a private repo, npm rejects the OIDC provenance bundle with an `E422 ... Unsupported GitHub Actions source repository visibility: "private"` error. To publish from a private repo, opt out of provenance by setting `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'` in the publish step's `env` (a commented-out hint is included in the scaffolded `publish.yml`): ```yaml filename=".github/workflows/publish.yml" - name: Publish to npm env: TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true' run: yarn twenty app:publish ``` ### Pinning the reusable actions The `ci.yml` and `cd.yml` workflows reference reusable actions at `@main`, so action updates in the `twentyhq/twenty` repo are picked up automatically. If you want deterministic builds, replace `@main` with a commit SHA or release tag on each `uses:` line. ## Publishing to npm Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twenty workspace can browse, install, and upgrade marketplace apps directly from the UI. ### Requirements - An [npm](https://www.npmjs.com) account - The `twenty-app` keyword in your `package.json` `keywords` array (add it manually — it is not included by default in the `create-twenty-app` template) ```json filename="package.json" { "name": "twenty-app-postcard-sender", "version": "1.0.0", "keywords": ["twenty-app"] } ``` ### Marketplace metadata The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logo` and `galleryImages` to reference images from the `public/` folder: ```ts src/application-config.ts export default defineApplication({ universalIdentifier: '...', displayName: 'My App', description: 'A great app', logo: 'public/logo.png', galleryImages: [ 'public/screenshot-1.png', 'public/screenshot-2.png', ], }); ``` See the [defineApplication accordion](/developers/extend/apps/config/application#marketplace-metadata) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.). #### Recommended gallery image dimensions The marketplace renders `galleryImages` in a fixed `8:5` container (for example, `1600×1000 px`). Gallery images of any aspect ratio are displayed in full and are never cropped, but anything significantly taller or narrower than `8:5` will show empty bands on the sides. #### Image size limit The `logo` and each `galleryImages` file must not exceed **10 MB**. Larger files are skipped when the marketplace rehosts your published assets, so they will not be displayed. ### Publish ```bash filename="Terminal" yarn twenty app:publish ``` To publish under a specific dist-tag (e.g., `beta` or `next`): ```bash filename="Terminal" yarn twenty app: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 dev:catalog-sync # To target a specific remote: # yarn twenty dev:catalog-sync --remote production ``` The metadata shown in the marketplace comes from your `defineApplication()` config — see [Marketplace metadata](#marketplace-metadata) above. 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`. ### CI publishing The scaffolded `publish.yml` workflow described above publishes to npm automatically on version tags, with provenance. Because `yarn twenty app:publish` adds `--provenance` and `--access public` for you when it runs in CI, the workflow needs no npm flags — only the one-time trusted-publisher setup. For other CI systems (GitLab CI, CircleCI, etc.), run `yarn install` then `yarn twenty app:publish`. Provenance is emitted when the environment can mint an OIDC token and skipped automatically otherwise. **npm 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. It is also what lets you claim ownership of your app in a Twenty marketplace. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for details. ## Installing apps Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI. Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed. {/* TODO: add screenshot of the UI when the app is registered */} You can also install apps from the command line: ```bash filename="Terminal" yarn twenty app:install ``` The server enforces semver versioning on install, mirroring the rules on deploy: - Installing the same version that is already installed in your workspace is rejected with an `APP_ALREADY_INSTALLED` error. - Installing a lower version than the one currently installed is rejected with a `CANNOT_DOWNGRADE_APPLICATION` error. To install a newer version, deploy or publish it first, then re-run `yarn twenty app:install`.