The Google Sheets add-on flow is: (Cloud Platform project)
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.
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.
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.