Unbound
A client modification for Discord on mobile.
Install plugins, themes, fonts, and icon packs on the official Discord app, on both iOS and Android.
Contents
- For Users: What Is Unbound? · Features · Installation
- For Developers: Creating Add-ons · Developing Unbound · Architecture
- Project: Contributing · Acknowledgements · Star History · License
For Users
What Is Unbound?
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 native loaders that install Unbound on a device are maintained in separate repositories.
Features
| 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. |
Installation
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.
For Developers
Creating Add-ons
- Create a project with the CLI:
bunx @unbound-app/cli create
- Set the add-on
typein its manifest:
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.
Developing Unbound
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.
build, then serve:
bun run --filter @unbound-app/client build
bun run --filter @unbound-app/client serve
The debugger (bunx @unbound-app/debugger) evaluates code and streams logs from the device.
Architecture
- 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
Packages
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. |
Project
Contributing
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.
Acknowledgements
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.
Star History
License
Unbound is licensed under the GNU General Public License v3.0.