> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publishing

> 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" theme={null}
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" theme={null}
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" theme={null}
yarn twenty app:publish --private
# To deploy to a specific remote:
# yarn twenty app:publish --private --remote production
```

### Sharing a deployed app

<Warning>
  Sharing private (tarball) apps across workspaces is an **Enterprise** feature. The **Distribution** tab will show an upgrade prompt instead of the share controls until your workspace has a valid Enterprise key. See [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to activate it.
</Warning>

Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. Once your workspace is on the Enterprise plan, you can share a deployed app like this:

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

<Note>
  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.
</Note>

### 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" theme={null}
{
  "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.

<Note>
  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.
</Note>

## 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.                                  |

<Note>
  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.
</Note>

**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.

<Note>
  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" theme={null}
        - name: Publish to npm
          env:
            TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'
          run: yarn twenty app:publish
  ```
</Note>

### 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" theme={null}
{
  "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 theme={null}
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`).

<Note>
  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.
</Note>

#### 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" theme={null}
yarn twenty app:publish
```

To publish under a specific dist-tag (e.g., `beta` or `next`):

```bash filename="Terminal" theme={null}
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" theme={null}
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.

<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 `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.

<Note>
  **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.
</Note>

## 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.

You can also install apps from the command line:

```bash filename="Terminal" theme={null}
yarn twenty app:install
```

<Note>
  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`.
</Note>
