Build an external file-request intake system
Collect uploads from outside people into a Northwind intake folder via a web app.
Published Aug 17, 2025
Northwind regularly needs files from people who do not have a Google account — a freelance photographer’s raw exports, a client’s brand assets, a contractor’s invoice. The usual options are all awkward: email attachments get lost, “share this folder with me” needs an account, and third-party transfer tools are one more login to manage.
This script turns Apps Script into a tiny upload endpoint. It deploys as a web
app that accepts a POST request carrying a base64-encoded file, decodes it, and
drops it straight into a single Northwind intake folder. Anyone with the URL can
send a file — no account, no shared permissions — and everything lands in one
place for you to sort.
What you’ll need
- An intake folder in Drive where uploads should land. Keep it separate from client folders so you can review everything before it gets filed properly.
- The folder’s ID — the string in its URL after
/folders/. - A deployed web app. You create the deployment once from the Apps Script editor; the steps are under “Deploy it” below.
- Whoever sends files needs only the deployment URL and a way to make an HTTP
request —
curl, a form, or a script on their side.
The script
// The Drive folder every upload lands in. Review it before filing.
const INTAKE_FOLDER_ID = '1abcIntakeFolderId';
// Default MIME type when the caller does not declare one.
const DEFAULT_MIME = 'application/octet-stream';
// Reject anything larger than this. Apps Script's own limits are
// generous, but a cap stops one huge upload from blocking the folder.
const MAX_BYTES = 25 * 1024 * 1024; // 25 MB
/**
* Web-app entry point. Accepts a JSON POST with a base64-encoded file,
* writes it into the intake folder, and returns the new file's ID.
* Deploy this script as a web app for the endpoint to exist.
*/
function doPost(e) {
// 1. Parse the request body, tolerating a missing or empty payload.
const data = e && e.postData ? JSON.parse(e.postData.contents) : {};
// 2. Reject requests missing the two required fields.
if (!data.name || !data.contents) {
return jsonResponse({ error: 'missing fields: name and contents are required' });
}
// 3. Decode the base64 payload into raw bytes.
const bytes = Utilities.base64Decode(data.contents);
// 4. Enforce the size cap before touching Drive.
if (bytes.length > MAX_BYTES) {
return jsonResponse({ error: 'file too large; limit is ' + MAX_BYTES + ' bytes' });
}
// 5. Wrap the bytes in a blob and write it to the intake folder.
const blob = Utilities.newBlob(bytes, data.mimeType || DEFAULT_MIME, data.name);
const file = DriveApp.getFolderById(INTAKE_FOLDER_ID).createFile(blob);
// 6. Hand the caller the new file's ID so they can confirm receipt.
return jsonResponse({ fileId: file.getId(), name: file.getName() });
}
/**
* Wraps an object as a JSON HTTP response. Keeps every return path
* in doPost consistent.
*/
function jsonResponse(obj) {
return ContentService.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
How it works
doPostruns whenever someone sends aPOSTto the deployment URL. It readse.postData.contents— the raw request body — and parses it as JSON, falling back to an empty object if the body is missing.- It checks for the two required fields,
nameandcontents. If either is absent it returns an error object rather than throwing, so the caller gets a readable response instead of a generic 500. - It decodes
contentsfrom base64 into a byte array. - It checks the byte count against
MAX_BYTESbefore writing anything, so an oversized upload is rejected cheaply. - It wraps the bytes in a blob — using the caller’s
mimeTypeorDEFAULT_MIME— and creates the file insideINTAKE_FOLDER_ID. - It returns the new file’s ID and name as JSON, which lets the sender confirm the upload succeeded.
Example run
A photographer sends a PDF with curl:
curl -X POST "$WEBAPP_URL" \
-H 'Content-Type: application/json' \
-d "{\"name\": \"brief.pdf\", \"mimeType\": \"application/pdf\", \"contents\": \"$(base64 brief.pdf)\"}"
The endpoint replies with the stored file’s ID:
{ "fileId": "1AbCdEfGhIjK…", "name": "brief.pdf" }
And brief.pdf now sits in the Northwind intake folder, ready to be reviewed and
filed. A request missing a field comes back just as clearly:
{ "error": "missing fields: name and contents are required" }
Deploy it
The endpoint only exists once the script is deployed as a web app:
- In the Apps Script editor, click Deploy → New deployment.
- Choose type Web app.
- Set Execute as to yourself, so files are created under your Drive.
- Set Who has access to Anyone — external senders have no Google account, so anonymous access is required.
- Click Deploy, approve the authorisation prompt, and copy the web-app URL. That URL is what you hand to anyone who needs to send you a file.
Re-deploy (Manage deployments → Edit) whenever you change the code, or the live endpoint keeps running the old version.
Watch out for
- An Anyone deployment accepts uploads from anyone who learns the URL. Treat the URL as a shared secret, and never run downstream processing on an upload without checking it first — that is what the separate intake folder is for.
- The script trusts the caller’s
name. Someone could send a file named to look like an existing one. Review the intake folder before moving anything into a client folder, and rename on the way. - Base64 inflates a file by roughly a third on the wire, and the whole request
is held in memory. Combined with the script’s quotas, that makes
MAX_BYTESa real ceiling — large media is better handled with a resumable upload to Drive directly. doPostonly fires onPOST. Opening the URL in a browser sends aGETand will show an error; that is expected, not a broken deployment.- There is no virus scanning here. Files an outside party uploads should be reviewed by a person before anyone at Northwind opens them.
Related
Build a recurring file-delivery system
Drop a fresh report file into a Northwind client folder weekly — they don't even ask.
Updated Dec 15, 2025
Build a Drive search index in Sheets
Make Northwind's file metadata searchable in a Sheet — like Spotlight for Drive.
Updated Dec 7, 2025
Build a shared-folder onboarding kit
Auto-grant new Northwind hires the folders they need on day one.
Updated Nov 29, 2025
Route saved email attachments to project folders
File Gmail attachments into the right Northwind client folder based on subject keywords.
Updated Nov 25, 2025
Bundle a folder of images into one PDF
Combine Northwind scans into a single deliverable PDF using a generation service.
Updated Nov 17, 2025