A client modification for Discord on mobile.
Install plugins, themes, fonts, and icon packs on the official Discord app, on both iOS and Android.
- For Users: What Is Unbound? · Features · Installation
- For Developers: Creating Add-ons · Developing Unbound · Architecture
- Project: Contributing · Acknowledgements · Star History · License
Unbound is a client modification for the official Discord mobile app. It runs inside Discord and exposes a stable API for add-ons, so plugins, themes, fonts, and icon packs can be installed without patching Discord manually.
Add-ons have direct access to the modules, state, and UI that Discord itself uses. They can patch both Discord's behavior and its React components. Every change is reversible: disabling an add-on returns Discord to its stock state.
Unbound runs on iOS and Android through a single native bridge. iOS is the primary platform, and Android support is at an early stage.
This repository contains the client, the plugins and the native loaders that install Unbound on a device. The loaders live in packages/loader: a shared C++ core, the iOS tweak and the Android Xposed module, built and released by the loader-* workflows.
| Feature | Description |
|---|---|
| Add-ons | Plugins, themes, fonts, and icon packs for the stock Discord app. |
| Hot reloading | Add-ons update at runtime without restarting Discord. |
| Error isolation | A failing add-on is caught and recorded instead of crashing the app. |
| Recovery mode | Returns Discord to a working state when an add-on or bundle breaks it. |
| Cross-platform | iOS and Android share one native bridge and behave the same way. |
| Typed SDK | @unbound-app/api provides type definitions for the entire client API. |
Follow the installation guide for your platform:
| Platform | Method |
|---|---|
| iOS without a jailbreak | Sideload with SideStore, Feather, or AltStore, or install the .ipa file. |
| iOS with a jailbreak | Install from the repo.unbound.rip package repository. |
| Android | Xposed module. Support is early, and no official download is available yet. |
For problems after installation, see Troubleshooting.
-
Create a project with the CLI:
bunx @unbound-app/cli create
-
Set the add-on
typein its manifest:Type Purpose Plugin Changes Discord's behavior through UI and patches. The client runs its start()/stop()lifecycle.Theme Overrides colors and can set a custom background. Font Registers custom font families natively. Icon pack Replaces Discord's built-in assets. -
Build against
@unbound-app/apifor typed access tometro,storage,toasts, and the rest of the client API.
The add-on documentation covers the manifest, the lifecycle, the SDK, and publishing.
Requirements: Bun and a device or emulator with Unbound installed.
-
Clone the repository and install the dependencies:
git clone https://github.com/marioparaschiv/unbound cd unbound bun install -
Start the development server. It rebuilds the bundle on every change and serves it on the local network (port
3000by default, configurable withPORT):bun run --filter @unbound-app/client dev
-
Set your Unbound installation's bundle URL to the printed LAN address, then restart Discord. See Local Builds for the exact steps.
To build once without watching for changes, run build, then serve:
bun run --filter @unbound-app/client build
bun run --filter @unbound-app/client serveThe debugger (bunx @unbound-app/debugger) evaluates code and streams logs from the device.
- A platform-specific native loader patches Discord to run JavaScript, then injects the client bundle.
- On boot, the client traps Discord's Metro registry (
__r/__d) and wraps every module factory. That gives it a searchable view of Discord's internal modules. - Add-ons find modules through the Metro layer (
findByProps,findByName,findStore) and patch them, both plain functions and React components, with possess. - The client calls into the loader for native capabilities such as reload, the filesystem, and
evaluateBytecode. - The client runs on a single
initialize/shutdownlifecycle. Each manager starts its add-ons on startup and tears them down on shutdown, and settings are saved before the global is cleared.
%%{init: {"flowchart": {"nodeSpacing": 40, "rankSpacing": 45}}}%%
flowchart TB
subgraph Native["Native Side · iOS / Android"]
direction LR
Loader["<b>Unbound Loader</b><br/>Tweak / Xposed Module"]
App["<b>Discord App</b><br/>UIKit / Android Views"]
Loader -->|Hooks Colors and Fonts| App
end
subgraph JS["JS Land · Hermes"]
direction LR
subgraph Client["Unbound Client"]
direction TB
Managers["<b>Add-on Managers</b><br/>Plugins, Themes,<br/>Fonts, Icon Packs"]
APIs["<b>APIs</b><br/>Storage, Toasts, Assets"]
Patcher["<b>Patcher</b><br/>possess"]
Metro["<b>Metro Layer</b><br/>findByProps, findByName"]
Managers --> APIs
Managers --> Patcher
APIs --> Metro
Patcher --> Metro
end
subgraph DJS["Discord JS"]
direction TB
Registry["<b>Module Registry</b><br/>__r / __d"]
UI["<b>React UI</b><br/>Components"]
Stores["<b>Flux Stores</b><br/>App State"]
Registry ~~~ UI ~~~ Stores
end
Client -->|Find and Patch| DJS
end
Native <-->|"Injects Bundle<br/>Native Bridge"| JS
classDef native fill:#2b2b2b,stroke:#6b6b6b,color:#ffffff
classDef core fill:#c74050,stroke:#c74050,color:#ffffff
classDef discord fill:#5865f2,stroke:#5865f2,color:#ffffff
class App,Loader native
class Managers,APIs,Patcher,Metro core
class Registry,UI,Stores discord
style Native fill:transparent,stroke:#6b6b6b,stroke-width:2px
style JS fill:transparent,stroke:#6b6b6b,stroke-width:2px
style Client fill:transparent,stroke:#c74050,stroke-width:2px
style DJS fill:transparent,stroke:#5865f2,stroke-width:2px
The client is the runtime that ships into Discord. The remaining packages support it:
| Package | What it is |
|---|---|
packages/client |
The Unbound runtime, the bundle that ships into Discord. |
packages/cli |
The ubd CLI for scaffolding and managing add-on projects. |
packages/debugger |
A REPL/CLI that connects to a running client to eval code, stream logs, and push add-on bundles. |
packages/api |
Public type declarations that add-on developers build against. |
packages/logger |
A small, dependency-free scoped logger with ANSI colors. |
packages/utils |
Per-file utilities, each imported individually for tree-shaking. |
packages/types |
Shared TypeScript types used across the project. |
Contributions are welcome. Before opening a PR:
- Run
bun run lintandbun run fmt:checkfrom the repo root. Both are enforced in CI. - Confirm that the client builds.
- Write commit messages in the Angular convention (
feat:,fix:,docs:, ...). A commit hook enforces this. - Keep the PR focused and up to date with
main.
Unbound wouldn't exist without the work of:
- @acquitelol for the bundle, the debugger, and several ideas.
- @castdrian for the iOS loader, vbound, autosupport, the automatic decryption service, and build automation / CI pipelines.
- @NotZoeyDev for Enmity, and for paving the way for client modifications on iOS and mobile in general.
- @rushiiMachine for libunbound-android.
Unbound is licensed under the GNU General Public License v3.0.