Skip to content

Document Submission with Manager Approval

A step-by-step guide to a user-triggered workflow where a requester uploads a document through the request catalog and their manager must approve it before the run completes. This is the single-tier building block. For a request that escalates to a second approver when a numeric threshold is crossed, see Multi-Step Approval Chain Workflow.

Prerequisites

  1. Every requester who will submit documents must have a manager configured in Floh (manager_id set on their user record) — the step's sole approver is the requester's manager, and the workflow fails closed if that reference can't be resolved (see Security model).
  2. The workflow:create, workflow:update, workflow:publish, and workflow:read permissions on your admin account. Starting a verification run also requires workflow:start.

Security model — read this before publishing

  • Approval is mandatory. The document_submission step below declares an approvers list — there is no path that skips straight to completion. If you copy this pattern, resist the temptation to remove approvers for a "trusted requester" shortcut; that reintroduces an unreviewed upload.
  • Unresolved approvers fail the run, not the check. If a requester has no manager on file, the step's only approver reference ({{requestor.manager.id}}) resolves to nothing, and the document upload handler fails the step with No approvals could be created — unresolved approver(s) rather than silently completing without approval. This is why manager assignment is a hard prerequisite, not a nice-to-have.
  • Rejections loop back by default. This workflow leaves allowResubmission unset (default true), so a rejected upload resets the task to waiting_submission and the requester can upload a corrected file. See Variations if you want a terminal rejection instead.
  • File uploads are constrained. allowedMimeTypes restricts uploads to PDF and image files; maxSizeBytes caps the file size. Don't widen these without a reason — an unconstrained upload accepts arbitrary file types.

Overview

flowchart TD
  Submit[Portal Submit]
  Start[Start]
  Upload["Submit Document (document_submission)"]
  Approved[Notify Submission Approved]
  Rejected[Notify Submission Rejected]
  Done[End]
  Submit --> Start --> Upload
  Upload -->|success| Approved
  Upload -->|error| Rejected
  Approved --> Done
  Rejected --> Done

Step 1 — Create the workflow

Navigate to Design in the sidebar (opens /workflows) and use the section tabs to open Workflows, then click New Workflow and fill in:

Field Value
Name Document Submission with Manager Approval
Description Submit a document for manager approval
Category user
Subject Variable requestor
Error Strategy stop
Trigger manual

Step 2 — Define variables

Open the Variables tab and add:

Name Type Required Secret Default Description
requestor Requestor yes no The submitting user (auto-filled from session, hidden in form)
submissionDescription String no no What the document is for, shown to the approver (e.g. "W-9 update")

The Requestor preset binds requestor to the authenticated caller and hides the field on the catalog form — the server overrides any caller-supplied value, so a requester cannot submit as someone else.

Step 3 — Add the workflow steps

3.1 — Start

Type: start. Entry point.

3.2 — Submit Document

Type: document_submission.

Config Field Value
Submitter ID {{requestor.id}}
Submitter Email {{requestor.email}}
Document Label Supporting Document
Instructions Upload the required document. Your manager will review it.
Allowed MIME Types application/pdf, image/*
Max Size 10485760 (10 MB)
Approvers {{requestor.manager.id}}
Notify Submitter true
Notify Approvers true

Transitions:

  • success → Notify Submission Approved
  • error → Notify Submission Rejected

document_submission's approver resolution does not support the manager:{{id}} ref shorthand that the approval step type accepts — it only resolves raw user IDs and group:<slug> refs. That's why the config interpolates the manager's ID directly ({{requestor.manager.id}}, using the shallow manager snapshot on the requestor variable) rather than writing manager:{{requestor.id}}.

3.3 — Notify Submission Approved

Type: notification.

Config Field Value
Recipient Type internal
Recipient User {{requestor.id}}
Subject Override Your document submission was approved
Custom Body Your submission ({{submissionDescription}}) has been approved.

3.4 — Notify Submission Rejected

Type: notification.

Config Field Value
Recipient Type internal
Recipient User {{requestor.id}}
Subject Override Your document submission was not approved
Custom Body Your submission ({{submissionDescription}}) was not approved. See your task inbox for next steps.

This step only fires when allowResubmission is false (or the approver uses a final_rejected decision) — see Variations. With the default resubmission loop, a plain rejection just resets the task and this notification never fires.

3.5 — End

Type: end. Exit point.

Step 4 — Wire the graph

In the Graph tab, draw these transitions:

  1. Start → Submit Document
  2. Submit Documentsuccess → Notify Submission Approved
  3. Submit Documenterror → Notify Submission Rejected
  4. Notify Submission Approved → End
  5. Notify Submission Rejected → End

Step 5 — Save, test, publish

  1. Click Save. Fix any validation errors the designer reports — an empty approvers list on the document_submission step will block save.
  2. Click Test Run and submit. Confirm the run pauses at Submit Document with waiting_submission. Upload a test file, then sign in as the requestor's manager and approve. Confirm the Notify Submission Approved email arrives.
  3. Test the rejection path: submit, upload a file, then reject it as the approver. Confirm the task resets to waiting_submission and the requester can upload a corrected file (the default allowResubmission: true behavior).
  4. Click Publish.
  5. On the Catalog tab, set Published, pick an icon, add a clear description, and restrict submission to the right groups if applicable.

Variations

Terminal rejection

Set allowResubmission to false on the Submit Document step. A rejected upload then follows the error transition to Notify Submission Rejected and End, instead of resetting to waiting_submission. Approvers can also force that terminal path on a resubmission-enabled step by choosing a final_rejected decision — that still fires the rejection notification even when allowResubmission is left at its default.