Short answer: yes, as of CrewAI 1.15 you can define a whole flow in a YAML file instead of writing a Python Flow subclass. The 1.15.0 release added unified declarative flow loading, a declarative Flow CLI, and — crucially — the ability to put agent actions and crew actions directly inside a FlowDefinition. Your orchestration becomes a config file: a name, some state, and a map of methods that trigger each other.
For a solo founder that is a bigger deal than it sounds. A YAML flow is diffable in a pull request, editable by a teammate who doesn't read Python, and reviewable at a glance. You stop hiding your business logic inside decorators and start treating it like the configuration it actually is.
Install / upgrade#
Declarative flows landed in 1.15.0 (June 25, 2026), so upgrade first:
# pip
pip install -U crewai
# or with uv
uv add crewai
# confirm you're on 1.15+
python -c "import crewai; print(crewai.__version__)"
Your first flow in YAML#
Here's the minimal shape. One method is the entry point (start: true); another listens for it and runs an agent. This is illustrative — the keys below (name, state, methods, start, listen, do, call, with) are all real FlowDefinition fields, but treat the layout as a starting point and check it against the changelog for your exact version.
# welcome_flow.yaml
name: welcome_flow
description: Greet a new signup and draft a welcome note.
state:
name: ""
plan: "free"
methods:
greet:
start: true
do:
call: agent
with:
role: Onboarding assistant
goal: Write a warm one-line greeting for {name}
backstory: You are friendly and concise.
draft_email:
listen: greet
do:
call: crew
with:
# an inline crew definition, or point at a folder with from_declaration
from_declaration: crews/welcome_email
Two action types are doing the work. call: agent runs a single agent — new in 1.15, you no longer need a full crew for a one-shot step. call: crew runs a crew, either inlined under with or loaded from a declaration path. The listen field is what threads them: draft_email fires when greet finishes.
Run it#
You don't need a Python Flow class anymore — you load the file and kick it off. The loader is the verified public entry point:
# run_flow.py
from crewai.flow.flow import Flow
flow = Flow.from_declaration(path="welcome_flow.yaml")
result = flow.kickoff(inputs={"name": "Rosa", "plan": "pro"})
print(result)
# run the declarative flow
python run_flow.py
# 1.15 also added declarative Flow CLI support — run a YAML flow
# straight from your project without an entrypoint script.
# Check the 1.15.0 changelog for the exact subcommand and flags:
# https://docs.crewai.com/v1.15.0/en/changelog
crewai flow kickoff
Flow.from_declaration() accepts a file path, an inline string, a dict, or a FlowDefinition object, so the same YAML can come from disk in production and from a fixture in tests. That's the honest, verified path; the CLI is real per the changelog, but confirm its exact invocation there before you script against it.
Add a branch with an if condition#
Branching is where declarative flows earn their keep. The each action iterates a list, and each step inside it can carry an optional if expression — the step only runs when the expression is true. So you can route pro signups to a full onboarding crew and free signups to a lightweight agent, all in config:
methods:
onboard_signups:
start: true
do:
call: each
in: "state.signups" # expression that evaluates to the list
do:
- if: "item.plan == 'pro'"
call: crew
with:
from_declaration: crews/pro_onboarding
- if: "item.plan == 'free'"
call: agent
with:
role: Onboarding assistant
goal: Send a short free-tier welcome to {item.name}
The in and if values are expressions evaluated at runtime against your state and the current item. Add a step without an if and it runs for every item; add one and it becomes conditional. No routing decorators, no Python if/else — just a list of guarded steps a reviewer can read top to bottom. For the deeper mechanics of parallelism and routing, see how to run CrewAI flows in parallel and branch.
When to use YAML vs Python#
Reach for YAML when the flow is mostly structure — who runs, in what order, under what condition — and when you want non-engineers to tweak it or reviewers to diff it cleanly. It's version-control-friendly and it keeps orchestration out of your application code.
Reach for Python when a step needs real logic: custom tools, dynamic input shaping, calls into your own libraries, or control flow that expressions can't express. The good news is you rarely choose all-or-nothing — a crew action can load a Python-defined crew, and custom tools stay in Python and get referenced by name. Start declarative, drop into code only where the config runs out of road.
For the full list of what 1.15 changed, read what changed in CrewAI 1.15's declarative FlowDefinition. If you're still deciding whether you even need a flow, flows vs crews is the place to start — and once flows are running in production, learn how to persist and resume a crashed run.



