// Apps Script

Fix "The script completed but did not return anything" in Apps Script.

When your Apps Script web app returns "The script completed but did not return anything," your doGet or doPost function is not returning an HtmlOutput or TextOutput object. Here is exactly what to fix — and why the /exec URL keeps showing the old error even after you patch it.

I deployed a Google Apps Script web app and it shows "The script completed but did not return anything" instead of my page or response.

The script

copy · paste · trigger
Code.gs
Apps Script
// Web app entry point — must return HtmlOutput or TextOutput
function doGet(e) {
  var page = e.parameter.page || 'home';

  if (page === 'home') {
    return HtmlService.createHtmlOutputFromFile('index')
      .setTitle('My App');
  }

  if (page === 'data') {
    var result = JSON.stringify({ status: 'ok', page: page });
    return ContentService.createTextOutput(result)
      .setMimeType(ContentService.MimeType.JSON);
  }

  // Always return something — even for the fallback path
  return HtmlService.createHtmlOutput('<p>Not found</p>');
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

What Apps Script actually requires from a web app function

When you deploy a script as a web app, Apps Script routes incoming HTTP requests to doGet (for GET) or doPost (for POST). Those functions must explicitly return one of two object types: an HtmlOutput, built via HtmlService, or a TextOutput, built via ContentService. Returning nothing, returning undefined, returning a plain string, or returning a raw JavaScript object all produce the same error: "The script completed but did not return anything."

The most common cause is a conditional branch that exits without a return statement. You add an if block for the happy path, wire it up, and forget that any request hitting the else case falls off the end of the function with an implicit undefined return. Apps Script cannot render undefined as HTTP output, so it surfaces that message to the browser.

The fix is mechanical: every reachable exit point in doGet and doPost must return HtmlService.createHtmlOutput(), HtmlService.createHtmlOutputFromFile(), or ContentService.createTextOutput(). A catch-all return at the bottom of the function, after all your conditionals, handles everything else. The snippet above shows that pattern: two conditional branches, each returning the correct type, and a fallback return as the last line.

The deployment version trap — why your fix seems to do nothing

This is where people lose an hour after the code is already correct. The /exec URL — the one that ends in /exec and is meant for end users — always serves the most recently published deployment version, not your current editor state. Saving the file and even refreshing the /exec URL in your browser does not pick up your changes.

To get the fixed code live on /exec, you must create a new version: open Deploy > Manage deployments, click the pencil icon on your active deployment, change the version dropdown from whatever it is to New version, and click Deploy. Only after that does /exec serve your patched doGet.

The /dev URL is the escape hatch. That URL always runs the HEAD of your current saved script, no versioning required. Use /dev while iterating so you see changes immediately. Switch to /exec for anything you share externally or embed in a form. I keep a browser tab for each open while building; mixing them up accounts for roughly half the "my fix didn't work" confusion I see in support threads.

Returning JSON without breaking the response type

A second common variant of this error comes from trying to return a JavaScript object directly from doGet when you want a JSON API response. Apps Script is strict: return { status: 'ok' } produces the same error as return nothing, because a plain object is not an HtmlOutput or TextOutput.

The correct path is ContentService.createTextOutput(JSON.stringify(yourObject)).setMimeType(ContentService.MimeType.JSON). The setMimeType call tells Apps Script to send the Content-Type: application/json header. Without it the content still goes through, but callers parsing the response by content type will treat it as plain text.

One thing to know before you ship a JSON endpoint this way: the response will include a Content-Security-Policy header that blocks embedding in certain contexts, and CORS is restricted by default. If you need cross-origin fetches from a browser client, you may need to set the deployment to execute as Me and allow access to Anyone, or proxy through a different service.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Does returning a plain string from doGet cause this error?
Yes. Apps Script requires an HtmlOutput or TextOutput object. A bare string like return '<html>...</html>' is not the same as HtmlService.createHtmlOutput('<html>...</html>'). Wrap any string in one of those two constructors before returning it.
I fixed the return statement but the /exec URL still shows the error. What did I miss?
You need to publish a new deployment version. Go to Deploy > Manage deployments, edit the active deployment, set the version to New version, and click Deploy. The /exec URL caches the previously published version; saving the script file alone does not update it. Test against the /dev URL while iterating.
Can I use doPost the same way as doGet?
Yes, the same return-type requirement applies. doPost must return an HtmlOutput or TextOutput. The difference is that request data arrives in e.postData.contents rather than e.parameter, and most doPost handlers return a ContentService JSON response rather than an HTML page.
My script runs fine when I click Run in the editor but fails when accessed via the /exec URL. Why?
The editor Run button executes the function directly without the web app wrapper, so it doesn't check the return type. The /exec URL triggers the full web app pipeline, which requires a valid HtmlOutput or TextOutput return value. A function that logs output or modifies a sheet but returns nothing will appear to work in the editor and fail at the URL.