I use Linkding to save stuff I find while browsing. Sometimes it’s a recipe I want to try, a impressive personal website, a good article on tech, or information on a health-related topic.

To save a bookmarks, i have three ways to push links to Linkding:

Recently I thought it would be cool to show a collection of the most interesting links on my homepage. Since it is markdown based, I wanted to have a way to create markdown files for all the bookmarks I set to Shared on linkding. That way, I could explicitly mark a bookmark to be shared on my homepage.

Pulling the data from linkding

First, I wrote a typescript script that gets executed by node. It reads a toml config file which contains:

  • The target folder, where the markdown files will be created
  • The linkding host, from where the bookmarks will be fetched
  • And an authentication token for the linkding api.

The script will first collect all the existing files and read their frontmatter, which contains the id of the bookmark on linkding. This way, when fetching the bookmarks, it can check if there are any new ones.

const existingFiles = await fs.readdir(config.bookmark_path)
const existingIds = await Promise.all(
  existingFiles.map(async (fileName) => {
    const filePath = path.join(config.bookmark_path, fileName)
    const buf = await fs.readFile(filePath)
    const content = buf.toString()
 
    const [_ignore, fm, _markdown] = content.split("---")
    const frontmatter: Frontmatter = yaml.load(fm) as Frontmatter
    return frontmatter["id"]
  }),
)

Now, the scripts fetches all shared bookmarks and filters out the existing ones. I’m using the shared option to selectively publish bookmarks on my site, after I cleaned them up and tagged them properly.

const res = await fetch(`${config.linkding.host}/api/bookmarks?limit=999`, {
  headers: {
    Authorization: "Token " + config.linkding.token,
  },
})

Finally, the scripts constructs the filename, yaml frontmatter and contents from the bookmark data. The packages json2md and js-yaml are doing the heavy lifting here. For formatting the date, I’m using date-fns.

const md: MarkdownFile[] = bookmarks.map((x) => {
  const title = x.title.replace(titleRegex, "").replace(/ +/, " ")
  return {
    title: title,
    frontmatter: yaml.dump({
      id: x.id,
      title: title,
      tags: x.tag_names,
      sources: [x.url],
      date: format(x.date_added, "yyyy-MM-dd"),
    }),
    content: json2md([{ p: x.description }]),
  } as MarkdownFile
})
 
md.forEach(async (x) => {
  const fileName = x.title + ".md"
  const filePath = path.join(config.bookmark_path, fileName)
  const fileContent = "---\n" + x.frontmatter + "---" + x.content
 
  console.debug("[FILE] " + fileName)
  fs.writeFile(filePath, new Uint8Array(Buffer.from(fileContent)))
})
 
console.log(`${md.length} files written.`)

Executing the script

The script gets executed from a cronjob on my vps where my homepage is also hosted. Every 5 minutes, new Bookmarks are created, if any. Since the website folder is a git repository, a wrapper script also commits and pushes any new bookmark files to the remote repository on my Forgejo instance. From there, every push a webhook triggers a website rebuild, so every bookmark I share, is automatically pushed to the homepage.

The whole thing took me an afternoon to write and it feels amazing, having this kind of automation on a static website. :)