DEV Community

Cover image for Solved: Form Submission and Approval Process?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Form Submission and Approval Process?

🚀 Executive Summary

TL;DR: Manual form submission and approval processes often create bottlenecks, lack auditability, and introduce inconsistencies within IT environments. This post outlines three technical solutions: low-code platforms, custom application development, and GitOps with Infrastructure as Code, to streamline, automate, and secure these critical workflows.

🎯 Key Takeaways

  • Low-code/no-code platforms like Microsoft Power Automate enable rapid development of multi-stage approval workflows for ‘citizen developers’ but can lead to vendor lock-in and limited customization for complex logic.
  • Custom application development, using frameworks like Django, offers maximum flexibility, tailored solutions, and deep integration for unique, complex requirements, though it demands higher development costs and ongoing maintenance.
  • GitOps, combined with Infrastructure as Code (IaC) and CI/CD pipelines, embeds approval processes directly into version control (e.g., Pull Requests with CODEOWNERS and terraform plan outputs), ensuring auditable, automated, and consistent management of technical changes.

Streamline your organizational workflows by implementing robust form submission and approval processes. This post explores three technical solutions, from low-code platforms to custom development and GitOps-driven automation, to ensure efficient data management and controlled access within IT environments.

Problem Symptoms: The Challenges of Manual Approval Processes

In many organizations, the process of submitting forms and obtaining approvals remains a significant bottleneck, particularly for IT-related requests. These inefficiencies often manifest through several key symptoms:

  • ### Manual and Email-Based Approval Chains

Many organizations still rely on email threads, shared spreadsheets, or even physical sign-offs for critical approvals. This approach is inherently slow, error-prone, and lacks centralized tracking, making it difficult to ascertain the current status of a request or identify where it’s stalled.

  • ### Lack of Auditability and Compliance Risks

Without a structured system, proving who approved what, when, and with what rationale becomes a forensic exercise. This absence of clear audit trails poses significant compliance risks, especially for regulated industries or internal security policies.

  • ### Inconsistent Data and Quality Issues

Manual forms, whether digital or physical, often lead to inconsistent data entry. Different users might interpret fields differently, miss required information, or use varied formats, resulting in poor data quality that impacts downstream systems and decision-making.

  • ### Security Vulnerabilities and Access Control Challenges

Ad-hoc approval processes can be exploited. Unauthorized individuals might submit requests, or approvals could be granted by those without appropriate authority. Managing granular access to forms and approval steps becomes nearly impossible without a dedicated system.

  • ### Process Bottlenecks and Operational Delays

Whether it’s provisioning a new server, granting software access, or approving a change request, delays in the approval process directly impact operational efficiency. These bottlenecks can halt project progress, delay deployments, and frustrate end-users.

  • ### High Resource Drain on IT Teams

IT teams frequently find themselves building one-off scripts or managing complex email filters to facilitate these processes. This diverts valuable resources from more strategic initiatives and leads to a fragmented, unsustainable landscape of custom solutions.

Solution 1: Low-Code/No-Code Platforms

Low-code/no-code platforms offer a visual, drag-and-drop interface to build applications and automate workflows with minimal or no traditional coding. They are excellent for rapidly deploying internal tools and streamlining administrative processes without heavy reliance on core development teams.

Concept

These platforms allow business analysts or “citizen developers” to design forms, define multi-stage approval workflows, set up conditional logic, and integrate with existing enterprise applications (e.g., SharePoint, Office 365, Salesforce). The focus is on speed and accessibility, empowering non-developers to solve immediate business problems.

Real Example: Microsoft Power Automate for IT Software Request

Imagine an IT department needing a structured way for employees to request new software licenses. Instead of emails, a Power Automate flow can manage the entire process:

  • An employee fills out a Microsoft Form with details like software name, justification, and cost center.
  • Upon submission, Power Automate triggers a workflow.
  • The workflow sends an approval request (e.g., via an Adaptive Card in Teams or an email) to the employee’s manager.
  • If approved by the manager, it then sends a second approval request to the IT budget owner.
  • Upon final approval, the flow could update an inventory list in SharePoint, create a ticket in a service desk system (e.g., ServiceNow), and notify the requester. If rejected, it notifies the requester with the reason.

Configuration Example (Conceptual Power Automate Steps)

1.  Trigger: "When a new response is submitted" (Microsoft Forms)
    *   Select the specific form.
2.  Action: "Get response details"
    *   Retrieve data submitted in the form.
3.  Action: "Start and wait for an approval" (v2)
    *   Approval type: "Approve/Reject - First to respond"
    *   Title: "Software Request Approval for ${{Get response details.SoftwareName}}"
    *   Assigned to: "Dynamic content: Manager (from AAD connector)"
    *   Details: "Request by ${{Get response details.RequesterEmail}} for ${{Get response details.SoftwareName}}. Justification: ${{Get response details.Justification}}"
4.  Condition: If "Outcome" from "Start and wait for an approval" is "Approve"
    *   If yes:
        *   Action: "Start and wait for an approval" (v2) (for IT Budget Owner)
            *   Approval type: "Approve/Reject - First to respond"
            *   Title: "Budget Approval for ${{Get response details.SoftwareName}}"
            *   Assigned to: "Specific Email Address or Group (e.g., ITBudget@contoso.com)"
        *   Condition: If "Outcome" from "IT Budget Owner approval" is "Approve"
            *   If yes:
                *   Action: "Create item" (SharePoint List: "ApprovedSoftwareRequests")
                    *   Map form fields to SharePoint list columns.
                *   Action: "Send an email (V2)"
                    *   To: ${{Get response details.RequesterEmail}}
                    *   Subject: "Software Request Approved: ${{Get response details.SoftwareName}}"
                    *   Body: "Your request has been fully approved. An IT ticket will be created shortly."
                *   Action: "Create a record" (ServiceNow/Jira connector, if available)
            *   If no (IT Budget Owner rejected):
                *   Action: "Send an email (V2)"
                    *   To: ${{Get response details.RequesterEmail}}
                    *   Subject: "Software Request Rejected by Budget Owner: ${{Get response details.SoftwareName}}"
                    *   Body: "Your request for ${{Get response details.SoftwareName}} was rejected by the budget owner. Reason: ${{IT Budget Owner approval.Comments}}"
    *   If no (Manager rejected):
        *   Action: "Send an email (V2)"
            *   To: ${{Get response details.RequesterEmail}}
            *   Subject: "Software Request Rejected by Manager: ${{Get response details.SoftwareName}}"
            *   Body: "Your request for ${{Get response details.SoftwareName}} was rejected by your manager. Reason: ${{Start and wait for an approval.Comments}}"
Enter fullscreen mode Exit fullscreen mode

Pros and Cons

  • Pros:
    • Rapid Development: Quickly build and deploy workflows without extensive coding.
    • User-Friendly: Accessible to non-developers, empowering citizen integrators.
    • Integration: Often comes with pre-built connectors to popular SaaS applications (e.g., Microsoft 365, Google Workspace, Salesforce).
    • Audit Trails: Platforms typically provide built-in logging and history for approvals.
  • Cons:
    • Vendor Lock-in: Highly dependent on the chosen platform’s ecosystem.
    • Limited Customization: May struggle with highly complex or unique business logic that requires bespoke code.
    • Scaling Challenges: Performance and cost can increase significantly for very high volumes or intricate workflows.
    • Licensing Costs: Can become expensive, especially for advanced features or a large number of users/flows.

Solution 2: Custom Application Development

For organizations with unique requirements, complex business logic, or a need for complete control over their applications, custom development is often the preferred path. This involves building a bespoke web application from scratch using traditional programming languages and frameworks.

Concept

A custom application provides maximum flexibility, allowing developers to design every aspect of the form, approval workflow, database schema, user interface, and integrations. It can be hosted on-premises or in the cloud, tailored precisely to an organization’s specific needs, and integrated deeply with legacy or specialized systems.

Real Example: Python/Django Application for Internal Change Requests

Consider an organization needing a sophisticated change management system for infrastructure modifications (e.g., server provisioning, network changes). A custom application using a framework like Django (Python) or Node.js/Express could provide:

  • A multi-step form with dynamic fields based on the type of change.
  • A robust role-based access control (RBAC) system for submitting and approving requests.
  • A complex approval matrix (e.g., requiring approvals from the team lead, security officer, and operations manager based on the impact level).
  • Integration with a Configuration Management Database (CMDB), ticketing systems (Jira, ServiceNow), and automation tools (Ansible, Terraform).
  • Comprehensive logging and reporting capabilities.

Configuration Example (Django Snippets)

Here are simplified snippets demonstrating how models and views might be structured in a Django application:

models.py (Database Schema for Change Requests)

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class ChangeRequest(models.Model):
    STATUS_CHOICES = [
        ('PENDING', 'Pending Approval'),
        ('APPROVED', 'Approved'),
        ('REJECTED', 'Rejected'),
        ('IN_PROGRESS', 'In Progress'),
        ('COMPLETED', 'Completed'),
        ('CANCELLED', 'Cancelled'),
    ]

    title = models.CharField(max_length=200, help_text="A brief title for the change request.")
    description = models.TextField(help_text="Detailed description of the proposed change.")
    requested_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='requests')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
    submitted_at = models.DateTimeField(auto_now_add=True)

    # Fields for approval tracking
    current_approver_group = models.CharField(max_length=100, blank=True, null=True, 
                                            help_text="e.g., 'Team Lead', 'Security', 'Operations'")
    rejection_reason = models.TextField(blank=True, null=True)

    def __str__(self):
        return f"CR-{self.id}: {self.title} ({self.status})"

class ApprovalStep(models.Model):
    request = models.ForeignKey(ChangeRequest, on_delete=models.CASCADE, related_name='approval_steps')
    approver = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    step_name = models.CharField(max_length=100) # e.g., "Manager Approval", "Security Review"
    status = models.CharField(max_length=20, choices=[('PENDING', 'Pending'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], default='PENDING')
    approved_at = models.DateTimeField(null=True, blank=True)
    comments = models.TextField(blank=True, null=True)

    class Meta:
        ordering = ['id'] # Maintain order of approval steps

    def __str__(self):
        return f"{self.step_name} for CR-{self.request.id} ({self.status})"
Enter fullscreen mode Exit fullscreen mode

views.py (Simplified Request Submission and Approval Logic)

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required, permission_required
from django.http import HttpResponseForbidden
from .models import ChangeRequest, ApprovalStep
from .forms import ChangeRequestForm, ApprovalForm # Assuming these forms exist
from django.utils import timezone

@login_required
def submit_change_request(request):
    if request.method == 'POST':
        form = ChangeRequestForm(request.POST)
        if form.is_valid():
            change_request = form.save(commit=False)
            change_request.requested_by = request.user
            change_request.save()

            # Initialize approval workflow (simplified)
            # In a real app, this would be more dynamic based on request type
            ApprovalStep.objects.create(request=change_request, step_name="Manager Approval", status="PENDING")
            # Notification logic here (e.g., email to manager)

            return redirect('request_detail', pk=change_request.pk)
    else:
        form = ChangeRequestForm()
    return render(request, 'change_requests/submit_request.html', {'form': form})

@login_required
@permission_required('app_name.can_approve_requests', raise_exception=True) # Requires a custom permission
def review_change_request(request, pk):
    change_request = get_object_or_404(ChangeRequest, pk=pk)

    # Get the current pending approval step for this user/group
    current_step = change_request.approval_steps.filter(status='PENDING', approver=request.user).first()
    if not current_step:
        # Check if user belongs to the approver group for the current step if no specific approver set
        # This logic would be more complex, involving User groups and roles
        # For simplicity, assume the current user is the explicit approver for `current_step`
        return HttpResponseForbidden("You are not assigned to approve this step or it's already approved.")

    if request.method == 'POST':
        form = ApprovalForm(request.POST)
        if form.is_valid():
            action = form.cleaned_data['action'] # 'approve' or 'reject'
            comments = form.cleaned_data.get('comments', '')

            current_step.approver = request.user # Record who approved
            current_step.comments = comments
            current_step.approved_at = timezone.now()

            if action == 'approve':
                current_step.status = 'APPROVED'
                current_step.save()

                # Check for next approval step or final approval
                next_step = change_request.approval_steps.filter(status='PENDING').exclude(pk=current_step.pk).first()
                if next_step:
                    # Notify next approver
                    pass
                else:
                    change_request.status = 'APPROVED'
                    # Trigger automated action (e.g., call API, create automation job)
            elif action == 'reject':
                current_step.status = 'REJECTED'
                change_request.status = 'REJECTED'
                change_request.rejection_reason = comments # Store overall rejection reason

            change_request.save()
            return redirect('request_list') # Redirect to a list of requests
    else:
        form = ApprovalForm()

    context = {
        'change_request': change_request,
        'current_step': current_step,
        'form': form,
    }
    return render(request, 'change_requests/review_request.html', context)
Enter fullscreen mode Exit fullscreen mode

Pros and Cons

  • Pros:
    • Maximum Flexibility: Complete control over UI, UX, business logic, and integrations.
    • Tailored Solutions: Build features and workflows that exactly match unique organizational processes.
    • Seamless Integration: Deep integration capabilities with any existing system via APIs.
    • No Vendor Lock-in: Freedom to choose technologies, hosting environments, and evolve the application independently.
    • Scalability: Can be engineered for high performance and scalability to meet demanding requirements.
  • Cons:
    • High Development Cost: Requires significant investment in skilled developers, design, and infrastructure.
    • Longer Development Time: Initial build and feature additions take more time compared to low-code solutions.
    • Ongoing Maintenance: Requires continuous maintenance, updates, and security patching.
    • Complexity: Can become complex to manage and evolve without robust architectural planning and documentation.

Solution 3: GitOps and Infrastructure as Code (IaC) for Approval Workflows

In a DevOps context, “form submission and approval” often translates to changes in code or infrastructure. GitOps principles, combined with Infrastructure as Code (IaC) and CI/CD pipelines, provide a powerful, auditable, and automated approach to managing approvals for technical changes.

Concept

GitOps treats Git as the single source of truth for declarative infrastructure and application configurations. All changes (the “form submissions”) are made via Pull Requests (PRs) or Merge Requests (MRs). The approval process is embedded directly into the Git workflow, typically through PR reviews, CODEOWNERS files, and protected branches. Upon approval and merge, automated CI/CD pipelines execute the changes, ensuring consistency and auditability.

Real Example: GitHub Actions for Terraform Infrastructure Approval

Consider a team managing AWS infrastructure with Terraform. Instead of filling out a web form to request a new EC2 instance or a security group change, a developer submits a PR with the desired Terraform code. The approval workflow would look like this:

  • A developer creates a new branch and adds/modifies Terraform configuration files (e.g., main.tf, variables.tf).
  • They open a Pull Request (PR) to merge their branch into the main branch.
  • A GitHub Actions workflow is triggered:
    • It runs terraform plan, showing exactly what infrastructure changes will occur.
    • It comments the plan output directly back to the PR for easy review.
  • Team leads or designated approvers review the PR, scrutinize the terraform plan output, and comment on the proposed changes.
  • Based on branch protection rules, the PR requires a minimum number of approvals (e.g., from CODEOWNERS) before it can be merged.
  • Once approved and merged into main, another GitHub Actions workflow is triggered:
    • This workflow runs terraform apply, deploying the approved infrastructure changes.
    • It might include an environment protection rule requiring manual approval before deploying to a “production” environment.

Configuration Example (GitHub Actions Workflow for Terraform)

This YAML defines a GitHub Actions workflow that plans Terraform changes on a Pull Request and applies them upon merging to main (with an optional manual approval for production deployments).

name: Terraform Plan & Apply with Approval

on:
  pull_request:
    branches: [ main ]
    types: [ opened, synchronize, reopened ]
    paths:
      - 'terraform/**' # Trigger only for changes in the 'terraform' directory

  push:
    branches: [ main ]
    paths:
      - 'terraform/**' # Trigger only for changes in the 'terraform' directory

env:
  TF_VERSION: 1.5.7
  AWS_REGION: us-east-1 # Specify your target AWS region

jobs:
  terraform_plan:
    name: Terraform Plan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Terraform Init
        id: init
        run: terraform -chdir=./terraform init

      - name: Terraform Plan
        id: plan
        run: terraform -chdir=./terraform plan -no-color -out=tfplan # Save plan to file
        continue-on-error: true # Allow plan to fail if there are errors, still output to PR

      - name: Upload Terraform Plan artifact
        uses: actions/upload-artifact@v4
        with:
          name: tfplan-artifact
          path: terraform/tfplan

      - name: Add Plan to PR Comment
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        with:
          script: |
            const output = `#### Terraform Plan 📖
            Click to expand

            \`\`\`terraform
            ${process.env.PLAN_OUTPUT}
            \`\`\`

            `;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })
        env:
          PLAN_OUTPUT: ${{ steps.plan.outputs.stdout }}

  terraform_apply:
    name: Terraform Apply
    runs-on: ubuntu-latest
    needs: terraform_plan # Ensure plan runs first
    if: github.event_name == 'push' && github.ref == 'refs/heads/main' # Only apply on push to main
    environment: # This requires an environment with protection rules configured in GitHub repo settings
      name: production
      url: 'https://your-application-url.com' # Optional: Link to deployed resource
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Download Terraform Plan artifact
        uses: actions/download-artifact@v4
        with:
          name: tfplan-artifact
          path: terraform/

      - name: Terraform Init
        id: init
        run: terraform -chdir=./terraform init

      - name: Terraform Apply
        id: apply
        run: terraform -chdir=./terraform apply -auto-approve tfplan # Apply the previously generated plan
Enter fullscreen mode Exit fullscreen mode

Note: For robust production deployments, consider using remote state locking (e.g., S3 backend with DynamoDB locking for Terraform) and dedicated service accounts with least privilege. GitHub Environments provide a mechanism to enforce manual approval gates for deployments, making this a powerful GitOps approval solution.

Pros and Cons

  • Pros:
    • Immutability & Auditability: Every change is version-controlled in Git, providing a complete, immutable audit trail.
    • Consistency: Automated pipelines ensure changes are applied consistently every time.
    • Security: PR reviews, branch protection, and CODEOWNERS enforce peer review and authorized approvals. Secrets management is integrated.
    • Automation: Reduces manual toil and human error by automating deployment after approval.
    • Self-Documenting: The Git repository itself serves as documentation of the current state and changes.
  • Cons:
    • Steeper Learning Curve: Requires familiarity with Git, IaC tools (e.g., Terraform), and CI/CD concepts.
    • Cultural Shift: Demands a shift to a “everything as code” mindset within the team.
    • Not Universal: Best suited for technical “form” submissions (infrastructure, code, configuration) rather than traditional HR or administrative forms.
    • Tooling Complexity: Involves setting up and managing Git repositories, CI/CD platforms, and IaC tools.

Solution Comparison Table

Choosing the right solution depends heavily on your organization’s specific needs, technical capabilities, budget, and the nature of the forms/approvals in question. Here’s a comparison:

Feature Low-Code/No-Code Platforms Custom Application Development GitOps & IaC Approval Workflows
Complexity Low (visual builder) High (coding, architecture, DevOps) Medium-High (Git, IaC, CI/CD, scripting)
Development Speed Very Fast (prototyping to deployment) Slow (initial build), Medium (iterations) Medium (setup), Fast (iterative changes)
Flexibility Limited (platform constraints, available connectors) Extremely High (full control over logic, UI, integrations) High (scriptable, integrates many technical tools)
Cost Subscription fees, potential scaling costs, training High upfront (development), ongoing maintenance, hosting Tooling subscriptions (Git host, CI/CD runner), expertise cost, infrastructure
Primary Use Cases Simple data entry, internal request forms, HR, basic IT workflows, small to medium business processes. Complex business logic, public-facing applications, unique integrations, high-scale, core business systems. Infrastructure provisioning, configuration management, software deployments, security policy enforcement, release management.
Required Skill Set Business analysts, citizen developers, domain experts Software engineers, database specialists, UI/UX designers, DevOps engineers DevOps engineers, SREs, developers familiar with IaC and CI/CD
Auditability Built-in logging, history, and analytics (platform-dependent) Custom logging, database history, application-level audit trails Git history, CI/CD logs, immutable changes, detailed pull request reviews
Scalability Platform-dependent, can incur higher costs for enterprise scale Highly scalable with proper architectural design and implementation Highly scalable for infrastructure and application deployments
Vendor Lock-in High (tied to platform ecosystem) Low (using open-source tools, cloud-agnostic options) Medium (depends on chosen Git host, CI/CD platform, but standards-based)

Darian Vance

👉 Read the original article on TechResolve.blog

Top comments (0)