Type-safe Gleam wrappers for every official Capacitor 8 plugin, plus a Lustre effect adapter — native iOS, Android and Web from one codebase.
18 Gleam packages that wrap Capacitor 8.x end-to-end and smooth the edges for Gleam + Lustre apps:
- A shared
glitor_corewith a normalised error ADT, Promise / listener / stream adapters, and theSystemBarsAPI bundled with@capacitor/core. - 16 plugin wrappers — one per official Capacitor plugin — built on that
core with a uniform
Promise(Result(a, GlitorError))return shape. - A
glitor_lustreadapter that folds those promises and listener handles intolustre/effect.Effect(msg)values.
Every wrapper is a first-class Gleam package: you depend on only the ones
you use, and each comes with its own README.md, CI-tested test suite, and
per-plugin setup notes for iOS / Android / Web.
| Package | Upstream | Tests | Highlights |
|---|---|---|---|
glitor_core |
@capacitor/core |
40 | Error ADT, Promise / listener / stream adapters, SystemBars, decoder helpers |
glitor_app |
@capacitor/app |
6 | 7 commands + 6 listeners, Android back-button three-state matrix |
glitor_device |
@capacitor/device |
3 | OS / platform / battery / per-app language |
glitor_network |
@capacitor/network |
2 | Connection status + change listener |
glitor_preferences |
@capacitor/preferences |
2 | Everyday API + legacy migration sub-module |
glitor_filesystem |
@capacitor/filesystem |
6 | Text / binary API split, read_file_in_chunks streaming, 9-variant Directory |
glitor_dialog |
@capacitor/dialog |
2 | alert / confirm / prompt (with the cancellation-as-field exception) |
glitor_haptics |
@capacitor/haptics |
2 | impact / notification / vibrate / selection helpers |
glitor_toast |
@capacitor/toast |
2 | One-shot toast with short / long duration |
glitor_action_sheet |
@capacitor/action-sheet |
7 | Button builder, destructive / cancel styles, result decoder |
glitor_status_bar |
@capacitor/status-bar |
3 | 6 commands + 2 listeners, SystemBars role split documented |
glitor_splash_screen |
@capacitor/splash-screen |
1 | show / hide with launch-config caveats |
glitor_keyboard |
@capacitor/keyboard |
3 | 7 commands + 4 listeners, will / did pairs |
glitor_camera |
@capacitor/camera |
14 | New 8.1 API + legacy (getPhoto / pickImages) with migration guide |
glitor_geolocation |
@capacitor/geolocation |
6 | watch_position via glitor/stream, 7-code error mapping |
glitor_local_notifications |
@capacitor/local-notifications |
10 | Full LocalNotificationSchema, channels, exact-alarm settings |
glitor_push_notifications |
@capacitor/push-notifications |
9 | FCM / APNS registration flow, channels, Android 13+ permission prompt |
glitor_lustre |
— (Lustre 5.6 adapter) | 4 | from_promise, register_listener, remove_listener(s), clear_watch |
| Total | 122 |
Pick the plugins your app needs and add them one by one — nothing is bundled.
gleam add glitor_app glitor_network glitor_dialog
gleam add glitor_lustre # only if you use LustreThen install the matching Capacitor peer packages in your application:
npm install @capacitor/core @capacitor/app @capacitor/network @capacitor/dialog
npx cap syncA minimal example — fetch the app info and log the current network link:
import gleam/javascript/promise
import glitor_app as app
import glitor_network as network
pub fn probe() {
use info <- promise.try_await(app.get_info())
use status <- promise.try_await(network.get_status())
promise.resolve(Ok(#(info.name, status.connected, status.connection_type)))
}Every glitor_* function returns Promise(Result(a, GlitorError)) — the
rejection path is already normalised, so promise.try_await composes them
directly. See each package's README.md for per-plugin examples.
Every wrapper honours the same behavioural contract so you can move between plugins without re-learning the idioms:
| Area | Rule |
|---|---|
| Error shape | GlitorError ADT with two-stage mapping: per-wrapper whitelist → project-wide keyword fallback |
| Option encoding | None fields omit the key entirely; the bridge never sees an explicit JS null |
| Permission state | Common 4-variant in glitor/permission; Camera extends with CameraLimited (5 variants) |
| Listener handles | Opaque, equality comparison is meaningless — register in init, not update |
| Native callback identifiers | Opaque CallbackId wrapping upstream type CallbackID = string |
| Numeric enums | Importance, Visibility, MediaType, EncodingType, MediaTypeSelection use glitor/numeric |
| FFI loader | Lazy import("@capacitor/*") — unit tests run without node_modules |
package_design.md |
Always the source of truth for semantics; upstream TypeScript wins on runtime values |
| Toolchain | Range |
|---|---|
| Gleam | >= 1.12.0 |
| Capacitor | 8.x (@capacitor/core ^8.0.0) |
| Lustre (adapter) | >= 5.0.0 < 6.0.0 |
| Node (Gleam JS target) | 22+ |
| iOS | 15+, Xcode 26+ |
| Android Studio | Otter (2025.2.1)+ |
App targetSdk |
36 is the edge-to-edge boundary (not runtime OS version) |
Capacitor 7.x and below are not supported.
# Test any single package
cd glitor_core && gleam test
# Test every package locally (mirror of CI)
for pkg in glitor_*; do (cd $pkg && gleam test) || exit 1; doneEvery package has its own gleam.toml. glitor_core is linked into the
other wrappers via a path dependency, so a change to the core is picked up
by the next gleam test in a dependent package.
Tests do not require any @capacitor/* npm package to be installed — every
FFI uses a lazy import(), exercised only when the bridge is actually
invoked (not by the encoder / decoder unit tests).
glitor/
├── LICENCE
├── README.md ← this file
├── glitor_core/ ← shared types + SystemBars
├── glitor_<plugin>/ ← one per official plugin (× 16)
├── glitor_lustre/ ← Lustre effect adapter
├── references/ ← Capacitor v8 docs snapshot + design notes
└── .github/workflows/ci.yml ← per-package test matrix
Design decisions for the wrapper set live in
references/package_design.md; it is the
definitive contract that every package conforms to.
Before opening a PR, run the full matrix locally:
for pkg in glitor_*; do (cd $pkg && gleam format src test && gleam test) || exit 1; doneWhen changing glitor_core, always check the downstream wrappers — the
path dependency means a core API break surfaces immediately in every
wrapper's test suite.
MIT — see LICENCE. Each package carries an identical copy of the same licence so Hex downloads include it alongside the source.
- Ionic Team for Capacitor and the public plugin catalogue.
- Louis Pilfold and the Gleam team.
- Lustre Labs for the Lustre framework the adapter targets.