The fastest path from nothing to a bundle running on a device. Pick the Capacitor path or the Electron path — the CLI steps are the same, only the client integration differs.
Prerequisites
- Node.js 20 or newer
- An account on the dashboard at https://app.deployyour.app
- A Capacitor 6+ project, or an Electron 28+ project
Install the CLI
npm install -g @deploy-your-app/cli
dya --version
Capacitor
1. Run the guided setup
From your project root:
dya setup
dya setup is interactive and needs a terminal on both stdin and stdout — piping it into tee is refused, not half-run. It walks through logging in, picking an organization, creating or selecting the app, writing the .dya/config.json project config, generating RSA key pairs, installing @deploy-your-app/capacitor-update-manager, wiring an init module into your app's entry point, writing the plugin configuration, and running npx cap sync.
Before any of that it checks your signing keys: two keypairs in one project, or a public key that is not the private key's public half, stops setup with Setup stopped before writing anything. The embedded public key is the only one your released apps ever trust, so it is not something the CLI guesses at.
For the same reason it will not overwrite a publicKey already sitting in capacitor.config.json with a different one. That is the shape of a fresh clone — the config is committed, .dya/* (the private key) is not — so on a project that has already shipped, setup asks you to type replace the signing key, and a bare Enter keeps the existing key and stops. Ask whoever made the first release for the private key instead.
dya init is an alias of the same command, so it also needs credentials and a server.
Run it from anywhere inside your project. In a Quasar-style split layout — web app at the root, Capacitor project in src-capacitor/ — both are resolved and the plugin is installed into both package.json files.
When it finishes, your project has:
.dya/
config.json # commit this
dya-signing-private.pem # never commit
dya-signing-public.pem
dya-encryption-public.pem
dya-encryption-private.pem # never commit
plus a .gitignore block that excludes the keys and keeps the config. Commit .dya/config.json and that block — CI reads them.
If you prefer to run the steps yourself, see the manual setup section below.
2. Confirm each successful launch
Setup already wrote this for you and registered it in your app's entry point — src/boot/dya.ts for Quasar, src/dya.js or src/dya.ts (plus one import './dya' line) for Vite, Vue CLI, CRA, Angular, and Svelte:
import { DeployYourApp } from '@deploy-your-app/capacitor-update-manager';
await DeployYourApp.notifyAppReady();
The generated file is yours to edit — move the call to after your real first paint if that suits your app better. It carries an @dya-generated marker comment; delete that line and setup will never rewrite the file again.
If one of those filenames is already yours, setup leaves it alone and writes the module as dya-update (then dya-ota, and so on), registering whichever name it used and reusing that name on later runs. It tells you which file it skipped and why. Delete the generated module but leave its registration in place, and the next run writes the file back rather than reporting that everything is already wired up.
If setup could not patch your entry point safely it says so and prints the one line to add. Add the call by hand in that case.
If notifyAppReady() is not called within appReadyTimeout (10000 ms by default) after an update is activated, the plugin rolls back to the previous bundle. That is the safety net for a bundle that fails to boot — leave it in.
That is the whole integration. With the default autoUpdate: true, the plugin checks on launch and every checkInterval seconds (600 by default), downloads new bundles, verifies them, and activates them according to applyMode (whenIdle by default, which loads the bundle on the next launch).
3. Build and deploy
npm run build
dya deploy --channel production
Realistic output:
ℹ Assets: /Users/you/my-app/dist/spa
✔ App: My App (com.example.app)
ℹ Version: 2026.0731.142530
ℹ Channel: production
ℹ Platform: capacitor
✔ 41 files to bundle
✔ All files are web assets
✔ Archive: 1.4 MB
✔ Signed with RSA-4096
✔ Uploaded: 2026.0731.142530 (1.4 MB)
✔ Deployed to production at 100% rollout
✓ Deploy complete! Version 2026.0731.142530 is live.
The version is generated from the current time when you do not pass --version.
Devices on the production channel receive the update on their next check.
Electron
1. Log in and register the app
dya login
dya apps create --name "My App" --app-id com.example.app --platform electron
Creating an app also creates a production channel and makes it the default.
2. Generate keys
From your project root:
dya keys generate --app-id com.example.app
That writes four PEM files into .dya/, adds the .gitignore block that protects them, and uploads the two public keys to the server:
| File | Keep it |
|---|---|
.dya/dya-signing-private.pem |
On your machine and in CI only. Never commit it. |
.dya/dya-signing-public.pem |
Uploaded to the server; also goes in your client config as publicKey. |
.dya/dya-encryption-public.pem |
Nothing reads it. Uploaded to the server and ignored. |
.dya/dya-encryption-private.pem |
Nothing reads it. Do not embed it in your app. |
Bundles are not encrypted. The encryption pair is a leftover from a removed feature and has no consumer — see Encryption. What protects a bundle is the RSA-4096 signature, which every client verifies before installing, plus a per-file SHA-256 manifest checked during extraction.
dya deploy needs an appId from .dya/config.json and exits 1 without one. The channel entry is optional — --channel wins, then this value, then production. dya setup is written for Capacitor projects: it warns when @capacitor/core is missing, and the init module it generates imports the Capacitor plugin. For Electron, write the config by hand:
{
"appId": "com.example.app",
"channel": "production",
"path": "dist/electron"
}
Commit it, along with the .gitignore block.
3. Install and wire up the updater
npm install @deploy-your-app/electron-update-manager
In your main process:
import path from 'node:path';
import { app, BrowserWindow } from 'electron';
import { ElectronUpdateManager } from '@deploy-your-app/electron-update-manager/main';
const updater = new ElectronUpdateManager(
{
appId: 'com.example.app',
channel: 'production',
autoUpdate: true,
},
app.getAppPath()
);
app.whenReady().then(async () => {
await updater.initialize();
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
},
});
// Required: without a window reference the updater cannot activate a bundle.
updater.setWindow(win);
win.loadFile(updater.getBundlePath());
app.on('before-quit', () => updater.destroy());
});
In your preload script:
import '@deploy-your-app/electron-update-manager/preload';
In your renderer, once the UI has rendered:
import { getUpdaterApi } from '@deploy-your-app/electron-update-manager/renderer';
await getUpdaterApi().notifyAppReady();
4. Build and deploy
npm run build
dya deploy --channel production --target electron
--target electron is required. The server only returns a bundle whose target platform matches the requesting client, so a bundle deployed with the default capacitor target is never served to a desktop app.
Manual setup without dya setup
The Capacitor equivalent of dya setup, one command at a time. Note that dya init is an alias of dya setup, so it is not a step here — write the config file yourself instead.
dya login
dya apps create --name "My App" --app-id com.example.app --platform both
dya keys generate --app-id com.example.app
npm install @deploy-your-app/capacitor-update-manager
npx cap sync
In a split layout, install the plugin in both the web root and the Capacitor root, so the JS import resolves and npx cap sync sees the native plugin.
Write .dya/config.json at your web root:
{
"appId": "com.example.app",
"channel": "production",
"path": "dist/spa"
}
Then add the plugin block to capacitor.config.json:
{
"plugins": {
"DeployYourApp": {
"appId": "com.example.app",
"channel": "production"
}
}
}
Set publicKey to the contents of .dya/dya-signing-public.pem. This is required, not optional: with no publicKey the plugin refuses every update with SIGNATURE_REQUIRED, because it will not install a bundle it cannot verify. There is no encryptionPrivateKey — bundles are not encrypted.
Finally, call notifyAppReady() on startup yourself. For Quasar that means a boot file:
// src/boot/dya.ts
import { defineBoot } from '#q-app/wrappers';
import { DeployYourApp } from '@deploy-your-app/capacitor-update-manager';
export default defineBoot(async () => {
await DeployYourApp.notifyAppReady();
});
On @quasar/app-vite v1 or @quasar/app-webpack v3 and older, the wrappers live at the old path instead — import { boot } from 'quasar/wrappers' and export default boot(...). Match whatever your quasar.config file imports; dya setup picks the right one automatically.
Register it by adding 'dya' to the boot: [] array in quasar.config.ts. For other frameworks, a side-effecting src/dya.js imported once from your entry file does the same job.
Verifying it worked
- The dashboard shows the deployment under the app's Deployments tab.
- The device appears under Devices after its first update check.
dya deployprints the version it uploaded; the same version appears on the channel.
If a device is not picking up the update, check that its appId matches the registered app, that the channel name matches, and that the bundle's target platform matches the client.
A client whose platform the app does not target gets an HTTP 409 with a message naming the mismatch, rather than a silent "no update". The usual cause is pointing a desktop build at an app registered as ios, android, or both (or the reverse). An app targets either the Capacitor family or Electron — never both — because a channel serves one active bundle and the two bundle types are not interchangeable. Register a second app for the other platform.
Next steps
- Core concepts — channels, bundles, deployments, rollouts
- CLI command reference — every command and flag
- Capacitor plugin setup and Electron updater setup — full configuration
- Code signing — key handling, rotation, and CI
- Encryption — what is encrypted, and why bundles are not