Skip to main content
Tasks are commands that are ran in the context of a project. Underneath the hood, a task is simply a binary or system command that is ran as a child process.

Overview

Tasks represent individual units of work in your build pipeline:
  • Building and compiling code
  • Running tests and linters
  • Type checking
  • Starting development servers
  • Deploying applications

Task IDs

A task identifier (or name) is a unique resource for locating a task within a project. The ID is explicitly configured as a key within the tasks setting, and can be written in camel/kebab/snake case.

ID Requirements

IDs support:
  • Alphabetic unicode characters
  • Numbers 0-9
  • Underscores _, hyphens -, forward slashes /, dots .
  • Must start with a character
moon.yml
tasks:
  build: {}
  type-check: {}
  test_unit: {}
  deploy/staging: {}
A task ID can be paired with a scope to create a target.

Task Types

Tasks are grouped into 1 of the following types based on their configured parameters:

Build Tasks

Task generates one or many artifacts, and is derived from the outputs setting.
moon.yml
tasks:
  build:
    command: 'webpack'
    inputs:
      - 'src/**/*'
      - 'webpack.config.js'
    outputs:
      - 'dist'
Build tasks are cached by default. Moon uses the outputs to determine if the task needs to re-run.

Test Tasks

Task asserts code is correct and behaves as expected. This includes linting, typechecking, unit tests, and any other form of testing. This is the default type.
moon.yml
tasks:
  test:
    command: 'jest'
    inputs:
      - 'src/**/*'
      - 'tests/**/*'
  
  lint:
    command: 'eslint .'
  
  typecheck:
    command: 'tsc --noEmit'

Run Tasks

Task runs a one-off, long-running, or never-ending process, and is derived from the options.persistent setting.
moon.yml
tasks:
  dev:
    command: 'vite'
    options:
      persistent: true
      cache: false

Task Modes

Alongside types, tasks can also be grouped into special modes that provide unique handling within the action graph and pipelines.

Local Server

Tasks either run locally, in CI (continuous integration pipelines), or both. For tasks that should only be ran locally, for example, development servers, we provide a mechanism for marking a task as local only server. When enabled:
  • Caching is turned off
  • The task will not run in CI
  • Terminal output is not captured
  • The task is marked as persistent
moon.yml
tasks:
  dev:
    command: 'start-dev-server'
    preset: 'server'

Internal Only

Internal tasks are tasks that are not meant to be ran explicitly by the user (via the command line), but are used internally as dependencies of other tasks. Additionally, internal tasks are not displayed in a project’s tasks list, but can be inspected with moon task.
moon.yml
tasks:
  prepare:
    command: 'intermediate-step'
    options:
      internal: true
  
  build:
    command: 'webpack'
    deps:
      - 'prepare'  # Runs automatically before build
Internal tasks help keep your task lists clean while enabling complex build workflows.

Interactive

Tasks that need to interact with the user via terminal prompts are known as interactive tasks. Because interactive tasks require stdin, and it’s not possible to have multiple parallel running tasks interact with stdin, we isolate interactive tasks from other tasks in the action graph. This ensures that only 1 interactive task is ran at a time.
moon.yml
tasks:
  init:
    command: 'init-app'
    options:
      interactive: true

Persistent

Tasks that never complete, like servers and watchers, are known as persistent tasks. Persistent tasks are typically problematic when it comes to dependency graphs, because if they run in the middle of the graph, subsequent tasks will never run because the persistent task never completes! However in moon, this is a non-issue, as we collect all persistent tasks within the action graph and run them last as a batch. This is perfect for a few reasons:
  • All persistent tasks are ran in parallel, so they don’t block each other
  • Running both the backend API and frontend webapp in parallel is a breeze
  • Dependencies of persistent tasks are guaranteed to have ran and completed
moon.yml
tasks:
  dev:
    command: 'start-dev-server'
    options:
      persistent: true
    # OR use preset
    preset: 'server'
Example: Running multiple services:
apps/api/moon.yml
tasks:
  dev:
    command: 'node --watch src/index.js'
    preset: 'server'
apps/web/moon.yml
tasks:
  dev:
    command: 'vite'
    preset: 'server'
    deps:
      - 'api:dev'  # API will start, then web, both run in parallel

Task Configuration

Tasks can be configured per project through moon.yml, or for many projects through .moon/tasks/**/*.

Commands vs Scripts

A task is either a command or script, but not both. So what’s the difference exactly? Command: A single binary execution with optional arguments, configured with the command and args settings (which both support a string or array). Script: One or many binary executions, with support for pipes and redirects, and configured with the script setting (which is only a string). A command also supports merging during task inheritance, while a script does not and will always replace values.
FeatureCommandScript
Configured asstring, arraystring
Inheritance merging✅ via mergeArgs option⚠️ always replaces
Additional args✅ via args setting
Passthrough args (from CLI)
Multiple commands (with && or ;)
Pipes, redirects, etc
Always ran in a shell
Custom platform/toolchain
Token functions and variables
Command Example:
moon.yml
tasks:
  build:
    command: 'webpack'
    args:
      - '--mode'
      - 'production'
      - '--color'
Script Example:
moon.yml
tasks:
  build:
    script: 'webpack --mode production && cp -r assets dist/'

Inputs and Outputs

Define inputs to determine when a task should re-run, and outputs for caching:
moon.yml
tasks:
  build:
    command: 'tsc'
    inputs:
      - 'src/**/*.ts'
      - 'tsconfig.json'
      - 'package.json'
    outputs:
      - 'dist'
Moon uses inputs to generate a hash for cache invalidation. If inputs haven’t changed and a cache exists, the task is skipped.

File Groups

Use file groups to reuse input patterns:
.moon/tasks/node.yml
fileGroups:
  sources:
    - 'src/**/*'
  tests:
    - 'tests/**/*'

tasks:
  lint:
    command: 'eslint'
    inputs:
      - '@globs(sources)'
      - '@globs(tests)'

Environment Variables

Define environment variables for tasks:
moon.yml
tasks:
  build:
    command: 'webpack'
    env:
      NODE_ENV: 'production'
      API_URL: 'https://api.example.com'

Task Dependencies

Define dependencies between tasks:
moon.yml
tasks:
  build:
    command: 'webpack'
    deps:
      - '~:typecheck'     # Run typecheck in same project first
      - '^:build'         # Run build in all dependencies first
      - 'api-client:build'  # Run specific task first
Dependency scopes:
  • ~:task - Task in the same project
  • ^:task - Task in all project dependencies
  • project:task - Specific project and task

Options

Control task behavior with options:
moon.yml
tasks:
  build:
    command: 'webpack'
    options:
      cache: true           # Enable caching (default)
      persistent: false     # Task completes (default)
      interactive: false    # No user interaction (default)
      runInCI: true         # Run in CI (default)
      priority: high        # Run priority (low, normal, high)
      shell: false          # Run in shell (default false for commands)

Real-World Examples

Development Server

moon.yml
tasks:
  dev:
    command: 'vite'
    deps:
      - '^:build'  # Build dependencies first
    preset: 'server'

Build Pipeline

moon.yml
tasks:
  typecheck:
    command: 'tsc --noEmit'
    inputs:
      - 'src/**/*.ts'
      - 'tsconfig.json'
  
  lint:
    command: 'eslint .'
    inputs:
      - 'src/**/*'
      - '.eslintrc.js'
  
  test:
    command: 'jest'
    deps:
      - '~:typecheck'
    inputs:
      - 'src/**/*'
      - 'tests/**/*'
  
  build:
    command: 'webpack'
    deps:
      - '~:typecheck'
      - '~:lint'
      - '~:test'
      - '^:build'
    inputs:
      - 'src/**/*'
      - 'webpack.config.js'
    outputs:
      - 'dist'

Conditional Tasks

moon.yml
tasks:
  deploy:
    command: 'deploy-script'
    options:
      runInCI: false  # Only run locally
  
  ci:
    command: 'run-tests'
    options:
      runInCI: true   # Only run in CI

Task Inheritance

View the official documentation on task inheritance.

Best Practices

Always specify task inputs to enable accurate caching. Avoid using **/* (the default) which includes everything.
Instead of chaining commands with &&, use the deps field to express task dependencies. This enables better parallelization and caching.
Commands are more portable, support better merging, and allow passthrough args. Use scripts only when you need shell features like pipes.
Always mark dev servers and watchers as persistent to ensure they run last and don’t block other tasks.

Configuration Reference

For detailed configuration options, see:

Build docs developers (and LLMs) love