Create Approval Tasks from Form Submission (Part 1)
This script creates approval task child records under a parent record based on business rules such as type, category, and value.
Use Case
Use this when you want a form submission or record creation to automatically:
- Route approvals based on conditions
- Create multi-step approval workflows
- Skip approvals for certain cases (small value, pass-through, etc.)
Examples:
- Expense approvals based on amount and department
- Purchase requests routed through finance and leadership
- Vendor onboarding approvals based on category
- Donations or grants requiring multi-level approval
What It Does
On trigger, the script will:
- Read the triggering parent record from
input.recordId - Evaluate whether approval is required
- If not required:
- Mark the record as
Approved - Skip all approval steps
- Mark the record as
- If required:
- Determine the approval path based on record data
- Create approval task child records
- Set the first task to
Pending Approval - Set remaining tasks to
Waiting - Set the parent record to
In Review - Set the current approval stage
Key Concept
Instead of embedding approval logic inside forms or workflows, this approach:
- Models each approval step as a child record
- Uses sequence-based progression
- Keeps a clear audit trail of decisions
- Allows workflows to scale without becoming brittle
This structure works across domains where approvals depend on multiple conditions and stakeholders.
Configuration
| Key | Description | Example |
|---|---|---|
parent.fields.donationType | Field storing the type of request | Donation Type |
parent.fields.donationCategory | Field storing category used for routing | Donation Category |
parent.fields.donationValue | Numeric field used for conditional logic | Donation Value |
parent.fields.status | Parent record status field | Status |
parent.fields.currentApprovalStage | Tracks current approval stage | Current Approval Stage |
parent.statuses.inReview | Status when approvals are in progress | In Review |
parent.statuses.approved | Status when auto-approved or completed | Approved |
child.type | Child record type for approval steps | Approval Task |
child.fields.approval | Field storing approval state | Approval |
child.fields.sequence | Field storing step order | Seq |
child.fields.approvalStage | Field storing step name | Approval Stage |
child.fields.department | Field storing assigned department | Department |
child.statuses.pending | Active approval step | Pending Approval |
child.statuses.waiting | Future steps not yet active | Waiting |
Inputs
| Input | Required | Description |
|---|---|---|
recordId | Yes | Parent record ID from the workflow trigger |
Outputs
| Output | Description |
|---|---|
createdCount | Number of approval task records created |
Script
const CONFIG = {
parent: {
fields: {
donationType: "Donation Type",
donationCategory: "Donation Category",
donationValue: "Donation Value",
status: "Status",
currentApprovalStage: "Current Approval Stage",
},
statuses: {
inReview: "In Review",
approved: "Approved",
},
},
child: {
type: "Approval Task",
fields: {
approval: "Approval",
sequence: "Seq",
approvalStage: "Approval Stage",
department: "Department",
},
statuses: {
waiting: "Waiting",
pending: "Pending Approval",
},
},
};
if (!input.recordId) {
throw new Error("No Donation record ID provided.");
}
const donation = await anydb.getRecordById(input.recordId);
if (!donation) {
throw new Error(`Donation not found: ${input.recordId}`);
}
const values = donation.cellValues || {};
const donationType = String(values[CONFIG.parent.fields.donationType] || "").toLowerCase();
const donationCategory = String(values[CONFIG.parent.fields.donationCategory] || "").toLowerCase();
const donationValue = Number(values[CONFIG.parent.fields.donationValue] || 0);
function getDepartment(category) {
const map = {
media: "Media & Ed Tech",
supplies: "Curriculum & Instruction",
tech: "IT Office",
pe: "PE / Health / Dance",
other: "Safety / Risk",
fac: "School Facilities",
};
return map[category] || "Department Approval";
}
function addStep(steps, stage, department) {
steps.push({
sequence: steps.length + 1,
stage,
department,
});
}
const approvalSteps = [];
// Gate checks
const approvalNotRequired =
donationType === "pass-through" ||
donationType === "vehicle" ||
donationValue < 500;
if (approvalNotRequired) {
await anydb.updateRecord({
adoid: donation.id,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.approved,
[CONFIG.parent.fields.currentApprovalStage]: "No approval required",
},
});
output.set("createdCount", 0);
return;
}
// Routing
if (donationType === "cash") {
addStep(approvalSteps, "Finance Approval", "Finance");
addStep(approvalSteps, "Division of Schools Approval", "Division of Schools");
addStep(approvalSteps, "Board Acceptance", "Board of Education");
} else if (donationType === "non-cash") {
const dept = getDepartment(donationCategory);
addStep(approvalSteps, "Department Approval", dept);
addStep(approvalSteps, "Division of Schools Approval", "Division of Schools");
addStep(approvalSteps, "Board Acceptance", "Board of Education");
}
// Create tasks
let createdCount = 0;
for (const step of approvalSteps) {
await anydb.yield();
const status =
step.sequence === 1
? CONFIG.child.statuses.pending
: CONFIG.child.statuses.waiting;
await anydb.createRecord({
name: `${step.sequence}. ${step.stage}`,
parentid: donation.id,
typename: CONFIG.child.type,
cellValues: {
[CONFIG.child.fields.approval]: status,
[CONFIG.child.fields.sequence]: step.sequence,
[CONFIG.child.fields.approvalStage]: step.stage,
[CONFIG.child.fields.department]: step.department,
},
});
createdCount++;
}
// Update parent
await anydb.updateRecord({
adoid: donation.id,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.inReview,
[CONFIG.parent.fields.currentApprovalStage]: approvalSteps[0].stage,
},
});
output.set("createdCount", createdCount);