Schedule cron jobs on your machine from inside Obsidian.
Point the plugin at a folder of shell scripts and it manages the corresponding cron jobs for you, so you can create, edit, and remove schedules without leaving your vault.
Put your shell scripts in the cron folder inside your vault's config folder:
<your vault>/.obsidian/cron/
The plugin picks up every .sh file it finds there and lists it under Settings > Cron. A newly discovered script starts out disabled, so nothing runs until you give it a schedule and turn it on. Enabling a job writes it to your crontab; disabling it takes it back out.
Each job has:
- a name, which is yours to change and is only used for display and for the command palette
- a schedule, written as a standard five-field cron expression (
0 3 * * *) or a macro (@daily) - an enable toggle
Every job also gets a Run script: <name> command in the command palette, so you can run it immediately instead of waiting for its schedule. A manual run goes through the same runner as a scheduled one, so the two behave identically.
Everything this plugin writes lives inside a delimited block:
# BEGIN obsidian-cron
...
# END obsidian-cron
Lines outside that block are never touched. If the block is ever damaged — say the # END line gets deleted — the plugin refuses to write anything at all and tells you, rather than guessing where the block ends.
To remove the block yourself at any time, use Remove all managed jobs now in the settings. That also turns every job off, so the block does not come back the next time something changes.
Scripts need the executable bit set. If one is missing it, the settings tab says so and offers a Make executable button.
#!/bin/sh
echo "Backing up..."Each run gets:
- the vault root as its working directory
OBSIDIAN_VAULT_PATHandOBSIDIAN_CRON_JOB_IDin its environment- a log at
.obsidian/cron/logs/<job id>.log, trimmed once it passes the size limit in settings
If a scheduled run comes around while the previous one is still going, it is skipped rather than run twice.
Jobs run under your login shell, which reads .zprofile (or .bash_profile) but not .zshrc — that file is only read by interactive shells. Homebrew's installer writes to .zprofile, so /opt/homebrew/bin is normally on the path. Tools you set up in .zshrc will not be.
If something is missing, either add its directory to Extra PATH entries in settings or use an absolute path in your script. The settings tab shows the exact PATH your scheduled jobs will see.
macOS blocks cron from reaching ~/Desktop, ~/Documents, ~/Downloads and iCloud Drive, and the failure is silent — the job simply never does anything. The plugin warns you when your vault is in one of these folders.
The simplest fix is to keep your vault somewhere else, such as ~/Vaults or ~/Notes. Those paths are not protected, so nothing needs granting and scheduled jobs work immediately.
If the vault has to stay where it is, the alternative is to grant Full Disk Access to cron:
- Open System Settings > Privacy & Security > Full Disk Access
- Click +, press Cmd-Shift-G, and enter
/usr/sbin/cron - Enable the entry
Weigh that up before doing it. The grant goes to /usr/sbin/cron itself and is inherited by every job in every crontab, not only this plugin's, including ones added later. Full Disk Access is also wider than the folders you are trying to reach: it covers Mail, Messages, Safari data, Time Machine backups and other apps' sandboxed containers.
Note that using launchd instead does not avoid this. A LaunchAgent inherits launchd's own permissions, not those of whatever loaded it, and hits the same denial.
Running a job from the command palette works either way, because it inherits Obsidian's own permissions. So "works when I press Run now, never runs on schedule" is almost always this.
Jobs keep running when Obsidian is closed — that is the point of using system cron. Quitting the app leaves your crontab alone.
Disabling or uninstalling the plugin removes its block from your crontab. You can turn that off with Remove jobs when the plugin is disabled.
Your scripts run with your full user privileges, exactly as if you had typed them into a terminal. A few things follow from that.
New scripts never run on their own. A script the plugin discovers is added disabled, with no schedule in your crontab, until you turn it on yourself.
A vault that syncs is a code delivery channel. If you use Obsidian Sync, iCloud, Dropbox or git, then whatever can write to your vault can put a script in .obsidian/cron. It will not run until you enable it, but two cases deserve care:
- changing the contents of a script that is already enabled changes what runs, with no further confirmation
.obsidian/plugins/cron/data.jsonholds the enabled flags, so editing that file directly can schedule a job
Treat .obsidian/cron as trusted code, the same way you would treat anything else you run on a schedule.
Only jobs you enable reach your crontab, inside a delimited block, and your own cron jobs are never modified.
macOS and Linux only. Windows is not supported, as it has no cron.
This is a desktop plugin. It does not run on Obsidian mobile.
Requires Obsidian 1.14.0 or newer.
- Bun — installs dependencies and runs the build scripts
- Node.js v22 — recommended
Check what you have installed:
bun --version
node --version-
Install dependencies:
bun install
-
Start a watch build. It compiles into
dist/and rebuilds whenever you save:bun run dev
For a one-off production build, run
bun run build.Run the test suite with
bun test. It covers the crontab splicing, the cron expression validator, job reconciliation, and the generated runner script end to end. -
Link
dist/into your vault so Obsidian can load the plugin. The folder name must match theidinmanifest.json:ln -s ~/Desktop/public-repos/obsidian-cron/dist ~/Desktop/obsidian-development/.obsidian/plugins/cron
-
Open Obsidian, go to Community plugins, and enable Cron
Obsidian does not pick up new plugin files on its own. If you have the official Obsidian CLI enabled, the build reloads the plugin for you.
-
Turn the CLI on in Obsidian under Settings → General → Command line interface (needs the 1.12.7 installer or newer), then follow the prompt to add it to your PATH. Check it with:
obsidian version
-
Copy the example env file and fill in the vault you develop against:
cp .env.example .env
OBSIDIAN_VAULT=obsidian-development
Run
obsidian vaultsto list the vault names Obsidian knows about.
Every successful build now reloads the plugin in that vault, whether it came from bun run dev or bun run build. To reload without rebuilding:
bun run reloadThe plugin to reload comes from the id field in manifest.json. If the folder you linked into .obsidian/plugins is named something else, set OBSIDIAN_PLUGIN_ID in .env to match the folder.
Reloading is entirely optional. With no .env, or without the CLI installed, the build prints a short note once and carries on. Note that the CLI starts Obsidian if it is not already running.
The included workflow builds the plugin and attaches the files to a GitHub release whenever you push a tag.
First, give the workflow permission to create releases:
- Open the repository on GitHub
- Go to Settings → Actions → General
- Under Workflow permissions, select Read and write permissions
- Click Save
Then tag a version and push it:
git tag 0.1.0
git push origin 0.1.0Tags are bare version numbers, with no v prefix (see .npmrc).
Built from the Obsidian Svelte plugin starter, an extension of the official Obsidian sample plugin.