Hello there! Recently, I successfully set up Notion + Jekyll synchronization using the jekyll-fetch-notion plugin. It has been meeting all my needs until my previous post, where I needed to attach some images.

What is the problem

I will remind you how our synchronization works. So, we have a pipeline called .gitlab-ci.yml, which (briefly):

  • Runs the jekyll fetch_notion command, which pulls everything from Notion, converts it to .md, and places everything inside your repository.
  • Stages all the newly pulled .md files, performs a git-commit for any new changes, and git-push them.
  • A new commit in the repository then triggers another pipeline, which builds and deploys the site.

It works great when our posts don’t have any images. The problem with images is that Notion generates a unique short-lived URL for them every time, and after a few minutes, these URLs stop working. This problem is relevant not only to the git-based Notion synchronization approach.

You can solve it without any additional code just by embedding images via a direct URL. Then, Notion won’t save them on their servers, and the output will always be the same. By doing so, you will always need to manually publish images to your site before posting. Well, it sounds uncomfortable, but at least it works.

How to solve it right

I prefer to follow a git-based approach here as well and save images to the repository using the fetch_notion command. All we need to do is monkey-patch the handling of the image block. Here is an example of how to work with block handling in general and how to handle custom blocks that are not handled by default: Embedding videos with jekyll-notion.

So, we are overriding the image block handling like this:

require 'open-uri'

module NotionToMd
  module Blocks
    class Types
      class << self
        def image(block)
          type = block[:type].to_sym
          url = URI.parse(block.dig(type, :url))
          # we can also retrieve a caption here like this:
          # caption = convert_caption(block)

          # https://example.com/filename.jpg?queryParams -> filename.jpg
          filename = "assets/#{url.to_s.split('/')[-1].split('?')[0]}"
          IO.copy_stream(url.open, filename)

          Jekyll.logger.info("Image #{File.absolute_path(filename)} #{"OK".green}")

          return "![](#{filename})" 
          # or you can use jekyll_picture_tag plugin and return liquid 
          # tag "picture" here, which automatically generates responsive 
          # images for you.

You should put it into _plugins/notion_to_md/blocks/types.rb (or any other .rb file inside _plugins directory) to make it works. Also you must include the following lines to Gemfile:

# Fetching files easily
gem 'open-uri'

# if you are going to use responsive images plugin
group :jekyll_plugins do
  # ...

  # github.com/rbuchberger/jekyll_picture_tag
  gem 'jekyll_picture_tag', '2.0.4'

That’s it! Now your images will be stored in a repository during the fetch_notion command.

Side note: How to make monkey-patching available for jekyll commands

One more thing to note is that monkey-patching is not available for every single Jekyll command by default. To make it work, your command must initialize a Jekyll::Site object during the processing; otherwise, your .rb won’t be loaded. That’s how I did it for the fetch_notion command:

module JekyllNotion
  class FetchCommand < Jekyll::Command
    def self.init_with_program(p)
      p.command(:fetch_notion) do |c|
        # ...
        c.action do |args, options|
          process(args, options)
    def self.process(args = [], options = {})
      @config = configuration_from_options(options)

      # ...

      # requires plugins (and _plugins/ directory) to be able to
      # define custom notion_to_md blocks via monkey-patching
      site = Jekyll::Site.new(@config)

      # ...

Just left this note here because actually, it was hard to find this out as long as it isn’t documented anywhere.


Thank you for reading this little note. I hope this information will be useful to someone. 🔔 Stay tuned for updates (🛫 Telegram channel / 📶 RSS / ✉️ Email). Have a nice day!