Your Phoenix starting point, as a workflow.
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 --obanStarter is built for freshly generated Phoenix apps (see Compatibility).
mix phx.new my_app
cd my_appAdd starter to your deps in mix.exs:
def deps do
[
{:starter, "~> 0.1", only: :dev},
# ...
]
endmix deps.getmix starter.newThis 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:
- Delete any step you don't want — the file is yours.
- Reorder steps freely; they run top to bottom.
- Tag a step with
if: :flagto make it optional — it then only runs when you pass the matching--flag.
mix starter.runNothing 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.serverNote: the generated workflow enables the
vectorPostgres extension via thepg_extensionsandpgvectorsteps. 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.
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.
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 --yesmix igniter.new --install a,b,c? Great for installing packages that ship installers, and Starter runs those same installers rather than competing with them. Starter is for everything around that: removingphx.newdefaults (nothing else does this), packages that don't ship installers, ordering steps, optional flags, and keeping the whole ritual versioned in your repo.- A boilerplate/template repo? Templates fork away from
phx.newand rot. A workflow replays your preferences on top of whateverphx.newcurrently generates.
{: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_depsStarter deliberately ships no step for packages that provide their own
Igniter installer — oban, 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.)
| Kind | Step | What it does |
|---|---|---|
| add | bun |
Replaces esbuild/tailwind with Bun: dep, package.json, config, watchers, aliases, tsconfig |
| add | credo |
Dev/test dependency for static analysis |
| add | dotenv_parser |
Loads .env in runtime config; creates and gitignores .env |
| add | exsync |
Auto-recompilation on file changes (dev) |
| add | libcluster |
Node clustering: dep, supervision child, Gossip topology in dev |
| add | mix_test_watch |
Runs tests on file changes |
| add | oban_pro |
Oban Pro: Smart engine, dynamic plugins, migration (requires license and an existing oban dep) |
| add | pgvector |
Vector search: dep, Postgrex types, config, extension migration (merges into existing extensions migration) |
| add | quokka |
Credo-configured formatter plugin |
| add | remixicons |
Remix Icons as a Tailwind plugin with remix-* classes and a CoreComponents icon clause |
| add | uuidv7 |
Time-sortable UUID primary keys; updates your Schema module when present |
| remove | agents_md |
Removes the generated AGENTS.md |
| remove | daisy_ui |
Removes the :daisyui Mix dependency and CSS plugin blocks |
| remove | live_title_suffix |
Removes the " · Phoenix Framework" title suffix |
| remove | theme_toggle |
Removes the theme scripts, component, and usage |
| remove | topbar |
Removes the topbar progress indicator |
| gen | base_schema |
MyApp.Schema module with sensible key/timestamp defaults |
| gen | ecto_force_drop |
ecto.drop --force-drop in the mix alias |
| gen | generator_defaults |
Generators default to binary_id + utc_datetime_usec |
| gen | gigalixir |
Gigalixir deploy: buildpacks, Procfile, release migration scripts, SSL config |
| gen | gigalixir_libcluster |
Kubernetes clustering strategy for Gigalixir |
| gen | gitignore |
Adds macOS system files to .gitignore |
| gen | minimal_app_layout |
Replaces Layouts.app/1 with a minimal header/main layout |
| gen | minimal_home_page |
Replaces the phx.new marketing page with a minimal home page |
| gen | mix_env_config |
config :app, env: Mix.env() for runtime environment checks |
| gen | pg_extensions |
Migration enabling citext, pg_trgm, unaccent, and vector |
| gen | sort_deps |
Sorts mix.exs dependencies alphabetically |
| gen | tailwind_formatter |
HEEx 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.
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
endReference 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).
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.WorkflowThe 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}
]
endSteps 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.
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.
- A community workflow registry
Contributions welcome — a step is a small, self-contained module with tests, and adding one is a great first PR.
MIT — see LICENSE.md.