Skip to content

Google Scholar annotation exporter

Published:

Today, someone walked into an office that no longer belongs to them and, besides giving me an excellent excuse not to work, indirectly presented me with a rather interesting challenge: programmatically extracting highlights and notes created with the Google Scholar PDF Reader extension. At first, the task seemed fairly trivial. A few simple JavaScript functions executed directly from the browser console would probably have been enough. And indeed, within a few minutes I had a simple script capable of extracting most of the data I was interested in from the modal window that appears when clicking the “Highlights” menu. The problem was that another request was soon added—one that sounded harmless at first:

“Would it be possible to make the highlights include the entire sentence, just to provide a bit more context?”

The added difficulty came from the fact that there is no direct way to retrieve this information. The text must therefore be extracted directly from the original PDF, a process whose potential complications I was completely unaware of.

By that point, however, my curiosity had been sufficiently piqued, so I decided to accept the challenge.

This post is therefore a report on how I approached the problem, the obstacles I encountered, and the reasons that led me to favor one methodology over another.


Design

Glossary

Requirements

The goal of this project is to create a tool that satisfies the following requirements:


Implementation

Project Structure

After cloning the repository, the project structure will look like this:

.
├── dist                 # Generated by Rolldown
├── src                  # Source code
│   ├── background       # Background script entry point
│   └── content.ts       # Content script entry point
├── assets               # Static resources
│   └── icons
├── _locales             # Translations
│   ├── en
│   └── it
├── package.json
├── rollup.config.ts     # Rolldown configuration
└── manifest.json        # Extension configuration

In the following sections, we will examine the role of each file and folder in detail.

Tool Type

The first decision concerned the type of tool to implement.

At first glance, there are several possibilities:

Among these options, the third seemed the most convenient.

Since users already need the Google Scholar PDF Reader extension installed in order to create annotations, installing an additional extension falls within their skill set.

Furthermore, this approach avoids dealing with security details (session cookies, authentication, etc.) that would be necessary with the second option, and it does not carry the same sense of “risk” or require even the minimal coding familiarity associated with the first option.

How the Extension Works

Chrome extensions are quite flexible and can execute code in different contexts depending on the requirements.

In our case, two categories of scripts are relevant:

Content scripts run within the web page currently being viewed by the user.

They can interact with the page’s DOM and generally have access to all the capabilities of a script imported directly into HTML.

Background scripts, on the other hand, run in a separate context.

They are used to manage extension state, communicate with content scripts, and do not block the execution of the web page.

The need for content scripts should be obvious: we need to read the page state (to access the user’s annotated articles) and modify the page content (to add commands).

The background script allows us to use the pdf.js library, which we use to enrich annotations with the context of the complete sentence.

To do this, we must manually download the original PDF, parse it correctly, locate the highlighted text, and return the complete sentence.

Most steps in this pipeline can fail quite easily—the PDF may not be available, the extracted text may be unreadable, or the sentence may be split across too many separate sections—but achieving something that works a good percentage of the time would already be a success.

The two scripts communicate through an asynchronous messaging system: the content script sends a request and receives a response after the background script has completed processing.

Loading diagram...

Language Choice

As for the programming language, the choice was fairly straightforward: TypeScript.

Since everything must run in a browser environment, the code ultimately needs to be transpiled to JavaScript and preferably minified as well—both tasks that Rolldown handles excellently with very little configuration.

In practice, it is enough to install Rollup, create a configuration file, and add a build script to package.json in order to obtain a distributable bundle.

# Install Rollup and Chrome TypeScript definitions
npm install --save-dev rollup chrome-types
// rollup.config.ts
import { defineConfig } from "rolldown";

export default defineConfig({
  input: "src/content.ts",
  output: {
    file: "dist/content.js",
    minify: true,
  },
});
{
  "scripts": {
    "build": "rollup -c",
    "watch": "rollup -c -w"
  }
}

Manifest

A Chrome extension consists of a collection of files, among which the most important is manifest.json, which contains the information required by the browser to install and manage the extension.

Most fields are self-explanatory, but for more information it is always advisable to consult the official documentation.

The only noteworthy aspect is the use of the localized strings __MSG_extension_name__ and __MSG_extension_description__, which allow the name and description fields to be translated according to the user’s language.

To provide these values, a messages.json file must be created inside _locales/<lang>/, where <lang> is the language code (en for English, it for Italian, etc.).

{
  "$schema": "https://json.schemastore.org/chrome-manifest",
  "manifest_version": 3,
  "name": "__MSG_extension_name__",
  "version": "0.0.1",
  "description": "__MSG_extension_description__",
  "icons": {
    "48": "./assets/icons/icon-48.png",
    "128": "./assets/icons/icon-128.png"
  },
  "default_locale": "en"
}
{
  "extension_name": {
    "message": "Google Scholar Annotation Exporter",
    "description": "The name of the extension"
  },
  "extension_description": {
    "message": "Export your Google Scholar PDF Reader annotations (highlights and notes) to a JSON, CSV, or Markdown file.",
    "description": "The description of the extension"
  }
}

Templating

There are quite a few JavaScript templating engines available, although many are understandably aimed primarily at generating HTML.

After a brief search, I decided to use Handlebars.js.

It has a syntax similar to what I am accustomed to, is easy to use, widely adopted, and well documented.


Limitations

In its current state, Google Scholar Annotation Exporter has several limitations, some of which appear difficult to overcome.

LimitationCausePossible Solution
Google rate limitingGoogle Scholar usage limitsUse an incognito browser session, change your connection, or use a different computer
Failure to extract contextPDF unavailable or irregular PDF structureEnsure the URL is valid; prefer PDFs with easily parseable structures
Non-customizable output formatThe tool exports in a predefined formatSee TODO #2
CORS-protected PDFMany PDFs enforce cross-origin request policiesSee TODO #3

TODO