Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32
Feature-rich Python client for interacting with the API and ETAPI of Trilium Notes. 用于与Trilium Notes的API和ETAPI交互的Python多功能客户端
License
Nriver/trilium-py
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
English |简体中文
Python client for Trilium Note's ETAPI and Web API, with additional advanced features :)
- 🐍 trilium-py
- 🦮 Table of Contents
- 🔧 Installation
- 🚀 Initialization
- 📖 (Basic) ETAPI Usage
- 📊 Application Information
- 🔍 Search note
- 🏭 Create Note
- 👀 Get note
- 🔄 Update note
- 🗑️ Delete note
- 📄 Create note clone/branch
- 🔍 Get note clone info
- ✏️ Modify note clone info
- ❌ Delete note clone
- 📅 Day note
- 📤 Export note
- 📥 import note
- 💾 Save revision
- 💾 Create data backup
- 🏷 Create attribute
- Get attachment list
- Get attachment info
- Update attachment info
- Get attachment content
- Update attachment content
- Create attachment
- (Advanced Usage) ✅ TODO List
- (Advanced Usage) 🚚 Upload Markdown files
- (Advanced Usage) 🎨 Beautify notes
- (Advanced Usage) 🧹 Sort note content
- (Advanced Usage) 🧹 Delete empty new note
- (Advanced Usage) 🗜️ Optimize image size
- (Advanced Usage) 🔗 Automatically Add Internal Links
- (Advanced Usage) 📝🌳 Traverse Note Tree
- (Advanced Usage) 🗓️🔁 Periodic TODOs
- (Basic) Web API Usage
- 🛠️ Develop
- 🔗 Original OpenAPI Documentation
- 💰 Donation
- 🙏 Thanks
python3 -m pip install trilium-py --user
In the following code example,ea stands for ETAPI, andwa stands for Web API. Please perform the correspondinginitialization process based on whether you need to use the ETAPI or the Web API.
If you have a ETAPI token, change theserver_url andtoken to yours.
fromtrilium_py.clientimportETAPIserver_url='http://localhost:8080'token='YOUR_TOKEN'ea=ETAPI(server_url,token)
If you haven't created ETAPI token, you can create one with your password. Please note, you can only see this tokenonce, please save it if you want to reuse the token.
fromtrilium_py.clientimportETAPIserver_url='http://localhost:8080'password='1234'ea=ETAPI(server_url)token=ea.login(password)print(token)
After initialization, you can use Trilium ETAPI with python now.
You need to login every time you use the web API due to the CSRF limit.
from trilium_py.src.trilium_py.web_client import WEBAPIserver_url = 'http://localhost:8080'password = '1234'wa = WEBAPI(server_url)wa.login(password)After initialization, you can use Trilium web API with python now.
These are basic function that Trilium's ETAPI provides. Down below are some simple example code to use this package.
To start with, you can get the application information like this.
print(ea.app_info())
It should give you the version of your server application and some extra information.
Search note with keyword.
res=ea.search_note(search="python",)forxinres['results']:print(x['noteId'],x['title'])
Search with regular expression. For example, search and get all child notes under certain note:
res=ea.search_note(# regular expression search for note titlesearch="note.title %= '.*'",ancestorNoteId="Parent Note ID",fastSearch=False,orderBy=["title"],limit=100,)
Note:limit will not work withoutorderBy.
You can create a simple note like this.
res=ea.create_note(parentNoteId="root",title="Simple note 1",type="text",content="Simple note example",noteId="note1")
ThenoteId is not mandatory, if not provided, Trilium will generate a random one. You can retrieve it in the return.
noteId=res['note']['noteId']
Image note is a special kind of note. You can create an image note with minimal information like this. Theimage_filerefers to the path of image.
res=ea.create_image_note(parentNoteId="root",title="Image note 1",image_file="shield.png",)
To retrieve the note's content.
ea.get_note_content("noteid")
You can get a note metadata by its id.
ea.get_note(note_id)
Update note content
ea.update_note_content("noteid","updated by python")
Modify note title
ea.patch_note(noteId="noteid",title="Python client moded",)
Simply delete a note by id.
ea.delete_note("noteid")
In the ETAPI, this is called a branch, but in the Trilium UI it represents a note clone.
Here is an example to create a clone ofnote2 undernote1.
res = ea.create_branch( noteId="note2", parentNoteId="note1",)It will return the note clone info like this:
{ 'branchId': 'note1_note2', 'noteId': 'note2', 'parentNoteId': 'note1', 'prefix': None, 'notePosition': 10, 'isExpanded': False, 'utcDateModified': '2025-10-21T03:25:16.397Z'}Retrieve details about a specific note clone (branch) by its branchId
res = ea.get_branch(branchId="note1_note2")Update an existing note clone’s display attributes such as prefix, position, or expansion state
res = ea.patch_branch(branchId="note1_note2", notePosition=0, prefix="patched", isExpanded=False)Remove a note clone (branch) from the tree.
res = ea.delete_branch(branchId="note1_note2")You can get the content of a certain date withget_day_note. The date string should be in format of "%Y-%m-%d", e.g. "2022-02-25".
ea.get_day_note("2022-02-25")
Then set/update a day note withset_day_note. The content should be a (html) string.
ea.set_day_note(date,new_content)
Export note comes in two formatshtml ormarkdown/md. SettingnoteId toroot will export all notes.
res=ea.export_note(noteId='sK5fn4T6yZRI',format='md',save_path='/home/nate/data/1/test.zip',)
This is the built-in feature in trilium. The input file should be a zip file.
res=ea.export_note(noteId='sK5fn4T6yZRI',file_path='/home/nate/data/1/test.zip',)
Save note revision manually.
res = ea.save_revision( noteId='MJzyFRXAVaC9',)This example will create a database backup file like thistrilium-data/backup/backup-test.db.
res=ea.backup("test")
You can use the cron utility in Linux to schedule regular automatic backups. For example, to set up a daily backup at 3:00 AM, you would use the following cron expression:
0 3*** python /path/to/backup-script.py
You can create a tag for a note
res=ea.create_attribute(noteId='noteid',type='label',name='name_of_the_tag',value='value_of_the_tag',isInheritable=True)
ThenoteId is not mandatory, if not provided, Trilium will generate a random one. You can retrieve it in the return.
noteId=res['note']['noteId']
Get all attachments of a single note.
res = ea.get_attachments('uMJt0Ajr1CuC')Get image title and etc.
res=ea.get_attachment('Y5V6pYq6nwXo')
Change image title and etc.
res=ea.update_attachment(attachmentId='2b7pPzqocS1s',title='hello etapi',role='image',mime='image/png')
Get the real image file
res=ea.get_attachment_content('icpDE4orQxlI')withopen('1.png','wb')asf:f.write(res)
Replace the image with new one
res=ea.update_attachment_content('icWqV6zFtE0V','/home/nate/data/1.png')
Upload a image file as attachment of a note.
res=ea.create_attachment(ownerId='8m8luXym5LxT',file_path='/home/nate/data/ksnip_20230630-103509.png',)
With the power of Python, I have expanded the basic usage of ETAPI. You can do something with todo list now.
You can useadd_todo to add a TODO item, param is the TODO description
ea.add_todo("买暖宝宝")
param is the index of the TODO item
ea.todo_check(0)ea.todo_uncheck(1)
Useupdate_todo to update a TODO item description at certain index.
ea.update_todo(0,"去码头整点薯条")
Remove a TODO item by its index.
ea.delete_todo(1)
As the title suggests, you can move yesterday's unfinished things to today. Unfinished todo's will be deleted fromyesterday's note.
ea.move_yesterday_unfinished_todo_to_today()
You can import Markdown file with images into Trilium now! Trilium-py will help you to upload the images and fix thelinks for you!
res=ea.upload_md_file(parentNoteId="root",file="./md-demo/manjaro 修改caps lock.md",)
When uploading Markdown files that contain dollar signs ($) which are not meant to be interpreted as math formulas, youcan disable the math formula parsing:
res=ea.upload_md_file(parentNoteId="root",file="./md-demo/document_with_dollar_signs.md",parse_math=False,# Disable math formula parsing)
You can upload a folder with lots of Markdown files to Trilium and preserve the folder structure!
Say, upload all the notes fromVNote, simply do this:
res=ea.upload_md_folder(parentNoteId="root",mdFolder="~/data/vnotebook/",ignoreFolder=['vx_notebook','vx_recycle_bin','vx_images','_v_images'],)
Joplin can be imported effortlessly.
res=ea.upload_md_folder(parentNoteId="root",mdFolder="/home/nate/data/joplin_data/",ignoreFolder=['_resources', ],)
res=ea.upload_md_folder(parentNoteId="root",mdFolder="/home/nate/data/logseq_data/",ignoreFolder=['assets','logseq'],)
Obsidian has a very unique linking system for files. You should useobsidian-export to convert a Obsidian vault to regular Markdown files. Then you should beable to import the note into Trilium with trilium-py.
Convert it first.
obsidian-export /path/to/your/vault /out
Then import just like a normal markdown, trilium-py will handle the images for you.
res=ea.upload_md_folder(parentNoteId="root",mdFolder="E:/data/out",)
Youdao does not provide an export feature anymore. Luckily, you can usehttps://github.com/DeppWang/youdaonote-pull todownload your notes and convert them into markdown files. After that, trilium-py should be able to help you import them.
res=ea.upload_md_folder(parentNoteId="root",mdFolder="/home/nate/gitRepo/youdaonote-pull/out/",)
You need to convert Turtl from json to markdown first.Seeturtl-to-markdown for details.
Then you can import with trilium-py like this:
res=ea.upload_md_folder(parentNoteId="root",mdFolder="/home/nate/gitRepo/turtl-to-markdown/out/",ignoreFolder=['_resources'],)
In general, markdown files have variety of standards. You can always try import them with
res=ea.upload_md_folder(parentNoteId="root",mdFolder="/home/nate/data/your_markdown_files/",)
If there is any problem, please feel free to create anissue.
Similarly, you can disable math formula parsing when importing a folder of Markdown files:
res=ea.upload_md_folder(parentNoteId="root",mdFolder="~/data/financial_documents/",parse_math=False,# Disable math formula parsing for all files in the folder)
Because of the constraints imposed by the library utilized by Trilium, imported notes may experience minor formattingproblems. These issues include an additional line appearing at the end of code blocks, images becoming integrated withthe note content, and the absence of line breaks between headings, resulting in a cramped appearance of the notecontent.
When collecting information, some notes copied from theweb, saved viaclipping plugins, or imported from *other applications* may contain redundant line breaks, inconsistent heading levels, or messy formatting, making themlook cluttered.
Beautify notes will automatically cleans up unnecessary content, normalizes headings and paragraph layouts, and makesyour notes cleaner, clearer, and easier to read.
Here is what you can do to beautify your note.
Specify a note id to beautify note content.
ea.beautify_note('krm8B9JthNfi')
ea.beautify_sub_notes('tlPuzU2szLJh')
Sort a note by the heading names. This feature could prove invaluable for notes containing extensive lists, such as booktitles sorted into various genres. It's equally useful for managing browser bookmarks or collecting links.
Additionally, you have the option to specify a language code for sorting based on your local language. This enhances thesorting process and tailors it to your linguistic preferences.
res=ea.sort_note_content('lPxtkknjR2bJ')res=ea.sort_note_content('y6hROhWjNmHQ','zh_CN.UTF-8')
Sometimes I inadvertently create numerous "new notes" which remain undeleted within my note tree. These "new notes"clutter myworkspace, scattered across various locations. I made this bulk deletion of these empty "new notes." Additionally, itgenerates warning messages for "new notes" that contain content, maybe we should change the title for those notes.
ea.delete_empty_note()
Try to reduce image size by using PIL's optimize feature. If the image in your note is not compressed, you can try this.I've successfully convert a note of 44MB to 9.9MB after this process. Backup your data before try this.
The default quality is set to 90.
optimize_image_attachments will keep the original image format and try to compress it.
ea.optimize_image_attachments('uMJt0Ajr1CuC')To save even more space, you can try the following method.
Theoptimize_image_attachments_to_webp function converts images to theWebP format, significantly reducing filesizes. Based on my experience,WebP images can be as little as 25% to 50% of the size ofPNG images.
ea.optimize_image_attachments_to_webp('H2q3901uFDCH')This action can save significant space if you have many clipped pages. Whoever inventedWebP is a genius.
This feature allows you to automatically create internal links within your notes. Let’s take a look at how it works.
Here is a sample note:
After running a single line of code:
auto_create_internal_link('put_note_id_here')
The note transforms into this:
As you can see, some text has been replaced with internal links. The feature follows these rules:
- Title Match: Content that matches any other note's title is replaced with an internal link.
- Duplicate Titles Ignored: If multiple notes share the same title, no link is created for that title.
- Longer Matches First: Longer titles take precedence. For example, in the above example, "Nate River" is linked,not just "River."
- Existing Links Remain: Pre-existing links in the text are left untouched.
However, some words like "make" and "work" in the example are part of my "English Words That I Do Not Know" note. Sincethey are common and frequently used, I don’t want them to create excessive internal links.
To prevent certain notes from being linked:
- Add the tag
#ignoreAutoInternalLinkto a note. This note (and optionally its sub-notes) will be excluded from linkcreation. - You can make itinheritable—you can apply it to a parent note and inheritable, then it will be automaticallyexclude all its sub-notes.
Here’s how it looks after applying the exclusion rule:
The result is cleaner and more intentional.
When multiple notes share the same title, a specific condition allows for internal links:
- Direct Sub-Notes Have Priority: Direct child notes take precedence over other notes with the same title.
For instance:
In this case, the noteTriliumNext links "How to compile" to its own child note, not the one fromTrilium.
A note will never create an internal link to itself.
Add an internal link to a specific note by its ID:
auto_create_internal_link('gLmmsIM8yPqx')
Add internal links for multiple notes:
auto_create_internal_link(target_notes=['gLmmsIM8yPqx','T4Ui3wNByO03'])
(Experimental - Use with Caution)Add internal links to all text notes:
This is an experimental feature.Backup your database before using it, as it may irreversibly modify your notes. Ifissues occur, please provide a minimal note sample to help debug.
auto_create_internal_link(process_all_notes=True)
Fetch a note's title and content along with its descendants'. Great for reassembling large notes split into smallerchild notes.
The method can bebfs ordfs.
res=ea.traverse_note_tree('XdOlGz7MeYWC',depth=3,limit=100,method='bfs')forxinres:logger.info(x)
You can use add_periodic_todos to automatically add recurring tasks (daily, weekly, monthly, yearly).If a task is due today and not already in today’s TODO list, it will be added automatically.
periodic_todos= [# Stretch body every day {"content":"Stretch body","type":"daily"},# Buy milk for Nriver every day :) {"content":"Buy milk for Nriver :)","type":"daily"},# Wash clothes on every Saturday {"content":"Wash clothes","type":"weekly","weekday":6},# Write monthly report on the first day of every month {"content":"Write monthly report","type":"monthly","day":1},# Pay rent on the last day of every month {"content":"Pay rent","type":"monthly","day":-1},# Buy anti-allergy medicine on the 10th of every month {"content":"Buy anti-allergy medicine","type":"monthly","day":10},# Annual review every year on December 31 {"content":"Annual review","type":"yearly","month":12,"day":31},# Happy new year on January 1 :) {"content":"Happy new year :)","type":"yearly","month":1,"day":1},# Pay bills on the 15th of every month until 2077-01-01 {"content":"Pay bills","type":"monthly","day":15,"end_date":"2077-01-01"},# Team meeting every Monday starting from 2025-01-01 {"content":"Team meeting","type":"weekly","weekday":1,"start_date":"2025-01-01"},# Special project from 2025-09-26 to 2025-09-30 {"content":"Work on special project","type":"daily","start_date":"2025-09-26","end_date":"2025-09-30"},]ea.add_periodic_todos(periodic_todos)
These features are made based on the web API from Trilium's web client. Be sure you have donetheinitialization before use it.
wa.share_note('your_note_id')wa.cancel_share_note('RfhYrtyQLU8o')
Install with pip egg link to make package change without reinstall.
python-mpipinstall--user-e .
The original OpenAPI document ishere. Youcan open it withswagger editor.
If you enjoy usingTrilium-py, your support would mean a lot!
ThanksMatt Wilkie for donating 10.0 USD!
About
Feature-rich Python client for interacting with the API and ETAPI of Trilium Notes. 用于与Trilium Notes的API和ETAPI交互的Python多功能客户端
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors13
Uh oh!
There was an error while loading.Please reload this page.






