Skip to main content

Overview

The connection-tree command returns a hierarchical tree of database objects that the host renders as a browsable sidebar. Supports nested structures like databases → schemas → tables → columns.

Command Invocation

<plugin-binary> connection-tree
# Alternative alias:
<plugin-binary> tree
Stdin: JSON-encoded ConnectionTreeRequest object Stdout: JSON-encoded ConnectionTreeResponse object Timeout: 30 seconds Required: No - optional command for enhanced browsing UX

Request Parameters

Response Structure

nodes
ConnectionTreeNode[]
required
Top-level array of tree nodes. Each node can have nested children.

ConnectionTreeNode Fields

ConnectionTreeAction Fields

Example Implementation

From plugins/mysql/main.go:
func (m *mysqlPlugin) ConnectionTree(ctx context.Context, req *plugin.ConnectionTreeRequest) (*plugin.ConnectionTreeResponse, error) {
    dsn, err := buildDSN(req.Connection)
    if err != nil || dsn == "" {
        return &plugin.ConnectionTreeResponse{}, nil
    }
    
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return &plugin.ConnectionTreeResponse{}, nil
    }
    defer db.Close()

    // If the user supplied a database explicitly we only show that one
    filterDB := getDatabaseFromConn(req.Connection)

    rows, err := db.Query("SHOW DATABASES")
    if err != nil {
        return &plugin.ConnectionTreeResponse{}, nil
    }
    defer rows.Close()

    var dbNodes []*plugin.ConnectionTreeNode
    for rows.Next() {
        var dbname string
        if err := rows.Scan(&dbname); err != nil {
            continue
        }
        if filterDB != "" && dbname != filterDB {
            continue
        }
        
        // For each database expose a child list of tables
        tables := []*plugin.ConnectionTreeNode{}
        tblRows, err := db.Query(fmt.Sprintf("SHOW TABLES FROM `%s`", dbname))
        if err == nil {
            for tblRows.Next() {
                var tbl string
                if tblRows.Scan(&tbl) == nil {
                    tables = append(tables, &plugin.ConnectionTreeNode{
                        Key:      dbname + "." + tbl,
                        Label:    tbl,
                        NodeType: plugin.ConnectionTreeNodeTypeTable,
                        Actions: []*plugin.ConnectionTreeAction{
                            {
                                Type:   plugin.ConnectionTreeActionSelect,
                                Title:  "Select rows",
                                Query:  fmt.Sprintf("SELECT * FROM `%s`.`%s` LIMIT 100;", dbname, tbl),
                                Hidden: true,
                                NewTab: true,
                            },
                            {
                                Type:  plugin.ConnectionTreeActionDropTable,
                                Title: "Drop table",
                                Query: fmt.Sprintf("DROP TABLE `%s`.`%s`;", dbname, tbl),
                            },
                        },
                    })
                }
            }
            tblRows.Close()
        }
        
        dbNodes = append(dbNodes, &plugin.ConnectionTreeNode{
            Key:      dbname,
            Label:    dbname,
            NodeType: plugin.ConnectionTreeNodeTypeDatabase,
            Children: tables,
            Actions: []*plugin.ConnectionTreeAction{
                {
                    Type:  plugin.ConnectionTreeActionCreateTable,
                    Title: "Create table",
                    Query: fmt.Sprintf("CREATE TABLE `%s`.`new_table` (\n  `id` INT NOT NULL AUTO_INCREMENT,\n  PRIMARY KEY (`id`)\n);", dbname),
                },
                {
                    Type:  plugin.ConnectionTreeActionDropDatabase,
                    Title: "Drop database",
                    Query: fmt.Sprintf("DROP DATABASE `%s`;", dbname),
                },
            },
        })
    }

    // Prepend a leaf node for the create-database action
    createNode := &plugin.ConnectionTreeNode{
        Key:      "__create_database__",
        Label:    "New database",
        NodeType: plugin.ConnectionTreeNodeTypeAction,
        Actions: []*plugin.ConnectionTreeAction{
            {
                Type:   plugin.ConnectionTreeActionCreateDatabase,
                Title:  "Create database",
                Query:  "CREATE DATABASE `new_database`;",
                Hidden: true,
            },
        },
    }

    return &plugin.ConnectionTreeResponse{
        Nodes: append([]*plugin.ConnectionTreeNode{createNode}, dbNodes...),
    }, nil
}

Request Format (stdin)

{
  "connection": {
    "credential_blob": "{\"form\":\"basic\",\"values\":{\"host\":\"127.0.0.1\",\"user\":\"root\",\"password\":\"secret\"}}"
  }
}

Response Format (stdout)

{
  "nodes": [
    {
      "key": "__create_database__",
      "label": "New database",
      "nodeType": "NODE_TYPE_ACTION",
      "actions": [
        {
          "type": "create-database",
          "title": "Create database",
          "query": "CREATE DATABASE `new_database`;",
          "hidden": true
        }
      ]
    },
    {
      "key": "mydb",
      "label": "mydb",
      "nodeType": "NODE_TYPE_DATABASE",
      "children": [
        {
          "key": "mydb.users",
          "label": "users",
          "nodeType": "NODE_TYPE_TABLE",
          "actions": [
            {
              "type": "select",
              "title": "Select rows",
              "query": "SELECT * FROM `mydb`.`users` LIMIT 100;",
              "hidden": true,
              "newTab": true
            },
            {
              "type": "drop-table",
              "title": "Drop table",
              "query": "DROP TABLE `mydb`.`users`;"
            }
          ]
        }
      ],
      "actions": [
        {
          "type": "create-table",
          "title": "Create table",
          "query": "CREATE TABLE `mydb`.`new_table` (\n  `id` INT NOT NULL AUTO_INCREMENT,\n  PRIMARY KEY (`id`)\n);"
        },
        {
          "type": "drop-database",
          "title": "Drop database",
          "query": "DROP DATABASE `mydb`;"
        }
      ]
    }
  ]
}

Action Execution Flow

  1. User right-clicks a node or double-clicks it
  2. Host shows context menu with non-hidden actions
  3. User selects an action
  4. Host calls the plugin’s exec command with the action’s query string
  5. Result is displayed in current or new tab based on newTab flag

Common Patterns

Default Click Action

Use hidden: true + newTab: true for the primary action triggered by double-clicking a node:
{
    Type:   "select",
    Title:  "Select rows",
    Query:  "SELECT * FROM ...",
    Hidden: true,  // Don't show in context menu
    NewTab: true,  // Open in new tab
}

DDL Actions

Provide template queries for creating/dropping objects:
{
    Type:  "create-table",
    Title: "Create table",
    Query: "CREATE TABLE `db`.`new_table` (\n  `id` INT AUTO_INCREMENT,\n  PRIMARY KEY (`id`)\n);",
}

Error Handling

  • If connection fails, return an empty tree: {"nodes": []}
  • Do not write to stderr or exit with non-zero status unless a fatal error occurs
  • The host gracefully handles empty trees by showing “No objects found”

Notes

  • Tree structure is plugin-defined - SQL databases typically use database → table → column hierarchy
  • Document stores might use database → collection hierarchy
  • Key-value stores might flatten to a single level or group by namespace
  • Action queries are executed via the exec command, so use the same query syntax