# How to Create Template Tasks and Auto-Generate Them in Knack

**URL:** <https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072>\
**Category:** API & Custom Code\
**Created:** [December 6, 2025, 12:58am UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072 "2025-12-06T00:58:35Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Paul6](https://avatars.discourse-cdn.com/v4/letter/p/ee7513/32.png) [@Paul6](https://forums.knack.com/u/Paul6)\
**Post date:** [December 6, 2025, 12:58am UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/1 "2025-12-06T00:58:35Z")

</div>

**Read this guide if you want to:**

1. Create reusable template tasks

2. Reduce your automation costs

3. 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**

1. Set up inspection

2. Visit the property

3. Check shingles

4. Identify water intrusion

5. Send invoice

**Storm Damage Inspection**

1. Contact insurance

2. Get liability coverage

3. Set up inspection

4. Check shingles

5. 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:**

1. **Project** – contains primary information

2. **Project Type** – groups your templates (e.g., Roofing, Renovation)

3. **Task Template** – predefined tasks for each project type

4. **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 | ❌ Not feasible | Requires manual entry for each new task |
| Knack Task | ❌ Not feasible | Still too much admin work |
| Knack Flow | ⚠ Possible | But will quickly burn through your monthly flow quota |
| Make / Zapier | ⚠ Possible | Also costly for high-volume operations |
| JavaScript + API | ✅ Best option | 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:

```auto
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):

```auto
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:

```auto
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.');

```

}

});

---

<div class="post-metadata">

**Author:** ![StephenChapman](https://sea2.discourse-cdn.com/flex020/user_avatar/forums.knack.com/stephenchapman/32/1759_2.png) [@StephenChapman](https://forums.knack.com/u/StephenChapman)\
**Post date:** [December 7, 2025, 8:49pm UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/2 "2025-12-07T20:49:40Z")

</div>

Nice write-up @Paul6! I do something similar in construction management apps I’ve built.

The one thing I’d recommend is to never expose the `X-Knack-REST-API-Key` in the JavaScript code, and to use [view-based requests](https://docs.knack.com/reference/view-based-requests) to GET, PUT, and POST records with a user token authorization instead.

---

<div class="post-metadata">

**Author:** ![Paul6](https://avatars.discourse-cdn.com/v4/letter/p/ee7513/32.png) [@Paul6](https://forums.knack.com/u/Paul6)\
**Post date:** [December 8, 2025, 8:00pm UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/3 "2025-12-08T20:00:42Z")

</div>

For sure.

This is beta just to test.

---

<div class="post-metadata">

**Author:** ![MichaelG](https://sea2.discourse-cdn.com/flex020/user_avatar/forums.knack.com/michaelg/32/758_2.png) [@MichaelG](https://forums.knack.com/u/MichaelG)\
**Post date:** [December 10, 2025, 5:32pm UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/4 "2025-12-10T17:32:00Z")

</div>

This is so correct and useful, and most economic.

I applied the same principles to Quote templates for my CRM.

Since my app is embeded inside a WordPress website, I linked the JS code to the WP Code plugin, and from there it calls the ID keys that are highly protected inside my external web host.

Sending quotes this way is EXTREMELY FAST.

Very useful. Knack should innovate with a Template functionality.

thanks for sharing @Paul6 !

---

<div class="post-metadata">

**Author:** ![Paul6](https://avatars.discourse-cdn.com/v4/letter/p/ee7513/32.png) [@Paul6](https://forums.knack.com/u/Paul6)\
**Post date:** [December 10, 2025, 6:58pm UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/5 "2025-12-10T18:58:36Z")

</div>

I think it will be a game changer if knack able to create templates.

---

<div class="post-metadata">

**Author:** ![Paul6](https://avatars.discourse-cdn.com/v4/letter/p/ee7513/32.png) [@Paul6](https://forums.knack.com/u/Paul6)\
**Post date:** [December 10, 2025, 11:03pm UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/6 "2025-12-10T23:03:29Z")

</div>

So, after attempting multiple time. I am unable to use the view base method to perform this task. Since I am using a parents UID it does not reconginzed.

---

<div class="post-metadata">

**Author:** ![StephenChapman](https://sea2.discourse-cdn.com/flex020/user_avatar/forums.knack.com/stephenchapman/32/1759_2.png) [@StephenChapman](https://forums.knack.com/u/StephenChapman)\
**Post date:** [December 11, 2025, 12:28am UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/7 "2025-12-11T00:28:30Z")

</div>

Hi @Paul6,

Here’s a video demo of the simplest API method I use: [**Creating Task Templates**](https://drive.google.com/file/d/1BHeJYj1XXnxtUQI5C1IW24KYluF0q3cQ/view?usp=sharing)

> **Expand for code**
>
> ```jsx
> $(document).on(`knack-form-submit.any`, async function (event, view, record) {
>   
> if (
> view.key === 'view_3' || // Add project form
> view.key === 'view_13' // Add tasks from template form 
> ) {
> await addTemplateRecords(record, { 
> taskProjectKey: 'field_22', // Tasks -> Project
> copyFromKey: 'field_30', // Tasks - Task to copy from
> taskTemplateKey: 'field_24', // Tasks -> Template
> projectTemplatesKey: 'field_25', // Projects -> Template(s) 
> tasksGridKey: 'view_9', // Tasks grid
> taskFormKey: 'view_10' // Add task form
> });
>     
> }
> 
> });
> 
> async function addTemplateRecords(record, { taskProjectKey, copyFromKey, taskTemplateKey, projectTemplatesKey, tasksGridKey, taskFormKey }) {
> 
> const projectId = record.id;
> const templateIds = record?.[`${projectTemplatesKey}_raw`].map(r => r.id) || [];
>   
> // For each template specified, get all tasks belonging to that template...
> for (const templateId of templateIds) {
> const templateTasksFilters = [{field: taskTemplateKey, operator: 'is', value: templateId}];
> const tasks = await KnackAPI.makeRequest('getMany', { scene: getSceneFromView(tasksGridKey)?.key, view: tasksGridKey, filters: templateTasksFilters }); // Get tasks connected to template
> 
> // For each task found in the template, add a record referencing the project 
> const recordsToCreate = [];
> for (const task of tasks) {
> recordsToCreate.push({
> [copyFromKey]: task.id,
> [taskProjectKey]: projectId
> })
> }
> 
> await KnackAPI.makeRequest('postMany', { scene: getSceneFromView(taskFormKey)?.key , view: taskFormKey, records: recordsToCreate }); // Add task form
>     
> }
> 
> // Get scene from view key
> function getSceneFromView(viewKey) {
> return Knack.scenes.models.find(scene => scene.views.models.some(view => view.id === viewKey))?.attributes;
> }
> 
> }
> 
> ```

Note that this utilises [Knack API Helper](https://socket.dev/npm/package/knack-api-helper) (thanks to Callum Boase), which you can learn how to install [**in this resource**](https://stephenchapman.knack.com/portal#r/682944c176e17503159189d2/).

> **Expand for full code with loadExternalFiles() function**
>
> ```jsx
> loadExternalFiles([
> { type: 'script', module: false, url: 'https://cdn.jsdelivr.net/npm/knack-api-helper@latest/browser.js' }, // Knack API Helper
> ]);
> 
> $(document).on(`knack-form-submit.any`, async function (event, view, record) {
>   
> if (
> view.key === 'view_3' || // Add project form
> view.key === 'view_13' // Add tasks from template form 
> ) {
> await addTemplateRecords(record, { 
> taskProjectKey: 'field_22', // Tasks -> Project
> copyFromKey: 'field_30', // Tasks - Task to copy from
> taskTemplateKey: 'field_24', // Tasks -> Template
> projectTemplatesKey: 'field_25', // Projects -> Template(s) 
> tasksGridKey: 'view_9', // Tasks grid
> taskFormKey: 'view_10' // Add task form
> });
>     
> }
> 
> });
> 
> async function addTemplateRecords(record, { taskProjectKey, copyFromKey, taskTemplateKey, projectTemplatesKey, tasksGridKey, taskFormKey }) {
> 
> const projectId = record.id;
> const templateIds = record?.[`${projectTemplatesKey}_raw`].map(r => r.id) || [];
>   
> // For each template specified, get all tasks belonging to that template...
> for (const templateId of templateIds) {
> const templateTasksFilters = [{field: taskTemplateKey, operator: 'is', value: templateId}];
> const tasks = await KnackAPI.makeRequest('getMany', { scene: getSceneFromView(tasksGridKey)?.key, view: tasksGridKey, filters: templateTasksFilters }); // Get tasks connected to template
> 
> // For each task found in the template, add a record referencing the project 
> const recordsToCreate = [];
> for (const task of tasks) {
> recordsToCreate.push({
> [copyFromKey]: task.id,
> [taskProjectKey]: projectId
> })
> }
> 
> await KnackAPI.makeRequest('postMany', { scene: getSceneFromView(taskFormKey)?.key , view: taskFormKey, records: recordsToCreate }); // Add task form
>     
> }
> 
> // Get scene from view key
> function getSceneFromView(viewKey) {
> return Knack.scenes.models.find(scene => scene.views.models.some(view => view.id === viewKey))?.attributes;
> }
> 
> }
> 
> // --------------------------------------------------------------------------------------------------------
> //Generic helper function to loader external JS and CSS files before your Knack app finishes loading
> function loadExternalFiles(files) {
> KnackInitAsync = function ($, callback) {
> Knack.showSpinner();
> window.$ = window.jQuery = $;
> 
> let loaded = 0;
> 
> files.forEach(file => {
> const el = document.createElement(file.type === 'script' ? 'script' : 'link');
> 
> if (file.type === 'script') {
> el.src = file.url;
> el.async = false;
> if (file.module) el.type = 'module';
> } else {
> el.href = file.url;
> el.rel = 'stylesheet';
> }
> 
> el.onload = () => {
> console.log(`loaded file ${file.url}`);
> if (++loaded === files.length) {
> console.log('all external files loaded');
> callback();
> }
> };
> 
> el.onerror = () => {
> alert(`Error loading ${file.url}. Please refresh and try again.`);
> Knack.hideSpinner();
> };
> 
> document.head.appendChild(el);
> });
> };
> }
> 
> ```

Let me know your feedback!

---

<div class="post-metadata">

**Author:** ![BradStevens](https://sea2.discourse-cdn.com/flex020/user_avatar/forums.knack.com/bradstevens/32/140_2.png) [@BradStevens](https://forums.knack.com/u/BradStevens)\
**Post date:** [December 11, 2025, 1:26am UTC](https://forums.knack.com/t/how-to-create-template-tasks-and-auto-generate-them-in-knack/20072/8 "2025-12-11T01:26:29Z")

</div>

Terrific stuff @StephenChapman  
I need an excuse to work with @Callum.Boase Knack API Helper!
