ArkHost/HelixNotes
14
150
Fork
You've already forked HelixNotes
13

I've got a Python script to go out of Joplin and uses your great app #58

Closed
opened 2026年05月05日 22:39:47 +02:00 by davi-jorge-art · 6 comments

I don't know programing...I've just had the basics a long, long time ago...But with the help of ChatGpt I've got a Python Script to migrate from Joplin and It has worked just fine...Until the IA has solved the issue it has made a lot of mistakes , as usual.

I hope it helps on some way...

Here the link for the chat (mostly in Brazilian Portuguese) : https://chatgpt.com/share/69f593c2-b488-83e9-8dfa-e466701c4851

The script :

"""
Joplin → HelixNotes Migration Script (Generic Version)
DESCRIPTION
-----------
Converts a Joplin "Markdown + Front Matter" export into a structure
compatible with HelixNotes.
FEATURES
--------
- Preserves folder structure (notebooks → folders)
- Converts tags to normalized format
- Fixes note titles (Windows-safe filenames)
- Copies attachments to HelixNotes internal folder
- Rewrites image links to absolute paths (Helix-compatible)
- Converts Joplin internal links to wiki-style [[links]]
REQUIREMENTS
------------
- Python 3.8+
- Joplin export in: "Markdown + Front Matter"
HOW TO USE
----------
1. Export your notes from Joplin:
 File → Export All → Markdown + Front Matter
2. Edit the paths below:
 SRC = Path(r"YOUR_JOPLIN_EXPORT_FOLDER")
 DST = Path(r"YOUR_HELIX_VAULT_FOLDER")
 Example:
 SRC = Path(r"C:\Users\YourName\Desktop\JoplinExport")
 DST = Path(r"C:\Users\YourName\Documents\HelixVault")
3. Run:
 python script.py
4. Open HelixNotes and select the DST folder as your vault.
NOTES
-----
- This script assumes HelixNotes stores attachments in:
 .helixnotes/attachments/
- Images are converted to absolute paths because HelixNotes
 may not render relative paths correctly.
- If images do not render, try removing "|size=small" in image links.
"""
import re
import shutil
from pathlib import Path
# ===== CONFIGURE HERE =====
SRC = Path(r"C:\PATH\TO\JOPLIN_EXPORT")
DST = Path(r"C:\PATH\TO\HELIX_VAULT")
ASSETS = DST / ".helixnotes" / "attachments"
DST.mkdir(parents=True, exist_ok=True)
ASSETS.mkdir(parents=True, exist_ok=True)
def slug_tag(tag):
 tag = tag.strip().lower()
 tag = re.sub(r"\s+", "-", tag)
 return tag
def safe_name(name):
 name = re.sub(r'[\\/:*?"<>|]', "-", name)
 return name.strip()
def parse_frontmatter(text):
 if not text.startswith("---"):
 return {}, text
 parts = text.split("---", 2)
 if len(parts) < 3:
 return {}, text
 raw = parts[1]
 body = parts[2].lstrip()
 meta = {}
 key = None
 for line in raw.splitlines():
 if re.match(r"^\s*-\s+", line) and key:
 meta.setdefault(key, [])
 meta[key].append(re.sub(r"^\s*-\s+", "", line).strip())
 elif ":" in line:
 k, v = line.split(":", 1)
 key = k.strip()
 v = v.strip()
 meta[key] = v if v else []
 return meta, body
def build_frontmatter(meta):
 out = ["---"]
 for k in ["title", "created", "updated"]:
 if meta.get(k):
 out.append(f"{k}: {meta[k]}")
 tags = meta.get("tags", [])
 if isinstance(tags, str):
 tags = [tags]
 out.append("tags:")
 for t in sorted(set(slug_tag(x) for x in tags if x.strip())):
 out.append(f" - {t}")
 out.append("---")
 out.append("")
 return "\n".join(out)
def copy_resources():
 res = SRC / "_resources"
 if not res.exists():
 print("No _resources folder found.")
 return
 for f in res.iterdir():
 if f.is_file():
 shutil.copy2(f, ASSETS / f.name)
def convert_image_links(txt):
 def repl(m):
 fname = m.group(1)
 full = str(ASSETS / fname).replace("\\", "/")
 return f"![|size=small]({full})"
 txt = re.sub(
 r'!\[\]\((?:.*?/)?_resources/([^)]+)\)',
 repl,
 txt
 )
 txt = re.sub(
 r'!\[[^\]]*\]\((?:.*?/)?_resources/([^)]+)\)',
 repl,
 txt
 )
 return txt
def convert_note_links(txt):
 txt = re.sub(
 r'\[([^\]]+)\]\(:/[a-f0-9]{32}\)',
 r'[[1円]]',
 txt,
 flags=re.I
 )
 return txt
def process_md(mdfile):
 rel = mdfile.relative_to(SRC)
 txt = mdfile.read_text(encoding="utf-8", errors="ignore")
 meta, body = parse_frontmatter(txt)
 title = safe_name(meta.get("title", mdfile.stem))
 meta["title"] = title
 body = convert_image_links(body)
 body = convert_note_links(body)
 final = build_frontmatter(meta) + body.strip() + "\n"
 out_dir = DST / rel.parent
 out_dir.mkdir(parents=True, exist_ok=True)
 (out_dir / f"{title}.md").write_text(final, encoding="utf-8")
def main():
 print("Copying attachments...")
 copy_resources()
 print("Processing notes...")
 for md in SRC.rglob("*.md"):
 if "_resources" not in str(md):
 process_md(md)
 print("DONE")
if __name__ == "__main__":
 main()
I don't know programing...I've just had the basics a long, long time ago...But with the help of ChatGpt I've got a Python Script to migrate from Joplin and It has worked just fine...Until the IA has solved the issue it has made a lot of mistakes , as usual. I hope it helps on some way... Here the link for the chat (mostly in Brazilian Portuguese) : https://chatgpt.com/share/69f593c2-b488-83e9-8dfa-e466701c4851 The script : ```python """ Joplin → HelixNotes Migration Script (Generic Version) DESCRIPTION ----------- Converts a Joplin "Markdown + Front Matter" export into a structure compatible with HelixNotes. FEATURES -------- - Preserves folder structure (notebooks → folders) - Converts tags to normalized format - Fixes note titles (Windows-safe filenames) - Copies attachments to HelixNotes internal folder - Rewrites image links to absolute paths (Helix-compatible) - Converts Joplin internal links to wiki-style [[links]] REQUIREMENTS ------------ - Python 3.8+ - Joplin export in: "Markdown + Front Matter" HOW TO USE ---------- 1. Export your notes from Joplin: File → Export All → Markdown + Front Matter 2. Edit the paths below: SRC = Path(r"YOUR_JOPLIN_EXPORT_FOLDER") DST = Path(r"YOUR_HELIX_VAULT_FOLDER") Example: SRC = Path(r"C:\Users\YourName\Desktop\JoplinExport") DST = Path(r"C:\Users\YourName\Documents\HelixVault") 3. Run: python script.py 4. Open HelixNotes and select the DST folder as your vault. NOTES ----- - This script assumes HelixNotes stores attachments in: .helixnotes/attachments/ - Images are converted to absolute paths because HelixNotes may not render relative paths correctly. - If images do not render, try removing "|size=small" in image links. """ import re import shutil from pathlib import Path # ===== CONFIGURE HERE ===== SRC = Path(r"C:\PATH\TO\JOPLIN_EXPORT") DST = Path(r"C:\PATH\TO\HELIX_VAULT") ASSETS = DST / ".helixnotes" / "attachments" DST.mkdir(parents=True, exist_ok=True) ASSETS.mkdir(parents=True, exist_ok=True) def slug_tag(tag): tag = tag.strip().lower() tag = re.sub(r"\s+", "-", tag) return tag def safe_name(name): name = re.sub(r'[\\/:*?"<>|]', "-", name) return name.strip() def parse_frontmatter(text): if not text.startswith("---"): return {}, text parts = text.split("---", 2) if len(parts) < 3: return {}, text raw = parts[1] body = parts[2].lstrip() meta = {} key = None for line in raw.splitlines(): if re.match(r"^\s*-\s+", line) and key: meta.setdefault(key, []) meta[key].append(re.sub(r"^\s*-\s+", "", line).strip()) elif ":" in line: k, v = line.split(":", 1) key = k.strip() v = v.strip() meta[key] = v if v else [] return meta, body def build_frontmatter(meta): out = ["---"] for k in ["title", "created", "updated"]: if meta.get(k): out.append(f"{k}: {meta[k]}") tags = meta.get("tags", []) if isinstance(tags, str): tags = [tags] out.append("tags:") for t in sorted(set(slug_tag(x) for x in tags if x.strip())): out.append(f" - {t}") out.append("---") out.append("") return "\n".join(out) def copy_resources(): res = SRC / "_resources" if not res.exists(): print("No _resources folder found.") return for f in res.iterdir(): if f.is_file(): shutil.copy2(f, ASSETS / f.name) def convert_image_links(txt): def repl(m): fname = m.group(1) full = str(ASSETS / fname).replace("\\", "/") return f"![|size=small]({full})" txt = re.sub( r'!\[\]\((?:.*?/)?_resources/([^)]+)\)', repl, txt ) txt = re.sub( r'!\[[^\]]*\]\((?:.*?/)?_resources/([^)]+)\)', repl, txt ) return txt def convert_note_links(txt): txt = re.sub( r'\[([^\]]+)\]\(:/[a-f0-9]{32}\)', r'[[1円]]', txt, flags=re.I ) return txt def process_md(mdfile): rel = mdfile.relative_to(SRC) txt = mdfile.read_text(encoding="utf-8", errors="ignore") meta, body = parse_frontmatter(txt) title = safe_name(meta.get("title", mdfile.stem)) meta["title"] = title body = convert_image_links(body) body = convert_note_links(body) final = build_frontmatter(meta) + body.strip() + "\n" out_dir = DST / rel.parent out_dir.mkdir(parents=True, exist_ok=True) (out_dir / f"{title}.md").write_text(final, encoding="utf-8") def main(): print("Copying attachments...") copy_resources() print("Processing notes...") for md in SRC.rglob("*.md"): if "_resources" not in str(md): process_md(md) print("DONE") if __name__ == "__main__": main() ```

Added to the website's docs page. Migration under the Getting started section.

Added to the website's docs page. Migration under the Getting started section.

Oh !!! Realy, really proud in help in anyway...It's a shame that I can't "maintain" or give advise on this..Worked for me...I'm using your app instead of Joplin happily ...Just the issue with the drag'n'drop of notebooks, but it's a minor thing.....

Oh !!! Realy, really proud in help in anyway...It's a shame that I can't "maintain" or give advise on this..Worked for me...I'm using your app instead of Joplin happily ...Just the issue with the drag'n'drop of notebooks, but it's a minor thing.....

@davi-jorge-art wrote in #58 (comment):

Oh !!! Realy, really proud in help in anyway...It's a shame that I can't "maintain" or give advise on this..Worked for me...I'm using your app instead of Joplin happily ...Just the issue with the drag'n'drop of notebooks, but it's a minor thing.....

Great to hear! The drag and drop issue is on the top priority list to fix in the next release.

@davi-jorge-art wrote in https://codeberg.org/ArkHost/HelixNotes/issues/58#issuecomment-15033591: > Oh !!! Realy, really proud in help in anyway...It's a shame that I can't "maintain" or give advise on this..Worked for me...I'm using your app instead of Joplin happily ...Just the issue with the drag'n'drop of notebooks, but it's a minor thing..... Great to hear! The drag and drop issue is on the top priority list to fix in the next release.

Hi, I just used this migration script and wanted to provide some feedback.

  1. There's a small bug with the script. The docstring contains unicode, so we need to use r on the first line:
"""
Joplin → HelixNotes Migration Script (Generic Version)

becomes

r"""
Joplin → HelixNotes Migration Script (Generic Version)
  1. Joplin has a "To-do" variant of notes (where it shows a todo checkbox on the note itself). This is different from regular to-dos inside of markdown files. In the MD+Frontmatter export, this is represented by completed?: yes or completed?: no at the end of the header:
---
title: Make sure to check the groceries❗ 
updated: 2022年12月26日 01:23:45Z
created: 2022年10月06日 01:23:45Z
completed?: yes
---

vs a normal note

---
title: A regular note
updated: 2022年12月26日 01:23:45Z
created: 2022年10月06日 01:23:45Z
---

AFAIK HelixNotes doesn't have this as a feature yet, but it would be cool to have. I think that's the only thing missing for me to be able to switch everything over seamlessly.

Right now, it just imports all of the "to-do" files as regular notes, which means I can't really tell which notes are to-dos, and/or which ones are already completed.

image

Hi, I just used this migration script and wanted to provide some feedback. 1) There's a small bug with the script. The docstring contains unicode, so we need to use `r` on the first line: ```py """ Joplin → HelixNotes Migration Script (Generic Version) ``` becomes ```py r""" Joplin → HelixNotes Migration Script (Generic Version) ``` 2) Joplin has a "To-do" variant of notes (where it shows a todo checkbox on the note itself). This is different from regular to-dos inside of markdown files. In the MD+Frontmatter export, this is represented by `completed?: yes` or `completed?: no` at the end of the header: ``` --- title: Make sure to check the groceries❗ updated: 2022年12月26日 01:23:45Z created: 2022年10月06日 01:23:45Z completed?: yes --- ``` vs a normal note ``` --- title: A regular note updated: 2022年12月26日 01:23:45Z created: 2022年10月06日 01:23:45Z --- ``` AFAIK HelixNotes doesn't have this as a feature yet, but it would be cool to have. I think that's the only thing missing for me to be able to switch everything over seamlessly. Right now, it just imports all of the "to-do" files as regular notes, which means I can't really tell which notes are to-dos, and/or which ones are already completed. ![image](/attachments/27e2d42c-fe3c-4b79-a84f-c5eaee2c403f)

@aman-anas wrote in #58 (comment):

Hi, I just used this migration script and wanted to provide some feedback.

1. There's a small bug with the script. The docstring contains unicode, so we need to use `r` on the first line:

..................

Hello Aman ! Thank you very much as user of the Helix app ! As I've said I don't have real programming skills ...I just had programing basics decades back ... "I've" made this with prompts to an IA ,testing, and prompting again several times...Nothing like real knowledge that you may have...

About ToDo's...One of the things that upset me is the way Joplin treat that...I've tried a lot to adjust my workflow to Jolplin until I've just gave up to work to a tool that have to work for me.....Maybe is why I've not noted the "todo migration issue".

And as far as I know, the migration from Joplin is not a priority for the developer...Maybe if you take charge of it I think a lot of unhappy users of Joplin like you and me will go for Helix not thinking twice...

I'm in fact divided...I've made this issue : #62 (comment)

I crave not to switch from app to work with my notes and tasks...But I'm doubtful if it in fact a good thing....

If it is not too much to ask from a repository/manager/etc of personal reference to be a task manger too and work smoothly for the two purposes...

My idea, that it can work in some way is to "separate" things....Like Daily notes....Don't know if it is the best and easy ....

There's this app "Todosian" (https://github.com/isotjs/todosian-app) . Take a look at it and in https://github.com/isotjs/todosian-app/issues/17#issuecomment-4451021722 ...

If you open a folder with at least one .md file, starts to make tasks on "Todosian" and open it on Helix the "only" problem is that it uses Obsidian task standards...And Helix don't see (? render ? parse?) this ...If there would a Todosian "inside" Helix that uses tasks.md standard...

Or at least a "Todelix" ( bad name, nice idea) , even separated from helix ...With priorities, subtasks , etc..By now I'm really happy to have Helix as my "Final" note taker app. Another simple app : https://github.com/RoBoT095/printnotes if "tweaked" to manage tasks will be great !

@aman-anas wrote in https://codeberg.org/ArkHost/HelixNotes/issues/58#issuecomment-15431465: > Hi, I just used this migration script and wanted to provide some feedback. > > 1. There's a small bug with the script. The docstring contains unicode, so we need to use `r` on the first line: > > .................. Hello Aman ! Thank you very much as user of the Helix app ! As I've said I don't have real programming skills ...I just had programing basics decades back ... "I've" made this with prompts to an IA ,testing, and prompting again several times...Nothing like real knowledge that you may have... About ToDo's...One of the things that upset me is the way Joplin treat that...I've tried a lot to adjust my workflow to Jolplin until I've just gave up to work to a tool that have to work for me.....Maybe is why I've not noted the "todo migration issue". And as far as I know, the migration from Joplin is not a priority for the developer...Maybe if you take charge of it I think a lot of unhappy users of Joplin like you and me will go for Helix not thinking twice... I'm in fact divided...I've made this issue : https://codeberg.org/ArkHost/HelixNotes/issues/62#issue-5085849 I crave not to switch from app to work with my notes and tasks...But I'm doubtful if it in fact a good thing.... If it is not too much to ask from a repository/manager/etc of personal reference to be a task manger too and work smoothly for the two purposes... My idea, that it can work in some way is to "separate" things....Like Daily notes....Don't know if it is the best and easy .... There's this app "Todosian" (https://github.com/isotjs/todosian-app) . Take a look at it and in https://github.com/isotjs/todosian-app/issues/17#issuecomment-4451021722 ... If you open a folder with at least one .md file, starts to make tasks on "Todosian" and open it on Helix the "only" problem is that it uses Obsidian task standards...And Helix don't see (? render ? parse?) this ...If there would a Todosian "inside" Helix that uses tasks.md standard... Or at least a "Todelix" ( bad name, nice idea) , even separated from helix ...With priorities, subtasks , etc..By now I'm really happy to have Helix as my "Final" note taker app. Another simple app : https://github.com/RoBoT095/printnotes if "tweaked" to manage tasks will be great !

Closing this issue for now. This issue is directly linked from the HelixNotes documentation. If modifications needs to be done. Please re-open this issue.

Closing this issue for now. This issue is directly linked from the HelixNotes documentation. If modifications needs to be done. Please re-open this issue.
Sign in to join this conversation.
No Branch/Tag specified
main
fix/vault-indication
v1.3.3
v1.3.2
v1.3.1
v1.3.0
v1.2.9
v1.2.8
v1.2.7
v1.2.6
v1.2.5
v1.2.4
v1.2.3
v1.2.2
v1.2.1
v1.2.0
v1.1.9
v1.1.8
v1.1.7
v1.1.6
v1.1.5
v1.1.4
v1.1.3
v1.1.2
v1.1.1
v1.1.0
v1.0.9
v1.0.8
v1.0.7
v1.0.6
v1.0.5
v1.0.4
v1.0.3
v1.0.2
v1.0.1
v1.0.0
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ArkHost/HelixNotes#58
Reference in a new issue
ArkHost/HelixNotes
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?