In a backend plugin that creates a file somehow i end up creating both the data type and data id in the Attached to field. Anyone who knows where i find some documentation
if (thingToConnect && typeof thingToConnect === "object") {
// Case 1: The pointer structure
if (thingToConnect._pointer &&
thingToConnect._pointer._id &&
thingToConnect._pointer._type) {
attachType = thingToConnect._pointer._type;
attachID = thingToConnect._pointer._id;
// Case 2: The direct { datatype, unique_id } structure
} else if (thingToConnect.datatype && thingToConnect.unique_id) {
attachType = thingToConnect.datatype;
attachID = thingToConnect.unique_id;
}
if (attachType && attachType.startsWith("custom.")) {
attachType = attachType.replace(/^custom\./, "");
if (attachType && attachID) {
form.append("attach_to", `${attachType},${attachID}`);
form.append("private", "true");
console.log(`Attaching to: ${attachType}, ${attachID}`);
} else {
console.warn("No valid Thing detected. The file will be uploaded publicly.");
}

your plugin should return a file url with after you uploaded the file with your plugin. This is what you need to use and do an action like make change to a thing to link the file with the item.
The plugin is creating a file server-side (.zip). After creating the zip the following steps are performed:
* 7) DETERMINE ATTACH_TO PARAMETERS (Data Type & ID)
*************************************************************/
let attachType = null;
let attachID = null;
// Check if the Thing exists and has a valid _pointer structure
if (thingToConnect && typeof thingToConnect === "object" && thingToConnect._pointer) {
if (thingToConnect._pointer._id && thingToConnect._pointer._type) {
attachID = thingToConnect._pointer._id;
attachType = thingToConnect._pointer._type;
}
}
// Ensure that attachType is properly formatted (remove "custom." if present)
if (attachType && attachType.startsWith("custom.")) {
attachType = attachType.replace(/^custom\./, "");
}
/*************************************************************
* 8) DOWNLOAD ALL FILES AND GENERATE THE ZIP
*************************************************************/
const allPromises = addToZip(zip, folderData);
return Promise.all(allPromises)
.then(() => {
// When all downloads finish, generate the ZIP buffer
return zip.generateAsync({ type: "nodebuffer" });
})
.then(buffer => {
/***********************************************************
* 9) UPLOAD THE ZIP TO BUBBLE'S FILEUPLOAD ENDPOINT
***********************************************************/
const form = new FormData();
form.append("file", buffer, {
filename: outputFileName,
contentType: "application/zip"
});
// Attach to Bubble "Thing" if valid
if (attachType && attachID) {
form.append("attach_to", `${attachType},${attachID}`);
form.append("private", "true");
console.log(`đź”— Attaching to: ${attachType}, ${attachID}`);
} else {
console.warn("⚠️ No valid Thing detected. File will be uploaded publicly.");
}
// Perform the POST request to fileUploadURL
return axios.post(fileUploadURL, form, {
headers: form.getHeaders()
});
})
.then(response => {
/***********************************************************
* 10) PARSE THE RESPONSE AND RETURN THE FINAL FILE URL
***********************************************************/
const data = response.data;
let fileUrl;
// Bubble can return the URL as a string or as { url: ... }
if (typeof data === "string") {
fileUrl = data.trim();
} else if (data && data.url) {
fileUrl = data.url;
} else {
throw new Error("File upload failed. Unexpected response: " + JSON.stringify(data));
}
// Bubble might return protocol-relative URLs
if (fileUrl.startsWith("//")) {
fileUrl = "https:" + fileUrl;
}
// Return data so Bubble can store or display it
return {
zip_file_url: fileUrl, // text field
zip_file: fileUrl // file field
};
})
.catch(err => {
// Any error in the chain is caught here
throw new Error("Error generating ZIP file: " + err.message);
});
}
After the file is created… you need to upload the file to bubble storage. From your code, you seem to have this part. This step will return an url. you need to use the return function and add a field to store the url that could be use in step after this plugin action (result of step X’s file url for example)
Take note that, if there’s no server side zip plugin actually on plugin marketplace, this because there’s a good reason: You will be limited to very small zip file on server side.
thanks for looking into this. The file is uploaded to storage (and private) as part of the flow (works well). The files are fairly small (containing data) and are used in e-mail reporting so front-end is not an option.