# Packaging Your Commerce App

Package your Commerce App into a CAP (Commerce App Package) for distribution.

## CAP Structure Requirements

Your packaged Commerce App must conform to the CAP structure defined in [Architecture Overview](/docs/commerce/b2c-commerce/guide/architecture.md#commerce-app-package-cap-structure). The ZIP must contain a single root directory named `commerce-{app-name}-app-v{version}/` and contain only the recognized subdirectories.

## Required Files

- `commerce-app.json` at the root of the package (see [commerce-app.json Schema](/docs/commerce/b2c-commerce/guide/reference.md#commerce-appjson-schema))
- `README.md` at the root of the package
- `app-configuration/tasksList.json` defining the setup wizard
- `icons/` directory with at least one icon file named `{isv-name}.{ext}` (PNG, SVG, JPG, or JPEG)
- At least one of: `cartridges/` (back-end logic), `storefront-next/` (frontend extensions), or `impex/` (configuration data)

## Optional Files

- `app-configuration/adminComponents.json` — declares Business Manager admin UI components shown to merchants after install. Use this to ship `storefrontComponentVisibility` toggles that let merchants enable or disable individual UI targets. See [adminComponents.json Schema](/docs/commerce/b2c-commerce/guide/reference.md#admincomponentsjson-schema) for the field reference and a complete example.
- `app-configuration/translations/<locale>.json` — localized strings for `tasksList.json` and (when shipped) `adminComponents.json`. See [App-shipped string localization](/docs/commerce/b2c-commerce/guide/reference.md#app-shipped-string-localization).

## The tasksList.json

This file defines the post-install configuration steps that appear in the Business Manager setup wizard. Each task has a stable `taskKey`, a task number, name, description, a deep link to the relevant BM page, and an initial status. The `taskKey` must be a unique snake_case identifier (matching `^[a-z][a-z0-9_]*$`); locale files reference it to translate the `name` and `description` fields. See [App-shipped string localization](/docs/commerce/b2c-commerce/guide/reference.md#app-shipped-string-localization).

Example:

```json
[
  {
    "taskKey": "enable_tax_service",
    "taskNumber": 1,
    "name": "Enable Tax Service",
    "description": "Navigate to Site Preferences and enable the tax calculation service",
    "link": "/on/demandware.store/Sites-Site/default/ViewApplication-BM?..."
  },
  {
    "taskKey": "configure_api_credentials",
    "taskNumber": 2,
    "name": "Configure API Credentials",
    "description": "Enter your account ID and license key in site preferences",
    "link": "/on/demandware.store/Sites-Site/default/ViewApplication-BM?..."
  }
]
```

## Localizing app-shipped strings

To render `tasksList.json` and `adminComponents.json` strings in additional locales, ship locale files under `app-configuration/translations/`. Each locale file uses two reserved top-level namespaces — `tasks` (keyed by `taskKey`) and `adminComponents` (keyed by `componentKey`) — and Business Manager falls back to literal English when no translation is shipped. `en-US.json` is required if the directory exists. See [App-shipped string localization](/docs/commerce/b2c-commerce/guide/reference.md#app-shipped-string-localization) for the full schema, supported locales, fallback chain, and CI validation rules.

## IMPEX Structure

The `impex/` directory contains site-scoped XML organized into `install/` and `uninstall/` subdirectories. The `install/` directory is processed during app installation; the `uninstall/` directory during removal.

Within `install/`, use standard Commerce Cloud IMPEX conventions:

- `jobs.xml` for job definitions
- `services.xml` for service credentials, profiles, and definitions
- `meta/system-objecttype-extensions.xml` for system object attribute extensions
- `meta/custom-objecttype-definitions.xml` for custom object type definitions
- `sites/SITEID/preferences.xml` for site preferences

### Placeholder Replacement

The install job automatically replaces `SITEID` and `LIBRARYID` placeholders in both **file paths** and **file content** during processing. Placeholder replacement applies to the following text file types: `.xml`, `.csv`, `.txt`, `.json`, `.properties`, `.js`. Binary files are copied as-is without replacement.

- Use `SITEID` in directory paths (for example, `sites/SITEID/preferences.xml`) and inside XML content for site-scoped data.
- Use `LIBRARYID` for content library references. The library ID is resolved from the target site's content library at install time.
- Empty text files are skipped during processing.

**Example placeholder usage in `preferences.xml`:**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<preferences xmlns="http://www.demandware.com/xml/impex/preferences/2007-12-31">
    <custom-preferences>
        <custom-preference preference-id="myAppEnabled"
            site-id="SITEID">true</custom-preference>
    </custom-preferences>
</preferences>
```

### Install IMPEX

Install IMPEX files are processed via Site Import in **merge mode**, meaning they add or update data without deleting existing resources. Use placeholder credentials in service definitions — merchants configure real credentials during the post-install setup tasks.

**Example `impex/install/services.xml`:**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://www.demandware.com/xml/impex/services/2014-09-26">
    <service-credential service-credential-id="myapp.api.cred">
        <url>https://api.example.com</url>
        <user-id>YOUR_API_KEY</user-id>
        <password>YOUR_API_SECRET</password>
    </service-credential>
    <service-profile service-profile-id="myapp.api.profile">
        <timeout-millis>5000</timeout-millis>
        <rate-limit-enabled>true</rate-limit-enabled>
        <rate-limit-calls>10</rate-limit-calls>
        <rate-limit-millis>1000</rate-limit-millis>
    </service-profile>
    <service service-id="myapp.api">
        <service-type>HTTP</service-type>
        <enabled-flag>true</enabled-flag>
        <service-credential service-credential-id="myapp.api.cred"/>
        <service-profile service-profile-id="myapp.api.profile"/>
    </service>
</services>
```

> **Important:** Use dotted notation for service IDs (for example, `myapp.api`) and camelCase with an app prefix for attribute IDs (for example, `myAppEnabled`). Don't hardcode production credentials.

### Uninstall IMPEX

The `impex/uninstall/` directory contains XML files that clean up resources when the app is removed. Use `mode="delete"` in uninstall IMPEX files to remove services, profiles, and credentials.

During installation, the platform serializes the contents of `impex/uninstall/` and persists them in the `CommerceFeatureState` record. At uninstall time, the platform reconstructs these files from persisted state — **the original CAP archive isn't needed for uninstall**.

**Deletion order matters:** Delete in this order to respect foreign key relationships: credential → profile → service.

**Example `impex/uninstall/services.xml`:**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://www.demandware.com/xml/impex/services/2014-09-26">
    <service-credential service-credential-id="myapp.api.cred" mode="delete"/>
    <service-profile service-profile-id="myapp.api.profile" mode="delete"/>
    <service service-id="myapp.api" mode="delete"/>
</services>
```

> **Note:** Uninstall IMPEX only runs when the last site uninstalls the app, because services and custom object definitions are instance-scoped resources shared across all sites. See [Multi-Site Behavior](/docs/commerce/b2c-commerce/guide/reference.md#multi-site-behavior) for details.

### Naming Conventions

| Resource          | Convention                | Example                   |
| :---------------- | :------------------------ | :------------------------ |
| Service IDs       | Dotted notation           | `avalara.tax.api`         |
| Attribute IDs     | camelCase with app prefix | `avalaraTaxEnabled`       |
| Custom object IDs | camelCase with app prefix | `avalaraTaxConfig`        |
| Credential IDs    | Dotted notation           | `avalara.tax.api.cred`    |
| Profile IDs       | Dotted notation           | `avalara.tax.api.profile` |

## Packaging and Validation Workflow

### Using Claude Code Skills (Recommended)

The Commerce Apps repository includes Claude Code skills that automate packaging and validation. See the full skill catalog in [Development Environment Setup — Claude Code Skills](/docs/commerce/b2c-commerce/guide/development-environment.md#claude-code-skills).

**Typical packaging workflow:**

1. `/scaffold-app` — Generate initial app structure
2. Build your app code and logic
3. `/generate-service-impex`, `/generate-site-preferences-impex` — Generate IMPEX files
4. `/validate-impex` — Validate IMPEX files during development
5. `/package-app` — Package app into ZIP
6. `/validate-app` — Comprehensive package validation
7. **ISVs only:** `/submit-pr` — Submit to the registry (targets the appropriate release branch, such as `release/26.8`)

### Manual Workflow

If you aren't using Claude Code, follow these steps:

1. Organize your project into the CAP directory structure.

2. Ensure `commerce-app.json` fields (`id`, `version`, `domain`) describe the package. For an ISV registry update, ensure they match the App Registry root manifest entry.

3. Run `b2c cap validate` to verify basic manifest fields and CAP structure. Validate IMPEX content separately.

4. Run `b2c cap package` to produce the `.zip` artifact. The ZIP must contain a single root directory: `commerce-{app-name}-app-v{version}/`.

5. Verify the ZIP contains no junk files (`.DS_Store`, `__MACOSX`, hidden files). Use exclusion flags when creating the ZIP manually:

   ```bash
   zip -r my-app-v1.0.0.zip commerce-my-app-app-v1.0.0/ \
     -x "*.DS_Store" -x "__MACOSX/*" -x "*/.*" -x "Thumbs.db"
   ```

6. Compute the SHA256 hash. For a registry submission, verify it matches the root manifest:

   ```bash
   shasum -a 256 my-app-v1.0.0.zip
   ```

7. Test the packaged `.zip` on a sandbox before distribution.

8. **ISVs only:** Open a pull request against the appropriate release branch in the App Registry (for example, `release/26.8` or `release/26.9`). Do **not** target `main` — submissions must target a release branch. Private apps skip this step.
