Proposed Plugin System for Mob - BEAM Mobile Framework

Plugin System for Mob

Mob is growing by leaps and bounds! I realize now we will not be able to accommodate all the functionality people would want in the main framework itself. It is designed to be extensible. The build system should be able to handle plugins now.

The idea for the plugins is similar to React Native plugins. This is more than coincidence because React Native and Mob have similar architectures, with Mob being a bit simpler. The native code in Kotlin/Java, Swift/ObjC, C is likely very close to what Mob would need. The main difference is the logic is in Elixir, not JS and the distribution is hex.

One of the upsides of the similarity is we could migrate many of these to Mob which would be a big boost to the ecosystem. There are ~150-200 that land in this ‘native heavy’ category that would be very attractive to use such as date pickers, etc. that translate to native elements.

So you’d have the Android native code in /android and the iOS native code in /ios. You could have one sided plugins that only land on one platform.

I’ve brainstormed a pretty extensive document on this with Claude. Here’s the shorter summary:

Mob Plugins: Manifest Schema Proposal

Three design choices anchor this proposal. First, the manifest is data, not code — mob_dev reads priv/mob_plugin.exs at compile time rather than having plugins call register_plugin at runtime. Static, inspectable, validatable; closer to mix.exs than to Phoenix’s runtime route registration. Second, activation is explicit and separate from installation, borrowed from how iOS entitlements work: a framework supporting capability X doesn’t mean your app uses X without explicit declaration. This mitigates supply-chain risk by ensuring mix deps.get can never silently modify your app’s permission set. Third, the schema scales with plugin tier rather than being exhaustive everywhere — small plugins write three fields, large plugins write a dozen sections, and the shape of the manifest doesn’t make trivial plugins look heavy. Underlying all of this: Hex is the substrate, so versioning, dep resolution, security posture, and hexdocs publication come free, and static linking is required (no dlopen) to stay App-Store-compatible.

The schema spans five tiers. Tier-0 is a pure Elixir Hex package needing no manifest at all. Tier-1 adds NIFs and per-platform native sources — Kotlin bridge files, Swift files, Gradle deps, frameworks, permissions, plist keys. Tier-2 introduces :ui_components for new render-tree node types with paired SwiftUI views and Composables. Tier-3 layers on :screens, :migrations, and :assets for plugins that ship entire mini-applications. Tier-4 adds :lifecycle, :settings, and :notifications for embedded sub-apps with supervised children, persisted user settings, and push notification handlers. Required top-level fields are just :name, :mob_version, and :plugin_spec_version; everything else is independently optional.

Installation is deliberately two-step. Adding a plugin to deps and running mix deps.get makes it resolvable but does not merge native code, permissions, or plist keys. Activation requires an explicit entry in config :mob, :plugins in mob.exs. mob_dev prints the diff at compile time so users see exactly what’s being added — permission set changes are never silent. mix mob.add_plugin wraps both steps plus optional interactive setup prompts where plugin authors guide tier-3/4 integration (e.g., “Register MobChatKit.MessageListScreen in your App.navigation/1?”). The standard flow always works; the convenience command is not a required entry point.

Validation runs at two stages and fails loud. mix mob.validate_plugin checks the plugin in isolation: paths exist, files parse, version requirements are valid, and single-platform UI components are flagged as warnings (the #1 React Native plugin pain point). mob_dev then re-validates at compile time across all activated plugins — no duplicate component atoms, no colliding screen routes, no migration namespace collisions, all activated plugins present in deps. Hot-pushability is computed automatically from which sections are populated: tier-0 hot-pushes fully, tiers 1-2 require a native rebuild, tiers 3-4 are partial (Elixir hot-pushes; native does not). Forward compatibility is handled via :plugin_spec_version, letting the schema evolve without breaking existing plugins — bump the integer, support both versions in mob_dev for a migration window, deprecate.

Any comments or questions are appreciated.

2 Likes