Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/metabase/metabase/llms.txt

Use this file to discover all available pages before exploring further.

The driver interface defines how Metabase communicates with different databases. Understanding this interface is essential for building new drivers or customizing existing ones.

What is a driver?

In Metabase, a driver is simply a keyword (like :postgres, :mysql, :mongodb) plus implementations of driver multimethods. There are no classes or objects—just a keyword and functions that dispatch on that keyword.
;; A driver is just this:
(driver/register! :mydriver)

;; Plus method implementations:
(defmethod driver/display-name :mydriver [_]
  "My Database")

Driver architecture

A Metabase driver provides four main capabilities:
1

Database information

Provides metadata like display name, connection properties, and capabilities.
2

Schema introspection

Discovers and reports database schema (tables, columns, foreign keys).
3

Query compilation

Converts MBQL (Metabase Query Language) queries into native database queries.
4

Query execution

Executes native queries and returns formatted results.

Driver multimethods

Multimethods are Clojure’s polymorphism mechanism. They dispatch on the driver keyword:
(ns metabase.driver.mydriver
  (:require [metabase.driver :as driver]))

;; Register the driver
(driver/register! :mydriver, :parent :sql-jdbc)

;; Implement multimethods
(defmethod driver/display-name :mydriver [_]
  "My Database")

(defmethod driver/supports? [:mydriver :foreign-keys] [_ _]
  true)

Listing available methods

To see all available driver methods:
# List method names
clojure -M:run driver-methods

# List with documentation
clojure -M:run driver-methods docs

Essential driver methods

Display and metadata

(defmethod driver/display-name :mydriver [_]
  "My Database")

Connection management

(defmethod sql-jdbc.conn/connection-details->spec :mydriver
  [_ {:keys [host port dbname user password]}]
  {:classname "com.mydb.Driver"
   :subprotocol "mydb"
   :subname (str "//" host ":" port "/" dbname)
   :user user
   :password password})

Database capabilities

;; Declare what features the database supports
(defmethod driver/database-supports? [:mydriver :foreign-keys]
  [_ _ _]
  true)

(defmethod driver/database-supports? [:mydriver :nested-queries]
  [_ _ _]
  true)

(defmethod driver/database-supports? [:mydriver :set-timezone]
  [_ _ _]
  false)

(defmethod driver/database-supports? [:mydriver :left-join]
  [_ _ _]
  true)

(defmethod driver/database-supports? [:mydriver :expressions]
  [_ _ _]
  true)

SQL generation

(defmethod sql.qp/quote-style :mydriver [_]
  :ansi)  ; or :mysql, :sqlserver, etc.

Type mapping

(defmethod sql-jdbc.sync/database-type->base-type :mydriver
  [_ database-type]
  (case (keyword database-type)
    :varchar    :type/Text
    :text       :type/Text
    :integer    :type/Integer
    :bigint     :type/BigInteger
    :float      :type/Float
    :double     :type/Float
    :decimal    :type/Decimal
    :date       :type/Date
    :datetime   :type/DateTime
    :timestamp  :type/DateTimeWithLocalTZ
    :boolean    :type/Boolean
    :json       :type/JSON
    :type/*))   ; Default fallback

Parent drivers

Parent drivers provide shared functionality, similar to inheritance:

Common parent drivers

The most common parent for SQL databases with JDBC support:
(driver/register! :mydriver, :parent :sql-jdbc)
Provides:
  • Connection pooling
  • Query execution
  • Transaction management
  • Basic schema sync
  • Result set processing
For SQL databases without JDBC (like BigQuery):
(driver/register! :bigquery, :parent :sql)
Provides:
  • SQL query compilation
  • Query preprocessing
  • Some schema sync functionality
You implement:
  • Connection management
  • Query execution
Some drivers inherit from other concrete drivers:
(driver/register! :redshift, :parent :postgres)
Only override methods where behavior differs.

Multiple parents

Drivers can have multiple parents:
(driver/register! :bigquery, :parent #{:sql :google})
This combines functionality from both parents.

Calling parent implementations

Call parent methods using get-method:
(defmethod driver/mbql->native :mydriver
  [driver query]
  ;; Do some custom preprocessing
  (let [query (preprocess-query query)]
    ;; Then call parent implementation
    ((get-method driver/mbql->native :sql-jdbc) driver query)))
Always pass the driver argument as-is to parent implementations. Don’t replace it with a keyword.
;; ✅ CORRECT
((get-method driver/mbql->native :sql) driver query)

;; ❌ WRONG - breaks method dispatch
((get-method driver/mbql->native :sql) :sql query)
((get-method driver/mbql->native :sql) :mydriver query)

Driver namespaces

Driver code can be organized across multiple namespaces:
metabase/driver/mydriver/
├── core.clj             # Main driver registration
├── query_processor.clj  # MBQL compilation
├── sync.clj            # Schema introspection
└── execute.clj         # Query execution
Example structure:
;; metabase.driver.mydriver.core
(ns metabase.driver.mydriver.core
  (:require [metabase.driver :as driver]
            [metabase.driver.mydriver.query-processor]
            [metabase.driver.mydriver.sync]))

(driver/register! :mydriver, :parent :sql-jdbc)

(defmethod driver/display-name :mydriver [_]
  "My Database")

Driver initialization

Drivers can run initialization code once using driver/initialize!:
(driver/initialize! :mydriver
  (fn []
    ;; Run once when driver loads
    (setup-connection-pool)
    (register-type-handlers)
    (log/info "MyDriver initialized")))
Use initialize! only for one-time setup like resource allocation or system property configuration.

JDBC-specific methods

For JDBC-based drivers, implement these SQL-JDBC methods:

Connection

(defmethod sql-jdbc.conn/connection-details->spec :mydriver
  [_ {:keys [host port dbname user password ssl]}]
  (merge
    {:classname "com.mydb.Driver"
     :subprotocol "mydb"
     :subname (str "//" host ":" port "/" dbname)
     :user user
     :password password}
    (when ssl
      {:ssl true
       :sslfactory "org.postgresql.ssl.NonValidatingFactory"})))

Timezone handling

(defmethod sql-jdbc.execute/set-timezone-sql :mydriver [_]
  "SET TIME ZONE %s;")

Result processing

(defmethod sql-jdbc.execute/read-column-thunk [:mydriver Types/TIMESTAMP]
  [_ ^ResultSet rs ^Integer i]
  (fn []
    (.getTimestamp rs i)))

Non-JDBC drivers

For databases without JDBC, implement query execution:
(defmethod driver/execute-reducible-query :mydriver
  [driver query context respond]
  (let [native-query (qp/compile driver query)
        conn (get-connection (:database query))]
    (try
      (let [results (execute-query conn native-query)]
        (respond {:cols (result-columns results)
                  :rows (result-rows results)}))
      (finally
        (close-connection conn)))))

Schema introspection

Implement sync methods for database schema discovery:
(defmethod driver/describe-database :mydriver
  [_ database]
  {:tables (get-tables database)})

(defmethod driver/describe-table :mydriver
  [_ database table]
  {:name   (:name table)
   :schema (:schema table)
   :fields (get-fields database table)})

(defmethod driver/describe-table-fks :mydriver
  [_ database table]
  (get-foreign-keys database table))

Testing driver methods

Test your driver implementations:
(ns metabase.driver.mydriver-test
  (:require
   [clojure.test :refer :all]
   [metabase.driver :as driver]
   [metabase.test :as mt]))

(deftest display-name-test
  (is (= "My Database"
         (driver/display-name :mydriver))))

(deftest supports-foreign-keys-test
  (is (driver/database-supports? :mydriver :foreign-keys nil)))

(deftest connection-test
  (testing "Can connect with valid credentials"
    (is (driver/can-connect? :mydriver
                             {:host "localhost"
                              :port 5432
                              :dbname "test"}))))

Working from the REPL

Develop and test drivers interactively:
# Start REPL with driver dependencies
clojure -A:dev:drivers:drivers-dev
In the REPL:
;; Load your driver
(require 'metabase.driver.mydriver :reload)

;; Test methods
(driver/display-name :mydriver)
(driver/database-supports? :mydriver :foreign-keys nil)

;; Test connection
(driver/can-connect? :mydriver {:host "localhost" :port 5432})

;; Run a query
(require 'metabase.test :as mt)
(mt/dataset test-data
  (mt/run-mbql-query venues {:limit 10}))

Reference

Key namespaces

  • metabase.driver - Core driver interface
  • metabase.driver.sql - SQL driver abstractions
  • metabase.driver.sql-jdbc - JDBC driver abstractions
  • metabase.driver.sql.query-processor - SQL query compilation
  • metabase.driver.sql-jdbc.connection - JDBC connections
  • metabase.driver.sql-jdbc.sync - JDBC schema sync
  • metabase.driver.sql-jdbc.execute - JDBC query execution

Example drivers to study

  • PostgreSQL - Full-featured JDBC driver
  • SQLite - Simple JDBC driver
  • MongoDB - Non-SQL driver
  • BigQuery - SQL non-JDBC driver
  • Redshift - Driver with parent override

Next steps