diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index fd6b16f5208c3aa7d819888eae7180ff476754af..0000000000000000000000000000000000000000
--- a/.babelrc
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-    "presets": [
-        "@babel/preset-env",
-        "@babel/preset-react"
-    ]
-}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8dc3284558c0597ae984c05eb7089227a658b512..935ba8c3b900caf74fd0fe29acd75d08e6e10ac6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added ###
 
+* Map component @indiscale/caosdb-webui-ext-map
+
 ### Changed ###
 
 ### Deprecated ###
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..f671719a831ec21872189eb330aef4f732156e45
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ['@babel/preset-env', '@babel/preset-react'],
+};
diff --git a/package.json b/package.json
index e4b08c2875650f14696f835116eef552fb011f40..e2505c814d7c4e5a4dce0c674097f8d68483d507 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
 {
   "dependencies": {
     "@indiscale/caosdb-webui-core-components": "0.0.8",
+    "@indiscale/caosdb-webui-ext-map": "file:../caosdb-webui-ext-map/indiscale-caosdb-webui-ext-map-0.1.0.tgz",
     "react": "^18.2.0",
     "react-bootstrap": "^2.7.2",
     "react-dom": "^18.2.0"
diff --git a/src/map.js b/src/map.js
new file mode 100644
index 0000000000000000000000000000000000000000..2db53b02b122708bf8926b5671a5cfc12ef110e1
--- /dev/null
+++ b/src/map.js
@@ -0,0 +1,63 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "regenerator-runtime/runtime";
+import { TransactionService } from "@indiscale/caosdb-webui-entity-service";
+import { Map, ToggleMapButton } from "@indiscale/caosdb-webui-ext-map";
+import { queryCallback } from "./queryCallback";
+
+const caosdb_map_2 = {
+  /**
+   * Create the map panel (div.caosdb-f-map-panel) and insert it into the dom
+   * tree. Then return the map panel.
+   */
+  init_map_panel: function () {
+    // remove old
+    if (document.querySelector(".caosdb-f-map-panel")) {
+      document.querySelector(".caosdb-f-map-panel").remove();
+    }
+
+    const panel = document.createElement("div");
+    panel.classList = "caosdb-f-map-panel container mb-2";
+
+    document.querySelector("nav").after(panel);
+
+    return panel;
+  },
+
+  /**
+   * Create a button item in the navbar and return it.
+   *
+   * The button contains a dummy link tag which is to be removed/overriden when
+   * React inserts the ToggleMapButton component.
+   */
+  create_navbar_item: function () {
+    const dummy = document.createElement("a");
+    return navbar.add_button(dummy);
+  },
+
+  init: async function () {
+    // create map panel
+    const panel = this.init_map_panel();
+
+    // insert map into container
+    const map_root = ReactDOM.createRoot(panel);
+    map_root.render(
+      <React.StrictMode>
+        <Map queryCallback={queryCallback} />
+      </React.StrictMode>
+    );
+
+    const navItem = this.create_navbar_item();
+
+    const button_root = ReactDOM.createRoot(navItem);
+    button_root.render(
+      <React.StrictMode>
+        <ToggleMapButton className="nav-link" />
+      </React.StrictMode>
+    );
+  },
+};
+
+$(document).ready(function () {
+  caosdb_modules.register(caosdb_map_2);
+});
diff --git a/src/query-form.js b/src/query-form.js
index dd20fe7d7c2d48e013a2a6a177002f50a151701a..dea4ec80b529db02e00acb0cc64287008849db66 100644
--- a/src/query-form.js
+++ b/src/query-form.js
@@ -7,6 +7,7 @@ import {
   makeQueryTemplate,
   createTab,
 } from "@indiscale/caosdb-webui-core-components";
+import { queryCallback } from "./queryCallback";
 
 function queryPanel(
   submitCallback,
@@ -60,11 +61,15 @@ function resolveDefaultTab(tabs, defaultTab) {
  * The config file must validate against the tabs-config.schema.json.
  */
 async function initQueryPanel() {
-  if(!document.getElementById("caosdb-navbar-query")) {
+  if (!document.getElementById("caosdb-navbar-query")) {
     // document not ready, retrigger when ready
-    document.addEventListener('DOMContentLoaded', function () {
-      initQueryPanel();
-    }, false);
+    document.addEventListener(
+      "DOMContentLoaded",
+      function () {
+        initQueryPanel();
+      },
+      false
+    );
     return;
   }
 
@@ -129,11 +134,7 @@ async function initQueryPanel() {
   }
 
   // the submit callback is responsible for actually executing the query
-  const submitCallback = (queryString, pageSize) => {
-    const paging = pageSize < 1 ? "" : `&P=0L${pageSize || 10}`;
-    const newHref = connection.getEntityUri([]) + `?query=${encodeURIComponent(queryString)}${paging}`;
-    window.location.href = newHref;
-  };
+  const submitCallback = queryCallback;
   const restore = true;
 
   queryPanel(
diff --git a/src/queryCallback.js b/src/queryCallback.js
new file mode 100644
index 0000000000000000000000000000000000000000..e54845f78ee6dc285b49824e38fab135bff599ca
--- /dev/null
+++ b/src/queryCallback.js
@@ -0,0 +1,9 @@
+const queryCallback = (queryString, pageSize) => {
+  const paging = pageSize < 1 ? "" : `&P=0L${pageSize || 10}`;
+  const newHref =
+    connection.getEntityUri([]) +
+    `?query=${encodeURIComponent(queryString)}${paging}`;
+  window.location.href = newHref;
+};
+
+export { queryCallback };
diff --git a/webpack.config.js b/webpack.config.js
index 756ab6cc82913b03aeff6043b1fb39b5f52ce960..d1b7db4d607faedf8e843f97723037302323b1ec 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,18 +1,20 @@
 const path = require('path');
+const webpack = require('webpack');
 
 module.exports = {
-	devtool: "eval-source-map",
-	entry: {
-		'query-form' : './src/query-form.js',
-	},
+  devtool: "eval-source-map",
+  entry: {
+    'query-form' : './src/query-form.js',
+    'map' : './src/map.js',
+  },
   output: {
     path: path.resolve(__dirname, 'build'),
     filename: '[name].bundle.js',
-		library: {
-			name: "CaosDBWebui2",
-			type: "window",
-		},
-		clean: true,
+    library: {
+      name: "CaosDBWebui2",
+      type: "window",
+    },
+    clean: true,
   },
   resolve: {
     modules: [path.join(__dirname, 'src'), 'node_modules'],
@@ -22,11 +24,11 @@ module.exports = {
   },
   module: {
     rules: [
-			{
-				test: /\.(js|jsx)$/,
-				enforce: "pre",
-				use: ["source-map-loader"],
-			},
+      {
+        test: /\.(js|jsx)$/,
+        enforce: "pre",
+        use: ["source-map-loader"],
+      },
       {
         test: /\.(js|jsx)$/,
         exclude: /node_modules/,
@@ -47,5 +49,8 @@ module.exports = {
       },
     ],
   },
+  plugins: [
+    new webpack.DefinePlugin({"process": {"env": {"GRPC_API_URI": undefined}}}),
+  ],
 };