Have you ever wanted to create a note and attach a file in HubSpot using Zapier? In this week’s...
Remove File Name IDs from Form File Uploads in HubSpot Using Workflows
How to Remove File Name IDs from Form File Uploads in HubSpot Using Workflows
This guide demonstrates how to streamline file uploads in HubSpot by removing unwanted IDs from file names from form submissions using a simple workflow. By integrating HubSpot's automation capabilities, you can enhance the efficiency of your customer service operations.
Introduction
When you upload files through HubSpot forms, a unique ID is often added to the beginning of the file name. This can clutter your records and complicate file management. This tutorial will show you how to automate the removal of these IDs, ensuring cleaner and more professional file handling.
Problem This Workflow Solves
The addition of random IDs to file names in HubSpot can lead to confusion and inefficiency in managing customer tickets and records. This workflow automates the renaming process, eliminating unnecessary clutter.
Tools Required
- HubSpot
- HubSpot Workflows
- Custom Code (optional for advanced control)
Workflow Summary
Upon form submission in HubSpot, a ticket is automatically created. This workflow intercepts the file upload process, removes unwanted ID prefixes from file names, and updates the ticket with the cleaned file name.
Step-by-Step Guide
- Submit Form: Start by submitting a form in HubSpot that includes a file upload.
- Create Ticket: Ensure the form submission automatically creates a ticket in your help desk system.
- Access File: Navigate to the newly created ticket to view the attached file with the unwanted ID.
- Set Up Workflow: Create a HubSpot workflow that triggers when a ticket is created and includes a file upload.
- Custom Code Step: Use HubSpot's custom code action to strip the unwanted ID from the file name. See code below.
- Update Ticket: Save the changes, and the ticket will now display the cleaned file name.
Common Use Cases
- Improving file management for customer support tickets.
- Ensuring consistent naming conventions for uploaded files.
- Reducing administrative overhead by automating file renaming.
Why This Improves Operations
By implementing this workflow, businesses can save time and reduce manual effort in managing customer tickets. It enhances client interactions by maintaining organized records and optimizes internal processes by eliminating redundant tasks.
For further questions or assistance, feel free to reach out. Bye for now.
Code for Custom Code Step
const axios = require("axios");
const FormData = require("form-data");
exports.main = async (event, callback) => {
const token = process.env.accessToken;
const ticketId = event.inputFields["hs_object_id"];
const headers = {
Authorization: `Bearer ${token}`
};
try {
if (!ticketId) {
throw new Error("Missing hs_object_id.");
}
const noteIds = await getAssociatedNoteIds(ticketId, headers);
if (!noteIds.length) {
return callback({
outputFields: {
success: true,
message: "No associated notes found.",
notesChecked: 0,
filesCleaned: 0
}
});
}
const notes = await batchReadNotes(noteIds, headers);
let filesChecked = 0;
let filesCleaned = 0;
let notesUpdated = 0;
const cleanedFileMappings = [];
for (const note of notes) {
const noteId = note.id;
const attachmentValue = note.properties?.hs_attachment_ids;
if (!attachmentValue) continue;
const originalAttachmentIds = extractFileIds(attachmentValue);
const updatedAttachmentIds = [];
let noteChanged = false;
for (const fileId of originalAttachmentIds) {
filesChecked++;
const fileMeta = await getFileMeta(fileId, headers);
const currentName = buildFileName(fileMeta.data, fileId);
const cleanedName = cleanHubSpotFormFilename(currentName);
if (cleanedName === currentName) {
updatedAttachmentIds.push(fileId);
continue;
}
const newFileId = await copyFileWithCleanName(
fileId,
cleanedName,
headers
);
updatedAttachmentIds.push(newFileId);
noteChanged = true;
filesCleaned++;
cleanedFileMappings.push(`${fileId} -> ${newFileId}`);
}
if (noteChanged) {
await updateNoteAttachments(noteId, updatedAttachmentIds, headers);
notesUpdated++;
}
}
callback({
outputFields: {
success: true,
message: "Attachment cleanup complete.",
notesChecked: notes.length,
notesUpdated,
filesChecked,
filesCleaned,
cleanedFileMappings: cleanedFileMappings.join("; ")
}
});
} catch (error) {
callback({
outputFields: {
success: false,
message:
error.response?.data?.message ||
JSON.stringify(error.response?.data) ||
error.message
}
});
}
};
async function getAssociatedNoteIds(ticketId, headers) {
const noteIds = [];
let after;
do {
const url = new URL(
`https://api.hubapi.com/crm/v4/objects/tickets/${ticketId}/associations/notes`
);
url.searchParams.set("limit", "500");
if (after) url.searchParams.set("after", after);
const res = await axios.get(url.toString(), { headers });
for (const result of res.data.results || []) {
if (result.toObjectId) {
noteIds.push(String(result.toObjectId));
}
}
after = res.data.paging?.next?.after;
} while (after);
return [...new Set(noteIds)];
}
async function batchReadNotes(noteIds, headers) {
const notes = [];
for (let i = 0; i < noteIds.length; i += 100) {
const batch = noteIds.slice(i, i + 100);
const res = await axios.post(
"https://api.hubapi.com/crm/v3/objects/notes/batch/read",
{
properties: ["hs_attachment_ids"],
inputs: batch.map(id => ({ id }))
},
{
headers: {
...headers,
"Content-Type": "application/json"
}
}
);
notes.push(...(res.data.results || []));
}
return notes;
}
async function getFileMeta(fileId, headers) {
return axios.get(`https://api.hubapi.com/files/v3/files/${fileId}`, {
headers
});
}
function buildFileName(fileData, fileId) {
const baseName =
fileData.name ||
fileData.path?.split("/").pop() ||
`file-${fileId}`;
const extension = fileData.extension
? `.${String(fileData.extension).replace(/^\./, "")}`
: "";
if (extension && !baseName.toLowerCase().endsWith(extension.toLowerCase())) {
return `${baseName}${extension}`;
}
return baseName;
}
async function copyFileWithCleanName(fileId, cleanedName, headers) {
const signedUrlRes = await axios.get(
`https://api.hubapi.com/files/v3/files/${fileId}/signed-url`,
{ headers }
);
const downloadRes = await axios.get(signedUrlRes.data.url, {
responseType: "arraybuffer"
});
const fileBuffer = Buffer.from(downloadRes.data);
const form = new FormData();
form.append("file", fileBuffer, cleanedName);
form.append("fileName", cleanedName);
form.append("folderPath", "/Cleaned Ticket Uploads");
form.append(
"options",
JSON.stringify({
access: "PRIVATE"
})
);
const uploadRes = await axios.post(
"https://api.hubapi.com/files/v3/files",
form,
{
headers: {
...headers,
...form.getHeaders()
}
}
);
return String(uploadRes.data.id);
}
async function updateNoteAttachments(noteId, fileIds, headers) {
return axios.patch(
`https://api.hubapi.com/crm/v3/objects/notes/${noteId}`,
{
properties: {
hs_attachment_ids: fileIds.join(";")
}
},
{
headers: {
...headers,
"Content-Type": "application/json"
}
}
);
}
function extractFileIds(value) {
return String(value)
.split(/[;,\n]/)
.map(v => v.trim())
.filter(Boolean)
.map(v => {
const match = v.match(/(\d{6,})/);
return match ? match[1] : null;
})
.filter(Boolean);
}
function cleanHubSpotFormFilename(filename) {
return String(filename).replace(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-[A-Z]+\.[^-]+-/i,
""
);
}