Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Form-driven workflow engine. Making city government better, one form at a time.

Government agencies, like many organizations, have a ton of processes. If an employee wants to attend a conference and needs travel reimbursement, their supervisor must approve, then their director must approve, and if it costs more than $500, the CFO must approve. New hire on-boarding consists of several steps that can happen in parallel.

In many cases, there is already software out there -- especially in the HR world -- that handle these processes. When they're available, you should use them. But when there isn't software already available, or the agency doesn't have access to it, the processes still exist; they just take place over email. Stately is for these processes.

Eventually, we'd like Stately to have a user-friendly web interface for designing forms and workflows. For now, it's driven by a configuration file that looks like this:

name: Travel Request
  - name: Not started
      - name: Initiate
            - name: Name
            - name: Initiator Email
            - name: Destination
            - name: Cost
            - name: Supervisor Email
        handler: |
          change_state('Awaiting Supervisor Approval')
          assign(data.supervisor_email, send_email=True)
  - name: Awaiting Supervisor Approval
      - name: Approve
        handler: |
          if (data.cost) >= 500:
            change_state('Awaiting CFO Approval')
            assign(cfo_email, send_email=True)
            assign(data.initiator_email, send_email=True)
      - name: Reject
        # ...... (see the rest in workflows/travel-request.yml)

This renders a form that, when submitted, kicks off the handler, changing the state of the request and sending emails to reviewers with unique access tokens.

screenshot of travel request form

The aim of this approach is to provide a balance between configurability and flexibility, accommodating many workflows out-of-the-box, with an option to write basic python code for any advanced / edge-case workflows.

screenshot of submitted travel request

Example workflows

We're building with the following use cases from Philadelphia City Government in mind. Have your own that comes to mind? Tell us about it.

  • Leave request
    • employee submits form, manager approves, entry is able to be looked up later
  • Travel reimbursement request
    • like leave request, but director must also approve, and if >$500, CFO must approve
  • New hire onboarding
    • multiple tasks that can be completed asynchronously. perhaps when all 3 tasks are done, the 4th task is able to begin
  • Contract
    • Document-oriented (w/signature) vs content-oriented
  • Freedom of Information Act request
    • public submits form, can track its progress, assigned based on department field, can be re-assigned, auto-reply if not fulfilled in 5 business days, requests can be shared publicly like forum posts
  • New-hire Indebtedness Check (ensure new hires don't owe the city money)
    • HR manager enters a candidate for employment, and reps from various departments verify that the candidate does not owe any money to the city


Brainstorming, API spec, etc. is on a hackpad

Client usage

The client application requires Node JS to build. Install dependencies by navigating into the client directory in the terminal and run:

npm install

Afterwards, run a local development server using:

npm start

Server usage

The server application requires python. It is recommended that you create a virtual environment before installing dependencies. Once you've activated your virtual environment, install dependencies by navigating to the server directory and running:

pip install .

Then setup the database using:

python src/ migrate

Load the workflow files from the workflows directory into the database using:

python src/ workflow_load ../workflows/*

Check the workflows that are loaded into the database using:

python src/ workflow_list

Finally, run the server using:

python src/ runserver

Prior art


Form-driven workflow engine






No releases published


No packages published

Contributors 4