Learn how to add bulk update functionality to your Knack table views with this comprehensive JavaScript implementation guide. This script enables users to select multiple records using checkboxes and update their status fields simultaneously, dramatically improving workflow efficiency. Includes complete code with detailed explanations, step-by-step customization instructions, and troubleshooting tips for common issues.
What This Script Does
This script adds a powerful bulk update feature to your Knack table view. It allows you to:
- Select multiple tasks using checkboxes
- Update all selected tasks' status at once
- Save time by avoiding individual edits
Before You Start - What You'll Need
1. Find Your View Key
- Go to your Knack Builder
- Navigate to the page with your table
- Look for the view number (it looks like
view_XX
) - Write this down - you'll need it!
2. Find Your Application ID
- In Knack Builder, go to Settings → API & Code
- Copy your Application ID (looks like:
68eef3e76bebd002898bdb11
) - Keep this handy
3. Find Your Status Field Key
- In your table's data source (object), find your status field
- Click on the field to see its key (looks like
field_XX
) - Note this down
The Complete Script with Explanations
// This tells Knack to wait until the page is fully loaded before running our code
Knack.ready().then(async () => {
// CUSTOMIZE THIS: Replace 'view_XX' with your table view key
// This listens for when your specific table loads on the page
Knack.on('records:render:view_28', function (event) {
// These grab the data from your table
const records = event.records; // All the records in your table
const viewKey = event.viewKey; // The view identifier
// CUSTOMIZE THIS: Replace with your Application ID from Knack Settings
const appId = '68eef3e76bebd002898bdb11';
// CUSTOMIZE THIS: These are your status options
// Change the values and labels to match your workflow
const statusOptions = [
{ value: 'To Do', label: 'To Do' },
{ value: 'In Progress', label: 'In Progress' },
{ value: 'Review', label: 'Review' },
{ value: 'Completed', label: 'Completed' }
];
// Wait half a second for the table to fully render
// (Sometimes Knack needs a moment to build the table)
setTimeout(function () {
// Find the container that holds your table view
const viewContainer = document.getElementById(viewKey);
if (!viewContainer) {
console.log('View container not found');
return; // Stop if we can't find it
}
// Find the actual table inside the container
const table = viewContainer.querySelector('table');
if (!table) {
console.log('Table not found');
return; // Stop if there's no table
}
console.log('Table found - adding bulk update features');
// ===== CREATE THE BULK UPDATE BUTTON =====
// Create a container for our button
const buttonDiv = document.createElement('div');
buttonDiv.style.marginTop = '15px'; // Space above
buttonDiv.style.marginBottom = '20px'; // Space below
buttonDiv.style.paddingLeft = '16px'; // Align with table
// Create the actual button
const bulkButton = document.createElement('button');
bulkButton.id = 'bulk-status-btn';
bulkButton.textContent = 'Bulk Update Status'; // Button text
// Style the button to look nice
bulkButton.style.backgroundColor = '#3b82f6'; // Blue color
bulkButton.style.color = 'white'; // White text
bulkButton.style.padding = '10px 20px'; // Internal spacing
bulkButton.style.border = 'none'; // No border
bulkButton.style.borderRadius = '6px'; // Rounded corners
bulkButton.style.fontSize = '14px'; // Text size
bulkButton.style.fontWeight = '500'; // Medium bold
bulkButton.style.cursor = 'pointer'; // Hand cursor on hover
// Make button darker on hover (visual feedback)
bulkButton.addEventListener('mouseenter', function () {
bulkButton.style.backgroundColor = '#2563eb'; // Darker blue
});
bulkButton.addEventListener('mouseleave', function () {
bulkButton.style.backgroundColor = '#3b82f6'; // Back to normal
});
// Add the button to its container, then add container above the table
buttonDiv.appendChild(bulkButton);
table.parentNode.insertBefore(buttonDiv, table);
// ===== ADD CHECKBOXES TO THE TABLE =====
// Add "Select All" checkbox to the table header
const headerRow = table.querySelector('thead tr');
if (headerRow) {
const selectAllTh = document.createElement('th');
selectAllTh.style.width = '50px';
selectAllTh.style.textAlign = 'center';
selectAllTh.innerHTML = '<input type="checkbox" id="select-all-tasks">';
headerRow.insertBefore(selectAllTh, headerRow.firstChild); // Add as first column
}
// Add individual checkboxes to each row
const bodyRows = table.querySelectorAll('tbody tr');
bodyRows.forEach(function (row, index) {
// Make sure this row has a record with an ID
if (records[index] && records[index].id) {
const recordId = records[index].id; // Get the record's unique ID
// Create a table cell with a checkbox
const checkboxTd = document.createElement('td');
checkboxTd.style.textAlign = 'center';
checkboxTd.style.verticalAlign = 'middle';
// Store the record ID with the checkbox so we know what to update
checkboxTd.innerHTML = '<input type="checkbox" class="task-checkbox" data-record-id="' + recordId + '">';
row.insertBefore(checkboxTd, row.firstChild); // Add as first column
}
});
console.log('Checkboxes added to table');
// ===== MAKE "SELECT ALL" CHECKBOX WORK =====
const selectAllCheckbox = document.getElementById('select-all-tasks');
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', function () {
// Find all individual checkboxes
const checkboxes = document.querySelectorAll('.task-checkbox');
// Check/uncheck them all based on "Select All" state
checkboxes.forEach(function (checkbox) {
checkbox.checked = selectAllCheckbox.checked;
});
});
}
// ===== HANDLE BULK UPDATE BUTTON CLICK =====
bulkButton.addEventListener('click', function () {
// Collect all selected record IDs
const selectedTasks = [];
const checkedBoxes = document.querySelectorAll('.task-checkbox:checked');
checkedBoxes.forEach(function (checkbox) {
const recordId = checkbox.getAttribute('data-record-id');
if (recordId) {
selectedTasks.push(recordId); // Add to our list
}
});
// Make sure at least one task is selected
if (selectedTasks.length === 0) {
alert('Please select at least one task.');
return; // Stop here
}
// ===== CREATE THE POPUP MODAL =====
// Create dark overlay that covers the whole screen
const modal = document.createElement('div');
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.backgroundColor = 'rgba(0,0,0,0.5)'; // Semi-transparent black
modal.style.display = 'flex';
modal.style.alignItems = 'center'; // Center vertically
modal.style.justifyContent = 'center'; // Center horizontally
modal.style.zIndex = '9999'; // Put on top of everything
// Create the white popup box
const modalContent = document.createElement('div');
modalContent.style.backgroundColor = 'white';
modalContent.style.padding = '30px';
modalContent.style.borderRadius = '8px'; // Rounded corners
modalContent.style.maxWidth = '400px';
modalContent.style.width = '90%';
// Add heading showing how many tasks will be updated
const heading = document.createElement('h3');
heading.style.marginTop = '0';
heading.textContent = 'Update ' + selectedTasks.length + ' tasks';
// Create dropdown for status selection
const selectElement = document.createElement('select');
selectElement.id = 'status-select';
selectElement.style.width = '100%';
selectElement.style.padding = '10px';
selectElement.style.marginBottom = '20px';
selectElement.style.fontSize = '16px';
// Add default "choose one" option
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = '-- Select Status --';
selectElement.appendChild(defaultOption);
// Add all your status options to the dropdown
statusOptions.forEach(function (status) {
const option = document.createElement('option');
option.value = status.value; // What gets saved
option.textContent = status.label; // What user sees
selectElement.appendChild(option);
});
// Create container for buttons
const buttonContainer = document.createElement('div');
buttonContainer.style.textAlign = 'right';
// Create Cancel button
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.style.marginRight = '10px';
cancelBtn.style.padding = '8px 16px';
cancelBtn.style.cursor = 'pointer';
// Create Update button
const updateBtn = document.createElement('button');
updateBtn.textContent = 'Update';
updateBtn.style.padding = '8px 16px';
updateBtn.style.cursor = 'pointer';
// Put everything together
buttonContainer.appendChild(cancelBtn);
buttonContainer.appendChild(updateBtn);
modalContent.appendChild(heading);
modalContent.appendChild(selectElement);
modalContent.appendChild(buttonContainer);
modal.appendChild(modalContent);
document.body.appendChild(modal); // Show the modal
// Handle Cancel button - just close the modal
cancelBtn.addEventListener('click', function () {
document.body.removeChild(modal); // Remove the popup
});
// Handle Update button - this does the actual work
updateBtn.addEventListener('click', function () {
const selectedStatus = selectElement.value; // Get chosen status value
const selectedStatusLabel = selectElement.options[selectElement.selectedIndex].text; // Get display text
// Make sure a status was selected
if (!selectedStatus) {
alert('Please select a status.');
return;
}
// Close the modal
document.body.removeChild(modal);
// Track progress
let updateCount = 0; // How many succeeded
let errorCount = 0; // How many failed
const totalTasks = selectedTasks.length;
// ===== UPDATE EACH SELECTED RECORD =====
selectedTasks.forEach(function (recordId) {
// CUSTOMIZE THIS: Change 'field_32' to your status field key
const updateData = {
field_32: selectedStatus // The field to update and its new value
};
// Make the API call to update this record
// CUSTOMIZE THIS: Change 'scene_14' and 'view_28' to match your page/view
fetch('https://api.knack.com/v1/pages/scene_14/views/view_28/records/' + recordId, {
method: 'PUT', // PUT means "update"
headers: {
'X-Knack-Application-Id': appId, // Your app ID
'X-Knack-REST-API-Key': 'knack', // Authentication
'Content-Type': 'application/json' // We're sending JSON data
},
body: JSON.stringify(updateData) // Convert our data to JSON format
})
.then(function (response) {
if (!response.ok) {
throw new Error('Update failed');
}
return response.json();
})
.then(function () {
updateCount++; // Success! Increment counter
// Check if we're done with all updates
if (updateCount + errorCount === totalTasks) {
if (errorCount > 0) {
// Some failed - show both counts
alert(updateCount + ' updated, ' + errorCount + ' failed');
} else {
// All succeeded!
alert(totalTasks + ' tasks updated to ' + selectedStatusLabel);
}
location.reload(); // Refresh the page to show updates
}
})
.catch(function () {
errorCount++; // This one failed
// Check if we're done with all updates
if (updateCount + errorCount === totalTasks) {
alert(updateCount + ' updated, ' + errorCount + ' failed');
location.reload(); // Refresh the page
}
});
});
});
});
}, 500); // End of setTimeout
}); // End of Knack.on
}); // End of Knack.ready
How to Customize This Script
Step 1: Update the View Key
Find this line near the top:
Knack.on('records:render:view_XX', function (event) {
Replace view_28
with your actual view key.
Step 2: Update the Application ID
Find this line:
const appId = '[Application ID]';
Replace with your Application ID from Knack Settings.
Step 3: Update Status Options
Find this section:
const statusOptions = [
{ value: 'To Do', label: 'To Do' },
{ value: 'In Progress', label: 'In Progress' },
{ value: 'Review', label: 'Review' },
{ value: 'Completed', label: 'Completed' }
];
Change these to match your actual status values. The value
is what gets saved to the database, and label
is what users see.
Step 4: Update the Field Key
Find this line in the update section:
const updateData = {
field_XX: selectedStatus
};
Replace field_XX
with your actual status field key.
Step 5: Update the API URL
Find this line:
fetch('https://api.knack.com/v1/pages/scene_XX/views/view_XX/records/' + recordId, {
Replace scene_XX
with your scene key and view_XX
with your view key.
Testing Your Script
- Start Small: Test with just one or two records first.
- Check Console: Open browser Developer Tools (F12) to see any error messages.
- Verify Updates: After running, check that your records actually updated.
- Test Edge Cases: Try with no selection, try canceling, etc.
Troubleshooting Common Issues
"Table not found" in console
- Make sure your view key is correct.
- Ensure you're on the right page.
- Check that it's actually a table view (not a list or grid).
Updates aren't working
- Double-check your Application ID.
- Verify your field key is correct.
- Make sure the status values match exactly (including capitalization).
Checkboxes don't appear
- The script might be running too fast - try increasing the timeout from
500
to1000
. - Check that your table has records.
Some updates fail
- This usually means those records are locked or have validation rules.
- Check if those records have required fields that aren't being updated.
Safety Tips
- Always backup your data before testing bulk operations.
- Test in a development environment first if possible.
- Start with a small batch to make sure it works correctly.
- Keep the original code saved somewhere safe.
Need More Help?
If something isn't working:
- Check all your keys and IDs are correct.
- Look at the browser console for error messages.
- Try updating just one record manually first to ensure it's possible.
- Make sure you have permission to update these records in Knack.