Skip to main content

How to build a dual-approval request workflow in Formaloo

Learn how to build a dual-approval employee request workflow in Formaloo, from form creation and approval logic to employee and admin portals.

By the end of this guide, you'll have a fully working employee request approval system, complete with a two-stage approval process, an admin portal, and an employee-facing portal where staff can track their request status in real time. The workflow routes each submission through two approvers in sequence, automatically updates the request status at each stage, and rejects the entire request if either approver declines. No code required.

When to use this

  • Your team handles employee requests (IT, HR, procurement, travel) and approvals are currently tracked in emails or spreadsheets

  • You need two separate people to sign off before a request is considered approved

  • Employees have no visibility into where their request stands

  • Admins spend time manually updating statuses or chasing approvers

  • You want the same logic extended to three or more approval stages in the future

What you'll need

  • A Formaloo account (any plan)

  • Two approvers who have Formaloo accounts in your workspace

  • About 20–30 minutes to build everything from scratch (or use the template at the bottom to start in 2 minutes)

Step 1: Create the form

πŸ“– Text guide

First, we need to create the form. In order to do so:

  1. Open your Formaloo dashboard and click New project.

  2. Select Form as your starting point.

  3. Inside the form editor, click the Magic Create icon (the sparkle icon in the toolbar).

  4. Paste the prompt below into the Magic Create panel, then click Create.

Prompt

Create an employee request workflow approval form.
Step 1 β€” Employee intake fields (visible to all): Full Name (Short text, required), Email Address (Email, required), Division (Dropdown, required), Request Details (Long text, required)
Step 2 β€” Admin-only approval fields (hidden from employees, visible only to admins): Overall status: Request Status (Single choice, admin only) β€” options: Pending First Approval, Pending Second Approval, Successful Approval, Rejected
First approval section: First Approver Name (Assign field, admin only), First Approver Signature (Signature field, admin only), First Approver Decision (Single choice, admin only) β€” options: Approved, Not Approved
Second approval section: Second Approver Name (Assign field, admin only), Second Approver Signature (Signature field, admin only), Second Approver Decision (Single choice, admin only) β€” options: Approved, Not Approved
Admin notes: Rejection Reason (Long text, admin only β€” shown only if status is Rejected)
Magic Create builds all the fields, admin-only fields, pages, and field IDs for you automatically.

🎬 Video guide

Step 2: Build the approval logic

πŸ“– Text guide

Now let's tie the logic together. The three decision fields β€” First Approver Decision, Second Approver Decision, and Request Status β€” need to be connected so that each approver's action automatically moves the request to the right stage.

In order to do so:

  1. On the form island, click the Settings icon.

  2. In the right sidebar, click Advanced logic.

  3. Switch to the On update tab.

  4. Apply the following four rules:

Rule 1 β€” First approver approves:

  • Condition: First Approver Decision is equal to Approved

  • Action: Set Request Status to Pending Second Approval

Rule 2 β€” First approver rejects:

  • Condition: First Approver Decision is equal to Not Approved

  • Action: Set Request Status to Rejected

Rule 3 β€” Second approver approves:

  • Condition: Second Approver Decision is equal to Approved

  • Action: Set Request Status to Successful Approval

Rule 4 β€” Second approver rejects:

  • Condition: Second Approver Decision is equal to Not Approved

  • Action: Set Request Status to Rejected

🎬 Video guide

πŸ“– New to logic? See What is logic in Formaloo

Step 3: Set up the employee & admin portal

πŸ“– Text guide

The employee portal gives your staff one place to submit requests and track their status β€” without seeing any admin fields.

To learn how to create a portal and set it up, see How to create a portal and manage users' access.

Employee:

Once your portal is ready, add the following pages:

Page 1 β€” Submit a request

Add a Form page and link it to your approval form. This is where employees fill in their details.

Page 2 β€” My records

Add a Table page linked to the same form. To make sure each employee sees only their own submissions:

  1. Click the Settings icon on the data block.

  2. Click Manage access.

  3. Enable the Profile field by selecting your user directory.

  4. You can then choose between:

    • Allow users to view only their data β€” logged-in users see only their own records. They will not see other users' data.

    • Allow users to edit their data β€” logged-in users can see and edit only their own records. They will not see or edit other users' data.

Page 3 β€” Approval status

Add a Kanban page linked to the same form. Set the Group by field to Request Status. Employees can now see their requests organized by stage at a glance.

Admin:

The admin portal is where your team manages the full approval pipeline. Create a second portal for your admin team and add the following pages:

Page 1 β€” Form & records

Add a Table page linked to your approval form. Admins see all submissions here, including all page 2 admin fields.

Page 2 β€” Approval status

Add a Kanban page grouped by Request Status. This gives the whole team a high-level view of where every request stands.

Page 3 β€” First approver

Add a Kanban page grouped by Request Status. To restrict each approver to seeing only their own assigned requests:

  1. Click the Settings icon on the data block.

  2. Click Manage access.

  3. Scroll to the Assignee field section (directly below the profile field settings).

  4. Select First Approver Name as the assignee field.

  5. You can then choose between:

    • Allow team members to view assigned entries β€” team members can view entries assigned to them or their team.

    • Allow team members to edit assigned entries β€” team members can modify and update entries assigned to them or their team.

Page 4 β€” Second approver

Add another Kanban page grouped by Request Status. Apply the same Manage access steps above, but select Second Approver Name as the assignee field.

🎬 Video guide

Bonus: PDF template

You can use the templates below to send professional notifications and generate approval documents directly from your workflow.

PDF template Use this HTML code to create a formatted approval record for each request β€” useful for audits, archives, or sending to the employee on final approval.

See the code

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Employee Status Change Form</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&display=swap');

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
font-family: 'Source Sans 3', Arial, sans-serif;
font-size: 11px;
color: #1a1a1a;
background: #fff;
}

.page {
width: 816px;
min-height: 1056px;
margin: 0 auto;
padding: 36px 48px 48px;
background: #fff;
}

/* ── HEADER ── */
.header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2.5px solid #1a1a1a;
padding-bottom: 14px;
margin-bottom: 22px;
}

.logo-area {
display: flex;
flex-direction: column;
align-items: flex-start;
}

.logo-text {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.5px;
color: #1a1a1a;
line-height: 1;
}

.logo-text span {
color: #c0392b;
}

.logo-sub {
font-size: 8px;
letter-spacing: 3px;
text-transform: uppercase;
color: #888;
margin-top: 2px;
}

.form-title {
font-size: 17px;
font-weight: 700;
letter-spacing: 0.3px;
text-transform: uppercase;
color: #1a1a1a;
}

/* ── SECTION LABELS ── */
.section-header {
background: #1a1a1a;
color: #fff;
font-size: 9px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
padding: 5px 10px;
margin: 18px 0 10px;
}

.section-header.light {
background: #f0f0f0;
color: #1a1a1a;
}

/* ── FIELD GRID ── */
.field-row {
display: flex;
gap: 16px;
margin-bottom: 10px;
}

.field {
flex: 1;
display: flex;
flex-direction: column;
gap: 3px;
}

.field.w-half { flex: 0 0 calc(50% - 8px); }
.field.w-third { flex: 0 0 calc(33.333% - 11px); }
.field.w-quarter { flex: 0 0 calc(25% - 12px); }
.field.w-two-thirds { flex: 0 0 calc(66.666% - 8px); }

.field-label {
font-size: 8px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: #555;
}

.field-label .field-id {
font-weight: 400;
color: #aaa;
text-transform: none;
letter-spacing: 0;
font-size: 7.5px;
margin-left: 4px;
}

.field-value {
border-bottom: 1px solid #bbb;
min-height: 18px;
padding: 2px 0;
font-size: 11px;
color: #1a1a1a;
font-style: italic;
color: #888;
}

.field-value.tall {
min-height: 48px;
border: 1px solid #bbb;
padding: 4px 6px;
}

/* ── CHECKBOX ROW ── */
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 6px 18px;
margin-bottom: 10px;
}

.checkbox-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 10px;
}

.checkbox-box {
width: 11px;
height: 11px;
border: 1px solid #555;
display: inline-block;
flex-shrink: 0;
}

/* ── CHANGE(S) DIVIDER ── */
.changes-label {
text-align: center;
font-size: 10px;
font-weight: 700;
letter-spacing: 1.5px;
text-transform: uppercase;
color: #555;
margin: 14px 0 10px;
position: relative;
}

.changes-label::before,
.changes-label::after {
content: '';
position: absolute;
top: 50%;
width: 38%;
height: 0.5px;
background: #bbb;
}

.changes-label::before { left: 0; }
.changes-label::after { right: 0; }

/* ── ACKNOWLEDGEMENT ── */
.ack-section {
margin-top: 22px;
border-top: 1px solid #ccc;
padding-top: 14px;
}

.ack-title {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.5px;
margin-bottom: 6px;
}

.ack-text {
font-size: 10px;
color: #555;
line-height: 1.6;
margin-bottom: 24px;
}

.sig-row {
display: flex;
gap: 48px;
margin-top: 8px;
}

.sig-block {
flex: 1;
}

.sig-line {
border-bottom: 1px solid #555;
min-height: 36px;
margin-bottom: 4px;
}

.sig-label {
font-size: 8px;
text-transform: uppercase;
letter-spacing: 1px;
color: #888;
}

/* ── APPROVAL SECTION ── */
.approval-section {
margin-top: 28px;
border-top: 2px solid #1a1a1a;
padding-top: 14px;
}

.approval-title {
font-size: 10px;
font-weight: 700;
letter-spacing: 1.5px;
text-transform: uppercase;
margin-bottom: 12px;
}

.approval-grid {
display: flex;
gap: 24px;
margin-bottom: 16px;
}

.approval-block {
flex: 1;
border: 1px solid #ccc;
padding: 12px 14px;
}

.approval-block-title {
font-size: 8px;
font-weight: 700;
letter-spacing: 1.5px;
text-transform: uppercase;
color: #888;
margin-bottom: 10px;
padding-bottom: 6px;
border-bottom: 0.5px solid #e0e0e0;
}

.status-badge {
display: inline-block;
padding: 3px 10px;
font-size: 8px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
border: 1px solid #1a1a1a;
margin-top: 6px;
}

/* ── FORM STATUS ── */
.form-status-bar {
display: flex;
align-items: center;
justify-content: space-between;
background: #f7f7f7;
border: 1px solid #e0e0e0;
padding: 7px 12px;
margin-bottom: 18px;
}

.form-status-label {
font-size: 8px;
font-weight: 700;
letter-spacing: 1.5px;
text-transform: uppercase;
color: #888;
}

.form-status-value {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.5px;
color: #1a1a1a;
font-style: italic;
color: #888;
}

.form-status-id {
font-size: 7.5px;
color: #aaa;
font-style: normal;
}

/* ── PRINT ── */
@media print {
body { background: #fff; }
.page { margin: 0; padding: 28px 40px 40px; width: 100%; }
}
</style>
</head>
<body>
<div class="page">

<!-- HEADER -->
<div class="header">
<div class="logo-area">
<div class="logo-text">inf<span>i</span>nity</div>
<div class="logo-sub">Labs</div>
</div>
<div class="form-title">Employee Status Change Form</div>
</div>

<!-- FORM STATUS BAR -->
<div class="form-status-bar">
<span class="form-status-label">Form Status</span>
<span class="form-status-value">
{{form_status}}
<span class="form-status-id"> Β· form_status</span>
</span>
</div>

<!-- SECTION: EMPLOYEE INFORMATION -->
<div class="section-header">Employee Information</div>

<div class="field-row">
<div class="field w-third">
<div class="field-label">Employee Name <span class="field-id">emp_name</span></div>
<div class="field-value">{{emp_name}}</div>
</div>
<div class="field w-third">
<div class="field-label">Date of Hire <span class="field-id">emp_hire_date</span></div>
<div class="field-value">{{emp_hire_date}}</div>
</div>
<div class="field w-third">
<div class="field-label">Job Title <span class="field-id">emp_job_title</span></div>
<div class="field-value">{{emp_job_title}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-quarter">
<div class="field-label">Supervisor <span class="field-id">emp_supervisor</span></div>
<div class="field-value">{{emp_supervisor}}</div>
</div>
<div class="field w-quarter">
<div class="field-label">Branch Lead <span class="field-id">emp_branch_lead</span></div>
<div class="field-value">{{emp_branch_lead}}</div>
</div>
<div class="field w-quarter">
<div class="field-label">Branch <span class="field-id">emp_branch</span></div>
<div class="field-value">{{emp_branch}}</div>
</div>
<div class="field w-quarter">
<div class="field-label">Division <span class="field-id">emp_division</span></div>
<div class="field-value">{{emp_division}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">Division/Operations Lead <span class="field-id">emp_division_ops_lead</span></div>
<div class="field-value">{{emp_division_ops_lead}}</div>
</div>
</div>

<!-- SECTION: PAYROLL CHANGE -->
<div class="section-header">Payroll Change</div>

<div class="field-label" style="margin-bottom:6px;">Reason(s) for Adjustment <span class="field-id">pay_change_reasons</span></div>
<div class="checkbox-group">
<div class="checkbox-item"><span class="checkbox-box"></span> Promotion</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Classification/Status</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Supervisor</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Division</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Merit Increase</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Branch</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Title</div>
<div class="checkbox-item"><span class="checkbox-box"></span> Other</div>
</div>
<div class="field-row" style="margin-bottom:6px;">
<div class="field">
<div class="field-value" style="font-style:normal;font-size:9px;color:#aaa;">Selected: {{pay_change_reasons}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">Effective Date <span class="field-id">pay_effective_date</span></div>
<div class="field-value">{{pay_effective_date}}</div>
</div>
</div>

<!-- CHANGES -->
<div class="changes-label">Change(s)</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">New Classification <span class="field-id">pay_new_classification</span></div>
<div class="field-value">{{pay_new_classification}}</div>
</div>
<div class="field w-half">
<div class="field-label">Current Classification <span class="field-id">pay_current_classification</span></div>
<div class="field-value">{{pay_current_classification}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">New Status <span class="field-id">pay_new_status</span></div>
<div class="field-value">{{pay_new_status}}</div>
</div>
<div class="field w-half">
<div class="field-label">Current Status <span class="field-id">pay_current_status</span></div>
<div class="field-value">{{pay_current_status}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">New Job Title <span class="field-id">pay_new_job_title</span></div>
<div class="field-value">{{pay_new_job_title}}</div>
</div>
<div class="field w-half">
<div class="field-label">New Supervisor <span class="field-id">pay_new_supervisor</span></div>
<div class="field-value">{{pay_new_supervisor}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">New Branch <span class="field-id">pay_new_branch</span></div>
<div class="field-value">{{pay_new_branch}}</div>
</div>
<div class="field w-half">
<div class="field-label">New Division <span class="field-id">pay_new_division</span></div>
<div class="field-value">{{pay_new_division}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">New Division Lead <span class="field-id">pay_new_division_lead</span></div>
<div class="field-value">{{pay_new_division_lead}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">New Pay Type <span class="field-id">pay_new_pay_type</span></div>
<div class="field-value">{{pay_new_pay_type}}</div>
</div>
<div class="field w-half">
<div class="field-label">Current Pay Type <span class="field-id">pay_current_pay_type</span></div>
<div class="field-value">{{pay_current_pay_type}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-quarter">
<div class="field-label">Current Salary <span class="field-id">pay_current_salary</span></div>
<div class="field-value">{{pay_current_salary}}</div>
</div>
<div class="field w-quarter">
<div class="field-label">New Salary <span class="field-id">pay_new_salary</span></div>
<div class="field-value">{{pay_new_salary}}</div>
</div>
<div class="field w-quarter">
<div class="field-label">New Pay Rate ($/hr) <span class="field-id">pay_new_rate_hourly</span></div>
<div class="field-value">{{pay_new_rate_hourly}}</div>
</div>
<div class="field w-quarter">
<div class="field-label">Percent Change <span class="field-id">pay_percent_change</span></div>
<div class="field-value">{{pay_percent_change}}</div>
</div>
</div>

<div class="field-row">
<div class="field w-half">
<div class="field-label">Current PTO Accrual <span class="field-id">pay_current_pto_accrual</span></div>
<div class="field-value">{{pay_current_pto_accrual}}</div>
</div>
<div class="field w-half">
<div class="field-label">New PTO Accrual <span class="field-id">pay_new_pto_accrual</span></div>
<div class="field-value">{{pay_new_pto_accrual}}</div>
</div>
</div>

<div class="field-row">
<div class="field">
<div class="field-label">Additional Comments or Remarks <span class="field-id">pay_comments</span></div>
<div class="field-value tall">{{pay_comments}}</div>
</div>
</div>

<!-- ACKNOWLEDGEMENT -->
<div class="ack-section">
<div class="ack-title">Acknowledgement</div>
<div class="ack-text">In signing this document, I acknowledge that I have received the information outlined above.</div>
<div class="sig-row">
<div class="sig-block">
<div class="sig-line"></div>
<div class="sig-label">Employee Signature</div>
</div>
<div class="sig-block" style="flex: 0 0 200px;">
<div class="sig-line"></div>
<div class="sig-label">Date</div>
</div>
</div>
</div>

<!-- APPROVAL SECTION -->
<div class="approval-section">
<div class="approval-title">Approval</div>
<div class="approval-grid">

<!-- APPROVER 1 -->
<div class="approval-block">
<div class="approval-block-title">First Approver</div>
<div class="field-row" style="margin-bottom:8px;">
<div class="field">
<div class="field-label">Name <span class="field-id">approval_1_name</span></div>
<div class="field-value">{{approval_1_name}}</div>
</div>
</div>
<div class="field-label" style="margin-bottom:4px;">Signature <span class="field-id">approval_1_signature</span></div>
<div class="sig-line" style="min-height:40px; margin-bottom:8px;">{{approval_1_signature}}</div>
<div class="field-label" style="margin-bottom:4px;">Status <span class="field-id">approval_1_status</span></div>
<div class="status-badge">{{approval_1_status}}</div>
</div>

<!-- APPROVER 2 -->
<div class="approval-block">
<div class="approval-block-title">Second Approver</div>
<div class="field-row" style="margin-bottom:8px;">
<div class="field">
<div class="field-label">Name <span class="field-id">approval_2_name</span></div>
<div class="field-value">{{approval_2_name}}</div>
</div>
</div>
<div class="field-label" style="margin-bottom:4px;">Signature <span class="field-id">approval_2_signature</span></div>
<div class="sig-line" style="min-height:40px; margin-bottom:8px;">{{approval_2_signature}}</div>
<div class="field-label" style="margin-bottom:4px;">Status <span class="field-id">approval_2_status</span></div>
<div class="status-badge">{{approval_2_status}}</div>
</div>

</div>
</div>

</div>
</body>
</html>

What you've built

You now have a complete dual-approval request workflow with two separate portals β€” one for employees, one for admins. New submissions automatically enter the first approval stage. Approver decisions drive the status forward or reject the request without anyone manually updating a field. Each approver only sees what's assigned to them. Employees can check their status in real time. And every approved request can be exported as a signed PDF.

The same logic pattern scales to three or more approval stages β€” just add a third approver section to the form and extend the rules to match.

Start faster with a template

You don't have to build this from scratch. The Employee request workflow form template has the form, logic, and structure pre-built and ready to customize.

Ready to build this?

πŸš€ Use this template Start in minutes with a pre-built version of this workflow.

πŸ’¬ Get help from our concierge team Our team will help you set this up for your specific use case.

πŸ“… Book a demo for Team/Enterprise setup See how teams use Formaloo at scale with advanced permissions and integrations.

Did this answer your question?