launchd plists,
without the friction.

Take a Python script, Rust binary, Go service, or shell one-liner and have it running as a launchd background service in under a minute. mklaunchd is the macOS equivalent of mkunit for systemd.

~/code — zsh — 80×24
$ mklaunchd generate --label io.uradical.helloserver \
                     --binary /usr/local/bin/hello-server \
                     --write --yes

 Validation passed
 Created log directory: ~/Library/Logs/io.uradical.helloserver
 Plist written to ~/Library/LaunchAgents/io.uradical.helloserver.plist

-- Next steps

  launchctl bootstrap gui/501 ~/Library/LaunchAgents/io.uradical.helloserver.plist
  launchctl print gui/501/io.uradical.helloserver
  launchctl list | grep io.uradical.helloserver

  To stop the service:
  launchctl bootout gui/501/io.uradical.helloserver
The problem

Writing launchd plists shouldn't require a manual.

launchd is the right tool for personal automation on macOS — but its plist format is verbose XML, the keys are easy to miss, the failure modes are silent, and launchctl still bootstraps services into domain-scoped strings you have to remember by hand. mklaunchd writes the file and prints the launchctl commands. That's it. It does not wrap launchctl, replace it, or hide it.

What it does

Generates a valid plist, validates it before writing, creates the log directory, and prints the exact launchctl commands you need to load, inspect, reload, and unload the job.

What it doesn't do

It doesn't run launchctl on your behalf, abstract over it, or maintain a daemon of its own. Once the plist is on disk, you're back in standard Apple tooling.

Features

Six things, done well.

Interactive or one-shot

Walk through prompts the first time, then drop the same flags into a script for the next twelve services.

Validation up front

Catches the silent failures — KeepAlive on a scheduled job, relative paths, UserName on a user agent — before launchd does.

Hands off to launchctl

Prints the exact bootstrap, print, list, and bootout commands tailored to the scope you picked. Nothing magic, nothing hidden.

Every scope, no friction

User agent, system agent, or daemon — mklaunchd picks the right plist directory, the right launchd domain, and prepends sudo where you need it.

Plugins, the git way

Drop an executable named mklaunchd-foo on PATH and it becomes a subcommand. Write them in any language; environment contract is documented.

One Homebrew formula

One binary, one tap, one install. macOS-only by design — no cross-platform abstraction tax, no Linux compatibility shims.

How it works

From idea to running service in three steps.

01

Install

Tap and install via Homebrew. macOS 13+.

brew tap uradical/mklaunchd
brew install mklaunchd
02

Generate

Pass flags or run interactively. mklaunchd validates the plist and writes it to the right LaunchAgents directory.

mklaunchd generate \
  --label io.uradical.helloserver \
  --binary /usr/local/bin/hello-server \
  --scope user-agent \
  --write --yes
03

Bootstrap with launchctl

Copy the printed launchctl commands. Your service is now under standard macOS tooling — restart on reboot, log to ~/Library/Logs/<label>/, manageable from the same launchctl you'd use for any other plist.

launchctl bootstrap gui/$UID \
  ~/Library/LaunchAgents/io.uradical.helloserver.plist
Before & after

Same job. One sixteenth the typing.

A run-at-load Launch Agent that restarts on crash, with a sane PATH and a stdout/stderr log directory. Hand-written on the left, generated on the right.

By hand io.uradical.helloserver.plist · 28 lines
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>io.uradical.helloserver</string>
  <key>Program</key>
  <string>/usr/local/bin/hello-server</string>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <dict>
    <key>SuccessfulExit</key>
    <false/>
  </dict>
  <key>ThrottleInterval</key>
  <integer>10</integer>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key>
    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
  </dict>
  <key>StandardOutPath</key>
  <string>~/Library/Logs/io.uradical.helloserver/stdout.log</string>
  <key>StandardErrorPath</key>
  <string>~/Library/Logs/io.uradical.helloserver/stderr.log</string>
</dict>
</plist>
With mklaunchd $ — 4 lines
mklaunchd generate \
  --label io.uradical.helloserver \
  --binary /usr/local/bin/hello-server \
  --write --yes

# PATH, log paths, KeepAlive, ThrottleInterval, the
# XML scaffolding, the log directory, and the
# launchctl bootstrap commands all come for free.


# Want to see it without writing? Pipe to a file:
mklaunchd generate --label ... --binary ... --stdout > out.plist


# Want it interactive? Just run:
mklaunchd generate
Install

One tap. One brew install.

Requires macOS 13+. Source on GitHub, MIT licensed.

Pre-1.0. In active development — CLI flags and conventions may change before v1.

brew tap uradical/mklaunchd
brew install mklaunchd

mklaunchd generate

Until v1.0: the Homebrew tap is not yet published. Build from source instead:

git clone https://github.com/uradical/mklaunchd
cd mklaunchd
make install

Reading the docs locally: man mklaunchd  ·  Plugin guide  ·  Source code