google-apps-scriptgoogle-cloud-platformgoogle-docsadd-oninline-images

Google Script add-on insert image "Action not allowed"


The Google Sheets add-on flow is: (Cloud Platform project)

  1. user uploads image, add-on saves image to a folder in users drive
  2. Add-on can access the image because the logs show "Logo file accessible: imagefilename.png.
  3. When add-on tries to insert image into a doc, the error "Action not allowed" appears.
function insertLogoIntoDocument(body, userProperties) {
  const functionName = "insertLogoIntoDocument";
  const functionNameError = `${functionName}_error`;
  basicLog(functionName, "LOGO_DEBUG_INIT", "Starting function");

  try {
    // Check if body is valid
    if (!body) {
      basicLog(functionNameError, "LOGO_DEBUG_INVALID_BODY", "Document body is null or undefined");
      return { isError: true, message: "Invalid document body provided" };
    }

    const useCustomLogo = isUsingCustomLogo();
    const logoTextElement = body.findText("<LOGO>");

    if (useCustomLogo) {
      try {
        const logoImageId = userProperties.getProperty(LOGO_IMAGE_ID);
        basicLog(functionName, "LOGO_DEBUG_CUSTOM_DETECTED", "Checking for logo placeholder");

        // Log whether the logo placeholder was found
        basicLog(functionName, "LOGO_DEBUG_SEARCH_RESULT", "Logo placeholder search completed");

        if (logoTextElement) {
          try {
            const logoImage = DriveApp.getFileById(logoImageId);
            const logoBlob = logoImage.getBlob();
            const logoElement = logoTextElement.getElement();
            const logoPosition = logoElement.getParent().getChildIndex(logoElement);
            const logoParagraph = logoElement.getParent();

            basicLog(functionName, "LOGO_DEBUG_CUSTOM_FILE_DATA",
                `Logo ID: ${logoImageId}, File: ${logoImage.getName()}, Blob: ${logoBlob.getBytes().length} bytes, Element: ${logoElement.getType()}, Parent: ${logoParagraph.getType()}, Position: ${logoPosition}`
            );

            logoElement.setText("");
            const inlineImage = logoParagraph.insertInlineImage(logoPosition, logoBlob);

            const originalWidth = inlineImage.getWidth();
            const originalHeight = inlineImage.getHeight();
            const aspectRatio = originalWidth / originalHeight;

            let targetHeight = 72;
            let targetWidth;

            try {
              const savedLogoSize = userProperties.getProperty(LOGO_SIZE_PROPERTY);
              basicLog(functionName, "LOGO_DEBUG_SIZE_RAW", "Processing saved logo size");

              if (savedLogoSize) {
                targetHeight = parseInt(savedLogoSize, 10);
                if (isNaN(targetHeight)) {
                  basicLog(functionName, "LOGO_DEBUG_SIZE_INVALID", "Using default size");
                  targetHeight = 72;
                }
              } else {
                basicLog(functionName, "LOGO_DEBUG_SIZE_MISSING", "Using default size");
                targetHeight = 72;
              }

              basicLog(functionName, "LOGO_DEBUG_SIZE_FINAL", `Height: ${targetHeight}`);
            } catch (err) {
              const errorMessage = err.message || "No error message available";
              const stackTrace = err.stack || "No stack trace available";
              basicLog(functionNameError, "LOGO_DEBUG_SIZE_ERROR", `Error: ${errorMessage}, Stack: ${stackTrace}`);
              // Continue with default height if there's an error
              targetHeight = 72;
            }

            targetWidth = Math.round(targetHeight * aspectRatio);
            inlineImage.setHeight(targetHeight);
            inlineImage.setWidth(targetWidth);

            basicLog(functionName, "LOGO_DEBUG_INSERT_SUCCESS", `Height: ${targetHeight}, Width: ${targetWidth}`);
            return { isError: false, message: "Logo inserted successfully" };
          } catch (err) {
            const errorMessage = err.message || "No error message available";
            const stackTrace = err.stack || "No stack trace available";
            basicLog(functionNameError, "LOGO_DEBUG_INSERTION_ERROR", `Error: ${errorMessage}, Stack: ${stackTrace}`);
            return { isError: true, message: "Error inserting logo: " + errorMessage };
          }
        } else {
          try {
            const firstParagraph = body.getParagraphs()[0] ? body.getParagraphs()[0].getText() : "No paragraphs";
            const secondParagraph = body.getParagraphs()[1] ? body.getParagraphs()[1].getText() : "No second paragraph";
            basicLog(functionName, "LOGO_DEBUG_PLACEHOLDER_MISSING", `First paragraph: ${firstParagraph}, Second paragraph: ${secondParagraph}`);
            return { isError: true, message: "Logo placeholder not found in document" };
          } catch (err) {
            const errorMessage = err.message || "No error message available";
            const stackTrace = err.stack || "No stack trace available";
            basicLog(functionNameError, "LOGO_DEBUG_TEMPLATE_ERROR", `Error: ${errorMessage}, Stack: ${stackTrace}`);
            return { isError: true, message: "Error checking document paragraphs" };
          }
        }
      } catch (err) {
        const errorMessage = err.message || "No error message available";
        const stackTrace = err.stack || "No stack trace available";
        basicLog(functionNameError, "LOGO_DEBUG_HANDLING_ERROR", `Error: ${errorMessage}, Stack: ${stackTrace}`);
        return { isError: true, message: "Error handling logo: " + errorMessage };
      }
    } else if (logoTextElement) {
      try {
        body.replaceText("<LOGO>", "");
        basicLog(functionName, "LOGO_DEBUG_PLACEHOLDER_REMOVED", "No custom logo used");
        return { isError: false, message: "Logo placeholder removed successfully" };
      } catch (err) {
        const errorMessage = err.message || "No error message available";
        const stackTrace = err.stack || "No stack trace available";
        basicLog(functionNameError, "LOGO_DEBUG_REMOVAL_ERROR", `Error: ${errorMessage}, Stack: ${stackTrace}`);
        return { isError: true, message: "Error removing logo placeholder" };
      }
    }
    
    // Default return if no conditions above were met
    return { isError: false, message: "No logo processing needed" };
  } catch (err) {
    const errorMessage = err.message || "No error message available";
    const stackTrace = err.stack || "No stack trace available";
    basicLog(functionNameError, "LOGO_DEBUG_UNEXPECTED_ERROR", `Error: ${errorMessage}, Stack: ${stackTrace}`);
    return { isError: true, message: "Unexpected error in logo processing" };
  }
}

I have the appropriate scopes so that is not the issue. Any ideas?

I have tried different variations of inserting images however still "Action not allowed" . I also tested to make sure multiple Google accounts were not logged in at the same time and ensured only 1 account.

LOGO_DEBUG_CUSTOM_FILE_DATA. Additional Info: Logo ID: "123456...", File: image_name.png, Blob: 158768 bytes, Element: TEXT, Parent: PARAGRAPH, Position: 0.

LOGO_DEBUG_INSERTION_ERROR. Additional Info: 
    Error: Action not allowed, Stack: Exception: Action not allowed
    at insertLogoIntoDocument (reportsGenerate/utils/logoHelper:38:47)

since it displays Blob: 158768 bytes i'm assuming the add-on was able to successfully retrieve the image.

Also when I run it with "Test Deployments" it works fine, it's just published version that gives the error.


Solution

  • Update: Switching to the Docs API resolved the "Action not allowed" error when inserting images in a Google Sheets Add-on.

    The Docs API allows image insertion even in container-bound scripts. One caveat is that it’s subject to usage quotas, unlike DocumentApp, but if you're only inserting an image occasionally (e.g., once per report), this should not be an issue.

    Sample Code (Using Google Docs API)

    function insertImageUsingDocsAPI(documentId, imageUrl) {
      const requests = [
        {
          replaceAllText: {
            containsText: {
              text: "<LOGO>",
              matchCase: true,
            },
            replaceText: "LOGO_IMAGE_PLACEHOLDER",
          },
        },
        {
          insertInlineImage: {
            uri: imageUrl,
            location: {
              index: 1, // Replace with correct index for your use case
            },
          },
        },
      ];
    
      Docs.Documents.batchUpdate(
        {
          requests: requests,
        },
        documentId
      );
    }
    

    ✅ Make sure the image URL is publicly accessible (e.g., shared Google Drive file or a hosted image).
    ✅ This example assumes you're using Advanced Google Services with the Docs API enabled in your Apps Script project.