Custom File Uploads
Unlike other form builders Formally allows for complete control of the files uploaded in forms, allowing you to send them to your own servers (bonus privacy!)
Other cloud form providers insist on hosting the files your users upload. We offer that feature, but we don't insist on it. If you want to keep the files to yourself that's fine. This concept is commonly associated with Data Sovereignty.
You'll need to set up your own file host with an upload API. Eg, an AWS S3 bucket using signed URLs, or any file host that you can upload files to via JavaScript). Setting up your own file host server and upload API is outside of the scope of this documentation.
Developers
In React the <Formally>
component can be given an onUpload
prop to
handle LiveUploads.
The function is async
and it is responsible for uploading the files
and returning an array of file upload successes or errors. LiveUploads
can allow multiple files to be uploaded in a single <input type="file">
so each file is an item in this array.
Because the function is async
you can await
the upload of each user file
to your custom file host. For performance reasons you might want to use
FormData
, await Promise.all
, or similar to simultaneously upload all
files at once.
While uploads are occuring you can optionally inform the user of upload progress
by repeatedly calling the setProgressRatio
function with a number between 0
and 1. 0 is 0% complete, and 1 is 100% complete. If there are multiple files
uploaded at once be sure to set a fraction of that ratio per file.
Once files are uploaded the function's response should be an array of either successes or errors:
For success:
export type LiveUploadFileResponseSuccess = {
type: 'success';
originalFilename: string;
key: string;
};
The key
is a unique file upload id (ie, an S3 key or equivalent).
And for errors:
export type LiveUploadFileResponseError = {
type: 'error';
localisedMessage: string;
};
This return value of an array of file upload successes or errors becomes
the field value submitted to the server when the user hits the submit
button. So the binary file data itself isn't submitted with the form,
but the success's key
string will identify the file.
File host server
As noted setting up your own file host server and upload API is outside of the scope of this documentation.
However there are some considerations that are worth mentioning.
LiveUpload files are uploaded immediately when a user chooses the file — long before a user submits the form. This is done to optimise for user bandwidth (uploading earlier means it will complete faster), and to give user feedback nearer to where they can fix any errors (on the same page of the form). It also means required fields can prevent page navigation until a file is provided.
File host upload garbage collection
It's possible for a user to repeatedly change their mind about the files they wish to upload. As such, they might upload more files than those that are eventually submitted in the LiveUpload value array of file upload successes and errors. To handle this any file host server should periodically 'garbage collect' files that meet these two conditions:
- are a few hours old, AND
- have
key
s that weren't in any form submission.
The amount of time ("a few hours old") should reflect a worst-case long duration of time for a user to complete a form, so feel free to instead choose any duration time you think is appropriate.
If a file host's key
isn't in any form submission then one of either
of these scenarions occurred:
- the user hasn't submit the form yet, or
- the user changed their mind and instead uploaded a different file.
Because file upload hosts can't distinguish between these scenarios the
necessity of the 2 conditions becomes clear. Garbage collect only those
files that are a few hours old and have an unknown key
.
File host server legal obligations
Operating any file host comes with legal obligations to takedown files that violate laws (copyright, abuse images, etc). Be sure your custom file host has staff to available to comply.
Use of onUpload
import { Formally } from 'formally';
<Formally
onUpload={async ({
inputChangeEvent,
// Can be called multiple times to indicate upload progress.
// Set the value between 0 and 1.
setProgressRatio,
abortControllerSignal, // See MDN abortControllerSignal
uploadNode,
}) => {
const { files } = inputChangeEvent.target;
if (!files) return [];
// If you were to upload files it might look like...
const formData = new FormData(); // See MDN FormData
Array.from(files).forEach((file, index) => {
formData.append(`file${index}`, file);
});
const response = await fetch(`https://example.com/api/upload`, {
method: 'POST',
body: formData,
});
const uploadResults = await response.json();
return uploadResults;
}}
/>;