All posts
MermaidState DiagramsWorkflow Automation

Modeling workflows with Mermaid state diagrams

4 min readThe MermaidCreator team

Every system that tracks status is hiding a state machine. Order management, user onboarding, approval queues, CI pipelines — they all move objects through a defined set of states via transitions. The problem is that this logic usually lives scattered across conditional branches in your code, implicit in column values in your database, and memorized by the one engineer who wrote the feature.

Mermaid's stateDiagram-v2 makes that implicit model explicit. The diagram is plain text, lives next to your code, and updates in the same pull request as the transition logic it describes.

What a state diagram shows

A state diagram has three building blocks:

  • States — the possible statuses an object can be in (Pending, Active, Cancelled)
  • Transitions — the events or actions that move an object from one state to another
  • Forks and joins — for parallel workflows where multiple branches run simultaneously

Every stateDiagram-v2 has two implicit special states: [*] is the entry point (start), and [*] as a transition target is the exit point (terminal state).

Syntax in 60 seconds

stateDiagram-v2
    [*] --> Draft
    Draft --> Submitted : submit()
    Submitted --> UnderReview : assign()
    UnderReview --> Approved : approve()
    UnderReview --> Rejected : reject()
    Approved --> [*]
    Rejected --> Draft : revise()

Read each line as: from state, to state, on event. A rejected submission goes back to Draft for revision; an approved one reaches the terminal state. The whole lifecycle fits in seven lines.

A real-world example: e-commerce order lifecycle

Most order systems have around eight states. Here's a complete lifecycle with a cancellation path:

stateDiagram-v2
    [*] --> Placed

    Placed --> PaymentPending : initiate_payment()
    PaymentPending --> Confirmed : payment_succeeded()
    PaymentPending --> Cancelled : payment_failed()

    Confirmed --> Processing : warehouse_accepted()
    Processing --> Shipped : dispatch()
    Shipped --> Delivered : delivery_confirmed()
    Delivered --> [*]

    Placed --> Cancelled : cancel()
    Confirmed --> Cancelled : cancel()
    Cancelled --> [*]

Compare this to hunting through a status enum, a handful of database triggers, and three separate webhook handlers to assemble the same picture. The diagram doesn't replace those artifacts — it maps how they fit together.

Nested states for complex sub-flows

Some states contain their own internal logic. stateDiagram-v2 supports nested state blocks, which keeps the top-level diagram readable while preserving detail for the states that need it:

stateDiagram-v2
    [*] --> Onboarding

    state Onboarding {
        [*] --> EmailVerification
        EmailVerification --> ProfileSetup : email_verified()
        ProfileSetup --> TeamInvite : profile_saved()
        TeamInvite --> [*]
    }

    Onboarding --> Active : onboarding_complete()
    Active --> Suspended : suspend()
    Suspended --> Active : reactivate()
    Active --> Deleted : delete()
    Deleted --> [*]

The user's onboarding sub-flow is self-contained. Anyone reading the top-level diagram knows what Active means without needing to see the internal steps — but those steps are still there when needed.

Parallel states with forks

When a workflow splits into independent concurrent tracks, use the fork / join construct to make the parallelism explicit:

stateDiagram-v2
    [*] --> Processing

    state Processing {
        [*] --> fork_state
        state fork_state <<fork>>
        fork_state --> InventoryCheck
        fork_state --> FraudCheck
        state join_state <<join>>
        InventoryCheck --> join_state
        FraudCheck --> join_state
        join_state --> [*]
    }

    Processing --> ReadyToShip : all_checks_passed()
    Processing --> OnHold : any_check_failed()

The fork and join make it unambiguous that inventory and fraud checks run in parallel and both must complete before the order moves on.

Notes and descriptions

Add a note to a state to surface context that can't be expressed as a transition label:

stateDiagram-v2
    [*] --> Pending
    Pending --> Active : activate()
    Active --> Expired : ttl_elapsed()
    Expired --> [*]

    note right of Pending
        Created when user
        completes checkout.
        TTL: 15 minutes.
    end note

Notes are especially useful in diagrams shared with non-engineers — they replace a paragraph of inline comments with a label attached to exactly the right state.

Where state diagrams earn their keep

Code review. Paste a state diagram into a PR description when you're adding or changing a transition. Reviewers see the full lifecycle at once instead of tracing conditionals across multiple files.

Incident postmortems. "The order got stuck in Processing because the join never fired" is a sentence that makes sense to everyone when there's a diagram to point at.

Onboarding documentation. New engineers learn a domain model in minutes from a state diagram. They spend hours tracing the equivalent code path.

Specification first. Sketch the states before writing the code. Edge cases like "can a Shipped order be Cancelled?" surface naturally when you try to draw the transition and realize you don't have an answer.

State diagrams vs. flowcharts

Flowcharts and state diagrams look similar but model different things. A flowchart documents a process — the steps a human or system performs in sequence. A state diagram documents the lifecycle of an object — the statuses it can hold and the events that change them.

If you're mapping "what does the checkout function do step by step," use a flowchart. If you're mapping "what states can an order be in," use a state diagram. Getting this distinction right makes both diagrams sharper.

Rule of thumb: if your diagram has rectangles labeled with verbs, it's a flowchart. If it has rounded nodes labeled with nouns, it's a state machine.

Draw the state machine before you write the migration that adds the status column. By the time the PR is open, the whole team already understands what the column means.

Related posts