docs/configuration/init.md
init.ts)Fresh auto-loads ~/.config/fresh/init.ts on startup. It's a TypeScript file that runs once with full access to the plugin API, and it complements the declarative config — it doesn't replace it.
The declarative config (config.json, the Settings UI, keybindings editor, theme selector) and init.ts cover different needs. Reach for the declarative side first:
config.json / Settings UI.Reach for init.ts when the decision depends on where or how Fresh is starting, and can't be baked into a shared config file without lying to the next person who opens it:
rust-analyzer lives in a different prefix on your work laptop).FRESH_PROFILE=writing fresh → wrap at 80, turn off diagnostics).If you're building something reusable, prefer a plugin — it's installable, shareable, and gets the plugin lifecycle for free.
Sometimes a plugin is the right unit, but part of its behavior only makes sense at startup or depends on the environment. In that case expose the knobs as a plugin API and call them from init.ts. Plugin APIs are typed automatically — editor.getPluginApi("dashboard") returns the right interface or null, no as-cast needed:
// In init.ts — plug the parts together for this machine.
editor.on("plugins_loaded", () => {
// Add a custom section to the Dashboard plugin.
const dash = editor.getPluginApi("dashboard");
if (dash) {
dash.registerSection("todo", async (ctx) => {
ctx.kv("open", "3", "warn");
ctx.newline();
});
}
// Configure another plugin for this environment.
const todo = editor.getPluginApi("todo-highlighter");
if (todo) todo.configure({ tags: ["TODO", "FIXME", "HACK"] });
});
That way the plugin stays declarative and shareable, and the environment-specific glue lives in the one file that isn't meant to be shared.
// Calmer UI over SSH. setSetting writes to a runtime layer — nothing
// is persisted, so removing this file is a complete undo.
if (editor.getEnv("SSH_TTY")) {
editor.setSetting("editor.diagnostics_inline_text", false);
editor.setSetting("terminal.mouse", false);
}
// Host-specific LSP path.
if (editor.getEnv("HOSTNAME") === "work-mac") {
editor.registerLspServer("rust", {
command: "/opt/homebrew/bin/rust-analyzer",
args: [],
autoStart: true,
});
}
// Env-driven profile: FRESH_PROFILE=writing fresh
if (editor.getEnv("FRESH_PROFILE") === "writing") {
editor.setSetting("editor.line_wrap", true);
editor.setSetting("editor.wrap_column", 80);
}
init: Edit init.ts from the command palette opens (or creates) ~/.config/fresh/init.ts with a starter template. The same command also refreshes types/fresh.d.ts, writes types/plugins.d.ts (so editor.getPluginApi("dashboard") and friends are typed), and creates a tsconfig.json on first run.init: Reload init.ts re-runs the file without restarting Fresh.init: Check init.ts runs a syntax check (oxc parser) and reports parse errors. It does not run a full TypeScript type-check.fresh --no-init (alias --safe) skips loading for a single launch — useful if the file errors out.init.ts fails to evaluate three times in a row within five minutes, the next launch auto-skips it until you fix or remove the file. A successful evaluation clears the counter.The full API surface is the same as plugins — see the Plugin API reference.
See it in action: What's New in 0.3.0 → init.ts.