# Stage, preview, and publish privately

Private publishing needs no certification. Certification only matters if you want a public listing on the Connect marketplace. What does gate a private Connector is the validation pipeline that runs when you request a preview or publish, and its report is the result you act on.

Zen Electron's team ran `commercetools connect validate` locally and saw critical findings. The preview request for the same tag passed. By the end of this page, you know which result to trust, and you have a privately published Connector ready to install.

This page applies the lifecycle from [Choose where it runs and how to adopt it](/learning-integrate-with-commercetools/integration-essentials/choose-where-it-runs.md#the-connect-lifecycle). You act as the Creator: you turn a repository tag into a ConnectorStaged, preview it, and publish it as a Connector. The next page installs it as a Client.

## The Creator flow

```mermaid title="From tag to private Connector"
flowchart LR
 Tag[Git tag] --> Staged[ConnectorStaged]
 Staged --> Preview[Request preview]
 Preview --> Pipeline{Validation pipeline}
 Pipeline -->|isPreviewable: true| PreviewDeploy[preview Deployment]
 Pipeline -->|isPreviewable: false| Fix[Read the report and fix]
 Fix --> Tag
 PreviewDeploy --> Publish[Publish, certification false]
 Publish --> Private[Private Connector]
 Staged -.-> Certify[Publish, certification true]
 Certify -.-> Public[Certification review, then public listing]
```

A **ConnectorStaged** is the editable draft: a name, a creator, a repository URL, and a tag. A **Connector** is the read-only result of publishing it. You request a preview and publish through the ConnectorStaged, and every request runs the [validation pipeline](/connect/validation-pipeline.md) against the tag.

This page shows each step with the CLI first and the Connect API second. [Connect workflow](/connect/connect-workflow.md#run-lifecycle-tasks-with-the-cli-api-or-merchant-center) maps every lifecycle task to its CLI command, API call, and Merchant Center screen.

## Before you start

You need the following:

- The `v0.2.0` tag from [Implement and test the event app to the contract](/learning-integrate-with-commercetools/deploy-and-build-connectors/implement-and-test-the-event-app.md), pushed to your repository.
- The CLI's API Client from [Start from the email template](/learning-integrate-with-commercetools/deploy-and-build-connectors/start-from-the-email-template.md#create-an-api-client-for-the-cli). This page uses its `manage_connectors` scope, and the preview Deployment uses `manage_connectors_deployments` and `manage_api_clients`.
- Repository access for Connect. If your repository is private, Connect needs its SSH URL and read access for the [connect-mu](https://github.com/connect-mu) machine user. [Create an Organization Connector](/merchant-center/connect.md#create-an-organization-connector) describes the SSH and HTTPS options.

The Connect API examples on this page use `curl` and `jq`. Get a token once and reuse it:

```bash
export CTP_PROJECT_KEY=<project-key>
export CTP_REGION=europe-west1.gcp
export CTP_CLIENT_ID=<client-id>
export CTP_CLIENT_SECRET=<client-secret>
export CONNECT_URL="https://connect.$CTP_REGION.commercetools.com"

TOKEN=$(curl -sS --fail-with-body -u "$CTP_CLIENT_ID:$CTP_CLIENT_SECRET" 
  -X POST "https://auth.$CTP_REGION.commercetools.com/oauth/token" 
  -d "grant_type=client_credentials" | jq -r .access_token)
```

Use the Connect API host for your Project's Region. [Hosts and authorization](/connect/hosts-and-authorization.md#hosts) lists them.

## Create a ConnectorStaged

### With the Connect CLI

```bash
commercetools connect connectorstaged create 
  --repository-url git@github.com:<your-account>/zen-order-confirmation.git 
  --repository-tag v0.2.0 
  --creator-email you@example.com 
  --name "Zen Electron order confirmation" 
  --key zen-order-confirmation 
  --integration-types email 
  --supported-regions europe-west1.gcp
```

Set the flags as follows. The [CLI reference](/connect/cli.md#commercetools-connect-connectorstaged-create) defines every flag.

- **`--key`**: choose a stable identifier now, because every later command uses it. This is easier than passing IDs around.
- **`--integration-types email`**: classifies the Connector without changing how the Connector runs.
- **`--supported-regions`**: set the Regions you've tested. Leave the flag out to allow every supported Region.

Check the result:

```bash
commercetools connect connectorstaged describe --key zen-order-confirmation
```

### With the Connect API

The CLI calls the [Create ConnectorStaged](/connect/connectors-staged.md#create-connectorstaged) endpoint. The endpoint isn't scoped to a Project path; your token's scope decides which Project owns the ConnectorStaged.

```bash
curl -sS --fail-with-body -X POST "$CONNECT_URL/connectors/drafts" 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: application/json" 
  -d '{
    "key": "zen-order-confirmation",
    "name": "Zen Electron order confirmation",
    "integrationTypes": ["email"],
    "creator": { "email": "you@example.com" },
    "repository": {
      "url": "git@github.com:<your-account>/zen-order-confirmation.git",
      "tag": "v0.2.0"
    },
    "privateProjects": ["europe-west1.gcp:<project-key>"],
    "supportedRegions": ["europe-west1.gcp"]
  }' | jq '{key, version, status, isPreviewable}'
```

[Create ConnectorStaged](/connect/connectors-staged.md#create-connectorstaged) lists the required fields. This example sets `privateProjects`, in the format `{region}:{projectKey}`, so that your Project can deploy the ConnectorStaged for preview and the published private Connector. The CLI's `connectorstaged create` has no flag for it, so CLI users add Projects with the [Add Project to Private Connector](/connect/connectors-staged.md#add-project-to-private-connector) update action. The response is a ConnectorStaged with `status: "Draft"` and `isPreviewable: "none"`. Use one route, not both: the second request fails because the key already exists.

In the Merchant Center, the same step is **Create connector**. See [Create an Organization Connector](/merchant-center/connect.md#create-an-organization-connector) for the fields.

## What the validation pipeline checks

Every preview and publish request runs the [validation pipeline](/connect/validation-pipeline.md#pipeline-steps) against the tag. A private preview or publish skips the [Test Connector](/connect/validation-pipeline.md#test-connector) step and the certification review, because both apply only when you request [certification](/connect/certification.md).

The dependency updates you made in [Start from the email template](/learning-integrate-with-commercetools/deploy-and-build-connectors/start-from-the-email-template.md#validate-and-update-dependencies) are why the [SCA step](/connect/validation-pipeline.md#software-composition-analysis-sca) passes here.

### Trust the remote report

`commercetools connect validate` runs the same steps locally, but its SAST can be stricter than the remote gate, so it might report code smells that the remote preview accepts. Use the local run as an early warning. The remote report decides whether you can preview and publish.

Read the remote result in the ConnectorStaged's `previewableReport` or `publishingReport`, or on the Connector's **Preview Report** and **Publish Report** tabs in the Merchant Center. [Check validation status](/connect/validation-pipeline.md#check-validation-status) describes both routes. Since 2 July 2026, the Merchant Center [reports update in real time](/connect/releases/2026-07-02-added-real-time-reports-for-validation-and-deployment-operations) as each step runs.

```bash
curl -sS --fail-with-body "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" 
  | jq '{isPreviewable, status, previewable: .previewableReport.entries, publishing: .publishingReport.entries}'
```

A passing preview report lists `Information` entries, with titles such as `SAST and SCA analysis check succeeded`. Entry titles can change, so check each entry's `type` ([ConnectorReportEntryType](/connect/connectors-staged.md#connectorreportentrytype)) rather than matching exact `title` text.

## Preview the ConnectorStaged

A `preview` Deployment runs the ConnectorStaged's tag in your Project before you publish. The `privateProjects` list that you set when you created the ConnectorStaged decides which Projects can create it. [Preview](/connect/deployment-behavior-and-environments.md#preview) describes the Deployment type, and [Deploy previewable ConnectorStaged](/connect/getting-started.md#deploy-previewable-connectorstaged) states the Project rule.

### With the Connect CLI

`connectorstaged preview` requests previewable status, runs the validations, and creates the `preview` Deployment in one command. Pass a value for each configuration key in your `connect.yaml`. The provider call is stubbed, so the provider key and template IDs can be placeholders:

```bash
CONFIG=(
  --configuration "mail-sender.CTP_REGION=europe-west1.gcp"
  --configuration "mail-sender.EMAIL_PROVIDER_API_KEY=stub"
  --configuration "mail-sender.SENDER_EMAIL_ADDRESS=orders@example.com"
  --configuration "mail-sender.CUSTOMER_REGISTRATION_TEMPLATE_ID=stub"
  --configuration "mail-sender.CUSTOMER_EMAIL_TOKEN_CREATION_TEMPLATE_ID=stub"
  --configuration "mail-sender.CUSTOMER_PASSWORD_TOKEN_CREATION_TEMPLATE_ID=stub"
  --configuration "mail-sender.ORDER_CONFIRMATION_TEMPLATE_ID=order-confirmation"
  --configuration "mail-sender.ORDER_STATE_CHANGE_TEMPLATE_ID=stub"
  --configuration "mail-sender.ORDER_REFUND_TEMPLATE_ID=stub"
)

commercetools connect connectorstaged preview 
  --key zen-order-confirmation 
  --deployment-key zen-order-confirmation-preview 
  --region europe-west1.gcp 
  "${CONFIG[@]}"
```

Each value uses the format `{applicationName}.{key}={value}`, because the keys belong to the `mail-sender` application. Keep the `CONFIG` array: the next pages reuse it.

### With the Connect API

The API splits the same work into two calls. First, apply the [Preview Connector](/connect/connectors-staged.md#preview-connector) update action with the ConnectorStaged's current `version`:

```bash
VERSION=$(curl -sS --fail-with-body "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" | jq .version)

curl -sS --fail-with-body -X POST "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: application/json" 
  -d "{\"version\": $VERSION, \"actions\": [{\"action\": \"updatePreviewable\"}]}" 
  | jq .isPreviewable
```

While validation runs, `isPreviewable` is `"pending"`. [Monitor Preview status](/connect/getting-started.md#monitor-preview-status) describes the transitions, and [IsPreviewable](/connect/connectors-staged.md#ispreviewable) defines the values. Poll the ConnectorStaged until `isPreviewable` isn't `"pending"`, then read the result:

```bash
until [ "$(curl -sS --fail-with-body "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" | jq -r .isPreviewable)" != "pending" ]; do
  sleep 30
done

curl -sS --fail-with-body "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" 
  | jq '{isPreviewable, report: .previewableReport.entries}'
```

Continue only if `isPreviewable` is `"true"`. If it's `"false"`, read the entries with `type: "Error"` and fix the cause before you request a new preview.

Then create a Deployment that references the ConnectorStaged with `staged: true` and `type: preview`. Define the configuration values once as a JSON array. The install and operate pages reuse it, in the same way that the CLI examples reuse `CONFIG`:

```bash
CONFIGURATIONS='[
  {
    "applicationName": "mail-sender",
    "standardConfiguration": [
      { "key": "CTP_REGION", "value": "europe-west1.gcp" }
    ],
    "securedConfiguration": [
      { "key": "EMAIL_PROVIDER_API_KEY", "value": "stub" },
      { "key": "SENDER_EMAIL_ADDRESS", "value": "orders@example.com" },
      { "key": "ORDER_CONFIRMATION_TEMPLATE_ID", "value": "order-confirmation" },
      { "key": "CUSTOMER_REGISTRATION_TEMPLATE_ID", "value": "stub" },
      { "key": "CUSTOMER_EMAIL_TOKEN_CREATION_TEMPLATE_ID", "value": "stub" },
      { "key": "CUSTOMER_PASSWORD_TOKEN_CREATION_TEMPLATE_ID", "value": "stub" },
      { "key": "ORDER_STATE_CHANGE_TEMPLATE_ID", "value": "stub" },
      { "key": "ORDER_REFUND_TEMPLATE_ID", "value": "stub" }
    ]
  }
]'

jq -n --argjson configurations "$CONFIGURATIONS" '{
  key: "zen-order-confirmation-preview",
  type: "preview",
  connector: { key: "zen-order-confirmation", staged: true },
  region: "europe-west1.gcp",
  configurations: $configurations
}' | curl -sS --fail-with-body -X POST "$CONNECT_URL/$CTP_PROJECT_KEY/deployments" 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: application/json" 
  -d @- | jq '{key, type, status}'
```

Keep the `CONFIGURATIONS` variable: the next pages reuse it.

A ConnectorStaged can be deployed only as `preview`. [Environments](/connect/deployment-behavior-and-environments.md#environments) explains which types need a published Connector.

### Test the preview, then delete it

A preview Deployment behaves like any other Deployment, so the checks on [Install and test your Connector](/learning-integrate-with-commercetools/deploy-and-build-connectors/install-and-test-your-connector.md#verify-the-resource-not-the-status) apply: confirm the Subscription exists, import an Order, and find the stub's log line. Then delete it:

```bash
commercetools connect deployment delete --key zen-order-confirmation-preview
```

The command waits at a `y/n` confirmation prompt, which can look like a hang in a script.

If provisioning fails, the report might not say why. For example, a preview Deployment with placeholder API Client credentials can fail with no reason in its report. Read the Deployment's logs and recheck its configuration values before you change code. [Operate a Deployment](/learning-integrate-with-commercetools/deploy-and-build-connectors/operate-a-deployment.md#debug-a-failed-deployment) covers this.

Every new tag needs a new preview request. A preview Deployment keeps running the tag it was created from.

## Publish privately

Publish with `certification: false` to create a private Connector:

```bash
commercetools connect connectorstaged publish --key zen-order-confirmation
```

The API equivalent is the [Publish](/connect/connectors-staged.md#publish) update action:

```bash
VERSION=$(curl -sS --fail-with-body "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" | jq .version)

curl -sS --fail-with-body -X POST "$CONNECT_URL/connectors/drafts/key=zen-order-confirmation" 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: application/json" 
  -d "{\"version\": $VERSION, \"actions\": [{\"action\": \"publish\", \"certification\": false}]}" 
  | jq '{status, isPreviewable}'
```

Publishing runs the validation pipeline again, and the Publish action blocks further updates to the ConnectorStaged until the process completes. Poll `status` until it isn't `Processing` ([ConnectorStatus](/connect/connectors-staged.md#connectorstatus) defines the values), then read `publishingReport` for the result. To wait for it in a script, reuse the polling loop from the preview step with `.status` and `Processing`.

Private publishing and a passing preview report don't run the remote automated tests required for certification. Before installing for real use, test the application behavior separately: the next page verifies an event and a stubbed provider call, not delivery through a real email provider. For Connectors with other application types, [test service, event, and job applications locally](/connect/test-applications-locally.md) against their respective contracts.

A private Connector is restricted to the Projects in its `privateProjects` list. The reliable approach is to list every intended Project, including the creator Project, either in the create request or with the [Add Project to Private Connector](/connect/connectors-staged.md#add-project-to-private-connector) update action before installation. Project identifiers use `{region}:{projectKey}`, for example `europe-west1.gcp:zen-electron-dev`.

With an empty list, only the creator can access the Connector, as [ConnectorStagedDraft](/connect/connectors-staged.md#connectorstageddraft) describes. Don't rely on that for any other Project: add the Project to `privateProjects` before you install there.

### Deleting a ConnectorStaged

The CLI has no delete command for a ConnectorStaged. Delete the Deployments first, then follow [Delete ConnectorStaged](/connect/connectors-staged.md#delete-connectorstaged) with the Connect API. Because the request also removes the published Connector, confirm that no Project still needs the Connector before you delete.

## Certification, briefly

Certification applies only to public listings on the Connect marketplace. [Scope of certification](/connect/certification.md#scope-of-certification) describes the review, and [Requirements for certification](/connect/certification.md#requirements-for-certification) lists what the repository must contain. Every new tag of a public Connector needs [re-certification](/connect/certification.md#when-to-request-re-certification). Zen Electron's Connector runs only in its own Projects, so Zen Electron's team skips certification.

## Production-readiness checklist

The validation pipeline checks that the Connector builds and passes security scans. Private publishing skips remote automated certification tests and doesn't prove that the Connector behaves correctly. The provider call in this module is stubbed, so the preview and install exercises don't prove that a real provider sends email. The [`commercetools-connect`](/learning-integrate-with-commercetools/dev-tooling/skills/commercetools-connect.md) skill's production-readiness checklist covers that gap and gives the full criteria and implementation patterns. The following items are recommended practice, not platform requirements, and are the ones this Connector needs most:

- **Delivery policy**: the README records the acknowledgment policy and deduplication for each email type. [Implement and test the event app to the contract](/learning-integrate-with-commercetools/deploy-and-build-connectors/implement-and-test-the-event-app.md#record-the-policy-and-know-the-alternative) did this.
- **Message-type and self-change filtering**: check the Connector against the skill's Correctness items.
- **Least-privilege scopes and secured secrets**: check `inheritAs.apiClient.scopes` and `securedConfiguration` against the skill's Security items.
- **Structured logs with correlation keys**: log lines carry no Customer data.
- **Repeatable lifecycle scripts**: verify the resource after deployment instead of assuming it from the Deployment status.
- **A runbook in the README**: cover what happens when the provider is down and how an operator finds missed sends.

## Worked example: publish Zen Electron's Connector

1. Create the ConnectorStaged `zen-order-confirmation` from tag `v0.2.0`.
2. Run `connectorstaged preview` with the `CONFIG` array. The preview report passes.
3. Check the preview Deployment as described on the next page, then delete it.
4. Publish with `certification: false` and confirm `status: "Published"`.

Zen Electron now has a private Connector, and the Project that created it can install it. No certification was needed. To install it in another Zen Electron Project, check the `privateProjects` note in [Publish privately](/learning-integrate-with-commercetools/deploy-and-build-connectors/stage-preview-and-publish-privately.md#publish-privately) first.

## Key takeaways

- A ConnectorStaged is the editable draft that references a repository tag. Publishing it creates the read-only Connector.
- Every preview and publish request runs the validation pipeline. Automated tests run only for certification.
- The remote report is the gate. Local `connect validate` can be stricter, so use it as an early warning.
- A ConnectorStaged can be deployed only as `preview`. Delete preview Deployments after testing, and request a new preview for each new tag.
- Publishing with `certification: false` creates a private Connector without certification. Certification is only for public listings.
- Deleting a ConnectorStaged also deletes its published Connector, and the CLI can't do it.

## Related pages

- [Area overview page with navigation](/learning-integrate-with-commercetools.md)
- [Previous page: Implement and test the event app to the contract](/learning-integrate-with-commercetools/deploy-and-build-connectors/implement-and-test-the-event-app.md)
- [Next page: Install and test your Connector](/learning-integrate-with-commercetools/deploy-and-build-connectors/install-and-test-your-connector.md)
- [Search documentation and API specs](/search.md)