The previous post looked at Betaflight firmware from the source side.

That answered the lower-level question:

What runs on the flight controller?

This post looks at the tool most pilots actually touch:

How do I configure, flash, inspect, back up, and debug that flight controller?

That tool is Betaflight App, the modern Betaflight configurator.

It is not only a website.

It is a PWA, a browser hardware app, an Android wrapper, a desktop wrapper, a firmware flasher, an MSP client, a blackbox viewer, and a UI around a very hardware-specific workflow.

Betaflight App GitHub Source Code Betaflight App PWA Betaflight App Master Snapshot Betaflight App Releases License: GPL-3.0

What is Betaflight App?

Betaflight App is the configuration and management application for Betaflight flight-controller firmware.

The repo README describes the current app as a Progressive Web Application, with the latest stable release at:

https://app.betaflight.com

There is also a master snapshot at:

https://master.app.betaflight.com

That master snapshot is useful for testing, but the README is explicit that it can be buggy, broken, or corrupt flight-controller settings.

I would not use it casually on a real quad unless I was deliberately testing and had backups.

The stable PWA is the interesting part.

Browser applications have slowly crossed a boundary that used to require native desktop software.

With WebSerial, WebUSB, Bluetooth, service workers, and installable PWA behavior, a browser can now become a real hardware tool.

Betaflight App is a practical example of that shift.

Why It Matters

Firmware is powerful, but the configurator is where mistakes become easy.

A normal Betaflight session can involve:

  • selecting a board target
  • flashing firmware
  • checking receiver channels
  • changing UART ports
  • tuning PID and filter settings
  • setting modes and failsafe behavior
  • reviewing GPS, sensors, motor order, OSD, VTX, and blackbox logs
  • using the CLI to inspect or restore configuration

That means the app is not just a front-end.

It is the control surface for the aircraft.

The useful mental model is:

Layer Project
Radio link ExpressLRS
Flight-controller firmware Betaflight
Configuration and flashing UI Betaflight App

That also makes the app a good companion to the previous hardware posts.

ExpressLRS solved the radio link, Betaflight firmware runs the aircraft, and Betaflight App is how a pilot sees and changes the flight-controller state.

Repository Snapshot

I cloned and inspected:

repo: https://github.com/betaflight/betaflight-configurator
HEAD: 4757d943
describe: 10.10.0-994-g4757d943
package: betaflight-app
package version: 2026.6.0-alpha
license: GPL-3.0
required Node engine: 24.x
local Node used: v22.22.0
local npm used: 10.9.4

Recent commits in the checkout were active app-maintenance work: Autopilot PID debug labeling, Cloudflare deploy workflow permissions, OSD preview helper tests, dependency bumps, blackbox legend fixes, and number-input hit target fixes.

The top-level technology stack is:

  • Vue 3 for the UI
  • Vite 8 for development and production builds
  • vite-plugin-pwa for the service worker and PWA manifest
  • Pinia for state
  • Vitest for tests
  • WebSerial, WebBluetooth, WebSocket, WebUSB DFU, and virtual serial transports
  • Capacitor for Android packaging
  • Tauri 2 for desktop packaging
  • Three.js, Leaflet, OpenLayers, D3, and app-specific visualization helpers

That is a modern web app, but it is aimed at hardware.

The App Architecture

The repo has a clean split between user-facing tabs, transport backends, Betaflight protocol logic, and native wrappers.

Important paths:

Path What It Contains
src/components/tabs/ Main app screens: setup, ports, receiver, PID tuning, OSD, CLI, firmware flasher, blackbox, motors, GPS, sensors, VTX
src/js/msp.js MSP communication flow used to talk to Betaflight
src/js/msp/ MSP codes and helpers
src/js/serial.js Serial orchestration
src/js/serial_backend.js Backend selection and routing
src/js/protocols/ WebSerial, WebBluetooth, WebSocket, WebUSB DFU, Capacitor, Tauri, and virtual transports
src/composables/ Vue composables for ports, flashing, CLI, cloud build, OSD preview, motors, VTX, preflight, and related workflows
src/blackbox-viewer/ Blackbox parsing and viewing code
src-tauri/ Tauri desktop wrapper
android/ Android packaging
test/js/ Vitest coverage for MSP, transports, stores, tabs, cloud build, CLI sessions, and utilities

The transport layer is the most interesting part for me.

Inside src/js/protocols/, the app has separate implementations for:

  • WebSerial.js
  • WebBluetooth.js
  • WebSocket.js
  • WebUsbDfuTransport.js
  • CapacitorSerial.js
  • CapacitorDfu.js
  • CapacitorTcp.js
  • TauriSerial.js
  • TauriTcp.js
  • VirtualSerial.js
  • usbdfu.js
  • webstm32.js
  • esp32.js

That explains how the same app can be a PWA, a desktop app, a mobile app, and a local development tool.

It also explains why a plain Docker container is not the right mental model here.

This project needs access to local hardware interfaces, browser permissions, native wrappers, and serial/DFU transport behavior.

PWA and Local Development

The Vite config sets the app root to src, builds into src/dist, and configures a generated service worker with vite-plugin-pwa.

The PWA manifest uses the package metadata and app icons:

name: Betaflight - Configuration and management application
short_name: Betaflight
theme_color: #ffffff
icons: /images/pwa/pwa-192-192.png, /images/pwa/pwa-512-512.png

For local development, the server defaults to:

http://localhost:8080

If local certificates are present, it can run HTTPS on:

https://local.betaflight.com:8443

That HTTPS detail matters because WebAuthn/passkey features require a secure context.

On this machine I did not have the local certificates, so the build and test output warned that WebAuthn features would be unavailable in HTTP mode.

Desktop and Android Packaging

The README still points users to standalone releases for Windows, Linux, macOS, and Android.

The source code backs that up in two ways.

First, Android uses Capacitor:

{
  "appId": "betaflight.app",
  "appName": "Betaflight App",
  "webDir": "src/dist"
}

Second, Tauri is configured for desktop builds:

productName: Betaflight App
identifier: com.betaflight.app
targets: deb, rpm, appimage, dmg, nsis
frontendDist: ../src/dist

That means the app is fundamentally web-first now, but the repo still has serious native packaging work around it.

The Tauri source also shows one important platform caveat:

USB serial is unavailable on iOS; TCP is the only transport there.

That kind of detail is why I prefer reading the source before describing an app as “cross-platform.”

The wrapper can exist everywhere, but not every transport exists everywhere.

Local Trial

I tested the repo locally with the documented Node/npm path.

The repo asks for Node 24.x in package.json and .nvmrc currently says:

24.12.0

This machine was on:

node v22.22.0
npm 10.9.4

So npm install warned:

EBADENGINE Unsupported engine
required: node 24.x
current: node v22.22.0

But it still completed:

npm install

The install added 1123 packages and reported:

6 vulnerabilities (1 low, 5 moderate)

For reproducible development, I would use Node 24:

nvm install 24.12.0
nvm use 24.12.0
npm install

Build Result

The production build passed:

npm run build

The build generated the PWA service worker:

PWA v1.3.0
mode generateSW
precache 743 entries
dist/sw.js
dist/workbox-2fbc6a65.js

There were warnings worth documenting:

  • local SSL certificates were missing, so the local server used HTTP and disabled WebAuthn features
  • ../../images/pattern_dark.png and ../images/corner.svg stayed unresolved until runtime
  • some legacy Three.js helper imports are always undefined with the current Three.js package
  • some dynamic imports are ineffective because the same modules are also imported statically
  • some generated chunks are larger than Vite’s default 500 kB warning threshold

Those warnings did not block the build.

They are still useful because they tell contributors where old app history, packaging paths, and bundle boundaries are rubbing against the newer Vite/PWA setup.

Test Result

There was one important local gotcha.

If I ran:

npm test -- --run

immediately after a production build, the test command failed during pretest.

The reason was not a source test failure. The package test script runs lint first:

"pretest": "npm run lint",
"lint": "eslint --ext .js,.vue src"

Because the build output lives under src/dist, ESLint tried to lint generated production files such as bundled JS, sw.js, and Workbox output. That produced many generated-file lint errors.

After removing the generated build directory:

rm -rf src/dist
npm test -- --run

the clean source test path passed:

Test Files  54 passed (54)
Tests       642 passed (642)
Duration    15.04s

So the practical contributor note is:

# clean test path after a production build
rm -rf src/dist
npm test -- --run

That is a small but useful repo-specific learning.

Commands That Worked

For a source checkout:

git clone https://github.com/betaflight/betaflight-configurator.git
cd betaflight-configurator
nvm install 24.12.0
nvm use 24.12.0
npm install
npm run dev

For a production-style local PWA build:

npm run build
npm run preview

For tests:

rm -rf src/dist
npm test -- --run

For Android development:

npm run android:run

For opening the Android project in Android Studio:

npm run android:open

For Tauri desktop development:

npm run tauri:dev

For Tauri desktop bundles:

npm run tauri:build

The desktop build path may require platform-specific Tauri prerequisites; the repo wraps that with scripts/check-tauri-prereqs.mjs.

How I Would Use It Safely

For normal configuration work, I would start with the stable PWA:

https://app.betaflight.com

Then I would use the CLI tab before making risky changes:

version
status
diff all

Save the output somewhere boring and findable.

That matters especially if you do not know the exact flight-controller target.

The model name of a drone frame, such as an iFlight Cidora SL5, is not enough by itself.

The same family can ship with different flight controllers across revisions and bundles.

The app can help you read what the controller reports, but it cannot make an unsafe target guess safe.

Where It Fits with Betaflight Firmware

The firmware repo and the app repo share a boundary: MSP.

Betaflight firmware exposes configuration and telemetry through the MultiWii Serial Protocol. Betaflight App implements the UI and transport side of that conversation.

That is why the app repo has both:

  • user-facing tabs such as Ports, Receiver, Motors, OSD, CLI, and Firmware Flasher
  • protocol code such as MSP helpers, serial backends, DFU transports, and platform-specific wrappers

For readers who like source-first learning, this repo is a good way to understand the operational surface of Betaflight without starting inside embedded C.

It shows what the firmware needs to expose to be usable by pilots.

FAQ

Conclusion

Betaflight App is a good example of where modern PWA development is going.

It is not a marketing site pretending to be an app.

It is a real hardware tool built with Vue, Vite, service workers, WebSerial, WebUSB DFU, MSP, Android Capacitor wrappers, and Tauri desktop packaging.

The local result was strong: install completed despite a Node-version warning, the production PWA build passed, and the clean source test suite passed with 642 tests.

The main improvement I would make as a contributor is small but practical: keep generated src/dist output out of the lint path, so npm test behaves the same whether or not someone just ran npm run build.

For pilots, the bigger lesson is simpler.

Use the stable app, back up CLI output before changing anything, and treat board targets as hardware facts, not guesses.