đ 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
CODEOWNERSandterraform planoutputs), 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}}"
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})"
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)
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
mainbranch. - 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.
- It runs
- Team leads or designated approvers review the PR, scrutinize the
terraform planoutput, 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.
- This workflow runs
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
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) |

Top comments (0)