better-fonts is a modular, configurable macOS command-line tool for patching and unpatching Electron and Native macOS applications to use custom fonts.
- Dual-Driver Architecture: Supports both Electron apps (via preload script CSS injection) and Native Cocoa / Qt / C++ apps (via
DYLD_INTERPOSECoreText hooking). - Custom Font Injection: Intercepts font creation and renders entire user interfaces in your chosen monospace or proportional font.
- Bi-directional Patching & Unpatching: Safely applies, replaces, or completely removes font patches without corrupting application bundles.
- Native ASAR Engine: Modifies Electron archive packages in-memory, preserving
.unpackednative binaries (.node,.dylib,.wasm) without relying on external extraction tools. - Dynamic Native Hooking: Compiles and injects lightweight CoreText interposition dylibs with native launcher wrappers on the fly.
- Electron Fuses & macOS Codesigning: Automatically disables
EnableEmbeddedAsarIntegrityValidationfuses and re-signs modified.appbundles with ad-hoc signatures. - XDG Base Directory Compliance: Stores user preferences in
$XDG_CONFIG_HOME/better-fonts/config.toml(~/.config/better-fonts/config.toml).
better-fonts includes built-in support for:
| Application | Driver | Default Path | Method |
|---|---|---|---|
| Paseo | electron |
/Applications/Paseo.app |
Preload CSS (dist/preload.js) |
| Signal | electron |
/Applications/Signal.app |
Preload CSS (preload.bundle.js) |
| Slack | electron |
/Applications/Slack.app |
Preload CSS (dist/preload.bundle.js) |
| Rekordbox | native-hook |
/Applications/rekordbox 7/rekordbox.app |
CoreText Interposition |
| Engine DJ | native-hook |
/Applications/Engine DJ.app |
CoreText Interposition |
| Telegram | native-hook |
/Applications/Telegram.app |
CoreText Interposition |
Custom applications can also be declared in config.toml or patched on demand.
For everyday users, better-fonts makes font customization on macOS effortless and reversible:
- Detects the App Type: Identifies whether the target application is built using web technology (like Slack, Signal, or Paseo) or as a native desktop program (like Rekordbox, Engine DJ, or Telegram).
- Injects Your Chosen Font: Seamlessly instructs the application to render its text using your preferred font instead of the hardcoded system default.
- Safe and Reversible: Makes changes in an isolated temporary staging area with automatic backups, allowing you to switch fonts at any time or restore the factory original with a single
better-fonts unpatchcommand.
For developers and technical users, better-fonts operates via two distinct engine drivers tailored to macOS app architectures:
- In-Memory ASAR Manipulation: Parses the 16-byte header and JSON directory tree of the application's
app.asararchive directly in Go. It locates the application preload script (such aspreload.bundle.jsordist/preload.js), strips any existing patch markers, and injects aDOMContentLoadedevent listener that attaches a global CSS style rule (* { font-family: "<font>", monospace !important; }). The ASAR is repacked in-place, preserving.unpackednative modules (.node,.dylib,.wasm) without needing to extract files to disk. - Pure Go Fuse Toggling: Scans the
Electron FrameworkMach-O binary for the sentinel byte sequencedL7pKGdnNz796PbbjQWNKmHXBZaB9tsXand modifies theEnableEmbeddedAsarIntegrityValidationfuse wire byte directly to'0'(DISABLE), eliminating ASAR integrity checksum verification crashes without external Node.js dependencies. - Bundle Re-Signing: Re-signs the modified bundle with an ad-hoc signature via
codesign --force --deep --sign -.
- CoreText Interposition (
DYLD_INTERPOSE): Dynamically compiles an Objective-C library (libfonthook.dylib) usingclangthat interposes low-level macOSCoreTextcreation routines (CTFontCreateWithNameandCTFontCreateWithFontDescriptor). When the target app requests fonts, the hook redirects the request to your configured font (with automatic variant mapping for Bold, Heavy, and Semibold weights) while explicitly passing through Apple private system fonts (prefixed with.) and symbol/emoji icon sets. - Launcher Binary Substitution: Renames the original Mach-O executable to
<binary>.origand substitutes a compiled C wrapper launcher (wrapper_launcher). When executed by macOS, the launcher discovers its directory via_NSGetExecutablePath, setsDYLD_INSERT_LIBRARIES=libfonthook.dylib, and callsexecvon the original binary, passing through all process arguments and environment variables. - Bundle Re-Signing: Re-signs the modified
.appbundle withcodesign --force --deep --sign -.
- macOS (Apple Silicon or Intel)
- Built-in macOS System Tools (pre-installed on every single Mac out of the box with zero setup required):
codesign,osascript,open, andplutil - Xcode Command Line Tools (
clang) — only required when patching Native apps like Rekordbox, Engine DJ, or Telegram (install withxcode-select --install)
Note: For Electron applications (Slack, Signal, Paseo), better-fonts is completely self-contained with a native ASAR parser and native Electron fuse manipulator in Go. It works immediately out of the box with zero external dependencies—Node.js, Bun, and npm are not required.
Go to the Latest Release Page and download the pre-compiled archive for your operating system. Extract and move the binary to your PATH:
tar -xzf better-fonts_*.tar.gz
sudo mv better-fonts /usr/local/bin/Ensure Go 1.26+ and just are installed:
git clone https://github.com/alexgorbatchev/better-fonts-cli.git
cd better-fonts-cli
just build
cp bin/better-fonts /usr/local/bin/Inspect all supported applications to see if they are installed, which driver is used, whether they are patched, and what font is currently applied:
better-fonts statusSample Output:
APP DRIVER INSTALLED PATCHED CURRENT FONT PATH
--- ------ --------- ------- ------------ ----
Paseo electron Yes Yes Maple Mono Normal NF /Applications/Paseo.app
Signal electron Yes Yes SF Mono /Applications/Signal.app
Slack electron Yes Yes Maple Mono Normal NF /Applications/Slack.app
Rekordbox native-hook Yes Yes Maple Mono Normal NF /Applications/rekordbox 7/rekordbox.app
Engine DJ native-hook Yes Yes Maple Mono Normal NF /Applications/Engine DJ.app
Telegram native-hook Yes No - /Applications/Telegram.app
Patch all applications specified in config.toml (default is all supported apps):
better-fonts patchTarget one or more specific applications by name, ID, or .app bundle path:
better-fonts patch slack
better-fonts patch rekordbox engine-dj telegram
better-fonts patch /Applications/SomeCustomApp.appApply a specific font without editing the configuration file:
better-fonts patch --font "JetBrains Mono" slack
better-fonts patch --font "Fira Code" telegramRestore original preload scripts or executables across all or targeted applications:
better-fonts unpatch
better-fonts unpatch rekordbox
better-fonts unpatch /Applications/SomeCustomApp.appCheck for and install the latest binary release directly from GitHub:
better-fonts upgradebetter-fonts listbetter-fonts reads its configuration from $XDG_CONFIG_HOME/better-fonts/config.toml (defaults to ~/.config/better-fonts/config.toml).
If the configuration file does not exist, running better-fonts creates a default one automatically.
# Print configuration file location
better-fonts config path
# Display current configuration
better-fonts config show# Default font applied to patched applications
font = "Maple Mono Normal NF"
# Applications to patch by default.
# Use ["*"] for all supported apps, or list specific IDs: ["slack", "rekordbox", "telegram"]
apps = ["*"]
# Automatically restart applications after patching/unpatching
restart = true
# Per-application overrides
[apps_config.slack]
font = "JetBrains Mono"
restart = true
[apps_config.rekordbox]
font = "Maple Mono Normal NF"
# User-defined custom apps
[[custom_apps]]
id = "custom-electron"
name = "Custom Electron App"
path = "/Applications/CustomElectron.app"
driver = "electron"
process_name = "CustomElectron"
asar_path = "app.asar"
preload_path = "dist/preload.js"
[[custom_apps]]
id = "custom-native"
name = "Custom Native App"
path = "/Applications/CustomNative.app"
driver = "native-hook"
process_name = "CustomNative"Global flags apply to all commands:
| Flag | Short | Default | Description |
|---|---|---|---|
--config |
-c |
~/.config/better-fonts/config.toml |
Custom path to configuration TOML file |
--font |
-f |
(from config) | Override font name |
--app |
-a |
(from config) | Target specific app(s) (e.g. -a slack,rekordbox) |
--restart |
true |
Restart application after patching or unpatching | |
--no-restart |
false |
Prevent application restart after operations | |
--dry-run |
false |
Simulate actions without modifying disk files | |
--verbose |
-v |
false |
Enable verbose operational logging |
--version |
Print version string |
# Build binary into bin/better-fonts
just build
# Run CLI with arguments
just run status
# Run unit tests with race detection
just test
# Check code hygiene and run vet
just lint
# Clean build artifacts and temporary files
just cleanThis project is licensed under the MIT License.

