Seed Project
Overview
Publish your build artifacts to a location accessible to the platform, then configure io.Connect Desktop to check that location for updates. You can upload artifacts via Electron Forge publishers or your own deployment tools.
⚠️ Note that auto updates are supported for installations created with Squirrel.Windows setup executables on Windows and DMG files on macOS. Deployments extracted directly from ZIP archives don't support auto updates. The macOS update payload is still a ZIP archive, even when the initial installation uses a DMG.
ℹ️ For details on all updater properties, see the Developers > Configuration > System > Auto Updates section.
Update Sources
Static Storage
Use "StaticStorage" for a file server, CDN, or cloud storage location serving the update artifacts and metadata. The "baseUrl" must identify the directory for the target platform and architecture. On macOS, the updater appends /RELEASES.json. On Windows, Squirrel uses the directory to retrieve RELEASES and its referenced packages.
New seed projects include a /modifications/base/iocd/config/system.json.merge-autoUpdate file that disables auto updates and contains a placeholder "Service" source. Merge files in the same folder are applied in an unspecified order, so don't set the same property in more than one of them. Replace the contents of the generated file with the settings shared by both platforms:
{
"autoUpdater": {
"enabled": true,
"updateSource": {
"type": "StaticStorage"
},
"updateIntervalMinutes": 60
}
}Then specify only the "baseUrl" for each platform in a separate merge file. The following example demonstrates a macOS update location. Save it in /modifications/base/iocd/config/system.json.merge-autoUpdate-darwin:
{
"autoUpdater": {
"updateSource": {
"baseUrl": "https://cdn.example.com/my-product/darwin/arm64"
}
}
}Use a system.json.merge-autoUpdate-win32 file with the Windows directory (e.g., https://cdn.example.com/my-product/win32/x64). The architecture directory must match the machine that builds the package. The generated GitHub Actions workflow builds macOS packages on Apple silicon (arm64) runners and Windows packages on x64 runners.
Update Service
Use "Service" when a server decides which update to return based on the platform, installed version, or update channel. The platform resolves update/<platform>/<version>/<channel> relative to the "baseUrl". If the "baseUrl" contains a path, end it with / - e.g., https://example.com/my-product/ resolves to https://example.com/my-product/update/..., while https://example.com/my-product resolves to https://example.com/update/....
The following example demonstrates a service configuration. Replace the contents of the generated /modifications/base/iocd/config/system.json.merge-autoUpdate file with it:
{
"autoUpdater": {
"enabled": true,
"updateSource": {
"type": "Service",
"baseUrl": "https://updates.example.com",
"channel": "stable"
},
"updateIntervalMinutes": 60
}
}A product at version 1.0.0 will request https://updates.example.com/update/darwin/1.0.0/stable on macOS or https://updates.example.com/update/win32/1.0.0/stable on Windows. Your service must implement that route and return update responses compatible with the platform updater.
ℹ️ For details on update services, see the Electron update documentation.
Publishing with Electron Forge
Install the publisher required by your hosting provider and add it to publishers in config/forge.config.js. Keep its version compatible with the Electron Forge version required by the CLI. Preserve the generated makers and hooks when editing this file.
Amazon S3
The S3 publisher uses the object path <prefix>/<platform>/<architecture>/<filename>. The prefix defaults to "name" from package.json, or can be set explicitly with folder.
The following example demonstrates consistent S3 paths for Windows x64 and macOS arm64 distributions with the prefix my-product. Apply these entries to the existing makers and publishers arrays in config/forge.config.js, preserving other settings and the generated hooks:
const updateRoot = "https://my-app-updates.s3.amazonaws.com/my-product";
const windowsUpdateUrl = `${updateRoot}/win32/x64`;
const macUpdateUrl = `${updateRoot}/darwin/arm64`;
module.exports = {
// Preserve the generated hooks and other configuration.
makers: [
{
name: "@electron-forge/maker-squirrel",
config: {
loadingGif: "assets/install.gif",
remoteReleases: windowsUpdateUrl
}
},
{
name: require.resolve("@interopio/iocd-cli/dist/services/installer/zip-maker"),
config: {
macUpdateManifestBaseUrl: macUpdateUrl
}
},
{
name: "@electron-forge/maker-dmg"
}
],
publishers: [
{
name: "@electron-forge/publisher-s3",
config: {
bucket: "my-app-updates",
region: "us-east-1",
folder: "my-product",
public: true
}
}
]
};Supply AWS credentials via the environment or the credential mechanism supported by the publisher. Set the platform updater "baseUrl" to the same directory as windowsUpdateUrl or macUpdateUrl, using separate platform merge files (see Static Storage). Update every corresponding URL when changing the prefix or architecture.
⚠️ Note that
public: trueuploads the objects with a public-read ACL. S3 buckets with ACLs disabled, the default for new buckets, reject these uploads. For such buckets, omitpublicand grant read access via a bucket policy or a CDN.
⚠️ Note that the CLI merges the Squirrel and DMG maker settings with its own, so use static
configobjects for these makers. The function formconfig: (arch) => ({ ... })shown in the Electron Forge documentation is ignored for them.
ℹ️ For details on publisher options and authentication, see the Electron Forge S3 publisher documentation.
GitHub Releases
The following example demonstrates a publisher for GitHub Releases:
module.exports = {
// Preserve the generated makers, hooks, and other configuration.
publishers: [
{
name: "@electron-forge/publisher-github",
config: {
repository: {
owner: "your-org",
name: "your-repo"
},
prerelease: false
}
}
]
};Publishing to GitHub Releases uploads artifacts. You must also provide a stable update metadata location or a compatible update service. A repository or release web page isn't itself a static update directory. Ensure that clients can access the referenced assets with the authentication available to the updater.
Update Service Publisher
To upload to a compatible Electron Release Server, configure its publisher:
module.exports = {
// Preserve the generated makers, hooks, and other configuration.
publishers: [
{
name: "@electron-forge/publisher-electron-release-server",
config: {
baseUrl: "https://updates.example.com",
username: process.env.RELEASE_SERVER_USERNAME,
password: process.env.RELEASE_SERVER_PASSWORD
}
}
]
};Configure the platform with the service URL and channel described in Update Service. For a custom service, use its supported upload API or administration interface.
Update Metadata & Manual Uploads
Packaging generates update metadata independently of publishing. You can upload the resulting files manually without configuring an Electron Forge publisher.
Windows
The Squirrel.Windows maker generates a setup executable, a full .nupkg package, and a RELEASES file. Upload these files to the directory configured as the Windows update source. Include generated delta packages if you use them. The package filenames must match the entries in RELEASES.
macOS
The ZIP maker supplied by the CLI generates RELEASES.json when macUpdateManifestBaseUrl is configured. As of @interopio/iocd-cli 1.1.1, the CLI supplies its own ZIP maker. Seeds whose config/forge.config.js still lists @electron-forge/maker-zip use it automatically, with the same configuration.
The maker reads an existing manifest at the macUpdateManifestBaseUrl without credentials, updates the entry for the product version, and writes the resulting manifest beside the ZIP archive. A missing manifest returning HTTP 404 starts a new release history. Any other HTTP status or a connection failure fails the build.
⚠️ Note that some storage services return HTTP
403instead of404for a missing object (e.g., S3 buckets that don't allow listing for anonymous requests). Before the first release, upload a manifest without releases,{"currentRelease": "", "releases": []}, asRELEASES.jsonto that location.
To set the release notes of the new entry, use the macUpdateReleaseNotes property of the maker configuration. The notes default to an empty string.
The following example demonstrates the manifest structure:
{
"currentRelease": "1.0.0",
"releases": [
{
"version": "1.0.0",
"updateTo": {
"version": "1.0.0",
"pub_date": "2026-10-18T10:00:00Z",
"notes": "Initial release",
"name": "My Product v1.0.0",
"url": "https://cdn.example.com/my-product/darwin/arm64/My%20Product-darwin-arm64-1.0.0.zip"
}
}
]
}Upload the generated ZIP archive and RELEASES.json to the directory identified by macUpdateManifestBaseUrl. Publish the DMG for initial installations as well. Keep the ZIP filename and URL generated by the maker. A publisher isn't required to generate the manifest, and you don't need to edit it manually when using this maker.
Upload Procedure
- Configure the platform update URL and the relevant maker settings.
- Build with
npm run buildand locate the artifacts under/dist/makeor your configured output directory. - Upload the packages before making the new metadata available to clients.
- Upload
RELEASESon Windows orRELEASES.jsonon macOS. - Check that the metadata and every referenced package URL are accessible to the clients.
Verifying an Update
- Install an earlier version using its signed Windows installer or macOS DMG.
- Increase the product version in
package.jsonand build the new distribution with the same update source. - Publish or manually upload the new packages and metadata.
- Start the installed version and verify that it detects and installs the update.
- Check the updated version, configuration, custom apps, and user data.
ℹ️ For details on build options, including the behavior of
--publish-only, see the Building for Production > Build Commands section.