---
title: Auto-Updates
description: Implement automatic application updates with Wails v3
sidebar:
  order: 5
---

import { Tabs, TabItem } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';
import { Card, CardGrid } from '@astrojs/starlight/components';
import { Aside } from '@astrojs/starlight/components';

# Automatic Updates

Wails v3 provides a built-in updater system that supports automatic update checking, downloading, and installation. The updater includes support for binary delta updates (patches) for minimal download sizes.

<CardGrid>
  <Card title="Automatic Checking" icon="rocket">
    Configure periodic update checks in the background
  </Card>
  <Card title="Delta Updates" icon="down-caret">
    Download only what changed with bsdiff patches
  </Card>
  <Card title="Cross-Platform" icon="laptop">
    Works on macOS, Windows, and Linux
  </Card>
  <Card title="Secure" icon="approve-check">
    SHA256 checksums and optional signature verification
  </Card>
</CardGrid>

## Quick Start

Add the updater service to your application:

```go title="main.go"
package main

import (
    "github.com/wailsapp/wails/v3/pkg/application"
    "time"
)

func main() {
    // Create the updater service
    updater, err := application.CreateUpdaterService(
        "1.0.0", // Current version
        application.WithUpdateURL("https://updates.example.com/myapp/"),
        application.WithCheckInterval(24 * time.Hour),
    )
    if err != nil {
        panic(err)
    }

    app := application.New(application.Options{
        Name: "MyApp",
        Services: []application.Service{
            application.NewService(updater),
        },
    })

    // ... rest of your app
    app.Run()
}
```

Then use it from your frontend:

```typescript title="App.tsx"
import { updater } from './bindings/myapp';

async function checkForUpdates() {
  const update = await updater.CheckForUpdate();

  if (update) {
    console.log(`New version available: ${update.version}`);
    console.log(`Release notes: ${update.releaseNotes}`);

    // Download and install
    await updater.DownloadAndApply();
  }
}
```

## Configuration Options

The updater supports various configuration options:

```go
updater, err := application.CreateUpdaterService(
    "1.0.0",
    // Required: URL where update manifests are hosted
    application.WithUpdateURL("https://updates.example.com/myapp/"),

    // Optional: Check for updates automatically every 24 hours
    application.WithCheckInterval(24 * time.Hour),

    // Optional: Allow pre-release versions
    application.WithAllowPrerelease(true),

    // Optional: Update channel (stable, beta, canary)
    application.WithChannel("stable"),

    // Optional: Require signed updates
    application.WithRequireSignature(true),
    application.WithPublicKey("your-ed25519-public-key"),
)
```

## Update Manifest Format

Host an `update.json` file on your server:

```json title="update.json"
{
  "version": "1.2.0",
  "release_date": "2025-01-15T00:00:00Z",
  "release_notes": "## What's New\n\n- Feature A\n- Bug fix B",
  "platforms": {
    "macos-arm64": {
      "url": "https://updates.example.com/myapp/myapp-1.2.0-macos-arm64.tar.gz",
      "size": 12582912,
      "checksum": "sha256:abc123...",
      "patches": [
        {
          "from": "1.1.0",
          "url": "https://updates.example.com/myapp/patches/1.1.0-to-1.2.0-macos-arm64.patch",
          "size": 14336,
          "checksum": "sha256:def456..."
        }
      ]
    },
    "macos-amd64": {
      "url": "https://updates.example.com/myapp/myapp-1.2.0-macos-amd64.tar.gz",
      "size": 13107200,
      "checksum": "sha256:789xyz..."
    },
    "windows-amd64": {
      "url": "https://updates.example.com/myapp/myapp-1.2.0-windows-amd64.zip",
      "size": 14680064,
      "checksum": "sha256:ghi789..."
    },
    "linux-amd64": {
      "url": "https://updates.example.com/myapp/myapp-1.2.0-linux-amd64.tar.gz",
      "size": 11534336,
      "checksum": "sha256:jkl012..."
    }
  },
  "minimum_version": "1.0.0",
  "mandatory": false
}
```

### Platform Keys

| Platform | Key |
|----------|-----|
| macOS (Apple Silicon) | `macos-arm64` |
| macOS (Intel) | `macos-amd64` |
| Windows (64-bit) | `windows-amd64` |
| Linux (64-bit) | `linux-amd64` |
| Linux (ARM64) | `linux-arm64` |

## Frontend API

The updater exposes methods that are automatically bound to your frontend:

### TypeScript Types

```typescript
interface UpdateInfo {
  version: string;
  releaseDate: Date;
  releaseNotes: string;
  size: number;
  patchSize?: number;
  mandatory: boolean;
  hasPatch: boolean;
}

interface Updater {
  // Get the current application version
  GetCurrentVersion(): string;

  // Check if an update is available
  CheckForUpdate(): Promise<UpdateInfo | null>;

  // Download the update (emits progress events)
  DownloadUpdate(): Promise<void>;

  // Apply the downloaded update (restarts app)
  ApplyUpdate(): Promise<void>;

  // Download and apply in one call
  DownloadAndApply(): Promise<void>;

  // Get current state: idle, checking, available, downloading, ready, installing, error
  GetState(): string;

  // Get the available update info
  GetUpdateInfo(): UpdateInfo | null;

  // Get the last error message
  GetLastError(): string;

  // Reset the updater state
  Reset(): void;
}
```

### Progress Events

Listen for download progress events:

```typescript
import { Events } from '@wailsio/runtime';

Events.On('updater:progress', (data) => {
  console.log(`Downloaded: ${data.downloaded} / ${data.total}`);
  console.log(`Progress: ${data.percentage.toFixed(1)}%`);
  console.log(`Speed: ${(data.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s`);
});
```

## Complete Example

Here's a complete example with a React component:

```tsx title="UpdateChecker.tsx"
import { useState, useEffect } from 'react';
import { updater } from './bindings/myapp';
import { Events } from '@wailsio/runtime';

interface Progress {
  downloaded: number;
  total: number;
  percentage: number;
  bytesPerSecond: number;
}

export function UpdateChecker() {
  const [checking, setChecking] = useState(false);
  const [updateInfo, setUpdateInfo] = useState<any>(null);
  const [downloading, setDownloading] = useState(false);
  const [progress, setProgress] = useState<Progress | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    // Listen for progress events
    const cleanup = Events.On('updater:progress', (data: Progress) => {
      setProgress(data);
    });

    // Check for updates on mount
    checkForUpdates();

    return () => cleanup();
  }, []);

  async function checkForUpdates() {
    setChecking(true);
    setError(null);

    try {
      const info = await updater.CheckForUpdate();
      setUpdateInfo(info);
    } catch (err) {
      setError(err.message);
    } finally {
      setChecking(false);
    }
  }

  async function downloadAndInstall() {
    setDownloading(true);
    setError(null);

    try {
      await updater.DownloadAndApply();
      // App will restart automatically
    } catch (err) {
      setError(err.message);
      setDownloading(false);
    }
  }

  if (checking) {
    return <div>Checking for updates...</div>;
  }

  if (error) {
    return (
      <div>
        <p>Error: {error}</p>
        <button onClick={checkForUpdates}>Retry</button>
      </div>
    );
  }

  if (!updateInfo) {
    return (
      <div>
        <p>You're up to date! (v{updater.GetCurrentVersion()})</p>
        <button onClick={checkForUpdates}>Check Again</button>
      </div>
    );
  }

  if (downloading) {
    return (
      <div>
        <p>Downloading update...</p>
        {progress && (
          <div>
            <progress value={progress.percentage} max={100} />
            <p>{progress.percentage.toFixed(1)}%</p>
            <p>{(progress.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s</p>
          </div>
        )}
      </div>
    );
  }

  return (
    <div>
      <h3>Update Available!</h3>
      <p>Version {updateInfo.version} is available</p>
      <p>Size: {updateInfo.hasPatch
        ? `${(updateInfo.patchSize / 1024).toFixed(0)} KB (patch)`
        : `${(updateInfo.size / 1024 / 1024).toFixed(1)} MB`}
      </p>
      <div dangerouslySetInnerHTML={{ __html: updateInfo.releaseNotes }} />
      <button onClick={downloadAndInstall}>Download & Install</button>
      <button onClick={() => setUpdateInfo(null)}>Skip</button>
    </div>
  );
}
```

## Update Strategies

### Check on Startup

```go
func (a *App) OnStartup(ctx context.Context) {
    // Check for updates after a short delay
    go func() {
        time.Sleep(5 * time.Second)
        info, err := a.updater.CheckForUpdate()
        if err == nil && info != nil {
            // Emit event to frontend
            application.Get().EmitEvent("update-available", info)
        }
    }()
}
```

### Background Checking

Configure automatic background checks:

```go
updater, _ := application.CreateUpdaterService(
    "1.0.0",
    application.WithUpdateURL("https://updates.example.com/myapp/"),
    application.WithCheckInterval(6 * time.Hour), // Check every 6 hours
)
```

### Manual Check Menu Item

```go
menu := application.NewMenu()
menu.Add("Check for Updates...").OnClick(func(ctx *application.Context) {
    info, err := updater.CheckForUpdate()
    if err != nil {
        application.InfoDialog().SetMessage("Error checking for updates").Show()
        return
    }
    if info == nil {
        application.InfoDialog().SetMessage("You're up to date!").Show()
        return
    }
    // Show update dialog...
})
```

## Delta Updates (Patches)

Delta updates (patches) allow users to download only the changes between versions, dramatically reducing download sizes.

### How It Works

1. When building a new version, generate patches from previous versions
2. Host patches alongside full updates on your server
3. The updater automatically downloads patches when available
4. If patching fails, it falls back to the full download

### Generating Patches

Patches are generated using the bsdiff algorithm. You'll need the `bsdiff` tool:

```bash
# Install bsdiff (macOS)
brew install bsdiff

# Install bsdiff (Ubuntu/Debian)
sudo apt-get install bsdiff

# Generate a patch
bsdiff old-binary new-binary patch.bsdiff
```

### Patch File Naming

Organize your patches in your update directory:

```
updates/
├── update.json
├── myapp-1.2.0-macos-arm64.tar.gz
├── myapp-1.2.0-windows-amd64.zip
└── patches/
    ├── 1.0.0-to-1.2.0-macos-arm64.patch
    ├── 1.1.0-to-1.2.0-macos-arm64.patch
    ├── 1.0.0-to-1.2.0-windows-amd64.patch
    └── 1.1.0-to-1.2.0-windows-amd64.patch
```

<Aside type="tip">
Keep patches from the last few versions. Users on very old versions will automatically download the full update.
</Aside>

## Hosting Updates

### Static File Hosting

Updates can be hosted on any static file server:

- **Amazon S3** / **Cloudflare R2**
- **Google Cloud Storage**
- **GitHub Releases**
- **Any CDN or web server**

Example S3 bucket structure:

```
s3://my-updates-bucket/myapp/
├── stable/
│   ├── update.json
│   ├── myapp-1.2.0-macos-arm64.tar.gz
│   └── patches/
│       └── 1.1.0-to-1.2.0-macos-arm64.patch
└── beta/
    └── update.json
```

### CORS Configuration

If hosting on a different domain, configure CORS:

```json
{
  "CORSRules": [
    {
      "AllowedOrigins": ["*"],
      "AllowedMethods": ["GET"],
      "AllowedHeaders": ["*"]
    }
  ]
}
```

## Security

### Checksum Verification

All downloads are verified against SHA256 checksums in the manifest:

```json
{
  "checksum": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
```

### Signature Verification

For additional security, enable signature verification:

<Steps>
1. **Generate a key pair**:
   ```bash
   # Generate Ed25519 key pair
   openssl genpkey -algorithm Ed25519 -out private.pem
   openssl pkey -in private.pem -pubout -out public.pem
   ```

2. **Sign your update manifest**:
   ```bash
   openssl pkeyutl -sign -inkey private.pem -in update.json -out update.json.sig
   ```

3. **Configure the updater**:
   ```go
   updater, _ := application.CreateUpdaterService(
       "1.0.0",
       application.WithUpdateURL("https://updates.example.com/myapp/"),
       application.WithRequireSignature(true),
       application.WithPublicKey("MCowBQYDK2VwAyEA..."), // Base64-encoded public key
   )
   ```
</Steps>

## Best Practices

### Do

- Test updates thoroughly before deploying
- Keep previous versions available for rollback
- Show release notes to users
- Allow users to skip non-mandatory updates
- Use HTTPS for all downloads
- Verify checksums before applying updates
- Handle errors gracefully

### Don't

- Force immediate restarts without warning
- Skip checksum verification
- Interrupt users during important work
- Delete the previous version immediately
- Ignore update failures

## Troubleshooting

### Update Not Found

- Verify the manifest URL is correct
- Check the platform key matches (e.g., `macos-arm64`)
- Ensure the version in the manifest is newer

### Download Fails

- Check network connectivity
- Verify the download URL is accessible
- Check CORS configuration if cross-origin

### Patch Fails

- The updater automatically falls back to full download
- Ensure `bspatch` is available on the system
- Verify the patch checksum is correct

### Application Won't Restart

- On macOS, ensure the app is properly code-signed
- On Windows, check for file locks
- On Linux, verify file permissions

## Next Steps

- [Code Signing](/guides/build/signing) - Sign your updates
- [Creating Installers](/guides/installers) - Package your application
- [CI/CD Integration](/guides/ci-cd) - Automate your release process
