Assign additional workflows to content types through behaviors, with full support in plone.restapi and Volto.
Plone's portal_workflow has always supported chains β more than one workflow active on the same content type at the same time. This package makes that capability practical and behavior-driven.
When a content type provides a participating behavior, this add-on appends one or more extra workflows to that type's workflow chain. The type's configured chain is never replaced, and content that does not provide the behavior is left completely untouched.
- Behavior-driven β enable a behavior on a type, and its extra workflows come along. No manual
portal_workflowchain editing per type. review_stateis never affected β each additional workflow uses its ownstate_variable, so Plone's publication workflow keeps working exactly as before.- Workflow-aware helpers β read and transition per-workflow state with
plone.api-style functions, whose defaults behave identically to today's API. - REST API β on participating content, the
@workflowendpoint gains achainkey describing every workflow in the chain (state, state variable, available transitions, history), and its top-leveltransitionslist is narrowed to the primary workflow so a client written before this package cannot show an unrelated workflow's transitions as publication actions.@historyreports every workflow's transitions in one stream, each entry tagged with itsworkflow_id(nullfor versioning entries). Existing keys keep their values, and content that provides no participating behavior is served exactly the payload Plone has always served, with nochainkey at all. - Volto β the workflow control shows one selector per additional workflow alongside the publication one, and the history view attributes every entry to the workflow that recorded it. Both render exactly as upstream on content without additional workflows.
- Searchable β one
workflow_statescatalog index describes an object's whole chain, so additional states can be queried like any other index and a site gains no further indexes as behaviors contribute more workflows. Existingreview_statecollection criteria are rewritten onto it and keep working. - Import-safe β
plone.exportimportrestores workflow state by writing it directly rather than by transitioning, which leaves an additional workflow's catalog entry stale. This package patches the importer so imported content is indexed correctly; the patch is written for upstreaming. - Ships no behavior of its own β the worked example lives in the
collective.multiworkflow.demosubpackage, which is not loaded by default. Installing the add-on changes nothing about your content types until you declare a behavior.
An additional workflow may manage permissions β a common reason to add one is precisely to control access the publication workflow does not. Each workflow rewrites only the permissions it declares, and a transition re-applies the mappings of the transitioning workflow alone, so the requirement is that the permission sets be disjoint:
- Each additional workflow must declare its own
state_variable, neverreview_state. Declareworkflow_states, the same name as the catalog index:WorkflowTool._reindexWorkflowVariablesthen keeps that index fresh on every transition at no cost. Sharing the name across workflows is safe, because DCWorkflow keys its status records by workflow id. - Transition ids must be unique across every workflow in a type's chain.
- No two workflows in a chain may manage the same permission. Where they do, the mapping is left as whichever workflow transitioned last wrote it, until the other transitions or
portal_workflow.updateRoleMappings()runs.api.conflicting_permissions(obj)reports any such overlap.
- Placeful workflows (
CMFPlacefulWorkflow) integration. - Sub-workflows β a transition in one workflow triggering another. The chain model is concurrent, not hierarchical.
- Reconciling workflows that manage the same permission β this package reports the conflict rather than arbitrating it.
- Any change to
review_statesemantics.
Full documentation lives in docs/ and is published at collective.github.io/collective-multiworkflow.
- Start here: Add a second workflow to a content type β build one end to end.
- How-to guides: installing, declaring a contribution, writing a workflow that composes, searching by state.
- Concepts: chains, state variables, permissions.
- Reference: Python API, ZCML directive, catalog index, REST API, Volto add-on.
Both packages are installed separately. The backend one is enough on its own; the frontend one requires it.
Requires Plone 6.2 and Python 3.11 or later.
uv add collective.multiworkflowThen install Multi-Workflow Support for Plone from the add-ons control panel.
Requires Volto 18 and above.
Add @plone-collective/volto-multiworkflow to your package.json.
"addons": [
"@plone-collective/volto-multiworkflow"
],
"dependencies": {
"@plone-collective/volto-multiworkflow": "*"
}Important
Adding the package to dependencies without listing it under addons installs the code but never registers it, so nothing is rendered.
- An operating system that runs all the requirements mentioned.
- uv
- nvm
- Node.js and pnpm 24
- Make
- Git
- Docker (optional)
-
Clone this repository, then change your working directory.
git clone git@github.com:collective/collective-multiworkflow.git cd collective-multiworkflow -
Install this code base.
make install
-
Create a new Plone site on your first run.
make backend-create-site
-
Start the backend at http://localhost:8080/.
make backend-start
-
In a new shell session, start the frontend at http://localhost:3000/.
make frontend-start
Voila! Your Plone site should be live and kicking! π
Deploy a local Docker Compose environment that includes the following.
- Docker images for Backend and Frontend πΌοΈ
- A stack with a Traefik router and a PostgreSQL database ποΈ
- Accessible at http://collective-multiworkflow.localhost π
Run the following commands in a shell session.
make stack-create-site
make stack-startAnd... you're all set! Your Plone site is up and running locally! π
This monorepo consists of the following distinct sections:
- backend: The Plone add-on
collective.multiworkflow, installed with uv, plus its test suite. - frontend: The Volto add-on
@plone-collective/volto-multiworkflow, plus its Storybook stories. - docs: The Sphinx documentation published at collective.github.io/collective-multiworkflow.
- Both halves of the add-on live together, so a change to the REST API payload and the change to the component that reads it are one commit.
- GitHub Workflows are triggered per section, so a documentation change does not rebuild the frontend (refer to .github/workflows).
- The documentation can generate its API reference from the backend source and its REST examples from the backend test suite, because both are in the same checkout.
To check your code against quality standards, run the following shell command.
make checkTo format and rewrite the code base, ensuring it adheres to quality standards, run the following shell command.
make format| Section | Tool | Description | Configuration |
|---|---|---|---|
| backend | Ruff | Python code formatting, imports sorting | backend/pyproject.toml |
| backend | zpretty |
XML and ZCML formatting | -- |
| frontend | ESLint | Fixes most common frontend issues | frontend/.eslintrc.js |
| frontend | prettier | Format JS and Typescript code | frontend/.prettierrc |
| frontend | Stylelint | Format Styles (css, less, sass) | frontend/.stylelintrc |
Formatters can also be run within the backend or frontend folders.
or lint:
make lint| Section | Tool | Description | Configuration |
|---|---|---|---|
| backend | Ruff | Checks code formatting, imports sorting | backend/pyproject.toml |
| backend | Pyroma | Checks Python package metadata | -- |
| backend | check-python-versions | Checks Python version information | -- |
| backend | zpretty |
Checks XML and ZCML formatting | -- |
| frontend | ESLint | Checks JS / Typescript lint | frontend/.eslintrc.js |
| frontend | prettier | Check JS / Typescript formatting | frontend/.prettierrc |
| frontend | Stylelint | Check Styles (css, less, sass) formatting | frontend/.stylelintrc |
Linters can be run individually within the backend or frontend folders.
Generate translation files for Plone and Volto with ease:
make i18nThis repository holds two packages, released separately and each under its own license.
| Package | Location | Registry | License |
|---|---|---|---|
collective.multiworkflow |
backend/ | PyPI | GPL-2.0-only |
@plone-collective/volto-multiworkflow |
frontend/ | npm | MIT |
The backend package is usable on its own: it needs no frontend to append workflows, index their states, or serve them over the REST API. The frontend package requires the backend, because everything it renders comes from the chain key that backend adds to the @workflow payload.
Generated using Cookieplone (2.0.0b3) and cookieplone-templates (61a8f90) on 2026-08-07 16:01:41.322560. A special thanks to all contributors and supporters!