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.
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.
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.
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.
Common Use Cases
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.
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,
""
);
}