Automatically update images in Google Docs-based documentation

Using a Google Doc to document internal processes (or external product docs, if you’re really daring) has the big plus of familiarity.

IMO, most everything else is a minus (which is why the awesome Archbee and other docs-focused platforms exist!). One minus you can at least work around is that images embedded in a Doc won’t auto-update, even if they were loaded from a remote URL.

That’s right, all 6 options here for Insert » Image result in a local copy of the image:

Even the promising By URL doesn’t work like an HTML <img src> where updates to the remote file are picked up on reload. Instead, it just copies the file into the document that one time. To pick up changes, you need to delete the image and Insert again.

But there’s a better way via a bit of Apps (Java)Script.

Here’s the key to making the code work. When you insert an image, also set its link to the image’s original URL. Here, I used Insert » By URL to grab the screenshot https://blog.teknkl.com/content/images/example.png. Then I click Insert link and set the same value https://blog.teknkl.com/content/images/example.png.[1]

Now the code knows where to refresh the image from. We’ve gotta use the standard link attribute (LINK_URL in Apps Script) because you can’t add arbitrary attributes to an InlineImage element. (N.B. I’m no fan of Google Docs because of this lack of extensibility.)

Get the code

This Apps Script function uses the DOM-like API to (a) get every InlineImage[2] in the document, (b) fetch the most recent version from the link URL, and (c) swap the images at the same document position:

function refreshImages() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();
  const images = body.getImages();

  for( const originalImage of images ){
    const pseudoSource = originalImage.getAttributes().LINK_URL;
    
    if(pseudoSource) {
      const parentEl = originalImage.getParent();
      const imagePosition = parentEl.getChildIndex(originalImage);

      const resp = UrlFetchApp.fetch(pseudoSource);
      const newImageBlob = resp.getBlob(); 
      const newImage = parentEl.insertInlineImage(imagePosition,newImageBlob);

      newImage.setAttributes(originalImage.getAttributes());
      originalImage.removeFromParent();
    }
  }
}

Set refreshImages to run On open, which includes refreshing the browser tab:

That’s it! Now it’s easier to keep screenshots, logos, etc. up to date.

Notes

[1] The image therefore opens its own source URL on click; you can’t have it open a web page. A worthy tradeoff, in my opinion. If you truly must link elsewhere, you could embed the URL in the image’s title instead of the link. Use a consistent format like <friendly title> (<url>), then getAltTitle() + regexp to parse it out.

[2] PositionedImage elements in a Google Doc — i.e. images with left and top offsets, like absolutely positioned images in HTML — don’t support links. If you wanted to include that type of image, you’d be forced to use the title as in [1].