How to Build a Website Thumbnail List with Google Apps Script in India
Build a responsive website gallery from Google Drive with Apps Script. This guide covers runnable code, missing thumbnails, deployment permissions, quotas, and troubleshooting.
Build the page with Apps Script HTML Service, read image files from a Drive folder on the server, convert available thumbnails into data URLs, and render them in the browser. The server-to-browser bridge is asynchronous, so include both success and failure handlers and a placeholder for files without thumbnails. In India, the implementation is the same; the relevant regional considerations are your Workspace data-region policy and the access rules for the Drive files.
This tutorial uses the built-in Drive service and an Apps Script web app. It shows a complete example that returns serializable values to the page. Test it against the actual folder and audience before sharing it: Drive permissions are determined by the web-app deployment identity.
1. What you will build
The browser loads an HTML page served by Apps Script. Its JavaScript calls a server-side function through google.script.run. That function lists image files in one Drive folder, reads each available thumbnail, converts the thumbnail bytes to a Base64 data URL, and returns a list of names, IDs, MIME types, and image URLs. The page then renders a responsive grid.
- Create a Drive folder for the images and copy its folder ID from the URL.
- Create an Apps Script project with
Code.gsandIndex.html. - Set the folder ID in the server code and run the server function once in the editor to authorize access.
- Deploy as a web app with an execution identity and access audience that match your sharing policy.
- Test the deployed page while signed in as a representative visitor.
2. Complete Apps Script implementation
In Code.gs, replace PASTE_FOLDER_ID_HERE with the ID of the Drive folder. The example only lists image files, sorts them by name, caps each response at 100 entries, and handles files whose thumbnail is absent. The cap limits the size and work of one response; it is not a Google platform limit.
const IMAGE_FOLDER_ID = 'PASTE_FOLDER_ID_HERE';
const MAX_ITEMS_PER_LOAD = 100;
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('Drive image gallery');
}
function getImageItems() {
if (!IMAGE_FOLDER_ID || IMAGE_FOLDER_ID === 'PASTE_FOLDER_ID_HERE') {
throw new Error('Set IMAGE_FOLDER_ID in Code.gs before loading the gallery.');
}
const folder = DriveApp.getFolderById(IMAGE_FOLDER_ID);
const files = folder.getFiles();
const items = [];
while (files.hasNext() && items.length < MAX_ITEMS_PER_LOAD) {
const file = files.next();
const mimeType = file.getMimeType();
if (!mimeType || !mimeType.startsWith('image/')) continue;
const thumbnail = file.getThumbnail();
let dataUrl = '';
if (thumbnail) {
const contentType = thumbnail.getContentType() || mimeType;
const encoded = Utilities.base64Encode(thumbnail.getBytes());
dataUrl = 'data:' + contentType + ';base64,' + encoded;
}
items.push({
id: file.getId(),
name: file.getName(),
mimeType: mimeType,
thumbnailDataUrl: dataUrl
});
}
items.sort((a, b) => a.name.localeCompare(b.name));
return items;
}
In Index.html, add the following page. The callbacks show loading, empty, and error states. Text and attribute values are inserted with DOM properties rather than interpolated into HTML, so filenames are not treated as markup.
<!doctype html>
<html>
<head>
<base target="_top">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 0 auto; max-width: 1100px; padding: 24px; }
h1 { margin-top: 0; }
#status { min-height: 1.5em; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px; }
.card { border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
.preview { align-items: center; aspect-ratio: 4 / 3; background: #f2f3f5; display: flex; justify-content: center; }
.preview img { height: 100%; object-fit: contain; width: 100%; }
.missing { color: #555; padding: 12px; text-align: center; }
.name { overflow-wrap: anywhere; padding: 10px; }
</style>
</head>
<body>
<h1>Image gallery</h1>
<p id="status" role="status">Loading images…</p>
<main id="gallery" class="grid" aria-live="polite"></main>
<script>
const statusEl = document.getElementById('status');
const galleryEl = document.getElementById('gallery');
function renderItems(items) {
galleryEl.replaceChildren();
if (!items.length) {
statusEl.textContent = 'No image files were found in this folder.';
return;
}
statusEl.textContent = 'Showing ' + items.length + ' image(s).';
for (const item of items) {
const card = document.createElement('article');
card.className = 'card';
const preview = document.createElement('div');
preview.className = 'preview';
if (item.thumbnailDataUrl) {
const img = document.createElement('img');
img.src = item.thumbnailDataUrl;
img.alt = item.name;
img.loading = 'lazy';
preview.appendChild(img);
} else {
const missing = document.createElement('div');
missing.className = 'missing';
missing.textContent = 'Preview unavailable';
preview.appendChild(missing);
}
const name = document.createElement('div');
name.className = 'name';
name.textContent = item.name;
card.append(preview, name);
galleryEl.appendChild(card);
}
}
function showError(error) {
statusEl.textContent = 'Could not load images: ' +
(error && error.message ? error.message : String(error));
}
google.script.run
.withSuccessHandler(renderItems)
.withFailureHandler(showError)
.getImageItems();
</script>
</body>
</html>
The server returns data URLs rather than Drive Blob objects. The client/server bridge accepts supported values, and a Blob should not be assumed to pass through it as a browser image. Base64 increases transfer size, so this simple approach fits modest galleries better than a very large image library. If you need a larger catalog, use bounded pages and consider a delivery design with appropriate file sharing and access controls.
3. Configure Drive access and deployment
Choose who the app runs as
| Deployment identity | Drive authorization | Use when | Access concern |
|---|---|---|---|
| User deploying the web app | Server-side Drive reads use the deployer’s authority. | The gallery is intended to show a controlled set of files to an audience that should not need individual Drive authorization. | A visitor may see data the deployer can access. Only expose a folder whose contents are approved for every allowed visitor. |
| User accessing the web app | Each visitor acts with their own authority and may be asked to authorize scopes. | Each visitor should see only files they can access, or the app is for a restricted internal audience. | Visitors need access to the folder and may face an authorization step. |
In Apps Script, choose Deploy → New deployment → Web app, set the execution identity, and choose who can access the web app. The exact audience options depend on account and organization settings. Review the current [Google web-app deployment guide](https://developers.google.com/apps-script/guides/web) before publishing.
File.getThumbnail() requires Drive authorization (the Drive read-only or broader Drive scope according to the [File reference](https://developers.google.com/apps-script/reference/drive/file#getThumbnail())). The code uses DriveApp. For shared-drive use or Drive capabilities beyond the built-in service, Google documents the [Advanced Drive service](https://developers.google.com/apps-script/advanced/drive); verify whether it is enabled and configured for your project.
India and data-region settings
There is no India-specific version of this code in the documented APIs. Google announced Apps Script data-region support for eligible Workspace organizations; the setting applies through the organization’s Workspace policy and is not a per-script India code switch. If your organization has residency requirements, ask its Workspace administrator which policy and services apply. Do not treat this tutorial as confirmation that a particular region is available or selected for your account.
4. Options and edge cases
- Only image files: The example filters by MIME type prefix
image/. Remove that condition if you intentionally want other Drive files, and provide a suitable representation for them. - No thumbnail:
getThumbnail()can returnnull. The example renders a placeholder. You can omit such files instead by filtering them out. - Sort order: The sample sorts names alphabetically after reading. For date or custom ordering, return the relevant metadata and sort explicitly.
- More than 100 items: The sample returns at most 100 image files per call. To build pagination, pass a page or continuation token to the server and return a bounded slice. Avoid repeatedly scanning a very large folder from the beginning; store a deliberate index if the catalog and update pattern justify one.
- Nested folders:
folder.getFiles()lists files in that folder; it does not recursively walk every descendant. Add a controlled recursive traversal if nested content is part of the requirement. - Privacy: A data URL returned to the page is visible to that visitor. Never include a private thumbnail in a response to a public audience.
- Filename safety: The client uses
textContentfor names. Keep that pattern; do not build HTML strings from file names. - Thumbnail size and formats: Drive provides a thumbnail Blob or null, not a guarantee that every image type has a preview or that the thumbnail has a particular dimension. Use the returned blob content type when constructing the data URL.
5. Performance, reliability, and cost
Each request enumerates files and reads thumbnails before returning a response. Runtime and response size grow with the number and size of thumbnails. Keep each page bounded, avoid sending original image bytes when a thumbnail is sufficient, and show a loading state while the server call runs. For frequently viewed galleries, cache metadata or a precomputed index only when you have a clear refresh strategy; Apps Script quotas and cache behavior must be checked against your usage.
Apps Script quotas are per user, differ between consumer and Workspace accounts, and may change. Google’s current quotas page lists a six-minute maximum script runtime per execution for both account categories in the cited table. A script can still hit other quotas or run out of time before that ceiling. Check the [Apps Script quotas page](https://developers.google.com/apps-script/guides/services/quotas) when planning a larger gallery.
Google Apps Script and Drive do not have a usage charge for this specific sample stated in the research sources. Account limits, Workspace plan terms, and any separate hosting or delivery choices may affect your actual cost; check the terms for the services you use. There is no India-specific cost figure established here.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| “Set IMAGE_FOLDER_ID…” appears | The placeholder folder ID remains in the code. | Copy the folder ID from Drive and update the constant, then save and redeploy. |
| Authorization required or permission denied | The script has not been authorized, or the chosen execution identity cannot read the folder. | Run the function in the editor to authorize, then confirm that the deployer or visitor has the required Drive access. |
| The page loads but shows no files | The folder is empty, contains no image MIME types, or the deployment points at a different project version. | Check the folder ID and file types; save and deploy a new version if needed. |
| Some cards say “Preview unavailable” | Drive returned null for those files. | Keep the placeholder, omit those entries, or provide a separate fallback image mechanism. |
| Page stays on “Loading images…” | The server call failed without a visible handler, the deployment is outdated, or the execution is slow. | Keep both handlers, inspect the displayed error and Apps Script execution logs, and reduce the batch size. |
| Public visitors can see unexpected files | The app runs as the deployer, whose Drive permissions are broader than the audience should receive. | Restrict the web-app audience and folder contents, or choose per-user execution with appropriate sharing and authorization. |
| Gallery is slow or response fails as it grows | Too many thumbnails are encoded and returned in one call. | Reduce the page size, add pagination, and avoid sending unnecessary fields or original images. |
| Shared Drive files are missing | The built-in Drive service may not cover the required shared-drive behavior or configuration. | Review the Advanced Drive service setup and the Drive API requirements for the project. |
7. Or skip the browser setup
If your goal is to collect website thumbnails rather than display images already stored in Drive, ScreenshotNeo returns a screenshot from one GET request. See the API documentation for parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; these steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use screenshot, page-info, and PDF tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Plans include the listed features, and yearly billing gives two months free. Create a free account at ScreenshotNeo sign-up.
8. FAQ
Does the gallery update automatically when Drive changes?
The sample fetches the folder contents when the page calls the server function. Reload the page or call the function again to refresh; it does not subscribe to Drive changes.
Can visitors download the original file from a thumbnail?
This example returns only thumbnail data and file metadata, not a download link. Add an original-file link only after deciding that the audience is allowed to access that file.
Do I need different code for India?
No India-specific code path is established by the cited Apps Script and Drive APIs. Workspace data-region obligations depend on your organization’s policy and configuration.
Can I use this as a public image CDN?
This example is a small gallery pattern, not a CDN design. For a public or high-volume catalog, evaluate access control, caching, transfer size, and a suitable image-delivery service separately.


