Creating a formatted list of plugins currently installed and whether they are enabled or disabled

I was looking for a clean way to get a list of my currently installed plugins as well as which are enabled/disabled. I also wanted to have them as links to there own pages with the idea being I could regenerate the list whenever I added more plugins (I tend to add none then several at once) and those without already created pages would have ghost links made. I was not able to get it to add the github repo as I couldnt find a consistent place they were listed (manifest files were inconsistent for this). Closest thing I could find to this was the issue of: Create a note from list of installed plugins Its not the cleanest code but figured I would share in case anyone wanted to manage there plugins in a similar way.

The code is below, written in python and executed via the execute code plugin. The vault path is dynamically provided by execute code so it should work without modifications.

Code:

import os
import json
import re


def get_value_from_json(file_path, key):
    with open(file_path, 'r') as json_file:
        data = json.load(json_file)
    return data.get(key, None)

community_plugin_folder_path = os.path.join(@vault_path, ".obsidian/plugins")
core_plugins_file_path =os.path.join(@vault_path, ".obsidian/core-plugins-migration.json")
community_plugins_file_path = os.path.join(@vault_path, ".obsidian/community-plugins.json")
   



plugin_folders = [name for name in os.listdir(community_plugin_folder_path) if os.path.isdir(os.path.join(plugin_path, name))]

#read in list of core plugins and activation state
with open(core_plugins_file_path, 'r') as f:
    core_plugins_dict = json.load(f)
    
#read in list of enabled community plugins
with open(community_plugins_file_path, 'r') as f:
    community_plugins_list = json.load(f)


key_name = 'name'
key_id = 'id'

community_plugin_name_list = list()

for plugin in plugin_folders:
	file_path = plugin_path + "/" + plugin + "/manifest.json"
	community_plugin_name_list.append((get_value_from_json(file_path, key_name),get_value_from_json(file_path, key_id)))


#split into enabled and not enabled lists. Do both core and community plugins, add (core) or (community) to end for tracking in name
enabled_plugins = list()
disabled_plugins = list()


for key, value in core_plugins_dict.items():

    key += " (core)"
    

    if value:
        enabled_plugins.append(key)
    else:
        disabled_plugins.append(key)


for plugin_name, plugin_id in community_plugin_name_list:
	plugin_name += " (community)"
	
	if plugin_id in community_plugins_list:
		enabled_plugins.append(plugin_name)
	else:
		disabled_plugins.append(plugin_name)






#output list of enabled plugins
print(f"Enabled Plugins ")
enabled_plugins.sort()
for name in enabled_plugins:
	name = re.sub(r'[\/\\<>"|?*]', '-', name)
	print(f"- [[{name}]]: ")

#output list of disabled plugins
print(f"Disabled Plugins ")
disabled_plugins.sort()
for name in disabled_plugins:
	name = re.sub(r'[\/\\<>"|?*]', '-', name)
	print(f"- [[{name}]]: ")


The Output it gives for me is:

Enabled Plugins

  • [[Admonition (community)]]:
  • [[Advanced Tables (community)]]:
  • [[Apply Patterns (community)]]:
  • [[Buttons (community)]]:
  • [[Checkbox Reorder (community)]]:
  • [[Creases (community)]]:
  • [[Customizable Menu (community)]]:
  • [[Dataview (community)]]:
  • [[Execute Code (community)]]:
  • [[Hotkeys for templates (community)]]:
  • [[Packrat (community)]]:
  • [[Paste Mode (community)]]:
  • [[Paste URL into selection (community)]]:
  • [[Periodic Notes (community)]]:
  • [[Prominent Bookmarked Files (community)]]:
  • [[Reading Time (community)]]:
  • [[Regex Find-Replace (community)]]:
  • [[Reminder (community)]]:
  • [[Search on Internet (community)]]:
  • [[Tasks (community)]]:
  • [[Templater (community)]]:
  • [[Timestamp Notes (community)]]:
  • [[backlink (core)]]:
  • [[bookmarks (core)]]:
  • [[canvas (core)]]:
  • [[command-palette (core)]]:
  • [[daily-notes (core)]]:
  • [[editor-status (core)]]:
  • [[file-explorer (core)]]:
  • [[file-recovery (core)]]:
  • [[global-search (core)]]:
  • [[graph (core)]]:
  • [[note-composer (core)]]:
  • [[outgoing-link (core)]]:
  • [[outline (core)]]:
  • [[page-preview (core)]]:
  • [[switcher (core)]]:
  • [[sync (core)]]:
  • [[tag-pane (core)]]:
  • [[templates (core)]]:
  • [[word-count (core)]]:
    Disabled Plugins
  • [[Convert url to preview (iframe) (community)]]:
  • [[Excalidraw (community)]]:
  • [[Google Calendar (community)]]:
  • [[Graph Analysis (community)]]:
  • [[Importer (community)]]:
  • [[Ozan’s Image in Editor Plugin (community)]]:
  • [[RSS Reader (community)]]:
  • [[Sliding Panes (Andy’s Mode) (community)]]:
  • [[Tag Wrangler (community)]]:
  • [[Tasks Calendar Wrapper (community)]]:
  • [[audio-recorder (core)]]:
  • [[markdown-importer (core)]]:
  • [[properties (core)]]:
  • [[publish (core)]]:
  • [[random-note (core)]]:
  • [[slash-command (core)]]:
  • [[slides (core)]]:
  • [[workspaces (core)]]:
  • [[zk-prefixer (core)]]:

I plan to add the github url to the right of the :

5 Likes

Thanks. I wanted to do the same thing. But I’m not a programmer. How do I use this code?

@Luhmann,

  1. Make sure you have Python installed on your system. If you don’t have it, you can get the installation package from the official source
  2. Take note of where the Python interpreter is installed (e.g. /usr/bin/Python)
  3. Install the “Execute Code” Obsidian community plugin
  4. In it’s settings, under “Language specific settings”, select “Python”
  5. In the Python-specific settings which will then display, under “Python path”, insert the information from step 2
  6. In a new note, insert a code block with the type “Python”
  7. In the body of the code block, paste the code from @thomaspholland`s message
  8. Be aware that the code has the same error on two lines; the variable plugin_path has to be replaced with community_plugin_folder_path
  9. Switch to “Reading mode”; if you now move the mouse over the code block, on it’s bottom line, a “run” button should appear
  10. Click the button. If all went well, the list of plugins will be displayed immediately below the code block as shown above.

HTH

1 Like

There is an easier way.

  • Open Command Pallete
  • Click on Debug Info
  • :white_check_mark: Done
3 Likes

Thanks this is the fastest way that requires the least effort to get a list of your installed plugins, I don’t know if it includes plugins that have been disabled or not though

1 Like

copy this to the note

[!note]
Obsidian Command Pallete → Show Debug Info

List of installed plugins (Dataview)

// 📦 Plugin Status Overview for Obsidian (enabled vs disabled)
// This script scans the Obsidian plugin folder, reads each plugin's `manifest.json`,
// and displays a side-by-side table of enabled and disabled plugins.
// It includes plugin name, version, last modified date, and description.
dv.container.classList.add("plugin-list-container-v2");

const pluginsDir = app.vault.configDir + "/plugins";

try {
  const pluginFolders = await app.vault.adapter.list(pluginsDir);
  
  if (!pluginFolders || !pluginFolders.folders) {
    dv.paragraph("Plugin folder not found or empty.");
    return;
  }

  // Get the set of currently enabled plugins from Obsidian
  const enabledPlugins = app.plugins.enabledPlugins || new Set();
  
  const enabledPluginsList = [];
  const disabledPluginsList = [];
  
  for (const pluginPath of pluginFolders.folders) {
    const manifestPath = `${pluginPath}/manifest.json`;
    
    try {
      const manifestExists = await app.vault.adapter.exists(manifestPath);
      if (!manifestExists) continue;
      
      const manifestContent = await app.vault.adapter.read(manifestPath);
      const manifest = JSON.parse(manifestContent);
      
      const pluginName = manifest.name || "Unknown Plugin";
      const pluginId = manifest.id || pluginPath.split('/').pop();
      const description = manifest.description || "No description available";
      const version = manifest.version || "unknown";
      
      // Get last modification date of manifest.json
      const manifestStat = await app.vault.adapter.stat(manifestPath);
      const lastModified = manifestStat ? manifestStat.mtime : 0;
      
      const pluginInfo = {
        name: pluginName,
        id: pluginId,
        description: description,
        version: version,
        lastModified: lastModified
      };
      
      const isEnabled = enabledPlugins.has(pluginId);
      if (isEnabled) {
        enabledPluginsList.push(pluginInfo);
      } else {
        disabledPluginsList.push(pluginInfo);
      }

    } catch (e) {
      // If manifest is unreadable or corrupted, show plugin as disabled with error
      const folderName = pluginPath.split('/').pop();
      disabledPluginsList.push({
        name: folderName,
        id: folderName,
        description: "Error reading manifest",
        version: "?",
        lastModified: 0
      });
    }
  }
  
  // Sort both lists by last modified date (most recent first)
  enabledPluginsList.sort((a, b) => b.lastModified - a.lastModified);
  disabledPluginsList.sort((a, b) => b.lastModified - a.lastModified);
  
  const totalPlugins = enabledPluginsList.length + disabledPluginsList.length;
  const maxRows = Math.max(enabledPluginsList.length, disabledPluginsList.length);
  const tableData = [];

  // Prepare HTML cells for both enabled and disabled plugin columns
  for (let i = 0; i < maxRows; i++) {
    const enabledPlugin = enabledPluginsList[i];
    const disabledPlugin = disabledPluginsList[i];
    
    const enabledName = enabledPlugin ? 
      `<div class="plugin-name-cell">
        <strong><a href="obsidian://show-plugin?id=${enabledPlugin.id}">${enabledPlugin.name}</a></strong>
        <small>v${enabledPlugin.version} • ${new Date(enabledPlugin.lastModified).toLocaleDateString()}</small>
      </div>` : 
      "—";
    
    const enabledDesc = enabledPlugin ? 
      `<div class="plugin-desc-cell">${enabledPlugin.description}</div>` : 
      "—";
    
    const disabledName = disabledPlugin ? 
      `<div class="plugin-name-cell">
        <span><a href="obsidian://show-plugin?id=${disabledPlugin.id}">${disabledPlugin.name}</a></span>
        <small>v${disabledPlugin.version} • ${new Date(disabledPlugin.lastModified).toLocaleDateString()}</small>
      </div>` : 
      "—";
    
    const disabledDesc = disabledPlugin ? 
      `<div class="plugin-desc-cell">${disabledPlugin.description}</div>` : 
      "—";
    
    tableData.push([enabledName, enabledDesc, disabledName, disabledDesc]);
  }
  
  // Create completely clean headers
  const enabledCount = enabledPluginsList.length;
  const disabledCount = disabledPluginsList.length;
  const total = totalPlugins;
  
  const col1 = "🟢 Enabled Plugins (" + enabledCount + "/" + total + ")";
  const col2 = "Description";
  const col3 = "đź”´ Disabled Plugins (" + disabledCount + ")";
  const col4 = "Description";
  
  // Debug output
  console.log("Headers:", col1, col2, col3, col4);
  
  // Render the final table with clean headers
  dv.table([col1, col2, col3, col4], tableData);
  
  // Add custom CSS with new class name to avoid conflicts
  const styleId = 'obsidian-plugin-list-styles-v2';
  const existingStyle = document.getElementById(styleId);
  if (existingStyle) {
    existingStyle.remove();
  }
  
  const style = document.createElement('style');
  style.id = styleId;
  style.textContent = `
    /* Use custom container class for high specificity */
    .plugin-list-container-v2 table {
      font-size: 0.9rem !important;
      border-spacing: 0;
      border-collapse: collapse;
      width: 100%;
      table-layout: fixed;
    }
    
    /* Style table cells within custom container */
    .plugin-list-container-v2 table td,
    .plugin-list-container-v2 table th {
      padding: 8px 12px;
      vertical-align: top;
      border-bottom: 1px solid var(--background-modifier-border);
    }
    
    /* Set specific column widths */
    .plugin-list-container-v2 table th:nth-child(1),
    .plugin-list-container-v2 table td:nth-child(1) {
      width: 25%;
    }
    
    .plugin-list-container-v2 table th:nth-child(2),
    .plugin-list-container-v2 table td:nth-child(2) {
      width: 25%;
    }
    
    .plugin-list-container-v2 table th:nth-child(3),
    .plugin-list-container-v2 table td:nth-child(3) {
      width: 25%;
    }
    
    .plugin-list-container-v2 table th:nth-child(4),
    .plugin-list-container-v2 table td:nth-child(4) {
      width: 25%;
    }
    
    .plugin-list-container-v2 table tr:last-child td {
      border-bottom: none;
    }
    
    /* Table headers */
    .plugin-list-container-v2 table th {
      font-size: 13px !important;
      font-weight: 600 !important;
      padding: 6px 12px !important;
      text-align: left !important;
      background-color: var(--background-modifier-form-field);
    }
    
    /* Plugin name cell styling */
    .plugin-list-container-v2 .plugin-name-cell {
      display: flex;
      flex-direction: column;
      gap: 4px;
      width: 100%;
      overflow: hidden;
    }
    
    .plugin-list-container-v2 .plugin-name-cell strong {
      font-size: 14px;
      font-weight: 500;
      line-height: 1.3;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    
    .plugin-list-container-v2 .plugin-name-cell span {
      font-size: 14px;
      font-weight: 400;
      line-height: 1.3;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    
    .plugin-list-container-v2 .plugin-name-cell small {
      font-size: 11px !important;
      color: var(--text-muted);
      line-height: 1.2;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    
    /* Plugin description cell styling */
    .plugin-list-container-v2 .plugin-desc-cell {
      font-size: 12px !important;
      color: var(--text-muted);
      line-height: 1.3;
      width: 100%;
      word-wrap: break-word;
      overflow-wrap: break-word;
      overflow: hidden;
      display: -webkit-box;
      -webkit-line-clamp: 4;
      -webkit-box-orient: vertical;
    }
    
    /* Links styling */
    .plugin-list-container-v2 table a {
      color: var(--link-color);
      text-decoration: none;
    }
    
    .plugin-list-container-v2 table a:hover {
      text-decoration: underline;
    }
    
    /* Responsive adjustments */
    @media (max-width: 768px) {
      .plugin-list-container-v2 table {
        font-size: 0.8rem !important;
      }
      
      .plugin-list-container-v2 table th:nth-child(1),
      .plugin-list-container-v2 table td:nth-child(1) {
        width: 40%;
      }
      
      .plugin-list-container-v2 table th:nth-child(2),
      .plugin-list-container-v2 table td:nth-child(2) {
        width: 60%;
      }
      
      .plugin-list-container-v2 table th:nth-child(3),
      .plugin-list-container-v2 table td:nth-child(3) {
        width: 40%;
      }
      
      .plugin-list-container-v2 table th:nth-child(4),
      .plugin-list-container-v2 table td:nth-child(4) {
        width: 60%;
      }
      
      .plugin-list-container-v2 .plugin-desc-cell {
        -webkit-line-clamp: 3;
      }
    }
  `;
  document.head.appendChild(style);

} catch (e) {
  console.error("General error:", e);
  dv.paragraph(`Error while reading plugin list: ${e.message}`);
}