πŸ“ Google Drive File Manager

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.

πŸ”’ Privacy & Security: When deployed correctly with Execute as: User accessing the web app, each person uses their own Google account. Their files go into their own Drive, not the template owner's Drive.
⚠️ Critical deployment setting: This app must be deployed with Execute as: User accessing the web app. If someone accidentally deploys it as Execute as: Me, Drive actions can run under the deployer's Google account instead of each user's account. That can let other people upload files into the deployer's Drive folder, view/manage files listed by the app, move selected files to Trash, or change sharing settings for files the script can access.

βž• Add to Your Google Account

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.

βž• Create Apps Script Project πŸ“‹ Jump to Copy Buttons

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.

πŸš€ How It Works

  1. Upload – Choose multiple files. A popup asks if you want to make them public. Files go into a folder called Web Uploads in your Google Drive, and URLs appear in a box.
  2. File Manager – Lists all files inside that folder. Click column headers to sort. Use checkboxes to select files.
  3. Bulk Actions with selected files:

πŸ“‹ Complete Code

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.

πŸ“„ Code.gs

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) {}
  });
}

πŸ“„ index.html

<!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>

πŸ“„ instructions.html

πŸ› οΈ How to Deploy Your Own

1. Click Create Apps Script Project above, or go to script.google.com and create a new project.
2. Copy the Code.gs content above and paste it into the default Code.gs file.
3. Create a new HTML file: File > New > HTML file, name it index. Paste the index.html code above.
4. Create another HTML file, name it instructions. Paste the instructions.html code above.
5. Click Deploy > New deployment, choose Web app.
6. Set Execute as: User accessing the web app. Do not choose β€œExecute as me” unless you understand that all Drive actions will run under your Google account.
7. Set Who has access to your preferred audience.
8. Before clicking Deploy, confirm again that Execute as says User accessing the web app.
9. Click Deploy and copy the web app URL.

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.

πŸ”— Try the App

Click the button below to launch the file manager directly. You are on the instructions page right now.

⚠️ Never trust a script you didn't deploy yourself. If you are viewing this on someone else's deployment, you are about to grant that script access to your Drive. Always deploy your own copy from the code above, and make sure your deployment uses Execute as: User accessing the web app.

Made for transparency – share the code, deploy your own, stay safe.