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/stripeon
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] # bothinputs
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]| Type | Description |
|---|---|
| text | Free text input (default) |
| select | Single choice from a list |
| confirm | Yes / no boolean |
| multiselect | Multiple 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: stripeRunning a shell command
- run: pnpm db:pushNaming a step (for logs)
- name: Install Stripe SDK
uses: xo/install-pkg
with:
pkg: stripeCapturing output with id
- uses: xo/detect-pm
id: pm
# Later steps reference: steps.pm.outputs.valueConditional step (if)
- if: "steps.hasNext.outputs.installed == true"
uses: xo/copy
with:
from: templates/route.ts
to: app/api/stripe/route.tsParallel 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 runsif 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.
| Namespace | Source |
|---|---|
| {{ 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: truedetect 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 tagdependenciesGenerators that must be applied before this one. xo resolves them in order.
conflictsGenerators that must NOT be applied. xo aborts if any conflict is found in xo.config.yaml.
providesAbstract 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.