Joplin note sharing tool




CLI tool to publish Joplin notes as a static website, currently supports both blog/wiki forms, framework supports hexo/vuepress/docsify.

blog demo
wiki demo


  • Install nodejs and npm(installed by default)
  • Knowledge of command line
  • Understanding VSCode


  1. Navigate to the relevant directory at the command line
  2. Add dependencies npm i -D joplin-blog.
  3. add a configuration file .joplin-blog.json (see configuration for details)
  4. add an npm script file "gen": "joplin-blog blog" (if you want to generate a wiki then "gen": "joplin-blog wiki")
  5. Run the command npm run gen
  6. Then you can see that the relevant directory already contains the notes and attached resources

Please add two files .joplin-blog.json and .joplin-cache.json to the .gitignore ignore file, the former contains sensitive information token, the latter is automatically generated.


The supported frameworks have examples in the examples directory, which you can clone locally to see.



Configuration Type Required Description
type hexo/vuepress/docsify yes type of integrated blog
rootPath string no hexo/vuepress directory, default is .
token string yes token for joplin web clipper
baseUrl string no base path of joplin web clipper, default is http://localhost:41184
tag string yes joplin's blog tag


configuration type description
stickyTopIdList string[] Topped note id (only valid under fluid topic)

I've shared over 190 notes with this tool, blog address (Chinese)


What does token/port refer to and where can I find it?

You can usually see it in Tools > Options > Web Clipper

joplin web clipper

Why did the note id of the exported blog, wiki change?

Some possible reasons are

nodejs 18 is not fully compatible

Currently, some ArchLinux users report that they cannot use joplin-blog, but after troubleshooting, they found that replacing nodejs 16 lts works, please use nodejs 16 lts first.

Related issue:


@thwaller @ser May be interested





  • Fix README update being overwritten error




  • Fixed passing tag from config instead of hard-coded to blog


  • Fix the problem that the \r in the title is not cleared when exporting hexo blog


  • Basic export function realization, currently supports hexovuepress
1 Like

Currently cli has been completely refactored, and v0.2.0 has been released, with major changes

  • Completely refactor the code
  • Realize the internationalization of output information
  • Implement a more friendly cli

recent goals

  • [ ] Achieve a blog project associated with joplin from scratch
  • [ ] Allow users to complete export, packaging, and deployment operations without touching the underlying dependency
  • [ ] Implement a wiki generator
  • [ ] Support docsify
  • [ ] Support vuepress
  • [ ] Integrated into the joplin plugin to provide a certain degree of visual interface

Release 0.3.0, support generating a wiki website with sidebar structure from Joplin, the general effect is as follows

Example project: joplin-blog/package.json at joplin-blog@0.3.0 · rxliuli/joplin-blog · GitHub

There are still some internationalization and details that have not been completed. I will fill up later, but I will not continue to develop the GUI version of joplin-blog (the previously developed parts, including generation, command packaging, and deployment have all been deleted), This is really troublesome, especially when I develop this tool but don't use it.


Changes since 0.3.0 => 0.4.0

  • feat(joplin-blog): Use worker_threads to parse and convert markdown to avoid blocking the main thread
  • fix(joplin-blog): Fix the error that asyncLimit will print numbers when running commands
  • refactor(joplin-blog): Expose more functions in src/index to facilitate third-party calls
  • fix (joplin-blog): fix missing international files
  • fix (joplin-blog): unified modification example generation command imp => gen
  • fix (joplin-blog): Fix the redundant space in the docsify sidebar configuration generated by Joplin
  • fix (blog-hexo-example): fix the bug that hexo cannot be deployed to the site subdirectory
1 Like

Publish 0.5.1

This is an important upgrade. The use of the cache function has increased the generation speed, and when there is no modification, it has been reduced from 20+s to 2+s. Optimization is based on two premises

  • The number of modified notes should be much smaller than the unmodified ones
  • Attachment resources will not be modified basically

npm link: joplin-blog - npm

Specific change information

  • feat(joplin-blog): add internationalization configuration of cache command
  • feat(joplin-blog): implement resource caching, add cached commands
  • refactor(joplin-blog): refactor the key of the internationalization string to use a more meaningful naming scheme
  • feat(joplin-blog): implements caching functionality
  • refactor(joplin-blog): refactor the functions previously written to notes and resources into fixed directories for easy calculation of cache locations
  • fix(joplin-blog): fix warning that comlink/dist/umd/node-adapter is not used as an external dependency when packaging
  • fix(joplin-blog): fix bug with i18next-util reference
  • docs(joplin-blog): update the deployment location of joplin-blog, and update the documentation
  • docs(joplin-blog): add json schema for configuration files
  • refactor(joplin-blog): utility @liuli-util/i18next-util in the joplin-blog project
  • chore(root): update the version of joplin-blog in the example module
  • chore(joplin-blog): update rollup-plugin-worker-threads
1 Like

Publish 0.6.0

  • feat(joplin-blog): Add a subcommand clean under the blog/wiki command to delete cached configuration files, notes and attachment resource directories
1 Like

Release 0.7.0

The main update is to support generating files for jeykll blog. The example project is at, the online example you can view: <https: //>


  • fix: Fix an issue where the integration object created when the generated blog type is jekyll is incorrect
  • fix: add missing development dependency builtin-modules
  • feat: integrate jekyll into cli commands
  • feat: support jekyll framework
  • chore: update liuli-cli to improve packaging performance (48s => 23s)
  • chore: add yarn plugin interactive-tools, upgrade liuli-cli
  • chore(root): merge conflicts
  • fix(joplin-blog): use diff only once to get deleted, modified and added notes and resources
  • chore(root): replace lerna with yarn 2

The original demand comes from:


Release 0.7.2

Release 0.8.0

  • feat: set the target of typescript to esnext, which is only compatible with the latest version of nodejs lts by default
  • feat: remove the dependency on the local joplin resource location, download the resource to the local through the clipper api
  • refactor: updated joplin-api sdk, no longer depends on axios

Release 0.8.1

The main change is to update the joplin-api dependency to support the use of the joplin clipper api running remotely, and also to reduce the bundle size by replacing the dependency

  • refactor: replace luxon => dayjs
  • refactor: update joplin-api sdk
  • feat: update to use latest joplin-api sdk, update port => baseUrl

Release 0.9.0

The main change is to support more flexible requirements programmatically, including modifying the meta metadata of each markdown file when it is generated, such as adding an author field, which looks like this

import {
} from 'joplin-blog'
import { CommonNote, CommonTag, CommonResource } from 'joplin-blog/dist/model/CommonNote'
import _config from './.joplin-blog.json'

class GeneratorEventsImpl implements GeneratorEvents {
  copyResources(options: ProcessInfo): void {
    console.log(`${options.rate}/${options.all} Reading note attachments and tags: `, options.title)

  parseAndWriteNotes(options: ProcessInfo): void {
      `${options.rate}/${options.all} Parsing Joplin internal links and attached resources in notes: ${options.title}`,

  readNoteAttachmentsAndTags(options: ProcessInfo): void {
    console.log(`${options.rate}/${options.all} Writing note: ${options.title}`, options.title)

  writeNote(options: ProcessInfo): void {
    console.log(`${options.rate}/${options.all} Processing resource: ${options.title}`)

class BlogIntegrated extends BlogHexoIntegrated {
  meta(note: CommonNote & { tags: CommonTag[]; resources: CommonResource[] }) {
    return {
      author: 'rxliuli'

async function main() {
  const config: ApplicationConfig & BlogHexoIntegratedConfig = {
    ...(_config as ApplicationConfig),
    rootPath: __dirname,
  const generatorEvents = new GeneratorEventsImpl()
  await new Application(config, new BlogIntegrated(config))
    .on('readNoteAttachmentsAndTags', generatorEvents.readNoteAttachmentsAndTags)
    .on('parseAndWriteNotes', generatorEvents.parseAndWriteNotes)
    .on('writeNote', generatorEvents.writeNote)
    .on('copyResources', generatorEvents.copyResources)


The configuration of .joplin-blog.json looks like

  "type": "hexo",
  "token": "",
  "tag": "blog"


  • feat: support adding custom meta when generating pages, ref:
  • fix: fix missing dependencies, remove unnecessary wallaby configuration
  • chore: update @liuli-util/async
  • docs: update readme, add nodejs 18 incompatibility note
  • chore: update joplin-api
  • fix: fix bin.cjs not adding #/usr/bin/env node error
  • fix: fix a bug where headers in body are not properly removed

joplin-blog released 0.9.4, which contains a very big performance improvement, now it only takes a few seconds to generate the files needed by the website without caching

no cache

$ time pnpm gen

real    0m4.838s
user    0m0.015s
sys     0m0.076s

with cache

$ time pnpm gen

real    0m2.093s
user    0m0.000s
sys     0m0.075s

generated content v 1.94  T=0.91 s (281.5 files/s, 57807.1 lines/s)
Language                     files          blank        comment           code
Markdown                       228          10568              0          37902
SVG                             24              0              7           4163
HTML                             5              6              0            128
SUM:                           257          10574              7          42193


  • migrated to esm module
  • Refactored the code to converge dependencies
  • Do not bundle all dependent code in bundle
  • no longer using workers (low performance as markdown parsing)


1 Like