Building Generators

Workflow Reference

A workflow is a YAML file that composes actions into a sequence with inputs, conditionals, and jobs. Workflows are the core unit of xo — every xo add, xo create, and xo run command executes a workflow.

Full example

A complete workflow.yaml showing all top-level fields:

name: payment/stripe
on: [add]
description: Add Stripe payment processing to an existing project

detects:
  - file: package.json
    exists: true
  - pkg: next
    exists: true

dependencies:
  - auth/jwt

conflicts:
  - payment/paddle

provides:
  - payment

inputs:
  secretKey:
    prompt: "Stripe secret key?"
    required: true
  webhookSecret:
    prompt: "Stripe webhook secret?"
    required: false

jobs:
  detect:
    steps:
      - uses: xo/detect-pm
        id: pm
      - uses: xo/pkg-installed
        id: hasNext
        with:
          pkg: next

  install:
    needs: [detect]
    steps:
      - uses: xo/install-pkg
        with:
          pkg: stripe

      - uses: xo/env
        with:
          file: .env.example
          variables:
            STRIPE_SECRET_KEY: ""
            STRIPE_WEBHOOK_SECRET: ""

  configure:
    needs: [install]
    steps:
      - if: "steps.hasNext.outputs.installed == true"
        uses: xo/copy
        with:
          from: templates/stripe-route.ts
          to: app/api/stripe/route.ts

      - uses: xo/ast-import
        with:
          file: src/app.module.ts
          import: StripeModule
          from: ./stripe/stripe.module

      - run: "{{ steps.pm.outputs.value }} db:push"

name

Namespaced identifier. Used when calling xo add <name> or xo create <name> after registering the generator.

name: payment/stripe

on

Trigger(s) that activate this workflow. A generator can respond to one or multiple triggers.

on: [add]           # xo add payment/stripe
on: [create]        # xo create payment/stripe
on: [run]           # xo run payment/stripe
on: [add, run]      # both

inputs

Named inputs collected interactively from the user before any jobs run. All answers are available as {{ inputs.* }} in steps.

inputs:
  componentName:
    prompt: "Component name?"
    required: true
    min: 2
    max: 50
  variant:
    prompt: "Choose variant"
    type: select
    choices: [primary, secondary, ghost]
    default: primary
  confirmSetup:
    prompt: "Continue with setup?"
    type: confirm
  features:
    prompt: "Select features"
    type: multiselect
    choices: [auth, payments, analytics]
TypeDescription
textFree text input (default)
selectSingle choice from a list
confirmYes / no boolean
multiselectMultiple choices from a list

Input validation fields: required (boolean), min (min character length), max (max character length), pattern (regex string).

jobs

A workflow contains one or more named jobs. Jobs run sequentially. Use needs to declare an explicit dependency order.

jobs:
  detect:
    steps: [...]
  install:
    needs: [detect]
    steps: [...]
  configure:
    needs: [install]
    steps: [...]

steps

Each step runs a uses action or an inline run command.

Using an action

- uses: xo/install-pkg
  with:
    pkg: stripe

Running a shell command

- run: pnpm db:push

Naming a step (for logs)

- name: Install Stripe SDK
  uses: xo/install-pkg
  with:
    pkg: stripe

Capturing output with id

- uses: xo/detect-pm
  id: pm

# Later steps reference: steps.pm.outputs.value

Conditional step (if)

- if: "steps.hasNext.outputs.installed == true"
  uses: xo/copy
  with:
    from: templates/route.ts
    to: app/api/stripe/route.ts

Parallel steps

- uses: xo/detect-pm
  id: pm
  parallel: true
- uses: xo/detect-lang
  id: lang
  parallel: true
- uses: xo/pkg-installed
  id: hasNext
  parallel: true
  with:
    pkg: next
# ^ all three run concurrently, then the next sequential step runs
if expressions support ==, !=, &&, ||. The expression is evaluated after context variable interpolation.

Step outputs

Detection actions produce outputs that later steps can read. Assign an id to a step to capture its outputs as steps.<id>.outputs.*.

- uses: xo/detect-pm
  id: pm
  # outputs: { value: "pnpm" }

- uses: xo/pkg-installed
  id: hasNext
  with:
    pkg: next
  # outputs: { installed: true, version: "^14.0.0" }

- uses: xo/file-exists
  id: hasPrisma
  with:
    path: prisma/schema.prisma
  # outputs: { exists: false }

# Use in if:
- if: "steps.hasNext.outputs.installed == true"
  uses: xo/copy
  with:
    from: templates/route.ts
    to: app/api/stripe/route.ts

# Use in run:
- run: "{{ steps.pm.outputs.value }} add stripe"

Context variables

Variables available inside any string value using {{ double-braces }} — in with:, run:, if:, and template files.

NamespaceSource
{{ inputs.* }}Answers collected from workflow inputs
{{ steps.<id>.outputs.* }}Outputs from a previous step that has an id
{{ config.* }}Values from xo.config.yaml in the project
{{ env.* }}Environment variables (process.env)
- uses: xo/copy
  with:
    from: templates/component.tsx
    to: "{{ config.ui.componentsDir }}/{{ inputs.componentName }}.tsx"

- run: "{{ steps.pm.outputs.value }} add {{ inputs.pkg }}"

- uses: xo/env
  with:
    file: .env.example
    variables:
      DB_URL: "{{ env.DATABASE_URL }}"

detects — pre-flight check

Runs before inputs are collected. All rules are AND-ed — every one must pass, or xo refuses to run. Use for quick compatibility checks: file existence and package presence.

detects:
  - file: package.json    # checks if file exists at this path
    exists: true
  - pkg: react            # checks package.json dependencies
    exists: true
For richer detection (package manager, language, reading JSON values), use detection actions in a detect job. See the Signals Reference.

Dependencies, conflicts, provides

Control how generators compose and validate against each other.

dependencies:
  - database/postgres                   # registry name — runs before this workflow
  - "@github/my-org/xo-base"           # direct GitHub ref
  - "@github/my-org/xo-base@v1.0.0"   # pinned to a tag

conflicts:
  - auth/firebase       # xo aborts if already installed

provides:
  - auth                # abstract capability tag
dependencies

Generators that must be applied before this one. xo resolves them in order.

conflicts

Generators that must NOT be applied. xo aborts if any conflict is found in xo.config.yaml.

provides

Abstract capability tags this generator satisfies. Other generators can list a tag in their conflicts to prevent double-installation.

Idempotency

Workflows are safe to re-run. Each built-in action handles idempotency internally — see the Actions Reference for per-action rules. For run: steps, guard with if conditions to prevent duplicate execution.