Publishing & versions
Every workflow has an editable draft and a history of immutable versions. You edit the draft freely; nothing you change affects running workflows until you publish it. Publishing snapshots the draft as the next version number and makes that version the one that runs.
| What it is | |
|---|---|
| Draft | The Workflow row itself. workflow:build and your own code edit it. |
| Version | A numbered, read-only snapshot (v1, v2, …) of the draft's name, trigger, nodes, edges, and meta. |
| Published version | The one version that runs. A workflow has at most one. |
Only published workflows run
Workflow::run(), Workflow::dispatch(), webhooks, schedules, model events,
and Call workflow nodes all run the
published version. A workflow that has never been published does not run —
Workflow::run() throws a RuntimeException
(Workflow [send-welcome-email] has not been published.), and its trigger is
not wired up at boot.
Editing the draft after publishing changes nothing until you publish again.
Publishing
php artisan workflow:publish send-welcome-email --note="Add a follow-up email"
Or in code:
use Qanna\WorkflowEngine\Models\Workflow;
$workflow = Workflow::findOrFail('send-welcome-email');
$version = $workflow->publish('Add a follow-up email');
$version->version; // 3
- The first publish creates
v1; each later publish creates the next number. - If the draft is identical to the published version, nothing is created and the current published version is returned.
- Save the model before publishing. Calling
publish()on a model with unsaved changes to its content throws aLogicException. - Toggling
activeis not a content change and never needs a publish.
$workflow->isPublished(); // has any version been published?
$workflow->hasUnpublishedChanges(); // does the draft differ from the published version?
$workflow->publishedVersion; // ?WorkflowVersion
$workflow->versions; // every version, as WorkflowVersion models
Triggers that need boot-time wiring (webhook, schedule, model event) pick up a newly published version on the next application boot — restart your app and queue workers after publishing.
Rolling back
A rollback copies an earlier version into a new version number and publishes that. Later versions stay in the history, and the draft is left untouched.
php artisan workflow:rollback send-welcome-email 2 --note="Revert follow-up email"
$version = $workflow->rollbackTo(2, 'Revert follow-up email');
// With v4 published, this publishes v5 with v2's content.
- The default note is
Rollback to v2. - Rolling back to content that is already published creates nothing.
rollbackTo()with a version number that doesn't exist throwsIlluminate\Database\Eloquent\ModelNotFoundException.
Running the draft
To try a draft without publishing it:
php artisan workflow:run send-welcome-email --draft
$execution = Workflow::runDraft('send-welcome-email', ['email' => 'ada@example.com']);
A draft run works for workflows that have never been published. Its execution
records workflow_version as null; a run of a published version records the
version number.
Exporting and importing
Export workflows to JSON files to keep them in git or move them between environments, then import them on the other side.
php artisan workflow:export # published version of every workflow
php artisan workflow:export send-welcome-email # one workflow
php artisan workflow:export --all # every version
php artisan workflow:export --latest=3 # the newest 3 versions
php artisan workflow:export --draft # also the draft, if it has unpublished changes
php artisan workflow:export --path=database/workflows
php artisan workflow:import # every workflow found in the directory
php artisan workflow:import send-welcome-email
php artisan workflow:import --path=database/workflows
php artisan workflow:import --force # replace drafts that have unpublished changes
Both default to resources/workflows.
File layout
Each workflow gets its own directory:
resources/workflows/
└── send-welcome-email/
├── workflow.json # { "id", "active", "published_version" }
├── v1.json
├── v2.json
└── v3.draft.json # the draft, exported as the version after the latest one
- Without
--draft, a workflow that has never been published is skipped with a warning. - With
--draft, a draft that matches the published version is not written, and any draft file left from an earlier export is removed.
Sensitive values
Fields declared ->sensitive() (such as the Webhook trigger's secret) are
never written in plain text. Exported files contain the placeholder
{__SENSITIVE__} instead.
On import, a placeholder is filled from the value already stored in that environment — from the same version if it exists, otherwise from the draft — so importing never overwrites a secret you've set. When there is no stored value (for example, a workflow imported into a fresh database), the field is imported as empty.
Warning: a Webhook trigger with an empty
secretaccepts requests without anX-Webhook-Secretheader. After importing a webhook workflow into a new environment, set its secret before relying on it.
What import does
- New workflows are created with the exported versions, the published
version from
workflow.json, and the draft file (or the published version's content when there is no draft file). - Existing versions are never changed. If
v2already exists with different content, it is skipped with a warning. - The published version only moves forward. An export that publishes an older version than the one currently published keeps the current one, with a warning.
- Drafts with unpublished changes are kept, with a warning, unless you pass
--force. - Version notes and creation dates are preserved.
Each workflow is imported in a single database transaction.