Why your project needs a justfile


You clone a new repo. What’s the command to run tests? Is it npm run dev or yarn dev? make build or ./scripts/build.sh? Every project is different. You check the README, scroll past setup instructions, find the commands section—maybe. A justfile fixes this.

The problem with what you’re probably using now

Most projects use one of three approaches for running commands, and they all have issues.

Makefiles were designed for compiling C programs in the 1970s. You need .PHONY everywhere to mark non-file targets. Tabs and spaces look identical but behave differently, causing silent failures. The syntax is cryptic—$@ and $< make sense once you learn them, but nobody remembers what they mean six months later. Make works, but it carries decades of baggage for use cases it wasn’t designed for. Writing a simple task requires understanding timestamp comparison and target resolution, concepts from incremental compilation that don’t apply to running a development server.

npm scripts lock you into the JavaScript ecosystem. They’re convenient for JS projects, but JSON doesn’t support comments or multiline commands without escaping hell. Try writing a script that loops through files or handles conditionals—you end up calling a separate shell script anyway. Plus, if you work across multiple languages, you’re maintaining different tools for different projects.

Shell scripts scattered in a scripts/ directory have no discoverability. What scripts exist? What do they do? What arguments do they take? You have to read each file. There’s no built-in help. And platform differences mean scripts/setup.sh might not work on Windows without WSL. Some teams try to solve this with a README listing all scripts, but documentation drifts from reality. The script you need is probably named something like run-local-dev-env.sh or start_dev_server.sh depending on who wrote it.

What just does differently

just is a command runner that solves these problems. It gives every project the same interface: type just to see available commands, type just <command> to run them. It works across languages, loads environment variables automatically, and runs on any platform.

Instead of explaining features abstractly, here are recipes worth stealing.

Recipes worth stealing

Arguments with defaults let you customize behavior without environment variables:

# Deploy to staging by default, production when needed
deploy env="staging":
    aws s3 sync ./dist s3://myapp-{{env}}
    aws cloudfront create-invalidation --distribution-id {{env}}-dist

Run just deploy for staging, just deploy production to override.

Recipe dependencies ensure steps run in order:

# Run all checks before deploying
ci: fmt lint test
    echo "All checks passed"

# Pass arguments through to pytest
test *args:
    pytest {{args}}

Now just ci runs each check sequentially. just test -v tests/api/ passes -v tests/api/ directly to pytest. If any step fails, the chain stops—no deployment until everything passes.

Environment loading happens automatically:

set dotenv-load

# Database migrations read DATABASE_URL from .env
migrate:
    alembic upgrade head

# Seed data uses the same connection
seed:
    python scripts/seed_db.py

Add set dotenv-load at the top of your justfile and every recipe gets access to .env variables without sourcing files manually.

Platform detection with shebang recipes handles OS differences:

# Install dependencies using the right package manager
install:
    #!/usr/bin/env bash
    if command -v brew &> /dev/null; then
        brew install postgresql
    elif command -v apt-get &> /dev/null; then
        sudo apt-get install postgresql
    else
        echo "Unsupported platform"
        exit 1
    fi

The #!/usr/bin/env bash line tells just to run the recipe as a script, giving you full shell features for complex logic.

The AI angle

AI coding assistants work better with stable interfaces. Put this in your project’s CLAUDE.md:

Before making changes, run `just check` to verify formatting and linting.
After implementing features, run `just test` to ensure tests pass.

Now Claude Code (or any AI assistant) can run the same commands across all your projects without parsing package.json, Makefiles, or guessing script locations. The AI doesn’t need to know if you use pytest or jest, ruff or eslint—it runs just check and just test consistently.

This works because the AI follows your documentation. When you tell it “run just check before committing,” it doesn’t need to understand your build system. It runs the command, sees the output, and responds accordingly. Compare this to telling an AI “run the linter,” which requires it to find and parse configuration files, identify the right tool, and construct the correct command. Simple instructions produce better results.

Get started

Install just and create a justfile in your project root:

# macOS
brew install just

# Linux
cargo install just

# Then add recipes
echo "test:\n    pytest tests/" > justfile

Type just to see your commands. Add more as you go.