A self-hosted web app that lets you upload files to a folder in your own Google Drive, manage them, get shareable links, and bulk-change sharing permissions.
This button starts the safe setup flow. It opens a new Google Apps Script project in your Google account. Google still requires you to paste the three files below and manually deploy the web app so you can confirm Execute as: User accessing the web app.
A truly automatic βcreate project + add files + deploy web appβ installer requires a separate OAuth app using the Apps Script API. This simple page avoids asking users for broad project-management permissions.
Web Uploads in your Google Drive, and URLs appear in a box.You need three files in the Apps Script project: Code.gs, index.html, and instructions.html. Each code area below is capped with its own scrollbar so the page does not get huge.
function doGet(e) {
// If ?page=instructions, show instructions page; else show file manager
if (e && e.parameter && e.parameter.page === 'instructions') {
return HtmlService.createHtmlOutputFromFile('instructions')
.setTitle('Drive File Manager β Instructions')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('Drive File Manager')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
// Add this somewhere among your other functions
function getScriptUrl() {
return ScriptApp.getService().getUrl();
}
// --- The rest of the server-side functions stay exactly the same ---
function getFolder_() {
const folderName = 'Web Uploads';
const folders = DriveApp.getFoldersByName(folderName);
if (folders.hasNext()) {
return folders.next();
} else {
return DriveApp.createFolder(folderName);
}
}
function processUploads(files, makePublic) {
const folder = getFolder_();
const results = [];
files.forEach(file => {
const parts = file.data.split(',');
const mimeType = parts[0].match(/:(.*?);/)[1];
const blob = Utilities.newBlob(Utilities.base64Decode(parts[1]), mimeType, file.name);
const driveFile = folder.createFile(blob);
if (makePublic) {
driveFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
results.push(`https://drive.google.com/file/d/${driveFile.getId()}/view?usp=sharing`);
} else {
results.push(driveFile.getUrl());
}
});
return results;
}
function getFileList() {
const folder = getFolder_();
const files = folder.getFiles();
const list = [];
while (files.hasNext()) {
const file = files.next();
list.push({
id: file.getId(),
name: file.getName(),
mimeType: file.getMimeType(),
size: file.getSize(),
lastUpdated: file.getLastUpdated().toISOString(),
url: file.getUrl()
});
}
return list;
}
function deleteFiles(ids) {
ids.forEach(id => {
try { DriveApp.getFileById(id).setTrashed(true); } catch (e) {}
});
}
function changeSharing(ids, makePublic) {
ids.forEach(id => {
try {
const file = DriveApp.getFileById(id);
if (makePublic) {
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
} else {
file.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.NONE);
}
} catch (e) {}
});
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 20px auto; padding: 0 15px; }
button, .btn { padding: 8px 16px; margin: 5px; cursor: pointer; }
.hidden { display: none; }
#urlBox { width: 100%; height: 100px; margin-top: 10px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { padding: 8px 12px; border: 1px solid #ddd; text-align: left; }
th { background: #f4f4f4; cursor: pointer; user-select: none; }
.sort-indicator::after { content: ' β'; }
.sort-asc::after { content: ' β'; }
.sort-desc::after { content: ' β'; }
.progress { margin: 10px 0; font-style: italic; }
#linkSection { margin-top: 20px; background: #f9f9f9; padding: 15px; border-radius: 5px; }
</style>
</head>
<body>
<h2>π Drive File Manager</h2>
<!-- Upload Section -->
<div>
<input type="file" id="fileInput" multiple class="hidden" accept="*/*" />
<button onclick="document.getElementById('fileInput').click()" class="btn">π€ Select Files</button>
<div id="uploadProgress" class="progress"></div>
</div>
<!-- Link display section -->
<div id="linkSection" class="hidden">
<h4>Uploaded File Links (copy all):</h4>
<textarea id="urlBox" readonly></textarea>
<br>
<button onclick="copyLinks()">π Copy Links</button>
<button onclick="document.getElementById('linkSection').classList.add('hidden')">β Close</button>
</div>
<!-- File Manager Table -->
<h3>π Files in Folder</h3>
<button onclick="deleteSelected()" class="btn" style="background:#ff4d4d; color:white;">π Delete Selected</button>
<button onclick="copySelectedUrls()" class="btn">π Copy URLs of Selected</button>
<button onclick="changeSharingSelected()" class="btn">π Change Sharing of Selected</button>
<table id="fileTable">
<thead>
<tr>
<th style="width:30px;"><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
<th data-sort="name" class="sort-indicator">Name</th>
<th data-sort="mimeType" class="sort-indicator">Type</th>
<th data-sort="size" class="sort-indicator">Size</th>
<th data-sort="lastUpdated" class="sort-indicator">Last Modified</th>
</tr>
</thead>
<tbody id="fileList"></tbody>
</table>
<script>
let filesData = []; // Full file list from server
let sortColumn = 'name';
let sortAsc = true;
// Fetch and render file list on load
window.onload = () => refreshFileList();
// ========== Upload Logic ==========
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', handleFileSelect);
function handleFileSelect(event) {
const files = event.target.files;
if (!files.length) return;
const makePublic = confirm('Do you want to make all uploaded files publicly viewable?');
const progressDiv = document.getElementById('uploadProgress');
progressDiv.textContent = 'Reading files...';
const readers = [];
for (let i = 0; i < files.length; i++) {
readers.push(readFileAsDataURL(files[i]));
}
Promise.all(readers)
.then(dataUrls => {
const uploadPayload = dataUrls.map((data, idx) => ({
name: files[idx].name,
data: data
}));
progressDiv.textContent = 'Uploading to Drive...';
return google.script.run.withSuccessHandler(uploadDone).processUploads(uploadPayload, makePublic);
})
.catch(err => {
progressDiv.textContent = 'Error: ' + err;
});
}
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => resolve(e.target.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function uploadDone(links) {
document.getElementById('uploadProgress').textContent = '';
fileInput.value = '';
if (links.length > 0) {
document.getElementById('urlBox').value = links.join('\n');
document.getElementById('linkSection').classList.remove('hidden');
}
refreshFileList();
}
function copyLinks() {
const urlBox = document.getElementById('urlBox');
urlBox.select();
document.execCommand('copy');
alert('Links copied to clipboard!');
}
// ========== File Manager Logic ==========
function refreshFileList() {
google.script.run.withSuccessHandler(renderTable).getFileList();
}
function renderTable(data) {
filesData = data;
sortFiles();
const tbody = document.getElementById('fileList');
tbody.innerHTML = '';
filesData.forEach(file => {
const row = tbody.insertRow();
row.innerHTML = `
<td><input type="checkbox" data-id="${file.id}" class="fileCheck"></td>
<td><a href="${file.url}" target="_blank">${escapeHtml(file.name)}</a></td>
<td>${file.mimeType}</td>
<td>${formatBytes(file.size)}</td>
<td>${new Date(file.lastUpdated).toLocaleString()}</td>
`;
});
}
// Sorting
document.querySelectorAll('th[data-sort]').forEach(th => {
th.addEventListener('click', () => {
const col = th.dataset.sort;
if (sortColumn === col) {
sortAsc = !sortAsc;
} else {
sortColumn = col;
sortAsc = true;
}
document.querySelectorAll('th').forEach(h => h.className = '');
th.classList.add(sortAsc ? 'sort-asc' : 'sort-desc');
sortFiles();
renderTable(filesData);
});
});
function sortFiles() {
filesData.sort((a, b) => {
let valA = a[sortColumn], valB = b[sortColumn];
if (sortColumn === 'size') {
valA = Number(valA);
valB = Number(valB);
} else if (sortColumn === 'lastUpdated') {
valA = new Date(valA).getTime();
valB = new Date(valB).getTime();
} else {
valA = valA.toString().toLowerCase();
valB = valB.toString().toLowerCase();
}
if (valA < valB) return sortAsc ? -1 : 1;
if (valA > valB) return sortAsc ? 1 : -1;
return 0;
});
}
// Bulk delete
function deleteSelected() {
const checked = document.querySelectorAll('.fileCheck:checked');
if (!checked.length) {
alert('No files selected.');
return;
}
if (!confirm(`Delete ${checked.length} file(s)? They will be moved to Trash.`)) return;
const ids = Array.from(checked).map(cb => cb.dataset.id);
google.script.run.withSuccessHandler(() => {
refreshFileList();
document.getElementById('selectAll').checked = false;
}).deleteFiles(ids);
}
// Copy URLs of selected files
function copySelectedUrls() {
const checked = document.querySelectorAll('.fileCheck:checked');
if (!checked.length) {
alert('No files selected.');
return;
}
// Gather URLs from filesData based on selected IDs
const selectedIds = new Set(Array.from(checked).map(cb => cb.dataset.id));
const urls = filesData
.filter(file => selectedIds.has(file.id))
.map(file => file.url);
if (urls.length === 0) {
alert('No URLs found.');
return;
}
// Create a temporary textarea to copy
const textarea = document.createElement('textarea');
textarea.value = urls.join('\n');
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert(`Copied ${urls.length} URL(s) to clipboard!`);
}
// Change sharing of selected files
function changeSharingSelected() {
const checked = document.querySelectorAll('.fileCheck:checked');
if (!checked.length) {
alert('No files selected.');
return;
}
const action = confirm('Click OK to make these files PUBLIC (anyone with link can view),\nCancel to make them PRIVATE.');
const ids = Array.from(checked).map(cb => cb.dataset.id);
google.script.run.withSuccessHandler(() => {
refreshFileList();
alert(`Sharing updated for ${ids.length} file(s).`);
}).changeSharing(ids, action); // true = public, false = private
}
function toggleAll(checkbox) {
document.querySelectorAll('.fileCheck').forEach(cb => cb.checked = checkbox.checked);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
function formatBytes(bytes, decimals = 2) {
if (!+bytes) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
</script>
</body>
</html>
Code.gs content above and paste it into the default Code.gs file.File > New > HTML file, name it index. Paste the index.html code above.instructions. Paste the instructions.html code above.Now upload the files to your Drive, manage them there, and get shareable URLs to send to DeepSeek. Tell DeepSeek something like βcheck these URLs,β not βview these uploaded files.β The wording matters.
Click the button below to launch the file manager directly. You are on the instructions page right now.
Made for transparency β share the code, deploy your own, stay safe.