Read this guide if you want to:
-
Create reusable template tasks
-
Reduce your automation costs
-
Build efficient internal tools for renovation, service, or construction workflows
Hey Knack Geeks!
After exploring construction platforms like Procore and Buildertrend, one standout feature became obvious — task templates.
Templates save hours of repetitive setup by letting you clone predefined tasks or schedules that follow a predictable pattern.
Let’s walk through how to recreate this feature inside Knack — efficiently and affordably.
Why Templates Matter
In construction, most projects repeat the same set of steps.
For instance, if you run a roofing company, your tasks might look like this:
Normal Roof Inspection
-
Set up inspection
-
Visit the property
-
Check shingles
-
Identify water intrusion
-
Send invoice
Storm Damage Inspection
-
Contact insurance
-
Get liability coverage
-
Set up inspection
-
Check shingles
-
Identify water intrusion
Rather than rebuilding these tasks every time, we can use Knack and JavaScript to clone task templates automatically.
Data Model Setup
Here are the objects and relationships you’ll need:
Objects:
-
Project – contains primary information
-
Project Type – groups your templates (e.g., Roofing, Renovation)
-
Task Template – predefined tasks for each project type
-
Task – the cloned, real tasks linked to an active project
Relationships:
-
A Project belongs to a Project Type
-
A Task Template belongs to a Project Type
-
A Task links to both a Project and a Project Type
Choosing the Right Automation Approach
Here’s a quick comparison of possible methods:
| Method | Feasibility | Notes |
|---|---|---|
| Knack Webpage Trigger | Requires manual entry for each new task | |
| Knack Task | Still too much admin work | |
| Knack Flow | But will quickly burn through your monthly flow quota | |
| Make / Zapier | Also costly for high-volume operations | |
| JavaScript + API | Fast, low-cost, and highly flexible |
Why Code Instead of Flow?
Because cost and control matter.
Knack Pro accounts support 3,000 API calls per day,
while Flows are limited to 2,000 transactions per month.
So coding isn’t just fun — it’s efficient.
Key Challenge: Hidden Primary and Foreign Keys
Knack doesn’t expose Primary Keys (PK) or Foreign Keys (FK) directly in the UI.
You’ll need to access them through the API or JavaScript console.
To get the Project’s Primary Key:
const view = Knack.views['view_42'];
const projectId =
view?.model?.attributes?.id ||
view?.model?.data?.id ||
(window.location.hash.match(/\/([a-f0-9]{24})(?:\/|$)/i) || [])[1];
To get the Project Type (connecting record):
const getProjectUrl = `https://api.knack.com/v1/objects/object_26/records/${projectId}`;
const getProject = await fetch(getProjectUrl, {
headers: {
'X-Knack-Application-Id': Knack.application_id,
'X-Knack-REST-API-Key': 'YOUR_API_KEY_HERE',
'Content-Type': 'application/json'
}
});
const projectData = await getProject.json();
const projectTypeArray = projectData.field_171_raw || [];
if (!Array.isArray(projectTypeArray) || projectTypeArray.length === 0) {
alert('⚠️ No Project Type found for this project.');
return;
}
const projectTypeId = projectTypeArray[0].id;
const projectTypeName = projectTypeArray[0].identifier;
console.log('🏷️ Project Type:', projectTypeName, '| ID:', projectTypeId);
Understanding the _raw Field
Notice the code uses field_171_raw instead of field_171 — this is intentional.
Connecting fields in Knack return an array containing both the ID and the display value.
Example:
field_171: "<span class='6931ccd07576d6391fde7531' data-kn='connection-value'>Weather Storm Inspection</span>"
field_171_raw: [
{ "id": "6931ccd07576d6391fde7531", "identifier": "Lite Renovation" }
]
Always use the _raw version to access structured data programmatically.
The Automation Logic
Once you’ve retrieved your Project Type, the next step is simple:
Whenever a Project is assigned to a Project Type, clone all associated Task Templates into the Task object for that Project.
In this setup:
-
The trigger occurs in View 42.
-
The script queries the Project Type.
-
It then duplicates every Task Template that belongs to that type into the Task table.
You’ll end up with a fully generated task list every time a new project is created —
without burning through Knack flows or Zapier executions.
Here is my code for the workflow:
//
View 42: Auto-clone template tasks into Actual Tasks when project is viewed/created
$(document).on(‘knack-view-render.view_42’, async function () {
try {
console.log('🟢 View 42 loaded → Starting Template→Actual Task cloning flow...');
// Step 1️⃣ — Get Project ID from view or URL
const view = Knack.views\['view_42'\];
const projectId =
view?.model?.attributes?.id ||
view?.model?.data?.id ||
(window.location.hash.match(/\\/(\[a-f0-9\]{24})(?:\\/|$)/i) || \[\])\[1\];
if (!projectId) {
alert('❌ Could not find Project ID.');
return;
}
console.log('📋 Project ID:', projectId);
// Step 2️⃣ — Get Project Type ID from the Project record
const getProjectUrl = \`https://api.knack.com/v1/objects/object_26/records/${projectId}\`;
const getProject = await fetch(getProjectUrl, {
headers: {
'X-Knack-Application-Id': Knack.application_id,
'X-Knack-REST-API-Key': ’Insert your api here’,
'Content-Type': 'application/json'
}
});
const projectData = await getProject.json();
const projectTypeArray = projectData.field_171_raw || \[\];
if (!Array.isArray(projectTypeArray) || projectTypeArray.length === 0) {
alert('⚠️ No Project Type found for this project.');
return;
}
const projectTypeId = projectTypeArray\[0\].id;
const projectTypeName = projectTypeArray\[0\].identifier;
console.log('🏷️ Project Type:', projectTypeName, '| ID:', projectTypeId);
// Step 3️⃣ — Get Template Tasks matching this Project Type
const filters = \[
{ field: 'field_178', operator: 'is', value: projectTypeId }
\];
const templateUrl = \`https://api.knack.com/v1/objects/object_28/records?rows_per_page=1000&filters=${encodeURIComponent(JSON.stringify(filters))}\`;
const getTemplates = await fetch(templateUrl, {
headers: {
'X-Knack-Application-Id': Knack.application_id,
'X-Knack-REST-API-Key': ‘Insert your API here’
}
});
const templateData = await getTemplates.json();
const templates = templateData.records || \[\];
if (templates.length === 0) {
alert('⚠️ No template tasks found for Project Type: ' + projectTypeName);
console.warn('No templates found for:', projectTypeId);
return;
}
console.log(\`📋 Found ${templates.length} Template Tasks for ${projectTypeName}\`);
// Step 4️⃣ — Clone Template Tasks into Actual Tasks
for (const t of templates) {
const payload = {
// Map template fields → actual fields
field_168: t.field_167, // Task Name
field_179: t.field_173, // Task Description
field_180: t.field_174, // Task Status
field_181: t.field_175, // Due Date
field_183: t.field_177, // Estimate Hours
field_185: \[t.id\], // Connection → Template Task
field_184: \[projectId\] // Connection → Project
};
const postUrl = 'https://api.knack.com/v1/objects/object_29/records';
console.log('📦 Creating Actual Task:', payload);
const res = await fetch(postUrl, {
method: 'POST',
headers: {
'X-Knack-Application-Id': Knack.application_id,
'X-Knack-REST-API-Key': 'Inset your API in here’,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const txt = await res.text();
console.log(\`📩 Task "${t.field_167}" → ${res.status}:\`, txt);
if (!res.ok) console.warn(\`⚠️ Failed to create task "${t.field_167}"\`);
}
// Step 5️⃣ — Final confirmation
alert(\`🎉 Successfully cloned ${templates.length} Template Tasks into Actual Tasks for "${projectTypeName}"!\`);
} catch (err) {
console.error('💥 Error in cloning flow:', err);
alert('❌ Error in cloning flow. Check console for details.');
}
});