Starter

Your Phoenix starting point, as a workflow.

CIHex.pmDocs

Every new Phoenix app begins the same way: run mix phx.new, then spend an hour undoing defaults you don't want and wiring in the packages you always use. Starter turns that hour into a workflow — an ordered, flag-aware list of steps that lives in your project — built on Igniter, so every change is applied as a reviewable patch rather than a blind file overwrite.

Your workflow is a plain module in your own project — generated for you by mix starter.new, then edited to taste:

defmodule Mix.Tasks.MyApp.Workflow do
use Starter.Workflow
@impl Starter.Workflow
def steps do
[
{:remove, :daisy_ui}, # undo a phx.new default
{:remove, :topbar},
{:gen, :gitignore}, # generate project hygiene
{:add, :credo}, # get a package into the app
{:add, :ash}, # same verb, runs ash's installer
{:add, :oban, if: :oban}, # optional, behind --oban
MyApp.Steps.DeployConfig # or your own step module
]
end
end

…and it runs as a Mix task, with flags derived from your optional steps:

$ mix starter.run --oban

Getting started

Starter is built for freshly generated Phoenix apps (see Compatibility).

1. Create an app and add the dependency

mix phx.new my_app
cd my_app

Add starter to your deps in mix.exs:

def deps do
[
{:starter, "~> 0.1", only: :dev},
# ...
]
end
mix deps.get

2. Generate your workflow

mix starter.new

This creates lib/mix/tasks/my_app.workflow.ex — your workflow. It lists every built-in step in a sensible order, each with a one-line description of what it does. Open it and make it yours:

3. Run it

mix starter.run

Nothing is applied blindly. The run prints a plan showing what every step resolved to, then shows all its changes — your steps and any package installers together — as one diff you confirm once. (mix starter.run finds and runs your workflow task; invoking it directly as mix my_app.workflow does the same thing.)

The one thing that happens before that confirmation is dependencies: packages that ship an installer are added to mix.exs and fetched first, because their installers have to be on disk to run at all. That dependency change is shown and confirmed on its own. When everything finishes, set up the database and go:

mix ecto.setup
mix phx.server

Note: the generated workflow enables the vector Postgres extension via the pg_extensions and pgvector steps. If your local Postgres doesn't have pgvector installed, either install it or delete those two steps from your workflow.

Keep the workflow file in your repo: it documents exactly how your app was set up, and it's the file you'll copy into your next project.

Running single steps

Every step also works on its own, no workflow required:

mix starter.add credo,quokka # install + configure packages
mix starter.remove daisy_ui # undo a phx.new default
mix starter.gen gitignore # generate config/code
mix starter.add --list # see what's available (also: remove, gen)

These run Starter's own steps only. For a package that ships its own installer, use Igniter directly: mix igniter.install oban.

Scripts and CI

All commands are interactive by default (diff, then confirm). In a script or CI, pass Igniter's standard --yes flag to auto-accept:

mix starter.new --yes && mix starter.run --yes

Why not just…

Built-in steps

{:add, :name} means "get this package into my app, correctly." Starter's own step runs when it has one; otherwise the package is installed and its own installer runs. Which of the two applies is upstream's business, and each run prints a plan telling you which it was:

Starter plan
starter.add.credo
oban — oban.install
nimble_options — dependency only, ships no installer
starter.gen.sort_deps

Starter deliberately ships no step for packages that provide their own Igniter installeroban, tidewave, ash and friends run upstream's installer, so upstream stays the authority. Built-in add steps exist only where upstream ships nothing, and each does the minimum wiring a missing installer would do. When a package later ships an installer, its step here retires and workflows naming it keep working unchanged — that's the point of the single verb.

({:install, :name} still exists, and forces upstream's installer even when Starter has a step of that name. You rarely want it.)

KindStepWhat it does
addbunReplaces esbuild/tailwind with Bun: dep, package.json, config, watchers, aliases, tsconfig
addcredoDev/test dependency for static analysis
adddotenv_parserLoads .env in runtime config; creates and gitignores .env
addexsyncAuto-recompilation on file changes (dev)
addlibclusterNode clustering: dep, supervision child, Gossip topology in dev
addmix_test_watchRuns tests on file changes
addoban_proOban Pro: Smart engine, dynamic plugins, migration (requires license and an existing oban dep)
addpgvectorVector search: dep, Postgrex types, config, extension migration (merges into existing extensions migration)
addquokkaCredo-configured formatter plugin
addremixiconsRemix Icons as a Tailwind plugin with remix-* classes and a CoreComponents icon clause
adduuidv7Time-sortable UUID primary keys; updates your Schema module when present
removeagents_mdRemoves the generated AGENTS.md
removedaisy_uiRemoves the :daisyui Mix dependency and CSS plugin blocks
removelive_title_suffixRemoves the " · Phoenix Framework" title suffix
removetheme_toggleRemoves the theme scripts, component, and usage
removetopbarRemoves the topbar progress indicator
genbase_schemaMyApp.Schema module with sensible key/timestamp defaults
genecto_force_dropecto.drop --force-drop in the mix alias
gengenerator_defaultsGenerators default to binary_id + utc_datetime_usec
gengigalixirGigalixir deploy: buildpacks, Procfile, release migration scripts, SSL config
gengigalixir_libclusterKubernetes clustering strategy for Gigalixir
gengitignoreAdds macOS system files to .gitignore
genminimal_app_layoutReplaces Layouts.app/1 with a minimal header/main layout
genminimal_home_pageReplaces the phx.new marketing page with a minimal home page
genmix_env_configconfig :app, env: Mix.env() for runtime environment checks
genpg_extensionsMigration enabling citext, pg_trgm, unaccent, and vector
gensort_depsSorts mix.exs dependencies alphabetically
gentailwind_formatterHEEx formatter that sorts Tailwind classes

Steps that pattern-match against phx.new output warn loudly when they find nothing to change, instead of silently no-oping.

Writing your own steps

A step is any module that implements Igniter.Mix.Task:

defmodule MyApp.Steps.DeployConfig do
use Igniter.Mix.Task
@impl Igniter.Mix.Task
def igniter(igniter) do
Igniter.create_new_file(igniter, "config/deploy.exs", "# ...")
end
end

Reference it directly in your workflow's step list. Starter.Helpers and Starter.Versions provide conveniences for common patterns (app/repo module names, checked file edits, migration timestamps, latest-version lookups).

Sharing workflows

Workflows are modules, so they compose and travel. Keep your personal or team ritual in a small "step pack" — a dev-dep containing custom steps and a shared workflow module — and generate each new app's workflow from it:

mix starter.new --from MyTeam.Workflow

The generated file expands the shared workflow's steps, so the app owns and documents its setup while your pack stays the template. Shared workflows can also be included directly:

def steps do
[
{:workflow, MyTeam.Baseline},
{:add, :pgvector}
]
end

Steps apply in list order, package installers included, so a step that patches what an installer wrote just goes after it:

{:add, :oban},
MyTeam.Steps.ObanTweaks

{:queue, "some.task"} is still there for work that genuinely has to run against the applied project on disk, after everything else.

Compatibility

Starter supports only the latest stable Phoenix (currently 1.8) and its phx.new output. Workflows warn when run against an app on an older Phoenix. CI runs the generated workflow against a freshly generated phx.new app on every commit, so generator drift breaks loudly here instead of silently in your project.

Roadmap

Contributions welcome — a step is a small, self-contained module with tests, and adding one is a great first PR.

License

MIT — see LICENSE.md.