Skip to content
Author Nejat Hakan
eMail nejat.hakan@outlook.de
PayPal Me https://paypal.me/nejathakan


Note Taking Trilium Notes

Introduction

Welcome to the comprehensive guide on self-hosting Trilium Notes, a powerful hierarchical note-taking application focused on building personal knowledge bases. In the realm of self-hosting, taking control of your own data and tools is paramount. Note-taking applications are particularly crucial as they often hold our most valuable thoughts, research, plans, and knowledge. Relying solely on third-party cloud services for such critical data can raise concerns about privacy, data ownership, cost, and long-term availability. Trilium Notes offers a compelling open-source, self-hosted alternative, putting you firmly in control.

This guide is designed for university students and enthusiasts eager to dive deep into the world of self-hosting and knowledge management. We will embark on a journey starting from the fundamental concepts and basic setup, progressing through intermediate features and customization, and finally delving into advanced techniques for optimization, security, and integration. Our approach emphasizes not just the "how" but also the "why," providing detailed explanations to foster a thorough understanding.

We will cover various aspects, including:

  • Understanding Trilium's Philosophy: Why it's structured the way it is (hierarchical, focus on relations).
  • Installation Methods: Primarily focusing on Docker for ease of deployment and management, but also touching upon native installations.
  • Core Features: Navigating the interface, creating and organizing notes, understanding attributes and relations.
  • Data Management: Backup, restore, and synchronization strategies.
  • Customization: Themes, scripting, and tailoring Trilium to your specific needs.
  • Advanced Topics: Performance tuning, security hardening using reverse proxies, leveraging the API.

Each theoretical section is complemented by a hands-on "Workshop" designed to solidify your understanding through practical, step-by-step exercises. These workshops simulate real-world scenarios, enabling you to apply the concepts learned immediately. By the end of this guide, you will not only have a functional self-hosted Trilium Notes instance but also the knowledge and confidence to manage, customize, and troubleshoot it effectively, creating a truly personal and powerful knowledge management system.

Let's begin by exploring the foundational aspects of Trilium Notes.

1. Getting Started The Basics

This initial section focuses on getting you up and running with your own Trilium Notes instance. We'll cover the essential background, different installation methods, the first launch experience, and the fundamental concepts you need to start organizing your knowledge effectively.

Understanding Trilium's Core Concepts

Before diving into installation, it's crucial to grasp the fundamental philosophy and structure of Trilium Notes. Unlike simpler note-taking apps that might use folders or tags as primary organization methods, Trilium is built around a hierarchical tree structure, much like a file system. Every piece of information in Trilium, whether it's a simple text note, an image, a code snippet, or even a bookmark, is considered a "note."

Key Concepts:

  • Notes as Building Blocks: Everything is a note. A note can contain text, images, files, code, diagrams, and more. Even folders are just notes that can contain other notes (child notes).
  • Hierarchy (Tree Structure): Notes are organized in a tree. Each note (except the root) has exactly one parent note. This enforces a clear structure and location for every piece of information. You navigate this tree similar to how you navigate folders on your computer.
  • Attributes: Notes can have attributes (metadata). These are key-value pairs you define. Common attributes include #label (for tagging), #template (for creating reusable note structures), and #iconClass (for customizing icons). Attributes are themselves notes, promoting consistency. You can create your own custom attributes.
  • Relations: Beyond the parent-child hierarchy, notes can be explicitly linked using "relations." This allows you to create complex knowledge graphs, connecting related ideas, concepts, or tasks regardless of their position in the tree. For example, a note about a specific "Project A" could have relations pointing to related "Meeting Notes," "Contact Persons," or "Reference Materials," even if those notes reside in different parts of the tree.
  • Cloning: Trilium allows "cloning" notes. A cloned note appears in multiple places in the tree but is still the same note. Editing the original or any clone updates all instances simultaneously. This is incredibly powerful for avoiding duplication while still allowing information to be contextually relevant in different locations. For example, a "Contact Person" note could be cloned under multiple "Projects" they are involved in.
  • Note Types: While everything is fundamentally a note, different "types" dictate how the note behaves and renders. Common types include Text, Code, Image, File, Mermaid Diagram, Canvas, Web View, and more. The type is often determined automatically (e.g., when uploading an image) but can also be set manually.
  • Journal: Trilium has a built-in daily journal feature, automatically creating a note for each day under a dedicated "Journal" branch. This is excellent for daily logs, tracking progress, or capturing fleeting thoughts.

Understanding these core concepts is vital because they influence how you should structure your information within Trilium for maximum benefit. The hierarchical nature encourages structured thinking, while attributes, relations, and cloning provide flexibility to connect and reuse information non-linearly.

Workshop Understanding the Concepts Through Examples

Goal: To mentally map real-world information onto Trilium's core concepts before installing.

Scenario: Imagine you are a university student taking several courses, working on projects, and managing personal tasks. Let's think about how you might organize this information using Trilium's principles.

Steps:

  1. Identify Information Types: List the different kinds of information you need to manage.
    • Example: Course Notes (Physics 101, CompSci 202), Project Details (Lab Report Alpha, Research Paper Beta), Meeting Notes, To-Do Lists, Contacts (Professor Smith, Project Partner Jane), Reference Papers (PDFs), Code Snippets, Personal Journal Entries, Bookmarks (Useful websites).
  2. Design a Basic Hierarchy: Sketch out a potential top-level tree structure on paper or in a simple text editor.
    • Example Tree:
      My Knowledge Base (Root)
      ├── Academia
      │   ├── Courses
      │   │   ├── Physics 101
      │   │   │   ├── Lecture Notes
      │   │   │   ├── Assignments
      │   │   │   └── Syllabus.pdf (File Note)
      │   │   └── CompSci 202
      │   │       ├── Lecture Notes
      │   │       ├── Code Examples (Code Note)
      │   │       └── Textbook Chapters (Text Notes)
      │   └── Projects
      │       ├── Lab Report Alpha
      │       │   ├── Drafts
      │       │   ├── Data Files (File Note)
      │       │   └── Related Research (Links/Notes)
      │       └── Research Paper Beta
      │           ├── Outline
      │           └── Bibliography (Collection of Reference Notes)
      ├── Personal
      │   ├── Tasks
      │   │   ├── Urgent
      │   │   └── Someday
      │   ├── Journal
      │   │   ├── 2024-01-15
      │   │   └── 2024-01-16
      │   └── Contacts
      │       ├── Professor Smith
      │       └── Jane Doe
      └── Resources
          ├── Code Snippets
          │   └── Python Utilities
          └── Bookmarks
              └── Web Development Tools
      
  3. Introduce Attributes: How could attributes enhance this structure?
    • Example:
      • Add #status=InProgress or #status=Completed attributes to Project notes (Lab Report Alpha, Research Paper Beta).
      • Add #priority=High attribute to Urgent Task notes.
      • Add #course=Physics101 attribute to Physics 101 Lecture Notes. This provides another way to find all Physics 101 related content, even if scattered.
      • Use labels like #TODO within any text note to mark specific action items.
  4. Incorporate Relations: Where would explicit links between notes be useful?
    • Example:
      • Create a relation from Lab Report Alpha note to the Professor Smith contact note (e.g., using a supervisor relation).
      • Link Meeting Notes related to Research Paper Beta directly to the Research Paper Beta note.
      • If a Code Snippet is used in CompSci 202, create a relation between them.
  5. Identify Cloning Opportunities: Where might the same information be relevant in multiple places?
    • Example:
      • The Jane Doe contact note could be cloned under Research Paper Beta if she's a collaborator, while the original stays under Contacts.
      • A specific Reference Paper.pdf note might be relevant to both Physics 101 and Lab Report Alpha. Clone the note into both locations instead of uploading it twice.

Outcome: By completing this exercise, you've proactively engaged with Trilium's organizational paradigm. You now have a clearer mental model of how its features work together, which will significantly aid you when you start using the application after installation. This foresight helps prevent chaotic structuring later on.

Installation Methods Explained

Trilium Notes offers flexibility in how it can be installed. The most common and generally recommended method for self-hosting applications, especially for ease of management, updates, and dependency handling, is using Docker. However, we will also briefly discuss native binary installation.

1. Docker Installation (Recommended)

Docker is a platform that allows you to package applications and their dependencies into isolated environments called "containers." This means you don't need to worry about installing specific versions of Node.js, databases, or other libraries directly onto your host operating system. Everything Trilium needs is bundled within its Docker image.

  • Why Docker?

    • Isolation: Trilium runs in its own container, separate from other applications and your host system's configuration. This prevents conflicts.
    • Consistency: The application runs the same way regardless of your underlying Linux distribution or operating system, as long as Docker is installed.
    • Ease of Updates: Updating Trilium is often as simple as pulling the latest Docker image and restarting the container.
    • Simplified Dependency Management: All dependencies are included in the image.
    • Portability: You can easily move your Trilium setup (container configuration and data volume) to another Docker host.
  • Prerequisites:

    • A host machine (physical server, virtual machine, or even a Raspberry Pi) running a Linux distribution (recommended), macOS, or Windows.
    • Docker Engine and Docker Compose installed on the host machine. (Docker Compose is highly recommended for managing container configurations easily).
  • Key Components in a Docker Setup:

    • Docker Image: A template containing the Trilium application, its runtime environment, and dependencies. You'll typically pull the official zadam/trilium image from Docker Hub.
    • Docker Container: A running instance of the Docker image. This is your live Trilium application.
    • Data Volume: Crucially, the Trilium application data (your notes, settings, etc.) needs to persist even if the container is stopped, removed, or updated. Docker volumes are used to store this persistent data outside the container, typically on the host filesystem. This ensures your notes are safe.
    • Port Mapping: You need to map a port from your host machine to the port inside the container that Trilium listens on (default is 8080). This allows you to access the Trilium web interface via your host's IP address and the mapped port (e.g., http://<your-server-ip>:8080).

2. Native Binary Installation

Trilium also provides pre-compiled binaries for Linux, Windows, and macOS. This involves downloading an archive, extracting it, and running the executable file directly on your host system.

  • Why Native Binary?

    • Simplicity (Sometimes): For a quick test on a local desktop, downloading and running the binary might seem faster than setting up Docker initially.
    • No Docker Dependency: If you are unwilling or unable to install Docker on your system.
  • Potential Downsides:

    • Dependency Management: You are responsible for ensuring any required system libraries are present and compatible.
    • Updates: Updating typically involves downloading the new binary, stopping the old process, replacing the files, and restarting. This can be more manual than the Docker approach.
    • Process Management: You need to manage running Trilium as a system process or service yourself to ensure it starts on boot and runs reliably in the background. This often involves creating systemd service files on Linux.
    • Isolation: Trilium runs directly on your host, potentially conflicting with other applications or system libraries.
    • Data Location: You need to be mindful of where the Trilium data directory (trilium-data) is created and manage its backups accordingly. By default, it's often created in the user's home directory or alongside the executable.

Recommendation: For a stable, manageable, and scalable self-hosted setup, Docker is strongly recommended. It aligns best with modern self-hosting practices. We will primarily focus on the Docker method in the workshops.

Workshop Installing Trilium Notes using Docker

Goal: To install Trilium Notes on your server using Docker and Docker Compose, ensuring data persistence.

Prerequisites:

  • A server (Linux recommended, e.g., Ubuntu/Debian) accessible via SSH.
  • Docker and Docker Compose installed on the server. If not, follow the official Docker installation guides for your distribution.
    • Quick Check: Run docker --version and docker compose version (or docker-compose --version for older versions). If these commands work, you're likely ready.
  • Basic command-line familiarity.

Steps:

  1. Connect to Your Server:
    • Open your terminal or SSH client.
    • Connect to your server: ssh your_username@your_server_ip
  2. Create a Directory for Trilium Configuration:
    • It's good practice to keep configuration files organized.
    • mkdir ~/trilium-docker
    • cd ~/trilium-docker
  3. Create a docker-compose.yml File:

    • This file defines the Trilium service, including the image to use, port mapping, and data volume.
    • Create the file using a text editor like nano: nano docker-compose.yml
    • Paste the following content into the file:

      version: '3.7' # Specifies the docker-compose version
      
      services:
        trilium:
          image: zadam/trilium:latest # Use the official Trilium image, latest version
          container_name: trilium-notes # A friendly name for the container
          restart: unless-stopped # Policy to automatically restart the container unless manually stopped
          ports:
            - "8080:8080" # Map host port 8080 to container port 8080
          volumes:
            # Mount a Docker volume named 'trilium_data' to the container's data directory
            # Docker manages this volume automatically within its own storage area (usually /var/lib/docker/volumes/)
            - trilium_data:/home/node/trilium-data
      
      volumes:
        trilium_data: # Define the named volume
          # You can specify options here if needed, but defaults are usually fine
      
    • Explanation:

      • version: '3.7': Defines the syntax version for Docker Compose.
      • services:: Starts the definition of the services (containers) to run.
      • trilium:: A custom name for our service.
      • image: zadam/trilium:latest: Tells Docker to use the latest official Trilium image from Docker Hub (zadam/trilium). You could pin this to a specific version like zadam/trilium:0.61.8 for stability.
      • container_name: trilium-notes: Assigns a specific, readable name to the running container.
      • restart: unless-stopped: Ensures the container restarts automatically if it crashes or if the Docker daemon restarts (e.g., after a server reboot), unless you explicitly stop it using docker stop trilium-notes.
      • ports: - "8080:8080": Maps port 8080 on your host machine to port 8080 inside the container, where the Trilium application listens. If port 8080 is already used on your host, you can change the host part (e.g., "8888:8080" would make Trilium accessible on http://your_server_ip:8888).
      • volumes: - trilium_data:/home/node/trilium-data: This is critical for data persistence. It tells Docker to mount a named volume called trilium_data into the container at the path /home/node/trilium-data. This is the directory inside the container where Trilium stores all its data (notes, settings, etc.). Docker manages the trilium_data volume on your host machine (typically in /var/lib/docker/volumes/). This ensures that even if you remove and recreate the container (e.g., during an update), your data remains safe in the volume.
      • volumes: trilium_data:: This section at the bottom explicitly defines the named volume trilium_data, allowing for potential future configuration if needed.
    • Save the file and exit nano (Ctrl+X, then Y, then Enter).

  4. Start the Trilium Container:

    • While still in the ~/trilium-docker directory (where your docker-compose.yml file is), run the following command:
      docker compose up -d
      
    • Explanation:
      • docker compose up: This command reads the docker-compose.yml file and creates/starts the services defined within it.
      • -d: This flag stands for "detached mode." It runs the container in the background and prints the container name, allowing you to continue using your terminal. Without -d, the container logs would occupy your terminal session.
    • Docker will first check if the zadam/trilium:latest image exists locally. If not, it will pull it from Docker Hub (this might take a few moments depending on your internet speed). Then, it will create and start the trilium-notes container and the trilium_data volume if they don't already exist.
  5. Verify the Container is Running:

    • You can check the status of your containers:
      docker ps
      
    • You should see an entry for trilium-notes, indicating it's "Up" and showing the port mapping (e.g., 0.0.0.0:8080->8080/tcp).
    • You can also view the container's logs (useful for troubleshooting):
      docker logs trilium-notes
      
      Press Ctrl+C to stop viewing logs.
  6. Access Trilium Notes:

    • Open your web browser.
    • Navigate to http://your_server_ip:8080. Replace your_server_ip with the actual IP address of your server.
    • You should be greeted by the Trilium Notes initial setup screen.

Troubleshooting Tips:

  • Port Conflict: If you get an error message about port 8080 already being in use, stop the container (docker compose down), edit the ports section in docker-compose.yml to use a different host port (e.g., "8888:8080"), save the file, and run docker compose up -d again. Then access Trilium using the new port (http://your_server_ip:8888).
  • Firewall: Ensure that the host port you chose (e.g., 8080) is allowed through your server's firewall (like ufw on Ubuntu). You might need to run a command like sudo ufw allow 8080/tcp.
  • Permissions: Docker volume permissions are usually handled correctly automatically. If you encounter persistent data saving issues (unlikely with named volumes), check Docker documentation regarding volume management.

Outcome: You have successfully deployed Trilium Notes using Docker and Docker Compose. Your instance is running, accessible via your browser, and critically, your data is stored persistently in a Docker volume, ready for the initial configuration.

First Launch and Initial Setup

After successfully installing Trilium (likely via Docker as per the workshop) and accessing it through your browser (http://your_server_ip:8080), you'll encounter the first-launch setup process. This is a straightforward but important step to secure your instance and establish your identity within Trilium.

The Setup Screens:

Trilium's initial setup guides you through configuring the essential parameters for your new knowledge base.

  1. Welcome Screen / Instance Type:

    • You'll likely be presented with an option asking if this is a new installation or if you want to sync with an existing sync server (relevant for multiple Trilium instances, covered later). For a fresh start, you'll choose the "New instance" or similar option.
    • You might also be asked if you are setting up a "Desktop" or "Server" instance. Since we are self-hosting for web access (even if accessing from your own desktop browser), choose Server installation. This typically sets defaults appropriate for a multi-user or remotely accessed instance (like enabling login protection).
  2. Set Administrator Password:

    • This is the most critical step for security. Trilium Notes, when run as a server, protects access with a password. You need to set a strong, unique password for the initial administrator account.
    • Importance: Anyone who knows your server's IP address and port could potentially access your notes if no password is set or if a weak password is used. Treat this password with the same care as your email or banking password.
    • Enter your desired password and confirm it. Store this password securely (e.g., in a password manager).
  3. Instance Identification (Optional but Recommended):

    • Trilium might ask for an "Instance Title" or similar identifier. This is helpful if you ever run multiple Trilium instances, perhaps for different purposes (e.g., "Work Notes," "Personal Knowledge Base"). It helps differentiate them, especially if you use synchronization features later. Choose a meaningful name like "My Personal Trilium" or "University Knowledge Hub."
  4. Setup Completion:

    • Once you've set the password, Trilium will finalize the setup. This usually involves creating the initial database structure and storing your password hash.
    • You will then be presented with the main Trilium Notes interface, likely showing an empty root note or some introductory welcome notes created by the application itself.

Post-Setup Interface Overview:

Upon successful setup, take a moment to familiarize yourself with the main Trilium interface. It typically consists of a few key areas:

  • Left Sidebar (Note Tree): This is the primary navigation area, displaying your hierarchical note structure. You can expand and collapse branches (notes with children) using the small triangles. Clicking on a note in the tree selects it and displays its content in the main area.
  • Main Content Area: This large central panel displays the content of the currently selected note. It includes a rich text editor for text notes, viewers for images or PDFs, code editors for code notes, etc.
  • Toolbar/Menubar: Usually located above the main content area or integrated with the note title, this provides options for editing, creating new notes, changing note types, managing attributes, and accessing other features.
  • Attributes Panel: Often displayed on the right sidebar or below the note content, this area shows the attributes (labels, relations, etc.) associated with the currently selected note. You can add, edit, or remove attributes here.
  • Search Bar: Typically located at the top, allowing you to quickly search through your notes by title or content.

Don't feel overwhelmed by the options. The core workflow initially involves creating notes, organizing them in the tree, and adding content. We'll explore advanced features progressively.

Workshop Navigating the Interface and Creating Your First Notes

Goal: To become comfortable with the Trilium interface by creating a basic structure and adding different types of notes.

Prerequisites:

  • Trilium Notes installed and running (from the previous workshop).
  • Access to the Trilium web interface in your browser.
  • The administrator password you set during the initial setup.

Steps:

  1. Log In:
    • If you are not already logged in, navigate to http://your_server_ip:8080.
    • You should see a login prompt. Enter the administrator password you created during setup.
  2. Explore the Initial State:
    • Observe the note tree on the left. You might see a single root note or some welcome/help notes provided by Trilium. Click on them to see their content.
    • Identify the main content area, the toolbar, and the attribute panel (if visible).
  3. Create Your First Note (Top Level):
    • Right-click on the root note (often named "Root" or represented by a house icon) in the note tree.
    • Select "Create new note inside" or a similar option.
    • A new note will appear in the tree, likely named "Untitled." The main content area will focus on this new note.
    • In the main content area, click on the title (where it says "Untitled") and rename it to something meaningful, like My University Notes. Press Enter.
  4. Create Child Notes (Building Hierarchy):
    • Right-click on the My University Notes note you just created in the tree.
    • Select "Create new note inside."
    • Rename this new child note to Courses.
    • Repeat the process: Right-click on Courses, create a new note inside, and name it Computer Science 101.
    • Create another note inside Courses named Physics 210.
    • Your tree should now look something like this:
      Root
      └── My University Notes
          └── Courses
              ├── Computer Science 101
              └── Physics 210
      
  5. Add Content to a Note:
    • Click on the Computer Science 101 note in the tree.
    • In the main content area, click below the title. You should see a blinking cursor.
    • Type some text, for example:
      Introduction to Computer Science.
      Professor: Dr. Ada Lovelace
      Topics:
          - Algorithms
          - Data Structures
          - Basic Programming Concepts
      
    • Trilium auto-saves frequently, but look for a save indicator or button if you want to be certain (often not explicitly needed).
  6. Create a Different Note Type (Code Note):
    • Right-click on Computer Science 101 in the tree.
    • Select "Create new note inside."
    • Name this note Hello World Example.
    • With Hello World Example selected, look for an option in the toolbar or note properties to change the note type. It might be a dropdown menu or an icon.
    • Select "Code" as the type. You might need to specify the language (e.g., Python, JavaScript). Choose "Python".
    • The main content area should now change to a code editor with syntax highlighting.
    • Enter some Python code:
      def greet(name):
          print(f"Hello, {name}!")
      
      greet("World")
      
  7. Create a Journal Note:
    • Look for a dedicated "Journal" section or button in the interface (often near the top or in the root of the tree).
    • Click the button or navigate to the Journal section. Trilium should automatically create or navigate you to a note titled with today's date (e.g., 2024-01-15).
    • Add an entry: Started setting up my Trilium Notes instance today. Created basic structure for university courses.
  8. Basic Navigation:
    • Click on different notes in the tree (My University Notes, Physics 210, the Journal entry, Hello World Example) to practice moving between them and seeing their content load in the main panel.
    • Use the triangles next to parent notes (My University Notes, Courses) to collapse and expand the branches.

Outcome: You have successfully navigated the basic Trilium interface, created a hierarchical structure, added text content, created a specialized code note, and made a journal entry. You should feel more comfortable with the fundamental operations of creating and organizing information within Trilium.

2. Intermediate Trilium Usage

Having mastered the basics of installation and fundamental note organization, we now move into Trilium's more powerful features. This section explores concepts that elevate Trilium from a simple note-taker to a true personal knowledge management system: attributes, relations, templates, cloning, basic scripting, and essential data management practices like backups.

Leveraging Attributes for Metadata

As introduced earlier, attributes are key-value pairs attached to notes, acting as metadata. They go beyond the rigid hierarchy, allowing you to categorize, filter, and add context to your notes in flexible ways. Think of them like tags, labels, or custom fields. In Trilium, even attributes themselves are notes, ensuring consistency and allowing you to define and manage them centrally.

Understanding Attribute Types:

  • Labels (#labelName): The most common type. Used for tagging, categorization, status tracking, etc. The # prefix is a convention indicating a label attribute. Examples: #project, #idea, #todo, #status=inprogress.
  • Relations (~relationName): Special attributes used to create explicit links between notes. The ~ prefix signifies a relation. We'll cover these in detail in the next sub-section. Examples: ~contactPerson, ~relatedProject, ~sourceDocument.
  • Templates (#template): Used to mark a note as a template, allowing you to quickly create new notes with predefined content or structure. Example: #template.
  • Internal Attributes (#key=value): Many internal Trilium features use attributes. Examples: #noteType=code, #iconClass="bx bx-code-block", #share, #readOnly. You can often modify these to customize note behavior or appearance.
  • Custom Attributes: You can create any attribute you need simply by typing #yourAttributeName=yourValue or ~yourRelationName in the attributes section of a note.

Creating and Managing Attributes:

  1. Adding Attributes:
    • Select the note you want to add an attribute to.
    • Locate the "Attributes" panel (often on the right or bottom).
    • Click the "Add Attribute" or "+" button.
    • Type the attribute name (e.g., #status). As you type, Trilium might suggest existing attributes.
    • If the attribute takes a value (like #status=pending), enter the value after an equals sign (=). If it's just a label tag (like #idea), you might not need a value, or Trilium might handle it as a boolean flag.
    • Press Enter to confirm. The attribute appears on the note.
  2. Attribute Inheritance: Attributes can be inherited from parent notes. If you add an attribute like #project=Alpha to a parent note (e.g., "Project Alpha Folder"), all child notes within it can optionally inherit this attribute. This is useful for applying context broadly. Look for "Inherited Attributes" usually displayed separately.
  3. Defining Attributes (Best Practice): While you can create attributes on the fly, it's good practice to define commonly used attributes as notes themselves.
    • Create a note (e.g., named Status Attributes) perhaps under a Templates or Metadata branch in your tree.
    • Inside this note, create child notes for each status: Pending, InProgress, Completed.
    • Add the attribute #label=status to the Status Attributes parent note.
    • Now, when you add the #status attribute to another note, Trilium might offer Pending, InProgress, Completed as predefined values, linking them to these definition notes. This promotes consistency.

Using Attributes for Organization and Search:

  • Filtering: The search function in Trilium can use attributes. You can search for "#status=inprogress" to find all notes with that specific status, regardless of their location in the tree. Or search for "#idea" to find all notes tagged as ideas.
  • Scripting: Attributes are heavily used in scripting (covered later) to control behavior, find specific notes, and automate tasks.
  • Visual Cues: You can use attributes like #iconClass (using icons from libraries like Boxicons or Font Awesome, depending on Trilium version/theme) or #color to visually distinguish notes in the tree.

Attributes transform Trilium from a simple tree into a multi-faceted database where information can be sliced, diced, and retrieved based on rich metadata.

Workshop Organizing Notes with Labels and Statuses

Goal: To use attributes, specifically labels, to categorize course notes and track the status of project tasks.

Prerequisites:

  • A running Trilium instance with the basic structure created in the previous workshop (including courses and potentially some placeholder project notes).

Scenario: You want to easily find all notes related to a specific course, regardless of whether they are lecture notes, assignments, or examples. You also want to track the progress of tasks related to your "Lab Report Alpha" project.

Steps:

  1. Define a Course Label Attribute:
    • Navigate to your My University Notes branch (or create a dedicated Metadata or Attributes branch if you prefer).
    • Right-click on it and create a new note named Attribute Definitions.
    • Right-click on Attribute Definitions and create a new note named Course Label.
    • Select the Course Label note. In its attributes panel, add the attribute #label=course. This tells Trilium that notes linked via the #course label relate to this definition.
  2. Define Status Label Attributes:
    • Right-click on Attribute Definitions and create a new note named Task Status.
    • Add the attribute #label=status to the Task Status note.
    • Right-click on Task Status and create three child notes: Not Started, In Progress, Completed.
    • (Optional) Add #iconClass or #color attributes to these status notes to make them visually distinct (e.g., #color=red for Not Started, #color=orange for In Progress, #color=green for Completed).
  3. Apply the Course Label:
    • Navigate to Courses -> Computer Science 101.
    • Select the main Computer Science 101 note.
    • In its attributes panel, click "Add Attribute."
    • Start typing #course. Trilium might auto-suggest based on the Course Label definition you created.
    • Set the value to Computer Science 101 (or you could link it directly to the definition note if your Trilium version supports relation-like labels easily). Add the attribute.
    • Go to the Hello World Example note (which is under Computer Science 101). Notice it might now show #course=Computer Science 101 under "Inherited Attributes." If not, or if you want to be explicit, add the #course=Computer Science 101 attribute directly to this note as well.
    • Navigate to Courses -> Physics 210. Select it.
    • Add the attribute #course=Physics 210.
  4. Apply Status Labels to Tasks:
    • Create a new top-level note (or under My University Notes) called Projects.
    • Inside Projects, create a note called Lab Report Alpha.
    • Inside Lab Report Alpha, create a few child notes representing tasks: Literature Review, Experiment Setup, Data Analysis, Write Report Draft.
    • Select the Literature Review task note.
    • In its attributes panel, add the attribute #status. Trilium should suggest the values you defined (Not Started, In Progress, Completed). Select Not Started.
    • Select the Experiment Setup task note. Add the attribute #status=Not Started.
    • Select the Data Analysis task note. Add #status=Not Started.
    • Select the Write Report Draft task note. Add #status=Not Started.
    • Now, imagine you've started the literature review. Select Literature Review again. Click on its #status attribute to edit it, and change the value to In Progress.
  5. Search Using Attributes:
    • Go to the search bar at the top of Trilium.
    • Type "#course=Computer Science 101" and press Enter. The search results should show the Computer Science 101 note and potentially the Hello World Example note.
    • Clear the search. Type "#status=In Progress" and press Enter. The search results should show the Literature Review task note.
    • Clear the search. Type "#status=Not Started" and press Enter. The results should show the other three task notes.

Outcome: You have successfully used attributes to add meaningful metadata to your notes. You've defined reusable labels for courses and statuses, applied them to relevant notes, observed inheritance, and utilized the search function to retrieve notes based on these attributes. This demonstrates how attributes provide a powerful layer of organization beyond the basic hierarchy.

Connecting Notes with Relations

While the hierarchy defines a primary parent-child structure and attributes add metadata, relations create explicit, named links between any two notes, regardless of their position in the tree. This is fundamental to building a true knowledge graph, representing connections like "is supervised by," "is referenced in," "depends on," or "is part of."

How Relations Work:

  • Relation Definition: Similar to labels, relations are typically defined by attributes starting with a tilde (~). For example, ~relatedTo, ~contactPerson, ~sourceMaterial.
  • Linking Notes: When you add a relation attribute to Note A, you set its value to be Note B. This creates a directed link from Note A to Note B, named by the relation.
  • Bidirectional View: Trilium usually displays relations bi-directionally. If Note A has a ~relatedTo relation pointing to Note B, when you view Note B, you'll often see an "incoming relation" or backlink indicating that Note A relates to it.
  • Relation Attributes are Notes: Just like labels, the relations themselves (e.g., the concept of ~relatedTo) can be defined as notes, often with a #relation=relatedTo attribute, allowing for consistency and potential metadata on the relation type itself.

Creating and Using Relations:

  1. Adding a Relation:
    • Select the source note (Note A) from which you want to create the link.
    • In the Attributes panel, click "Add Attribute."
    • Type the relation name, starting with ~ (e.g., ~supervisor).
    • In the value field, you need to specify the target note (Note B). Trilium usually provides a note picker/search interface here. Start typing the name of the target note (e.g., "Professor Smith"). Select the correct note from the suggestions.
    • Press Enter. The relation ~supervisor=Professor Smith is now added to Note A.
  2. Viewing Relations:
    • On Note A, you'll see the outgoing relation ~supervisor=Professor Smith in the attributes list. Clicking on "Professor Smith" will navigate you to that note.
    • Navigate to the "Professor Smith" note (Note B). Look for an "Incoming Relations" or "Links" section (the exact UI varies slightly by version/theme). You should see a link back from Note A, possibly labeled as supervisor of or similar, depending on how the relation is defined.
  3. Defining Relation Notes (Recommended):
    • Similar to labels, create notes to define your relations for consistency.
    • Create a note Relation Definitions (perhaps alongside Attribute Definitions).
    • Inside, create a note Supervisor Relation. Add the attribute #relation=supervisor to it.
    • You can even define an inverse relation. Edit the Supervisor Relation note and add another attribute like #inverseRelation=supervises. Now, when you link Note A to Note B with ~supervisor, Note B might automatically show an incoming link labeled supervises.

Use Cases for Relations:

  • Project Management: Link tasks (~dependsOn), meeting notes (~discusses), team members (~assignee).
  • Research: Link papers (~cites), concepts (~explains), experiments (~validates).
  • Personal Knowledge: Link ideas (~inspiredBy), people (~knows), places (~visited).
  • CRM: Link companies (~employs), contacts (~workedWith), deals (~relatedOpportunity).

Relations are the key to breaking free from purely hierarchical constraints and modeling the complex web of connections within your knowledge.

Workshop Linking Projects, Contacts, and Meetings

Goal: To use relations to connect a project note to a supervisor contact and related meeting notes.

Prerequisites:

  • A running Trilium instance.
  • The Projects -> Lab Report Alpha note structure from the previous workshop.

Scenario: Your "Lab Report Alpha" project is supervised by Professor Smith. You also had a meeting specifically about this project, and you want to link everything together logically.

Steps:

  1. Create Contact Notes:
    • Navigate to a suitable location (e.g., create a top-level Contacts note, or put it under My University Notes).
    • Create a new note named Professor Smith. Add some placeholder content like "Physics Department, Office 4B".
    • Create another contact note named Jane Doe (your potential lab partner).
  2. Create a Meeting Note:
    • Create a new top-level note named Meetings.
    • Inside Meetings, create a new note named 2024-01-17 Project Alpha Kick-off.
    • Add some placeholder content: "Discussed project scope, initial tasks assigned. Attendees: Professor Smith, Jane Doe, Me."
  3. Define Relations (Optional but Recommended):
    • Go to your Attribute Definitions note (or create one).
    • Create a child note named Supervisor Relation. Add attribute #relation=supervisor. Optionally add #inverseRelation=supervises.
    • Create a child note named Attendee Relation. Add attribute #relation=attendee. Optionally add #inverseRelation=attended.
    • Create a child note named Meeting Notes Relation. Add attribute #relation=meetingNotes. Optionally add #inverseRelation=notesFor.
  4. Link Project to Supervisor:
    • Navigate to the Projects -> Lab Report Alpha note.
    • In its attributes panel, click "Add Attribute."
    • Type ~supervisor.
    • In the value field, start typing Professor Smith. Select the Professor Smith contact note from the suggestions.
    • Press Enter. The attribute ~supervisor=[Professor Smith] (or similar link representation) should appear.
  5. Link Meeting to Project and Attendees:
    • Navigate to the Meetings -> 2024-01-17 Project Alpha Kick-off note.
    • Add an attribute ~notesFor. In the value field, select the Lab Report Alpha note.
    • Add an attribute ~attendee. In the value field, select Professor Smith.
    • Add another attribute ~attendee. In the value field, select Jane Doe.
    • (Self-reference) You could even add another ~attendee linking to your own user note if you have one, or just note it in the text.
  6. Explore the Connections:
    • Go back to the Lab Report Alpha note.
      • You should see the outgoing ~supervisor relation pointing to Professor Smith.
      • Look for incoming relations. You should see one from the 2024-01-17 Project Alpha Kick-off meeting note, possibly labeled notesFor.
    • Go to the Professor Smith note.
      • Look for incoming relations. You should see one from Lab Report Alpha (possibly labeled supervises) and one from the meeting note (possibly labeled attended).
    • Go to the 2024-01-17 Project Alpha Kick-off note.
      • You should see the outgoing ~notesFor relation to the project and the ~attendee relations to Professor Smith and Jane Doe.

Outcome: You have successfully established meaningful connections between different types of notes (project, contact, meeting) using relations. Clicking on these relations allows you to easily navigate between related pieces of information, creating a much richer context than a simple folder structure or tags alone could provide. This demonstrates the power of Trilium's graph capabilities.

Templates and Cloning for Efficiency

Managing information often involves repetition. You might have a standard structure for meeting notes, project briefs, bug reports, or character profiles. Similarly, information might be relevant in multiple contexts (e.g., a contact person involved in several projects). Trilium offers two powerful features to handle this efficiently: Templates and Cloning.

Templates:

  • Concept: A template is a regular Trilium note that you designate as a reusable pattern. When you create a new note from a template, the new note starts with a copy of the template's content, attributes, and even child notes.
  • Creating a Template:
    1. Create a new note that will serve as your template (e.g., "Standard Meeting Notes Template").
    2. Add the desired structure, placeholder text (e.g., "Attendees:", "Agenda:", "Action Items:"), and any standard attributes (e.g., #meeting).
    3. Add the special attribute #template to this note. This marks it as a template note.
    4. (Optional) Organize your templates. Keep them in a dedicated Templates branch in your note tree for easy management.
  • Using a Template:
    1. Right-click on the parent note where you want to create the new note (e.g., the Meetings note).
    2. Instead of "Create new note inside," look for an option like "Create new note from template."
    3. Trilium will present a list or search box for your available templates (notes marked with #template).
    4. Select your desired template (e.g., "Standard Meeting Notes Template").
    5. A new note will be created under the parent, pre-filled with the template's content and attributes. Rename it and fill in the specifics.

Cloning:

  • Concept: Cloning creates a mirror or instance of an existing note in another location in the tree. It's not a copy; it's the same note appearing in multiple places. Editing a clone updates the original and all other clones instantly.
  • Why Clone? Avoids data duplication and ensures consistency. If a contact's phone number changes, you update it once, and it's correct everywhere that contact note is cloned. It allows a single piece of information to exist logically within multiple contexts (hierarchies).
  • Creating a Clone:
    1. Find the original note you want to clone (e.g., the Professor Smith contact note).
    2. Right-click on the original note.
    3. Select "Clone note."
    4. Now, navigate to the destination parent note where you want the clone to appear (e.g., inside the Physics 210 course note, if Prof. Smith teaches it).
    5. Right-click on the destination parent note.
    6. Select "Paste note clone" or "Paste as clone."
    7. The Professor Smith note now appears under Physics 210 as well as in its original Contacts location. It's often visually distinguished slightly (e.g., italicized name or a link icon) to indicate it's a clone.
  • Identifying Clones: Trilium usually provides an indicator on cloned notes. There's often a "Clones" tab or section in the note properties that lists all locations where that note exists.
  • Deleting Clones vs. Deleting Originals: Deleting a clone instance usually just removes it from that specific location in the tree; the original and other clones remain. Deleting the original note (or the last remaining instance) typically deletes the note entirely (moving it to the trash bin). Be mindful of this distinction.

Templates streamline creation, while cloning streamlines organization and consistency for information relevant in multiple contexts.

Workshop Creating a Meeting Template and Cloning a Contact

Goal: To create a reusable template for meeting notes and clone a contact note to appear under a relevant project.

Prerequisites:

  • A running Trilium instance.
  • Notes created in previous workshops, including Contacts (Professor Smith, Jane Doe), Projects (Lab Report Alpha), and Meetings.

Scenario: You have frequent project meetings and want a standard format. You also want the contact details for Jane Doe (your lab partner) to appear directly within the Lab Report Alpha project folder for quick reference, without duplicating her main contact entry.

Steps:

  1. Create a Templates Branch:
    • Right-click on your root note (or My University Notes).
    • Create a new note named Templates.
  2. Create the Meeting Note Template:
    • Right-click on the Templates note.
    • Create a new note inside named Standard Meeting Template.
    • Select the Standard Meeting Template note.
    • In the main content area, add the following structure:
      **Date:** {{currentDate}}
      **Attendees:**
          -
      **Agenda:**
          1.
          2.
      **Discussion Notes:**
      
      **Action Items:**
          - [ ] Task 1 (Assignee: , Due: )
          - [ ] Task 2 (Assignee: , Due: )
      
      (Note: {{currentDate}} might be automatically replaced by the actual date by some Trilium features/scripts, or you might fill it manually. It serves as a placeholder here).
    • In the attributes panel for Standard Meeting Template, add the attribute #template.
    • (Optional) Add another attribute like #meetingNote to automatically categorize notes created from this template.
  3. Use the Meeting Note Template:
    • Navigate to your Meetings note.
    • Right-click on Meetings.
    • Select "Create new note from template."
    • Choose Standard Meeting Template from the list.
    • A new note appears under Meetings, pre-filled with your template structure. Rename it, for example, 2024-01-18 Lab Report Progress Meeting.
    • Fill in the details for this specific meeting (Attendees: Jane Doe, Me; Agenda: Review Literature Search, Plan Experiment; Action Items: Jane Doe to find sensor supplier by Friday).
  4. Clone the Contact Note:
    • Navigate to your Contacts branch and find the Jane Doe note.
    • Right-click on the Jane Doe note.
    • Select "Clone note."
    • Navigate to the Projects -> Lab Report Alpha note.
    • Right-click on Lab Report Alpha.
    • Select "Paste note clone" (or similar wording).
  5. Observe the Clone:
    • The Jane Doe note now appears under Lab Report Alpha. Notice if its appearance in the tree is slightly different (e.g., italics) to indicate it's a clone.
    • Click on this cloned Jane Doe note. In its properties or a dedicated tab, look for information about its clones. You should see it listed under both Contacts and Projects/Lab Report Alpha.
    • Go back to the original Jane Doe note under Contacts. Edit her note content (e.g., add a fake phone number: "Phone: 555-1234").
    • Now, navigate back to the cloned Jane Doe note under Lab Report Alpha. Verify that the phone number you just added is also visible there. This confirms that editing one instance updates all clones.

Outcome: You have successfully created and used a template to speed up the creation of structured notes (meeting minutes). You have also cloned an existing note (Jane Doe) so that it appears in a contextually relevant location (Lab Report Alpha) without duplicating the data, ensuring consistency. These techniques significantly improve efficiency and organization.

Basic Data Management Backup and Restore

Self-hosting gives you control, but it also means you are responsible for safeguarding your data. Losing your meticulously organized knowledge base due to hardware failure, accidental deletion, or software issues would be disastrous. Implementing a robust backup strategy for your Trilium Notes data is non-negotiable.

Understanding What to Back Up:

When using the recommended Docker setup with a named volume (trilium_data), the critical element to back up is the Docker volume itself. This volume contains the entire Trilium database (usually an SQLite file), attachments, settings, and everything that constitutes your knowledge base.

If you used a bind mount in Docker (mapping a host directory directly, e.g., - ./my-trilium-data:/home/node/trilium-data), then you need to back up that specific host directory (./my-trilium-data in this example).

If you installed Trilium natively, you need to locate and back up the trilium-data directory created by the application (its location depends on the OS and how you launched it, often in the user's home directory or application support folders).

Backup Strategies:

  1. Manual Backup (Simple but Risky):

    • Concept: Periodically stop the Trilium container and manually copy the data volume or directory to a safe location (external drive, cloud storage).
    • Docker Volume Method:
      1. Stop the Trilium container: cd ~/trilium-docker && docker compose down
      2. Find the location of the named volume on your host. This can be tricky. Use docker volume inspect trilium_data to find the Mountpoint (e.g., /var/lib/docker/volumes/trilium_data/_data).
      3. Copy the entire _data directory: sudo cp -a /var/lib/docker/volumes/trilium_data/_data /path/to/your/backup/location/trilium-backup-YYYYMMDD
      4. Restart Trilium: docker compose up -d
    • Bind Mount Method:
      1. Stop Trilium: cd ~/trilium-docker && docker compose down (or docker stop trilium-notes if not using compose)
      2. Copy the host directory: cp -a /path/to/your/host/trilium-data /path/to/your/backup/location/trilium-backup-YYYYMMDD
      3. Restart Trilium: docker compose up -d (or docker start trilium-notes)
    • Pros: Simple to understand.
    • Cons: Requires downtime. Manual process is easily forgotten. Finding Docker volume mountpoints can be cumbersome and prone to error. High risk of data loss if not done regularly. Not recommended for reliable backups.
  2. Automated Backup using Volume Backup Tools (Recommended):

    • Concept: Use tools designed to back up Docker volumes, ideally while the container is running or with minimal interruption, and automate the process.
    • Method using a Temporary Container: A common pattern is to run a temporary container that mounts the target volume (trilium_data) and a host directory for backups, then archives the volume content into the backup directory.
    • Example Script (backup_trilium.sh):

      #!/bin/bash
      
      # Configuration
      BACKUP_DIR="/opt/trilium-backups" # Choose a secure location on your host
      VOLUME_NAME="trilium_data" # Your Trilium data volume name (from docker-compose.yml)
      TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
      BACKUP_FILENAME="trilium-backup-${TIMESTAMP}.tar.gz"
      CONTAINER_NAME="trilium-notes" # Optional: Stop/start container if needed
      
      # Create backup directory if it doesn't exist
      mkdir -p ${BACKUP_DIR}
      
      echo "Starting Trilium backup for volume: ${VOLUME_NAME}"
      
      # Optional: Stop Trilium container for potentially safer backup
      # echo "Stopping Trilium container: ${CONTAINER_NAME}..."
      # docker stop ${CONTAINER_NAME}
      # sleep 5 # Give it a moment to stop
      
      # Run a temporary container to archive the volume
      # Mounts the target volume as read-only and the backup directory
      docker run --rm \
          -v ${VOLUME_NAME}:/volume-data:ro \
          -v ${BACKUP_DIR}:/backup \
          alpine \
          tar czf /backup/${BACKUP_FILENAME} -C /volume-data .
      
      # Optional: Restart Trilium container if stopped
      # echo "Starting Trilium container: ${CONTAINER_NAME}..."
      # docker start ${CONTAINER_NAME}
      
      # Check if backup was successful (basic check: file exists and is not empty)
      if [ -s "${BACKUP_DIR}/${BACKUP_FILENAME}" ]; then
          echo "Backup successful: ${BACKUP_DIR}/${BACKUP_FILENAME}"
      else
          echo "Backup FAILED!"
          exit 1
      fi
      
      # Optional: Prune old backups (e.g., keep last 7 days)
      echo "Pruning old backups in ${BACKUP_DIR} (keeping last 7)..."
      find ${BACKUP_DIR} -name 'trilium-backup-*.tar.gz' -mtime +7 -exec rm {} \;
      
      echo "Backup process finished."
      exit 0
      
    • Automation: Make the script executable (chmod +x backup_trilium.sh) and schedule it to run regularly using cron. Edit your crontab (crontab -e) and add a line like: 0 3 * * * /path/to/your/backup_trilium.sh >> /var/log/trilium_backup.log 2>&1 (This runs the backup every day at 3:00 AM and logs output).

    • Pros: Automated, reliable, can run while container is active (though stopping briefly might be safer for database consistency), includes pruning.
    • Cons: Requires scripting and cron setup.
  3. Trilium's Internal Backup Feature:

    • Trilium itself has a built-in mechanism to create backups (often .db file snapshots) within its own data directory structure. This is useful for quick rollbacks but should not be your only backup strategy, as it doesn't protect against failure of the underlying volume/disk. Configure this in Trilium's settings -> Application Data -> Create Backup Now / Configure automatic backups. Consider this a supplement to external volume backups.

Restore Process:

Restoring depends on the backup method used. For the automated .tar.gz volume backup:

  1. Stop Trilium: cd ~/trilium-docker && docker compose down
  2. Identify the Backup File: Choose the .tar.gz file you want to restore from (e.g., trilium-backup-YYYYMMDD_HHMMSS.tar.gz).
  3. Remove Existing Volume Content (Use with EXTREME caution): This step is destructive to current data. You might want to rename the existing volume first as a safety net if possible (docker volume rename trilium_data trilium_data_old), or be absolutely sure you want to overwrite. A simpler (but destructive) way is often to remove the volume and let Docker recreate it empty: docker volume rm trilium_data.
  4. (Re)Create the Volume (if removed): If you removed the volume, Docker Compose will recreate it empty on the next up. If you didn't remove it, ensure it's empty or ready for the restore.
  5. Run a Restore Container: Similar to the backup, use a temporary container to extract the archive into the volume.
    docker run --rm \
        -v trilium_data:/volume-data \
        -v /opt/trilium-backups:/backup \ # Adjust path if needed
        alpine \
        sh -c "tar xzf /backup/trilium-backup-YYYYMMDD_HHMMSS.tar.gz -C /volume-data && \
               chown -R node:node /volume-data" # Ensure correct ownership! Trilium runs as 'node' user (UID 1000) inside container.
    
    Replace trilium-backup-YYYYMMDD_HHMMSS.tar.gz with your actual backup file name. The chown command is crucial to set the correct file permissions inside the volume for the Trilium process.
  6. Restart Trilium: docker compose up -d
  7. Verify: Access Trilium in your browser and check if your data from the backup point is present.

Backup Best Practices:

  • Frequency: Back up daily, or even more frequently if you make critical changes often.
  • Location: Store backups on a separate physical device or cloud storage (consider encryption). Follow the 3-2-1 rule: 3 copies of your data, on 2 different media types, with 1 copy offsite.
  • Testing: Regularly test your restore process to ensure your backups are valid and you know how to use them. A backup you can't restore is useless.
  • Monitoring: Check your backup logs periodically to ensure they are running successfully.

Workshop Setting Up and Testing Automated Backups

Goal: To implement and test the automated Docker volume backup script for Trilium Notes using cron.

Prerequisites:

  • Trilium running via Docker Compose with the named volume trilium_data.
  • SSH access to the server.
  • Sufficient disk space for backups.
  • cron service installed and running (standard on most Linux distros).

Steps:

  1. Choose Backup Location:
    • Decide where to store your backups on the server. A location outside your home directory, like /opt/trilium-backups or /mnt/backups/trilium, is often preferred.
    • Create the directory: sudo mkdir -p /opt/trilium-backups
    • Ensure appropriate permissions (e.g., allow your user or a dedicated backup user to write here, or manage via root/sudo). For simplicity here, we'll manage via sudo initially. sudo chown $(whoami):$(whoami) /opt/trilium-backups (replace $(whoami) if needed, this gives your current user ownership). Adjust permissions based on your security model.
  2. Create the Backup Script:
    • On your server, create the backup_trilium.sh script file, for example in your home directory or /usr/local/bin: nano ~/backup_trilium.sh
    • Paste the example script content provided in the theoretical section above.
    • Crucially, review and adjust the variables:
      • BACKUP_DIR: Set this to the directory you created (e.g., /opt/trilium-backups).
      • VOLUME_NAME: Verify it matches your volume name (trilium_data in our docker-compose.yml).
      • CONTAINER_NAME: Set this to trilium-notes (or whatever you named your container). Decide if you want to uncomment the docker stop and docker start lines. For this workshop, let's leave them commented out initially to test backup while running.
    • Save and close the script (Ctrl+X, Y, Enter).
    • Make the script executable: chmod +x ~/backup_trilium.sh
  3. Perform an Initial Manual Backup:
    • Run the script manually to test it: ~/backup_trilium.sh
    • Check the output. It should indicate success and show the created backup file path (e.g., /opt/trilium-backups/trilium-backup-20240117_103000.tar.gz).
    • Verify the file exists: ls -lh /opt/trilium-backups
    • (Optional) You could try extracting it somewhere temporary (cd /tmp && tar xzf /opt/trilium-backups/YOUR_BACKUP_FILE.tar.gz) to peek inside and see if it contains familiar Trilium data structure files (like document.db).
  4. Schedule the Backup with Cron:
    • Open the crontab editor for your user: crontab -e (You might be asked to choose an editor like nano).
    • Go to the bottom of the file and add a new line to schedule the script. To run it daily at 3:30 AM:
      30 3 * * * /home/your_username/backup_trilium.sh >> /home/your_username/trilium_backup.log 2>&1
      
      • Replace /home/your_username/ with the actual path to your script and desired log file location.
      • 30 3 * * *: Cron schedule (Minute=30, Hour=3, DayOfMonth=, Month=, DayOfWeek=*).
      • /home/your_username/backup_trilium.sh: Full path to your executable script.
      • >> /home/your_username/trilium_backup.log: Appends standard output to a log file.
      • 2>&1: Redirects standard error to the same place as standard output (so errors are also logged).
    • Save and close the crontab editor. Cron will automatically pick up the new schedule.
  5. Test the Restore Process (Simulated):
    • Important: This involves stopping Trilium and potentially removing current data. Ensure you have a good backup from Step 3 before proceeding.
    • Stop Trilium: cd ~/trilium-docker && docker compose down
    • (Safety Check) Verify the backup file exists in /opt/trilium-backups. Let's use the one created in Step 3.
    • Destroy Current Data (Simulating Failure): Remove the Docker volume. THIS IS DESTRUCTIVE. docker volume rm trilium_data (Confirm if prompted).
    • Run the restore command (adjust backup filename and path if needed):
      docker run --rm \
          -v trilium_data:/volume-data \
          -v /opt/trilium-backups:/backup \
          alpine \
          sh -c "tar xzf /backup/YOUR_BACKUP_FILENAME.tar.gz -C /volume-data && chown -R 1000:1000 /volume-data"
      
      Make sure to replace YOUR_BACKUP_FILENAME.tar.gz. Using 1000:1000 for UID/GID as 'node' user in the official image is often UID 1000.
    • Restart Trilium: docker compose up -d
    • Wait a few moments for it to start, then access Trilium in your browser (http://your_server_ip:8080).
    • Log in and verify that your notes, structure, attributes, and relations from the time of the backup are present.

Outcome: You have created an automated backup script for your Trilium Docker volume, tested it manually, and scheduled it using cron. You have also successfully simulated a failure and restored your data from a backup, verifying the integrity of your backup strategy. This provides significant peace of mind for your self-hosted knowledge base. Remember to periodically check the logs and potentially move backups offsite.

3. Advanced Trilium Techniques

With a solid understanding of intermediate features and data management, we now venture into advanced territory. This section covers topics that allow for deeper customization, integration, performance tuning, and enhanced security for your Trilium Notes instance. We'll explore scripting, API usage, theming, security hardening with a reverse proxy, and troubleshooting common issues.

Automating Workflows with Scripting

Trilium Notes includes a powerful backend scripting environment based on JavaScript (Node.js). This allows you to automate tasks, create custom actions, modify note behavior, react to events, and integrate with external systems directly within Trilium. Scripts are stored as notes, typically of type "JS Backend."

Key Scripting Concepts:

  • Execution Context: Scripts run on the Trilium server (backend). They have access to the Trilium database and internal APIs.
  • API Object (api): The primary way scripts interact with Trilium is through the global api object. This object provides methods to find notes, get/set note content and attributes, create/delete notes, manage relations, log messages, and much more. Explore the api object's methods in the Trilium documentation or via introspection (console.log(api) in a script).
  • Triggers/Event Handlers: Scripts can be triggered in various ways:
    • Manual Execution: Add the #runnable attribute to a JS note, and a "Run script" button will appear when viewing it.
    • Event Handlers: Assign scripts to specific events using attributes on other notes. For example, adding #runOnNoteCreation=YourScriptName to a parent note will execute the YourScriptName script whenever a new note is created inside that parent. Other events include #runOnNoteChange, #runOnNoteDeletion, etc.
    • Scheduled Execution: Use the #runOnMinute, #runOnHour, #runOnDay attributes on a script note itself to have it run automatically at specified intervals.
    • Custom Buttons: Create button notes that, when clicked, execute a specific script.
  • Permissions and Security: Backend scripts have significant power. Be cautious when running scripts from untrusted sources. Trilium has some safeguards, but a poorly written script could potentially damage your data or cause performance issues.
  • Logging: Use console.log("Your message") within your scripts. The output will appear in the Trilium server logs (viewable via docker logs trilium-notes).

Common Use Cases for Scripting:

  • Automated Summaries: Create a script that runs daily (#runOnDay) to find all notes tagged #dailyLog and append their titles or summaries to a "Daily Digest" note.
  • Task Management Automation: When a task note's attribute is changed to #status=Completed, automatically move the note to an "Archive" branch.
  • Data Validation: Use #runOnNoteChange to check if a note meets certain criteria (e.g., a "Project Brief" must have a ~projectManager relation) and potentially add a #needsReview label if not.
  • Integration: Fetch data from an external API (e.g., weather, stock prices) using Node.js's http/https or fetch modules (requires Trilium server configuration to allow outbound requests) and update a note.
  • Custom Views: Generate complex reports or visualizations (e.g., Mermaid diagrams, charts) based on data aggregated from multiple notes.

Finding Notes Programmatically:

A core task in scripting is finding the notes you want to work with. The api.searchNotes() method is powerful:

// Find all notes with the label #todo that are NOT inside the Archive branch
const archiveNote = api.searchNotes('#archive')[0]; // Assuming an #archive label identifies the archive branch root
const todoNotes = api.searchNotes('#todo !#branch=' + archiveNote.noteId);

todoNotes.forEach(note => {
    console.log(`Found TODO: ${note.title} (ID: ${note.noteId})`);
    // Add your logic here, e.g., add a #priority attribute based on keywords
    if (note.title.toLowerCase().includes("urgent")) {
        note.addAttribute('label', 'priority', 'high');
    }
});

Modifying Notes:

// Get a specific note by ID (replace 'NOTE_ID_HERE')
const note = api.getNote('NOTE_ID_HERE');

if (note) {
    // Set note title
    note.setTitle("New Title Set By Script");

    // Set note content (be careful with formatting - use Markdown or HTML)
    note.setContent("## Updated Content\nThis content was set by a script.");

    // Add or update an attribute
    note.addAttribute('label', 'processedByScript', 'true');

    // Add a relation (replace 'TARGET_NOTE_ID')
    // note.addAttribute('relation', 'relatedDocument', 'TARGET_NOTE_ID');

    // Delete an attribute
    // note.removeAttribute('label', 'oldStatus');

    // Move the note (provide the target parent note ID)
    // note.setParent('TARGET_PARENT_ID');
}

Scripting unlocks immense potential for tailoring Trilium to your exact needs, automating tedious tasks, and creating dynamic knowledge management workflows.

Workshop Automatically Tagging New Notes

Goal: To create a backend script that automatically adds a #needsReview label to any new note created directly under the Projects branch.

Prerequisites:

  • A running Trilium instance.
  • The Projects note created in previous workshops.

Scenario: You want to ensure that any new project idea or task added directly under the main Projects folder is flagged for later review.

Steps:

  1. Create a Scripts Branch:
    • It's good practice to keep your scripts organized. Right-click on your root note (or My University Notes).
    • Create a new note named My Scripts.
  2. Create the Script Note:
    • Right-click on My Scripts.
    • Create a new note inside named Tag New Project Notes.
    • Select the Tag New Project Notes note.
    • Change its type to JS Backend. The editor should switch to a JavaScript editor.
  3. Write the Script Code:

    • Paste the following JavaScript code into the note's content area:

      // This script runs when a new note is created in the note where it's referenced.
      // The 'note' variable automatically refers to the newly created note.
      
      // Add the #needsReview label to the newly created note.
      // 'label' indicates the type of attribute.
      // 'needsReview' is the name of the label.
      note.addAttribute('label', 'needsReview');
      
      // Log a message to the server console for confirmation.
      console.log(`Added #needsReview label to new note: ${note.title} (ID: ${note.noteId})`);
      
    • Explanation:

      • When triggered by an event like #runOnNoteCreation, Trilium automatically provides the relevant note object (in this case, the newly created note) to the script via the note variable.
      • note.addAttribute('label', 'needsReview'): This is the core logic. It calls the addAttribute method on the note object. It specifies the attribute type as label and the label name as needsReview. You don't need to provide a value for simple labels.
      • console.log(...): This helps in debugging by printing information to the server logs.
  4. Find the Projects Note ID:

    • Navigate to your Projects note in the tree.
    • Select the Projects note.
    • Look for the "Note Info" tab or section in the note properties area.
    • Find the Note ID (it will be a short alphanumeric string like aBcDeF123). Copy this ID.
  5. Assign the Script as an Event Handler:
    • Go back to the Projects note.
    • In its attributes panel, click "Add Attribute."
    • For the attribute name, enter #runOnNoteCreation.
    • For the attribute value, paste the Note ID of your Tag New Project Notes script note (the ID you copied in the previous step). Alternatively, some newer Trilium versions might let you directly pick the script note using the note picker when setting the attribute value.
    • Press Enter to add the attribute. The Projects note now has the attribute #runOnNoteCreation=[ScriptNoteID].
  6. Test the Script:
    • Right-click on the Projects note in the tree.
    • Select "Create new note inside."
    • Name the new note New Project Idea X.
    • Select the New Project Idea X note.
    • Check its attributes panel. You should see the #needsReview label automatically added by your script!
    • Create another new note inside Projects called Task Y. Verify it also gets the #needsReview label.
  7. Check Server Logs (Optional):
    • If you have terminal access to your server, view the Trilium container logs:
      docker logs trilium-notes
      
    • Scroll through the logs. You should find the messages you added using console.log, confirming the script execution:
      Added #needsReview label to new note: New Project Idea X (ID: ...)
      Added #needsReview label to new note: Task Y (ID: ...)
      

Outcome: You have successfully created and deployed a backend JavaScript script in Trilium. This script automatically modifies new notes based on an event trigger (#runOnNoteCreation), demonstrating the power of automation within Trilium for enforcing workflows or adding contextual metadata.

Extending Trilium with the API

Beyond backend scripting within Trilium, the application also exposes a RESTful API (Application Programming Interface). This allows external applications, scripts, or services to interact with your Trilium instance programmatically over the network (HTTP).

Why Use the API?

  • Integration with Other Tools: Connect Trilium to your other favorite apps. For example:
    • Create notes automatically from emails or RSS feeds.
    • Sync tasks between Trilium and a dedicated task manager.
    • Push bookmarks saved in your browser directly into Trilium.
    • Use mobile apps like Tasker (Android) or Shortcuts (iOS) to quickly capture ideas into Trilium.
  • Custom Frontends or Clients: Build specialized interfaces or mobile clients tailored to specific workflows that interact with your Trilium data.
  • Bulk Operations: Perform large-scale data import, export, or modification tasks that might be cumbersome through the web UI or backend scripting alone.
  • Data Analysis: Extract data from Trilium for analysis or reporting in external tools.

API Basics:

  • Endpoint: The base URL for API calls is typically your Trilium instance URL (e.g., http://your_server_ip:8080/api).
  • Authentication: API requests need to be authenticated to prove they have permission to access/modify data. Trilium commonly uses API Tokens.
    • Generating a Token: You usually generate tokens within Trilium's settings (look for API Tokens or Authentication Tokens under Security or ETAPI). Create a new token, give it a name, and copy the generated token string immediately. This token is like a password for API access – keep it secure!
    • Using the Token: Include the token in the Authorization header of your HTTP requests, typically as a Bearer token: Authorization: Bearer YOUR_API_TOKEN_HERE.
  • Methods: The API uses standard HTTP methods:
    • GET: Retrieve data (e.g., get note details, search notes).
    • POST: Create new resources (e.g., create a note).
    • PATCH / PUT: Update existing resources (e.g., modify note content or attributes). PATCH is often preferred for partial updates.
    • DELETE: Remove resources (e.g., delete a note).
  • Data Format: The API typically consumes and produces data in JSON (JavaScript Object Notation) format.
  • Documentation: The definitive guide to available API endpoints, parameters, and expected request/response formats is the official Trilium REST API documentation. Look for a link within Trilium's Help/About section or search the Trilium GitHub repository/wiki.

Example API Interaction (using curl):

Let's assume:

  • Your Trilium is at http://192.168.1.100:8080
  • Your API Token is supersecretapitoken123
  • You want to create a simple text note titled "API Test Note" under the root note (which often has the ID root).
# Define variables (makes it easier)
TRILIUM_URL="http://192.168.1.100:8080"
API_TOKEN="supersecretapitoken123"
PARENT_NOTE_ID="root" # Or the ID of another note to create under

# Prepare the JSON data for the new note
JSON_DATA='{
  "parentNoteId": "'"${PARENT_NOTE_ID}"'",
  "title": "API Test Note",
  "type": "text",
  "mime": "text/html",
  "content": "<p>This note was created via the REST API.</p>"
}'

# Make the POST request using curl
curl -X POST "${TRILIUM_URL}/api/notes" \
     -H "Authorization: Bearer ${API_TOKEN}" \
     -H "Content-Type: application/json" \
     -d "${JSON_DATA}"

Explanation:

  • curl: A command-line tool for making HTTP requests.
  • -X POST: Specifies the HTTP method as POST (for creating a resource).
  • ${TRILIUM_URL}/api/notes: The API endpoint for creating notes.
  • -H "Authorization: Bearer ${API_TOKEN}": Adds the required authentication header with your token.
  • -H "Content-Type: application/json": Tells the server that the data being sent (-d) is in JSON format.
  • -d "${JSON_DATA}": Provides the JSON payload containing the details of the note to be created (parentNoteId, title, type, mime, content).

If successful, the API will respond with JSON data representing the newly created note, including its unique noteId.

The API opens up a vast landscape for integrating Trilium into your broader digital ecosystem.

Workshop Creating a Note via the REST API using Python

Goal: To write a simple Python script that uses Trilium's REST API to create a new note with a specific title and content.

Prerequisites:

  • A running Trilium instance.
  • Python 3 installed on your local machine or server (wherever you run the script).
  • The requests library for Python (pip install requests).
  • An API Token generated from your Trilium instance.

Scenario: You want to programmatically add a quick note to your Trilium journal for today without opening the web interface.

Steps:

  1. Generate an API Token in Trilium:
    • Open your Trilium web interface.
    • Go to Menu -> Options (or Settings).
    • Find the "Security" or "ETAPI / REST API" section.
    • Look for "Authentication Tokens" or "API Keys."
    • Create a new token. Give it a descriptive name (e.g., python_script_token).
    • Immediately copy the generated token string and save it somewhere secure temporarily (like a temporary text file or password manager). You won't be able to see it again.
  2. Create the Python Script:

    • Create a new file named create_trilium_note.py.
    • Open it in a text editor or IDE.
    • Paste the following Python code:

      import requests
      import json
      from datetime import date
      
      # --- Configuration ---
      TRILIUM_URL = "http://YOUR_SERVER_IP:8080"  # Replace with your Trilium URL
      API_TOKEN = "YOUR_API_TOKEN_HERE"       # Replace with your generated token
      # Optional: Find the Note ID of your Journal parent note if you want to add it there
      # Go to your Journal folder in Trilium, Note Info tab -> Note ID
      JOURNAL_PARENT_NOTE_ID = "root" # Defaulting to root, change if needed
      
      # --- Note Details ---
      today_str = date.today().strftime("%Y-%m-%d")
      note_title = f"Quick Entry {today_str}"
      note_content_html = f"<p>This is a quick note added via Python script on {today_str}.</p>"
      
      # --- API Request ---
      api_endpoint = f"{TRILIUM_URL}/api/notes"
      headers = {
          "Authorization": f"Bearer {API_TOKEN}",
          "Content-Type": "application/json",
      }
      payload = {
          "parentNoteId": JOURNAL_PARENT_NOTE_ID, # Create under Journal or 'root'
          "title": note_title,
          "type": "text", # Or "markdown" if using Markdown content and correct mime
          "mime": "text/html", # Use "text/markdown" for Markdown content
          "content": note_content_html,
      }
      
      print(f"Attempting to create note: '{note_title}'")
      
      try:
          response = requests.post(api_endpoint, headers=headers, data=json.dumps(payload), timeout=10)
          response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
      
          # If successful, print the response (contains new note details)
          new_note_data = response.json()
          print("Success! Note created.")
          print(f"Note ID: {new_note_data.get('noteId')}")
          # print(json.dumps(new_note_data, indent=2)) # Uncomment for full response
      
      except requests.exceptions.RequestException as e:
          print(f"Error connecting to Trilium API: {e}")
          if hasattr(e, 'response') and e.response is not None:
              print(f"Status Code: {e.response.status_code}")
              print(f"Response Body: {e.response.text}")
      except Exception as e:
          print(f"An unexpected error occurred: {e}")
      
  3. Configure the Script:

    • Replace "http://YOUR_SERVER_IP:8080" with the correct URL of your Trilium instance.
    • Replace "YOUR_API_TOKEN_HERE" with the actual API token you generated and saved.
    • (Optional) If you want the note created inside your Journal folder:
      • Go to Trilium, find your main "Journal" note (the parent of the daily notes).
      • Go to its "Note Info" tab and copy its Note ID.
      • Replace "root" in the JOURNAL_PARENT_NOTE_ID variable with the copied Journal note ID.
  4. Run the Script:
    • Open a terminal or command prompt in the directory where you saved create_trilium_note.py.
    • Execute the script: python create_trilium_note.py
  5. Verify the Result:
    • If the script runs successfully, it will print "Success! Note created." and the new Note ID.
    • Open your Trilium web interface.
    • Navigate to the parent note you specified (either the root or your Journal folder).
    • You should see a new note titled "Quick Entry YYYY-MM-DD" with the content specified in the script.
    • If the script prints an error, read the error message carefully. Common issues include:
      • Incorrect TRILIUM_URL.
      • Invalid API_TOKEN.
      • Network connectivity issues between where the script runs and the Trilium server.
      • Incorrect JOURNAL_PARENT_NOTE_ID.
      • Firewall blocking the connection.

Outcome: You have successfully used Python and the requests library to interact with the Trilium REST API, programmatically creating a new note in your knowledge base. This demonstrates the potential for integrating Trilium with external scripts and applications.

Customizing Appearance with Themes

While Trilium's default interface is functional, you might want to personalize its look and feel to better suit your preferences or working environment. Trilium supports theming, allowing you to change colors, fonts, layout elements, and more using standard CSS (Cascading Style Sheets).

How Theming Works:

  • CSS Notes: Themes are typically implemented as notes within Trilium itself. These notes have the type set to CSS.
  • #appTheme Attribute: To designate a CSS note as an active theme, you give it the #appTheme attribute. Only one theme note can be active at a time for your entire instance.
  • CSS Content: The content of the CSS note contains standard CSS rules that override or supplement Trilium's default styles. You can target specific HTML elements or CSS classes used by the Trilium interface.
  • Finding CSS Selectors: The most challenging part is identifying the correct CSS selectors (element IDs, classes) to target the specific UI elements you want to change. Using your browser's Developer Tools (usually opened by pressing F12) is essential:
    1. Open Trilium in your browser.
    2. Press F12 to open Developer Tools.
    3. Use the "Inspector" or "Elements" tab.
    4. Click the element selection tool (often looks like a mouse pointer in a square).
    5. Click on the UI element in Trilium you want to style (e.g., the note tree, the editor background, a button).
    6. The Developer Tools will highlight the corresponding HTML element and show its associated CSS classes and IDs in the "Styles" panel. You can use these classes/IDs in your custom CSS note.

Creating and Applying a Theme:

  1. Create a Theme Note:
    • Create a new note, perhaps under a Themes or Settings branch. Name it descriptively (e.g., "My Custom Dark Theme").
    • Change the note type to CSS.
  2. Add Basic CSS:
    • Start with a simple change. For example, to change the background color of the main editor area (you'll need to find the correct selector using Dev Tools - it might be something like .ck-editor__editable_inline for the CKEditor, but this can change between Trilium versions):
      /* Example: Change editor background - Find the correct selector! */
      .ck-editor__editable_inline {
          background-color: #2d2d2d !important; /* Dark grey */
          color: #cccccc !important; /* Light grey text */
      }
      
      /* Example: Change note tree background */
      .note-tree-widget {
           background-color: #252525 !important;
      }
      
    • !important: You might need to use !important frequently to override Trilium's default styles, as they might have higher specificity. Use it judiciously, as overuse can make CSS hard to manage.
  3. Activate the Theme:
    • Select your theme note ("My Custom Dark Theme").
    • In its attributes panel, add the attribute #appTheme.
  4. Reload Trilium:
    • Refresh the Trilium web page in your browser (Ctrl+R or Cmd+R).
    • Your CSS changes should now be applied. If not, double-check your CSS selectors using the Developer Tools and ensure the #appTheme attribute is correctly set.
  5. Iterate and Refine:
    • Continue using the Developer Tools to find selectors for other elements you want to change (fonts, sidebar colors, button styles, etc.).
    • Add more CSS rules to your theme note.
    • Reload Trilium after making changes to see the effect.

Finding Existing Themes:

  • Check the Trilium GitHub repository (wiki, discussions, or issues) or community forums. Users often share their custom themes. You can import their CSS notes into your instance and activate them.

Theming allows you to create a visually comfortable and personalized environment for managing your knowledge.

Workshop Applying a Simple Dark Theme

Goal: To create and apply a basic dark theme to your Trilium instance using a CSS note.

Prerequisites:

  • A running Trilium instance.
  • Familiarity with opening and using basic browser Developer Tools (F12).

Scenario: You prefer working in applications with dark backgrounds to reduce eye strain. You want to apply a simple dark mode to Trilium.

Steps:

  1. Create a Themes Branch (if you don't have one):
    • Right-click Root note -> New Note -> Name: Themes.
  2. Create the CSS Theme Note:
    • Right-click Themes -> New Note -> Name: Simple Dark Theme.
    • Select Simple Dark Theme.
    • Change Note Type to CSS.
  3. Inspect UI Elements (Using Developer Tools):
    • Press F12 in your browser to open Developer Tools.
    • Click the element selector tool.
    • Click on the main note tree area on the left. Observe the selected HTML element and its classes in the Dev Tools (e.g., it might have a class like note-tree-widget or similar). Note this class down.
    • Click inside the main note editing area (where you type text). Observe its classes (e.g., .ck-editor__editable_inline, .note-detail-text-editor, or similar depending on editor/version). Note this class.
    • Click on the background of the main content area (outside the editor but within the central panel). Note its class (e.g., .note-detail-pane, #center-pane).
  4. Add CSS Rules to the Theme Note:

    • Go back to your Simple Dark Theme CSS note in Trilium.
    • Paste CSS rules using the selectors you found. Adjust selectors based on your findings. This is a representative example, you must adapt the selectors (.note-tree-widget, .note-detail-pane, .ck-editor__editable_inline) based on what your Developer Tools show for your Trilium version:

      /* Simple Dark Theme for Trilium */
      
      /* Overall body/background (might need adjustment) */
      body, #root {
          background-color: #1e1e1e !important;
          color: #d4d4d4 !important;
      }
      
      /* Note Tree Area */
      .note-tree-widget { /* <<< ADAPT THIS SELECTOR */
          background-color: #252525 !important;
          color: #cccccc !important;
          border-right: 1px solid #333333 !important;
      }
      /* Selected item in tree */
      .note-tree-widget .fancytree-active .fancytree-title {
              background-color: #3a3d41 !important;
              color: #ffffff !important;
      }
      .note-tree-widget .fancytree-title:hover {
              background-color: #2a2d2e !important;
      }
      
      
      /* Main Content Pane Background */
      .note-detail-pane { /* <<< ADAPT THIS SELECTOR */
          background-color: #1e1e1e !important;
          color: #d4d4d4 !important;
      }
      
      /* Text Editor Area */
      .ck-editor__editable_inline { /* <<< ADAPT THIS SELECTOR */
          background-color: #2d2d2d !important;
          color: #cccccc !important;
          border: 1px solid #3f3f3f !important;
      }
      
      /* Note Title in Detail Pane */
      .note-title-widget input {
              background-color: #2d2d2d !important;
              color: #cccccc !important;
              border: 1px solid #3f3f3f !important;
      }
      
      /* Buttons (General - might need more specific selectors) */
      .btn {
          background-color: #3a3d41 !important;
          color: #ffffff !important;
          border: 1px solid #555555 !important;
      }
      .btn:hover {
              background-color: #4a4d51 !important;
      }
      
      /* Links */
      a {
          color: #7bbfea !important;
      }
      
    • Remember: The specific class names (.note-tree-widget, .note-detail-pane, .ck-editor__editable_inline, etc.) are crucial and might be different in your Trilium version. Use the Dev Tools!

  5. Activate the Theme:

    • Select the Simple Dark Theme note.
    • In its Attributes panel, click "Add Attribute."
    • Add the attribute #appTheme.
  6. Reload and Observe:

    • Reload the Trilium page in your browser (Ctrl+R or Cmd+R).
    • Your Trilium interface should now have a dark background for the tree, main pane, and editor, with lighter text.
    • If parts are unchanged or look wrong, revisit Step 3 & 4: Use Dev Tools to find the correct selectors for the elements that need adjustment and update the CSS rules in your Simple Dark Theme note. Reload again.

Outcome: You have successfully created a custom CSS note, identified key UI element selectors using developer tools, applied basic dark theme styles, and activated it using the #appTheme attribute. This demonstrates the process of customizing Trilium's appearance. You can continue refining this theme by targeting more specific elements.

Security Hardening with a Reverse Proxy

While Trilium's built-in password protection provides basic security, running any web application directly exposed to the internet on its default port (like 8080) is generally discouraged for several reasons:

  • No HTTPS: Communication between your browser and Trilium is unencrypted (HTTP). Passwords and note content are sent in plaintext, vulnerable to eavesdropping, especially over public Wi-Fi.
  • Direct Exposure: Your Trilium application server is directly reachable, potentially exposing it to vulnerabilities in the underlying Node.js runtime or Trilium itself.
  • Port Management: Using non-standard ports like 8080 can sometimes be inconvenient or blocked by firewalls.

A reverse proxy acts as an intermediary server that sits in front of your Trilium application. Your users connect to the reverse proxy, which then forwards the requests to the actual Trilium container. This offers significant security and management benefits. Popular reverse proxy servers include Nginx, Apache, Caddy, and Traefik.

Benefits of Using a Reverse Proxy:

  1. HTTPS/TLS Encryption: The reverse proxy can handle SSL/TLS certificates (e.g., free certificates from Let's Encrypt), encrypting all traffic between the user's browser and the proxy. The connection from the proxy to Trilium (on the internal Docker network) can remain HTTP, but it's protected within your server environment. This is the single most important security improvement.
  2. Standard Ports: Access Trilium via standard HTTP (80) and HTTPS (443) ports, making it more accessible and professional-looking (e.g., https://notes.yourdomain.com instead of http://your_ip:8080).
  3. Security Layer: The proxy acts as a buffer, potentially mitigating certain types of attacks before they reach Trilium. It can also implement security headers, rate limiting, or IP blocking.
  4. Load Balancing (Advanced): If you ever needed to scale Trilium horizontally (multiple instances), a reverse proxy could distribute traffic among them. (Not typically needed for personal Trilium).
  5. Centralized Management: Manage domain names, certificates, and access policies for multiple self-hosted applications from one place.

Conceptual Setup (using Nginx Proxy Manager as an example):

Nginx Proxy Manager (NPM) is a popular Docker-based reverse proxy with an easy-to-use web interface, making it ideal for managing proxies without complex Nginx configuration files.

  1. Docker Network: Ensure both Nginx Proxy Manager and your Trilium container are connected to the same custom Docker network. This allows them to communicate using container names.
  2. Trilium Configuration (docker-compose.yml changes):
    • Remove the ports mapping from the Trilium service. It no longer needs to be directly exposed to the host.
    • Add both Trilium and NPM to a custom Docker network.
  3. Nginx Proxy Manager Setup:
    • Install NPM (usually via Docker Compose).
    • Access the NPM web UI.
    • Add a Proxy Host:
      • Domain Names: Enter the domain or subdomain you want to use (e.g., trilium.yourdomain.com). Requires you have DNS configured to point this domain to your server's public IP.
      • Scheme: Set to http (since NPM talks to Trilium internally over HTTP).
      • Forward Hostname / IP: Enter the container name of your Trilium service (e.g., trilium-notes).
      • Forward Port: Enter the port Trilium listens on inside its container (e.g., 8080).
      • SSL: Go to the SSL tab, request a new Let's Encrypt certificate, and enable "Force SSL" and "HTTP/2 Support."
  4. Access: You can now access Trilium securely via https://trilium.yourdomain.com. NPM handles the HTTPS termination and forwards the request to the Trilium container over the internal Docker network.

Using a reverse proxy like Nginx Proxy Manager significantly enhances the security and professionalism of your self-hosted Trilium instance.

Workshop Setting Up Nginx Proxy Manager for Secure HTTPS Access

Goal: To configure Nginx Proxy Manager (NPM) as a reverse proxy for Trilium, enabling secure HTTPS access via a domain name.

Prerequisites:

  • Trilium running via Docker Compose (from previous workshops).
  • Docker and Docker Compose installed.
  • A registered domain name (e.g., yourdomain.com).
  • DNS records configured: An A record pointing your desired subdomain (e.g., trilium.yourdomain.com) to your server's public IP address.
  • Ports 80 and 443 open on your server's firewall and forwarded from your router if behind NAT. These ports are needed by NPM for HTTP/HTTPS traffic and Let's Encrypt validation.

Scenario: You want to access your Trilium instance securely using https://trilium.yourdomain.com instead of http://your_server_ip:8080.

Steps:

  1. Create a Custom Docker Network:

    • A shared network allows containers to communicate easily using their service names.
    • docker network create npm_network
  2. Modify Trilium's docker-compose.yml:

    • Navigate to your Trilium configuration directory: cd ~/trilium-docker
    • Edit the docker-compose.yml file: nano docker-compose.yml
    • Make the following changes:

      version: '3.7'
      
      services:
        trilium:
          image: zadam/trilium:latest
          container_name: trilium-notes # Keep this name, NPM will use it
          restart: unless-stopped
          # ports: # <-- REMOVE or COMMENT OUT this section
          #  - "8080:8080"
          volumes:
            - trilium_data:/home/node/trilium-data
          networks: # <-- ADD this section
            - npm_network # Connect Trilium to the shared network
      
      volumes:
        trilium_data:
      
      networks: # <-- ADD this section at the bottom
        npm_network:
          external: true # Specify that this network is created outside this compose file
      
    • Save and close the file.

  3. Create Nginx Proxy Manager docker-compose.yml:

    • Create a separate directory for NPM: mkdir ~/npm-docker && cd ~/npm-docker
    • Create a docker-compose.yml file for NPM: nano docker-compose.yml
    • Paste the following content (adapted from official NPM docs):

      version: '3.8'
      services:
        app:
          image: 'jc21/nginx-proxy-manager:latest'
          container_name: nginx-proxy-manager
          restart: unless-stopped
          ports:
            # Public HTTP Port:
            - '80:80'
            # Public HTTPS Port:
            - '443:443'
            # Admin Interface Port:
            - '81:81' # Map host port 81 to container port 81 for the NPM admin UI
          volumes:
            - ./data:/data # Persists NPM configuration
            - ./letsencrypt:/etc/letsencrypt # Persists SSL certificates
          networks: # <-- ADD this section
            - npm_network # Connect NPM to the shared network
      
      networks: # <-- ADD this section at the bottom
        npm_network:
          external: true # Specify that this network is created outside this compose file
      
    • Save and close the file.

  4. Restart Trilium and Start Nginx Proxy Manager:

    • Apply Trilium changes: cd ~/trilium-docker && docker compose down && docker compose up -d
    • Start NPM: cd ~/npm-docker && docker compose up -d
    • Wait a minute or two for NPM to initialize.
  5. Configure Nginx Proxy Manager:

    • Access the NPM Admin UI: Open your browser and go to http://your_server_ip:81.
    • Default Login (first time only):
      • Email: admin@example.com
      • Password: changeme
    • You will be forced to change the email and password immediately. Do so.
    • Navigate to "Hosts" -> "Proxy Hosts."
    • Click "Add Proxy Host."
    • Details Tab:
      • Domain Names: Enter your desired subdomain (e.g., trilium.yourdomain.com). Press Enter or Add.
      • Scheme: Select http.
      • Forward Hostname / IP: Enter the container name of your Trilium service: trilium-notes.
      • Forward Port: Enter the internal port Trilium uses: 8080.
      • Enable Block Common Exploits.
    • SSL Tab:
      • SSL Certificate: Select "Request a new SSL Certificate" from the dropdown.
      • Enable Force SSL.
      • Enable HTTP/2 Support.
      • Email Address for Let's Encrypt: Enter your valid email address.
      • Agree to the Let's Encrypt ToS.
      • Click Save. NPM will now attempt to contact Let's Encrypt to obtain a certificate for your domain. This requires your DNS records to be correct and ports 80/443 to be open and pointing to this server.
    • You should see your new host listed. The status might initially show "Pending" or "Offline" but should change to "Online" with a green indicator if the certificate was obtained successfully and NPM can reach the Trilium container.
  6. Test Secure Access:

    • Open a new browser tab or window.
    • Navigate to https://trilium.yourdomain.com (using https and your actual domain).
    • You should see the Trilium login page, served securely over HTTPS (check for the padlock icon in your browser's address bar).
    • Log in and verify everything works as expected.

Troubleshooting:

  • Certificate Failed: Double-check DNS points correctly to your server's public IP. Ensure ports 80 and 443 are open and forwarded correctly. Check NPM logs (cd ~/npm-docker && docker compose logs app).
  • 502 Bad Gateway: NPM cannot reach the Trilium container. Verify both containers are on the npm_network (docker network inspect npm_network). Check Trilium logs (cd ~/trilium-docker && docker compose logs trilium) to ensure it started correctly. Ensure the Forward Hostname / IP in NPM matches Trilium's container name (trilium-notes). Ensure the Forward Port is 8080.
  • Trilium Not Starting: If Trilium fails after removing the ports mapping, check its logs. It should still bind to port 8080 inside the container.

Outcome: You have successfully configured Nginx Proxy Manager to act as a reverse proxy for Trilium. Your Trilium instance is no longer directly exposed, and access is now secured with HTTPS via a user-friendly domain name. This is a standard and highly recommended setup for self-hosting web applications.

Troubleshooting Common Issues

Even with careful setup, you might encounter issues while running your self-hosted Trilium instance. This section covers common problems and systematic approaches to diagnosing and resolving them.

1. Trilium Container Fails to Start / Keeps Restarting:

  • Symptom: docker ps shows the container exiting or restarting repeatedly. docker compose up (without -d) shows error messages.
  • Diagnosis:
    • Check Logs: The most crucial step. docker logs trilium-notes (or cd ~/trilium-docker && docker compose logs trilium). Look for error messages near the end. Common errors include:
      • EADDRINUSE: Port conflict. Another process (or container) is using the port Trilium needs (either the host port mapping or the internal port if misconfigured). Check docker ps and sudo lsof -i :PORT_NUMBER on the host. Adjust port mappings in docker-compose.yml.
      • Database Errors (sqlite errors): The database file might be corrupted or have incorrect permissions. This often requires restoring from backup.
      • Permission Errors: Issues writing to the data volume. Check volume permissions (though Docker named volumes usually handle this well). If using bind mounts, ensure the host directory has correct ownership/permissions for the user ID Trilium runs as inside the container (often UID 1000).
      • Configuration Errors: Syntax errors in config.ini if manually edited (less common with Docker).
    • Resource Issues: Is the server out of memory or disk space? Check free -h and df -h.
    • Incorrect Image/Tag: Did you specify a non-existent Docker image tag in docker-compose.yml?
  • Resolution: Address the specific error found in the logs. Stop/remove the container (docker compose down), fix the configuration or underlying issue, and restart (docker compose up -d).

2. Cannot Access Trilium Web Interface:

  • Symptom: Browser shows "This site can’t be reached," "Connection refused," or times out when trying to access http://your_ip:8080 or https://your_domain.com.
  • Diagnosis:
    • Is the Container Running? Run docker ps. Is the trilium-notes container listed and Up? If not, diagnose why it failed to start (see point 1).
    • Port Mapping (Direct Access): If accessing directly via IP:Port (:8080), double-check the ports section in docker-compose.yml. Is it correctly mapped (e.g., "8080:8080")?
    • Reverse Proxy (HTTPS Access): If using a reverse proxy (like NPM):
      • Is the proxy container running (docker ps)? Check its logs (cd ~/npm-docker && docker compose logs app).
      • Is the proxy host configured correctly in NPM (Domain Name, Forward Hostname=trilium-notes, Forward Port=8080, Scheme=http)?
      • Can the proxy container ping the Trilium container on the Docker network? (docker exec -it nginx-proxy-manager ping trilium-notes).
      • Is the SSL certificate valid and correctly configured?
    • Firewall: Is the required port (8080 for direct access, or 80/443 for reverse proxy) allowed through the server's firewall (e.g., sudo ufw status)? Is it forwarded correctly by your router if applicable?
    • Network Issues: Are you on the same network as the server? Can you ping the server's IP address? Check DNS resolution if using a domain name (ping trilium.yourdomain.com).
  • Resolution: Systematically check each layer: Container Status -> Port Mapping/Proxy Config -> Firewall -> Network/DNS. Correct the configuration at the point where the connection fails.

3. Data Loss / Notes Disappeared:

  • Symptom: Notes that were previously present are missing after a restart or update.
  • Diagnosis:
    • Volume Mounting: Was the data volume correctly configured and mounted? Double-check the volumes section in docker-compose.yml. Did you accidentally delete or change the volume? Use docker volume ls and docker volume inspect trilium_data.
    • Accidental Deletion: Could the notes have been accidentally deleted within Trilium? Check Trilium's internal Trash bin (if the feature exists/is enabled in your version).
    • Database Corruption: Severe errors during operation could lead to database issues. Check logs for sqlite errors.
  • Resolution: Restore from backup. This highlights the critical importance of regular, tested backups. If you suspect accidental deletion, check the Trash. If corruption is suspected, restoring is usually the only safe option.

4. Performance Issues / Slowness:

  • Symptom: Trilium interface is sluggish, searches take a long time, saving notes is slow.
  • Diagnosis:
    • Server Resources: Is the server underpowered? Check CPU usage (top or htop), memory usage (free -h), and disk I/O (iotop). Trilium can consume significant RAM with very large note counts or complex scripting.
    • Note Complexity: Do you have extremely large notes (many MBs of text or large embedded images)? Are you running complex scripts frequently or on many notes simultaneously?
    • Network Latency: If accessing remotely, check your network connection speed and latency to the server.
    • Browser Issues: Try accessing Trilium from a different browser or in incognito/private mode to rule out browser extension conflicts or cache issues.
    • Trilium Logs: Check for any recurring errors or warnings in the Trilium logs that might indicate background processes struggling.
  • Resolution:
    • Upgrade server resources (CPU, RAM) if necessary.
    • Optimize notes: Split very large notes, potentially store large files externally and link to them.
    • Optimize scripts: Make scripts more efficient, reduce their frequency, or limit the number of notes they process at once.
    • Investigate network issues.
    • Clear browser cache/try different browser.

5. Scripting Errors:

  • Symptom: Automated workflows fail, custom buttons don't work, errors appear in logs related to JS scripts.
  • Diagnosis:
    • Check Script Logs: console.log statements within your scripts are invaluable. Check the Trilium server logs (docker logs trilium-notes) for output and error messages from your scripts (JavaScript syntax errors, runtime errors like accessing properties of null, API misuse).
    • Review Script Logic: Carefully read through your script code. Did you use the correct API methods? Are you handling cases where notes might not be found (checking for null results)?
    • Test Incrementally: Test small parts of your script logic before combining them. Use the #runnable attribute to test scripts manually.
    • API Changes: Did you recently update Trilium? Check the release notes for any changes to the backend scripting API that might break your scripts.
  • Resolution: Debug the script using logs and careful code review. Correct syntax errors, add error handling (e.g., try...catch blocks), and adjust logic based on API documentation or observed behavior.

General Troubleshooting Strategy:

  1. Identify the Symptom: What exactly is not working? Be specific.
  2. Check the Logs: Start with Trilium's logs (docker logs trilium-notes), then potentially the reverse proxy logs (docker logs nginx-proxy-manager), and system logs (/var/log/syslog or journalctl).
  3. Check Container Status: Is everything running (docker ps)?
  4. Check Configuration: Review docker-compose.yml files, proxy settings, firewall rules.
  5. Isolate the Problem: Can you reproduce the issue consistently? Does it happen only under certain conditions? Can you simplify the setup to narrow down the cause (e.g., bypass the reverse proxy temporarily)?
  6. Consult Documentation/Community: Check the official Trilium Wiki/Docs and GitHub discussions/issues. Someone else may have encountered the same problem.
  7. Restore (Last Resort): If data corruption is suspected or you're completely stuck, restore from a known good backup.

Workshop Diagnosing a "Connection Refused" Error

Goal: To simulate and diagnose a common "Connection Refused" error when trying to access Trilium, practicing the troubleshooting steps.

Prerequisites:

  • Trilium running via Docker Compose, potentially behind Nginx Proxy Manager (as set up previously).

Scenario: You try to access https://trilium.yourdomain.com (or http://your_ip:8080 if not using a proxy), but your browser shows "Connection Refused" or a similar error indicating the server isn't accepting connections on that port.

Steps (Simulate and Diagnose):

  1. Simulate the Problem (Option A - Trilium Container Stopped):
    • Connect to your server via SSH.
    • Stop only the Trilium container: cd ~/trilium-docker && docker compose stop trilium (or docker stop trilium-notes).
    • Try accessing Trilium in your browser again.
      • If using NPM: You'll likely get a "502 Bad Gateway" error from NPM because it can't reach the stopped Trilium backend.
      • If accessing directly (:8080): You should now get "Connection Refused."
  2. Simulate the Problem (Option B - Firewall Block):
    • Ensure Trilium container is running: cd ~/trilium-docker && docker compose start trilium
    • Check your firewall status (e.g., sudo ufw status).
    • If accessing directly (:8080) and port 8080 is allowed, block it: sudo ufw deny 8080/tcp.
    • If using NPM (:80, :443) and ports 80/443 are allowed, block them: sudo ufw deny 80/tcp && sudo ufw deny 443/tcp.
    • Try accessing Trilium in your browser. You should get a timeout or "Connection Refused" error because the firewall is blocking the connection before it reaches the container or proxy.
  3. Start Diagnosis (Assume you just encountered the error without knowing the cause):

    • Step 1: Check Container Status:

      • Run docker ps.
      • Observation (if simulating A): You see nginx-proxy-manager is UP, but trilium-notes is not listed or shows Exited. Diagnosis: Trilium container is down.
      • Observation (if simulating B): Both nginx-proxy-manager (if used) and trilium-notes are Up. Diagnosis: Containers are running, the issue is likely network/firewall/proxy config.
    • Step 2: Check Logs (If Container Down):

      • If trilium-notes was down (Simulation A), check its logs: docker logs trilium-notes. Look for the reason it stopped (maybe an error during startup, or maybe it was just manually stopped).
    • Step 3: Check Port Mapping / Reverse Proxy Config:

      • Scenario: Direct Access (:8080), Simulation B. Container is UP. Check docker ps output for the PORTS column for trilium-notes. Does it show 0.0.0.0:8080->8080/tcp (or similar)? If yes, mapping is likely okay.
      • Scenario: NPM Access (https://...), Simulation B. Container is UP. Check NPM logs: cd ~/npm-docker && docker compose logs app. Any errors related to connecting to trilium-notes:8080? Check NPM proxy host config in the UI. Let's assume it looks correct.
    • Step 4: Check Firewall:

      • Run sudo ufw status verbose (or the equivalent for your firewall).
      • Observation (if simulating B on :8080): You see 8080/tcp DENY IN Anywhere. Diagnosis: Firewall is blocking the required port.
      • Observation (if simulating B on :80/443): You see 80/tcp DENY IN Anywhere and 443/tcp DENY IN Anywhere. Diagnosis: Firewall is blocking the required ports for the reverse proxy.
  4. Resolve the Issue:

    • Resolution (Simulation A): The container was stopped. Start it: cd ~/trilium-docker && docker compose start trilium. Verify access again.
    • Resolution (Simulation B): The firewall was blocking the port(s). Allow them:
      • For direct access: sudo ufw allow 8080/tcp
      • For NPM access: sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
      • Verify firewall status again (sudo ufw status). Verify access to Trilium in the browser.

Outcome: By simulating common failure modes (stopped container, firewall block) and following a systematic diagnostic process (Check container -> Check logs -> Check config -> Check firewall), you were able to pinpoint the cause of the "Connection Refused" error and apply the correct resolution. This structured approach is key to effectively troubleshooting self-hosted applications. Don't forget to remove any simulation changes (e.g., re-allow ports if you blocked them for the test).

Conclusion

This comprehensive guide has navigated the process of self-hosting Trilium Notes, from initial setup through advanced customization and management. We began with the foundational concepts, emphasizing Trilium's hierarchical structure, attributes, and relations as keys to building a powerful personal knowledge base. We focused on Docker for installation, providing a robust and reproducible deployment method.

Progressing to intermediate usage, we explored the practical application of attributes for metadata, relations for connecting disparate information, templates for efficiency, and cloning for data consistency. Crucially, we established the importance of data management, implementing and testing an automated backup strategy to safeguard your valuable knowledge.

In the advanced section, we unlocked further potential by delving into backend scripting for automation, leveraging the REST API for external integrations, customizing the appearance with CSS themes, and significantly enhancing security by implementing HTTPS via the Nginx Proxy Manager reverse proxy. Finally, we addressed common troubleshooting scenarios, equipping you with a systematic approach to diagnose and resolve issues.

The workshops accompanying each section provided hands-on practice, translating theoretical knowledge into practical skills. By completing these, you have not only deployed Trilium but also configured backups, experimented with attributes and relations, created templates, secured access with HTTPS, and even automated tasks with scripting.

Self-hosting Trilium Notes places you in full control of your personal knowledge management system. It offers privacy, flexibility, and long-term ownership of your data. While it requires more initial effort than cloud-based solutions, the resulting control and customization capabilities are invaluable for students, researchers, and anyone serious about building and maintaining a structured knowledge base.

Continue exploring Trilium's features, experiment with scripting and relations, refine your organizational structure, and regularly maintain your backups. Your self-hosted Trilium instance is now a powerful tool ready to support your learning, research, and creative endeavors.