How to add file attachments to custom forms in HubSpot?

I would like to explain our current setup and the issue we are facing regarding the attachment functionality.

- Current Implementation

We are using a custom BigCommerce form integrated with HubSpot via the Forms API.
On form submission:

  • Contact details (Name, Email, Company, Phone, etc.) are sent to HubSpot
  • Product summary and enquiry details are captured correctly
  • Two email workflows are configured:
    1. Email to the user (submitter)
    2. Notification email to the owner/internal team

Form submission and email triggering are working, although we are currently addressing deliverability (spam) via domain authentication.

- Issue – Attachment Functionality

We have added a file attachment field (attach_a_file) in HubSpot and are attempting to pass attachment data from our custom form. However, we are facing the following issues:

  • The file is not being properly stored in HubSpot
  • The attachment link shows the error:

“This doesn’t appear to reference a file in your HubSpot account. It may have been edited or removed.”

- Expected Requirement

Our requirement is:

  • The user should be able to upload a file through the form
  • The file should be correctly stored in HubSpot
  • The attachment (or at least a valid file link) should be included in the emails sent to both the user and the owner

Understanding So Far

Based on our understanding:

  • HubSpot Forms API does not support direct file upload from frontend
  • Files must first be uploaded via the HubSpot Files API
  • Then the returned file URL should be passed in the form submission (attach_a_file)

- Clarification Required

Could you please confirm:

  1. Whether a backend service is mandatory to upload files via the Files API and pass the file URL
  2. If there is any alternative way to support attachments with a custom form without backend
  3. How we can ensure that the attachment (or file link) is included correctly in the emails sent to the user and owner

Looking forward to your guidance on completing the attachment functionality end-to-end.

Thanks!

Why the error occurs:

The error “This doesn’t appear to reference a file in your HubSpot account”
happens because the Forms API only accepts plain text strings for standard
custom fields [1.1.2]. If you attempt to send a raw binary file stream, a local
file path, or an unverified temporary file ID directly in your form submission
payload, HubSpot’s automated email personalization tokens will fail to resolve
the link [1.1.2].

  • Direct Answers to Your Clarifications

Q1: Is a backend service mandatory to upload files via the Files API?

Yes. The HubSpot Files API requires your Private App Access Token (Bearer Token)
for authentication. You must never expose this private token in frontend
client-side JavaScript (like BigCommerce stencil files), as doing so would allow
any visitor to inspect your code, steal your token, and access/delete your
entire portal’s data. A secure backend service (such as a serverless function,
Node.js, or PHP backend) is mandatory to securely proxy the file upload [1.1.3].

Q2: Is there an alternative way to support custom form attachments without a backend?

If you absolutely cannot run a backend proxy, the only secure alternative is to
embed a native HubSpot Form on your BigCommerce page rather than using a fully
custom HTML form [1.1.2]. A native HubSpot form has a built-in File Upload field
that securely handles the frontend upload directly to HubSpot’s secure storage
automatically [1.1.2].

Q3: How do we ensure the attachment link is correctly included in emails?

Once your backend uploads the file to the Files API, the API returns a JSON
response containing the public URL of the file [1.1.2]. You must pass this
returned URL string into your custom contact/ticket property (e.g.,
attach_a_file) in your Forms API payload [1.1.2]. In your HubSpot Email or
Workflow notification, you simply insert this custom property as a
Personalization Token, which dynamically renders as a clickable download link
for both the owner and the user [1.1.2].

  1. Step-by-Step Technical Implementation Roadmap

Step 1: Upload the file to the HubSpot Files API (Server-Side)

Send a multipart/form-data POST request to the Files API v3 endpoint [1.1.2]:

  • Endpoint: POST https://api.hubapi.com/files/v3/files [1.1.2]
  • Authorization: Bearer YOUR_PRIVATE_APP_ACCESS_TOKEN [1.1.3]
  • Form Parameters:
    • file: (The binary file from your BigCommerce form)
    • folderPath: (e.g., /form_attachments)
    • options: { “access”: “PUBLIC_NOT_INDEXABLE” } (This ensures the client
      can download it via the email, but search engine bots won’t index their
      private files on Google) [1.1.2].

Step 2: Extract the File URL

The Files API will return a JSON response containing the file metadata. Extract
the url property:

{
“id”: “12345678”,
“url”: “https://yourportal.hs-sites.com/hubfs/form_attachments/user_file.pdf”,
“name”: “user_file.pdf”
}

Step 3: Submit the Form Payload (Forms API v3)

Submit your contact fields to the Submit Form Submission endpoint, passing the
extracted file URL into your custom text property [1.1.2]:

{
“fields”: [
{
“name”: “email”,
“value”: “user@example.com
},
{
“name”: “firstname”,
“value”: “John”
},
{
“name”: “attach_a_file”,
“value”: “https://yourportal.hs-sites.com/hubfs/form_attachments/user_file.pdf
}
]
}

  1. Sample Node.js Backend Implementation (Express)

Here is a clean, modern Node.js controller example demonstrating how to combine
both API requests in a single, secure backend workflow [1.3.1]:

const express = require(‘express’);
const multer = require(‘multer’);
const axios = require(‘axios’);
const FormData = require(‘form-data’);

const app = express();
const upload = multer(); // Handle file uploads in-memory

const HUBSPOT_ACCESS_TOKEN = process.env.HUBSPOT_ACCESS_TOKEN;
const PORTAL_ID = ‘YOUR_PORTAL_ID’;
const FORM_ID = ‘YOUR_FORM_ID’;

app.post(‘/api/submit-form’, upload.single(‘user_file’), async (req, res) => {
try {
let fileUrl = ‘’;

// 1. If a file is uploaded, send it to HubSpot Files API first
if (req.file) {
  const fileForm = new FormData();
  fileForm.append('file', req.file.buffer, req.file.originalname);
  fileForm.append('folderPath', '/form-attachments');
  
  const fileOptions = {
    access: 'PUBLIC_NOT_INDEXABLE' // Safe for emails, hidden from Google
  };
  fileForm.append('options', JSON.stringify(fileOptions));

  const fileResponse = await axios.post('https://api.hubapi.com/files/v3/files', fileForm, {
    headers: {
      ...fileForm.getHeaders(),
      'Authorization': `Bearer ${HUBSPOT_ACCESS_TOKEN}`
    }
  });
  
  fileUrl = fileResponse.data.url; // Extract the public file URL
}

// 2. Submit the contact data and file URL to the Forms API
const formData = {
  fields: [
    { name: 'email', value: req.body.email },
    { name: 'firstname', value: req.body.firstname },
    { name: 'attach_a_file', value: fileUrl } // Pass the file URL as text
  ]
};

await axios.post(`https://api.hubapi.com/submissions/v3/integration/submit/${PORTAL_ID}/${FORM_ID}`, formData, {
  headers: { 'Content-Type': 'application/json' }
});

return res.status(200).json({ success: true, message: 'Form submitted successfully!' });

} catch (error) {
console.error(‘Integration Error:’, error.response ? error.response.data : error.message);
return res.status(500).json({ success: false, error: ‘Internal Server Error’ });
}
});

Official HubSpot Resources:

  • Review file security, folder configurations, and response schemas in the
    HubSpot Files API v3 Guide- Schemas API guide - HubSpot docs
  • Understand submission parameters and GDPR consent tracking in the HubSpot
    Submit Form Submission API Reference- Form submission v3