How to register a new vault programatically?

I am trying to automate the process of turning code development project folders into Obsidian vaults. I have 80% of the functionality tested and working but I am missing a critical piece: registering the new vault with Obsidian.

I have tried adding the new vault via $HOME/config/obsidian/obsidian.json in Linux but new notes will not open; a message with a “vault not registered” error will pop up. It will work if I manually open the new vault with the Obsidian GUI, but that breaks the automation.

Has anyone be able able to register a new Obsidian vault via code (bash, JS, or Python)?

Finally, tonight I cracked the code for programmatically registering an Obsidian vault. It wasn’t easy; it was tricky. I have added comments CRITICAL to show parts of the script that are essential to tweak to make this work.

This is the code of the shell script:

#!/bin/bash
#
# open-vault.sh - Obsidian Vault Manager
#
# Usage: open-vault.sh <vault-name|vault-path|vault-id>
#
# This script will:
# 1. Check if the vault is registered in Obsidian's config
# 2. If registered, open it
# 3. If not registered, register it with a random ID and open it
#

set -e

# Configuration
OBSIDIAN_CONFIG="$HOME/.config/obsidian/obsidian.json"
OBSIDIAN_CONFIG_BACKUP="$HOME/.config/obsidian/obsidian.json.backup"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Check if jq is installed
if ! command -v jq &> /dev/null; then
    echo -e "${RED}Error: jq is required but not installed.${NC}"
    echo "Install it with: sudo apt install jq"
    exit 1
fi

# Check if parameter provided
if [ $# -eq 0 ]; then
    echo "Usage: $0 <vault-name|vault-path|vault-id>"
    echo ""
    echo "Examples:"
    echo "  $0 books-vault                    # Open by name"
    echo "  $0 /path/to/vault                 # Open by path"
    echo "  $0 e0c208e7b153aa0b               # Open by ID"
    exit 1
fi

INPUT="$1"

# Check if Obsidian config exists
if [ ! -f "$OBSIDIAN_CONFIG" ]; then
    echo -e "${RED}Error: Obsidian config not found at $OBSIDIAN_CONFIG${NC}"
    exit 1
fi

# Function to generate random vault ID
generate_vault_id() {
    openssl rand -hex 8
}

# Function to get absolute path
get_absolute_path() {
    local path="$1"
    if [ -d "$path" ]; then
        # CRITICAL: Use readlink/realpath instead of cd to avoid issues with set -e
        # The previous implementation with 'cd "$path" && pwd' could fail in set -e mode
        # Using readlink -f properly resolves relative paths to absolute paths
        if command -v readlink &> /dev/null; then
            readlink -f "$path"
        elif command -v realpath &> /dev/null; then
            realpath "$path"
        else
            (cd "$path" && pwd)
        fi
    else
        echo ""
    fi
}

# Function to get vault name from path
get_vault_name() {
    basename "$1"
}

# Function to check if vault is registered
check_vault_registered() {
    local input="$1"
    local result=""

    # Check if input is a vault ID (16 hex characters)
    if [[ "$input" =~ ^[a-f0-9]{16}$ ]]; then
        result=$(jq -r --arg id "$input" '.vaults[$id] // empty' "$OBSIDIAN_CONFIG")
        if [ -n "$result" ]; then
            echo "id:$input"
            return 0
        fi
    fi

    # Check if input is a path
    if [ -d "$input" ]; then
        local abs_path=$(get_absolute_path "$input")
        result=$(jq -r --arg path "$abs_path" '.vaults | to_entries[] | select(.value.path == $path) | .key' "$OBSIDIAN_CONFIG" | head -1)
        if [ -n "$result" ]; then
            echo "id:$result|path:$abs_path"
            return 0
        else
            echo "path:$abs_path"
            return 1
        fi
    fi

    # Check if input is a vault name
    # Try to find vault by matching the folder name
    result=$(jq -r --arg name "$input" '.vaults | to_entries[] | select(.value.path | split("/")[-1] == $name) | .key + "|" + .value.path' "$OBSIDIAN_CONFIG" | head -1)
    if [ -n "$result" ]; then
        local vault_id=$(echo "$result" | cut -d'|' -f1)
        local vault_path=$(echo "$result" | cut -d'|' -f2)
        echo "id:$vault_id|path:$vault_path|name:$input"
        return 0
    fi

    # Not found
    echo "name:$input"
    return 1
}

# Function to register vault
register_vault() {
    local vault_path="$1"
    local vault_id=$(generate_vault_id)
    local timestamp=$(date +%s)000

    echo -e "${YELLOW}Registering new vault...${NC}"
    echo "  Path: $vault_path"
    echo "  ID: $vault_id"

    # Backup config
    cp "$OBSIDIAN_CONFIG" "$OBSIDIAN_CONFIG_BACKUP"

    # Add vault to config
    jq --arg id "$vault_id" \
       --arg path "$vault_path" \
       --argjson ts "$timestamp" \
       '.vaults[$id] = {"path": $path, "ts": $ts, "open": true}' \
       "$OBSIDIAN_CONFIG" > "$OBSIDIAN_CONFIG.tmp" && \
       mv "$OBSIDIAN_CONFIG.tmp" "$OBSIDIAN_CONFIG"

    echo -e "${GREEN}✓ Vault registered successfully${NC}"
    echo "$vault_id"
}

# Function to open vault
open_vault() {
    local vault_id="$1"
    local vault_path="$2"
    local use_uri="${3:-false}"

    echo -e "${GREEN}Opening vault...${NC}"

    # CRITICAL: Different opening methods for registered vs unregistered vaults
    # This is what makes the script work efficiently for both cases
    if [ "$use_uri" = "true" ] && [ -n "$vault_id" ]; then
        # REGISTERED VAULTS: Use URI method (obsidian://open?vault=ID)
        # This switches vaults WITHOUT restarting Obsidian - fast and smooth!
        # Works even if Obsidian is already running with a different vault
        xdg-open "obsidian://open?vault=$vault_id" >/dev/null 2>&1 &
    elif [ -n "$vault_path" ]; then
        # UNREGISTERED VAULTS: Open by path
        # Used after registering a new vault and restarting Obsidian
        # Subshell ensures proper detachment from terminal
        (obsidian "$vault_path" >/dev/null 2>&1 &)
    else
        # Fallback to URI with ID
        xdg-open "obsidian://open?vault=$vault_id" >/dev/null 2>&1 &
    fi

    sleep 0.5
}

# Function to restart Obsidian
restart_obsidian() {
    echo -e "${YELLOW}Restarting Obsidian...${NC}"
    # CRITICAL: Use 'pkill -f' to match the full command line
    # Obsidian runs as an electron process, so 'pkill obsidian' won't work
    # We need to match the full path '/usr/lib/obsidian' in the process command
    pkill -f "/usr/lib/obsidian" 2>/dev/null || true
    pkill -f "obsidian" 2>/dev/null || true

    # Wait for processes to fully terminate before trying to restart
    # This ensures clean shutdown and prevents conflicts
    sleep 3
    echo -e "${GREEN}✓ Obsidian closed${NC}"
}

# Main logic
echo -e "${GREEN}=== Obsidian Vault Manager ===${NC}"
echo ""

# CRITICAL: Temporarily disable 'set -e' to prevent script exit on vault check failure
# When a vault is not registered, check_vault_registered returns 1, which would
# normally exit the script with 'set -e' enabled. We need to capture this return
# value to distinguish between registered and unregistered vaults.
set +e
vault_info=$(check_vault_registered "$INPUT")
vault_found=$?
set -e

if [ $vault_found -eq 0 ]; then
    # REGISTERED VAULT PATH
    # Vault exists in Obsidian's config - use fast URI switching
    echo -e "${GREEN}✓ Vault found in registry${NC}"

    vault_id=$(echo "$vault_info" | grep -oP 'id:\K[^|]+')
    vault_path=$(echo "$vault_info" | grep -oP 'path:\K[^|]+' || echo "")

    # CRITICAL: Pass "true" as third parameter to use URI method
    # This allows instant vault switching without restarting Obsidian
    # The URI method (obsidian://open?vault=ID) works even when Obsidian is running
    open_vault "$vault_id" "$vault_path" "true"
else
    # UNREGISTERED VAULT PATH
    # Vault doesn't exist in Obsidian's config - need to register it first
    # This requires: finding the vault, validating it, registering it, restarting Obsidian
    echo -e "${YELLOW}⚠ Vault not found in registry${NC}"

    # Determine the path (handles relative paths, absolute paths, or vault names)
    if [ -d "$INPUT" ]; then
        vault_path=$(get_absolute_path "$INPUT")
    else
        # Try to find vault in common locations
        possible_paths=(
            "$HOME/obsidian/$INPUT"
            "$HOME/Documents/$INPUT"
            "$HOME/Obsidian/$INPUT"
        )

        vault_path=""
        for path in "${possible_paths[@]}"; do
            if [ -d "$path" ]; then
                vault_path=$(get_absolute_path "$path")
                echo -e "${GREEN}Found vault at: $vault_path${NC}"
                break
            fi
        done

        if [ -z "$vault_path" ]; then
            echo -e "${RED}Error: Could not find vault '$INPUT'${NC}"
            echo "Please provide the full path to the vault."
            exit 1
        fi
    fi

    # Check if path has .obsidian folder (is a valid vault)
    if [ ! -d "$vault_path/.obsidian" ]; then
        echo -e "${RED}Error: '$vault_path' does not appear to be a valid Obsidian vault${NC}"
        echo "(.obsidian folder not found)"
        exit 1
    fi

    # CRITICAL: Workflow for unregistered vaults (different from registered)
    # 1. Register: Add vault to obsidian.json with new random ID
    vault_id=$(register_vault "$vault_path")

    # 2. Restart: Obsidian must be restarted to load the updated config
    #    Without this, Obsidian won't know about the new vault
    restart_obsidian

    # 3. Open: Now open the newly registered vault by path (NOT URI)
    #    We use path method here because Obsidian was just closed/restarted
    #    Note: Third parameter is NOT "true", so it uses path-based opening
    open_vault "$vault_id" "$vault_path"
fi

echo ""
echo -e "${GREEN}✓ Done!${NC}"

I am calling the script open-vault.sh

Copy the script to the folder where you keep your Obsidian vault. But they can really be anywhere in your folders, but you will have to provide the full path to the unregistered vault.

Then do chmod +x open-vault.shto make it executable.

More information on the GitHub repo: https://github.com/Physics-of-Data/obsidian-open-vault

Enjoy!

[email protected]