# App Store Submission
URL: /docs/building-apps/app-store-submission
Markdown: /docs/building-apps/app-store-submission.md
Description: Package, authenticate, and publish a Rome App to the Rome App Store.

Rome does not currently have an in-instance submit form for app authors. The
Rome dashboard App Store is the browse/install surface. Publishing is done
through the Rome App Store API, usually via the `rome` CLI (shipped with
`@rome-os/app-web-sdk`).

The short version — first, build + pack via a source-mode install into your
running Rome instance. The daemon runs the app's own build (if it declares one)
and writes the packed artifact to `<path-to-app-source>/.rome/artifact`:

```jsonc
// system:app_management
{ "op": "install", "source": { "mode": "source", "path": "<path-to-app-source>" } }
```

Then authenticate and publish:

```bash
# 2. Authenticate to the Rome App Store.
export ROME_TOKEN=<token-from-store-settings>
export ROME_STORE_HOST=https://romeos.cc

# 3. Publish the artifact to the store.
rome publish <path-to-app-source>/.rome/artifact
```

Use `rome publish --dry-run` before the real publish when you want to verify
the bundle hash and size without uploading.

## What Gets Published [#what-gets-published]

The store hosts immutable app versions. A successful publish creates or updates
a listing and writes one live version for the `app.yaml` version in the bundle.
There is no separate review queue in the current implementation: if the API
accepts the upload, the version is live.

The directory passed to `rome publish` must contain `app.yaml`. For a
release, prefer passing the packed artifact a source-mode install writes to
`<app>/.rome/artifact`, not the raw development directory. The packed
artifact is the same shape used by local installs.

## Manifest Checklist [#manifest-checklist]

Before publishing, check the app identity fields in `app.yaml`:

```yaml
formatVersion: 2
id: your-app
name: Your App
version: 1.0.0
description: Short listing summary
icon: assets/icon.svg
```

Required by the store:

* `id`
* `version`
* `description`

Recommended for public listings:

* `name`
* `icon`
* `.rome_store/rome_store.yaml`
* `.rome_store/assets/*` for store-only images/videos

`version` must be strict SemVer. Publishing another version for the same listing
requires a strictly higher version than the listing's current high-water mark.

## Store Listing Fields [#store-listing-fields]

Public store listing pages derive canonical URLs, robots policy, Open Graph,
Twitter card metadata, JSON-LD, and sitemap entries from Rome Cloud. App authors
can supply richer store content in `.rome_store/rome_store.yaml`:

```yaml
title: Morning Brief for Rome
description: Get a daily AI-generated briefing from calendar, inbox, and reminders.
long_description: |
  Morning Brief turns your Rome into a calm daily planning surface.
categories:
  - Productivity
homepage: https://example.com
repository: https://github.com/you/morning-brief
keywords:
  - daily briefing
  - calendar assistant
  - inbox summary
image: assets/store-og.png
image_alt: Morning Brief app preview showing a daily briefing timeline
media:
  - type: image
    path: assets/screenshots/dashboard.png
    alt: Morning Brief dashboard with today's calendar and inbox summary
  - type: video
    path: assets/demo.mp4
    poster: assets/demo-poster.jpg
    alt: Morning Brief demo
preview: demo
noindex: false
```

Field rules:

* `title`: optional listing headline used for the page H1 and social title, up to
  70 chars. Defaults to `name`, then a humanized listing id.
* `description`: optional store/social summary, up to 180 chars. Defaults
  to the top-level `description`.
* `long_description`: optional rich store page copy, up to 12,000 chars.
* `category` or `categories`: optional category seeds for first publish.
* `homepage` and `repository`: optional links shown on the store page.
* `keywords`: optional list used for metadata and Rome Store search, up to
  12 entries, each up to 50 chars. Duplicate entries are folded
  case-insensitively.
* `image`: optional relative path under `.rome_store/` to the social card image. Use
  PNG, JPEG, or WebP; 1200x630 is recommended; max size is 2 MB.
* `image_alt`: optional alt text for `image`, up to 200 chars.
* `media`: optional list of up to 8 images/videos shown on the store page.
  Images may be PNG, JPEG, or WebP and must be at most 2 MB. Videos may be
  MP4, WebM, or MOV and must be at most 25 MB. Video `poster` paths must point
  at images.
* `preview`: optional preview link mode. `off` (the default) hides the preview
  link, `on` links to Rome Cloud's built-in `/preview/<listing>` page when the
  latest bundle has a web UI, and `demo` links to
  `https://demo.romeos.cc/full/apps/<appId>`.
* `noindex`: optional boolean. Use `true` only for a listing you want
  published but omitted from search indexes and the sitemap.

Store asset paths must point inside `.rome_store/`. Absolute URLs, absolute
paths, and `..` path traversal are rejected at publish time. Rome Cloud always
derives the canonical listing URL itself; apps cannot set `canonical`.

The `.rome_store` directory is a Rome App Store-only sidecar: `rome publish` uploads
it separately from the app bundle, and installable bundles exclude it so store
screenshots and videos are not sent to user machines.

## Listing IDs And Handles [#listing-ids-and-handles]

Publishing uses the `id` in `app.yaml` as the store listing id. For local
source installs, keep `id` as the plain app id you run in Rome. For store
publishes, the API also accepts scoped listing ids:

```yaml
id: "@your-handle/your-app"
```

> **Watch out:** the Rome runtime itself never accepts a scoped id.
> `app.yaml#id` must match `/^[a-z][a-z0-9-]{0,63}$/` to build, pack, or
> install, so the daemon cannot build or install an app whose manifest carries
> `@your-handle/your-app` — only the store accepts the scoped form. Keep the
> plain id in `app.yaml` for local installs; for a scoped publish, edit
> `app.yaml#id` in a copy of the packed artifact just before `rome publish`.

Use scoped ids when you are publishing directly to a handle-owned namespace. The
first successful publish for an unclaimed handle claims that handle for the
publishing account, with `owner` role. After that, only authorized publishers
for that handle can publish additional versions. The reserved `@rome` handle is
for operator-owned first-party apps.

Owners can grant additional teammates the right to publish under the same
handle from **Dashboard → My apps → Manage publishers**. The grant page lists
every account with access, lets the owner add a teammate by their store
account email (as `owner` or `publisher`), and lets the owner revoke access.
The handle's last `owner` cannot be removed.

Unscoped listing ids such as `your-app` are also valid, but they claim only that
single unscoped listing namespace. Scoped ids are easier to reason about for
third-party apps because the handle clearly identifies the publisher.

## Authenticate [#authenticate]

The `rome` CLI ships with `@rome-os/app-web-sdk`, which every scaffolded Rome
app already depends on — inside an app directory, `rome` is on your `PATH` via
`node_modules/.bin`. Outside an app, run it ad hoc:

```bash
npx -p @rome-os/app-web-sdk rome --help
```

For long-lived workflows, generate a token in the store dashboard under
**Settings -> CLI / API token** and export it:

```bash
export ROME_TOKEN=<token>
export ROME_STORE_HOST=https://romeos.cc
rome whoami
```

For an interactive shell, you can use a 30-day login session instead:

```bash
rome login --host https://romeos.cc
rome whoami
```

`ROME_TOKEN` wins when both authentication methods are present.

## Publish [#publish]

Package a local Rome app into a release artifact, then publish that artifact.
The daemon builds + packs into `<app>/.rome/artifact` during a source-mode
install:

```jsonc
// system:app_management
{ "op": "install", "source": { "mode": "source", "path": "<absolute app root>" } }
```

```bash
APP=~/.rome/<profile>/projects/apps/<appId>

rome publish "$APP/.rome/artifact" --dry-run --out "$APP/.rome/app.romeapp"
rome publish "$APP/.rome/artifact"
```

Use `--exclude <name>` for any files that should not be included in the upload:

```bash
rome publish "$APP/.rome/artifact" --exclude .env --exclude tmp
```

The CLI excludes `node_modules`, `.git`, and `.DS_Store` from the installable
app bundle by default, and always excludes `.rome_store`. When
`<app>/.rome_store/rome_store.yaml` exists, the CLI uploads `.rome_store` as a
separate store sidecar.

## After Publishing [#after-publishing]

After publish succeeds:

1. Open the store dashboard's **My apps** to see listings you can publish under.
2. Open the store dashboard's **Store** tab or the Rome dashboard App Store to
   confirm the listing appears.
3. Install from the Rome dashboard App Store, or install through the app
   lifecycle API with an `appstore` source that names the listing id.
   `version` and `contentHash` are optional — when omitted, the daemon
   resolves the latest live version and its hash from the listing.

For an update, bump `app.yaml#version`, re-run the source-mode install
(which rebuilds and repacks), and run `rome publish` again. The listing id
must stay the same.

## Common Failures [#common-failures]

* `Not logged in`: set `ROME_TOKEN` or run `rome login --host <url>`.
* `Not authorized to publish under handle`: publish under a handle you own, ask
  the owner to add you as a publisher, or choose a new unclaimed handle.
* `Handle is reserved`: reserved handles such as `@rome` require an operator
  account.
* `Version is not strictly higher`: bump `app.yaml#version` to a SemVer value
  greater than the listing's current highest version.
* `Missing app.yaml`: pass a Rome app artifact directory, usually the
  `<app>/.rome/artifact` directory a source-mode install writes.
* Missing web/API/action files during install: re-run the `system:app_management`
  source-mode install (it rebuilds and repacks), then republish the newly
  packed artifact.

CLI command details live in the `@rome-os/app-web-sdk` package README.