# Turn your web app into a Sage app

> Build one frontend that works in an ordinary browser and can also be installed in Sage.

Sage compatibility does not require rebuilding your whole product. Make sure the frontend can run from its production browser bundle, add the smallest valid Sage manifest, finalize that build, and test the same output in Sage and a normal browser.

## Produce an installable frontend build

Sage installs a snapshot of the files that run in the browser. Your existing app does not have to become entirely static, but its Sage-facing frontend must build to HTML, CSS, JavaScript, fonts, and images in a directory such as `dist/`. That browser bundle cannot depend on a server renderer or Node process to produce its interface after installation.

Your existing backend, API, and database can remain as they are. Anything the interface needs at runtime must either be included in the installed snapshot or requested from an HTTPS backend allowed by the app’s Sage network permissions and CORS policy.

## Install the Sage Apps SDK

Install the official SDK. Its `sage-app` command turns your source manifest and build directory into the package Sage installs. You do not need to import the runtime client unless your app calls Sage APIs.

**Terminal**

```shell
npm install sage-app-sdk
```

## Describe the app in a source manifest

Create `sage-manifest.json` in the project root. This is the smallest source manifest accepted by Sage 0.13.0: an app name, an app version, and the minimum supported Sage version. `manifestVersion` defaults to `0`, `index.html` is the default entry point, and omitted permissions default to none.

The app version is required metadata, but Sage does not use it to decide whether an update exists. It compares the finalized manifest and its hash instead.

**sage-manifest.json**

```json
{
  "name": "My Sage App",
  "version": "1.0.0",
  "sageVersion": {
    "min": "0.13.0"
  }
}
```

## Finalize the manifest after every build

The source manifest is not installable by itself. Sage also needs the path, size, and SHA-256 hash of every file it will download. The SDK’s CLI generates that list from your configured build output after the production build finishes.

Add only the Sage-specific script to `package.json` and adjust its output directory if your project does not build to `dist/`. Run it after your existing production build. It writes the installable manifest into that output directory. If any asset changes afterward, its recorded hash will be wrong and Sage should reject the snapshot.

The finalized file list must describe assets that Sage can retrieve byte-for-byte over HTTP. If your host consumes deployment-control files instead of serving them, exclude those entries from the finalized `files` array while leaving the files in the deployment directory. On Cloudflare, this includes `_headers` and `_redirects` when present. Keep Wrangler configuration outside the static asset directory instead of masking it with an exclusion.

**package.json**

```json
{
  "scripts": {
    "sage:finalize": "sage-app finalize-manifest --source ./sage-manifest.json --dist ./dist"
  }
}
```

**Finalize after your build**

```shell
npm run sage:finalize
```

## Install locally, then deploy the same build

Build first, serve dist/ locally, and add the preview URL in Sage. Sage accepts loopback HTTP for development. When the finalized manifest changes, its hash changes and Sage can detect an update; you do not need to bump the app version for that. While testing, use Sage’s update flow or remove and reinstall the app.

Use the exact Local URL printed by the preview command. Do not assume a port: the preview server may choose another one when its preferred port is already occupied.

Sage opens the packaged `index.html` file. If a custom client-side router shows “Page not found” for `/index.html`, normalize that path to your root route. For production, publish `dist/` to any static HTTPS host and use that public URL in Sage.

## One more thing: add an icon

Once the minimal app installs, add a square image to your frontend build and reference its path from the source manifest. For example, `public/img/icon.png` in Vite becomes `img/icon.png` in `dist/`.

Build again after adding it. The finalizer will include the icon in the file inventory and Sage can show it during installation and in the Apps screen.

**Add to sage-manifest.json**

```json
{
  "icon": "img/icon.png"
}
```

## Builder's note

Bridge APIs and client behavior can evolve. Verify implementation details against the Sage version you support, then test in both Sage and a normal browser.

---

Human-readable page: https://fancybudgie.com/sage/guides/make-your-app-sage-compatible

Guide index: [llms.txt](https://fancybudgie.com/llms.txt)
