diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java
index d6825c7..5064850 100644
--- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java
+++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java
@@ -96,4 +96,25 @@ public final class Config {
         return ConfigUtil.resolvePath(resolveCABasePath(), Config.<String> GetValue(ConfigValues.TruststoreUrl));
     }
 
+    /**
+     * Fetch the oVirtUiPluginsPath configuration value and, if it is not an absolute path, resolve it relative to
+     * the DataDir configuration value.
+     *
+     * @return an absolute path for oVirtUiPluginsPath
+     */
+    public static String resolveOVirtUiPluginsPath() {
+        return ConfigUtil.resolvePath(Config.<String> GetValue(ConfigValues.DataDir),
+                Config.<String> GetValue(ConfigValues.UiPluginsPath));
+    }
+
+    /**
+     * Examine the given filename and if it is not an absolute path, resolve it relative to the oVirtUiPluginsPath
+     *
+     * @return an absolute path for the UiPluginConfigFile
+     */
+    public static String resolveOVirtUiPluginConfigFile(String filename) {
+        String path = ConfigUtil.resolvePath(Config.<String> GetValue(ConfigValues.ConfigDir),
+                Config.<String> GetValue(ConfigValues.UiPluginsPath));
+        return ConfigUtil.resolvePath(path, filename);
+    }
 }
diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java
index 494ac71..5c685d8 100644
--- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java
+++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java
@@ -1442,6 +1442,10 @@ public enum ConfigValues {
     @DefaultValueAttribute("ovirt-engine")
     SSHKeyAlias(377),
 
+    @TypeConverterAttribute(String.class)
+    @DefaultValueAttribute("ui-plugins")
+    UiPluginsPath(378),
+
     Invalid(65535);
 
     private int intValue;
diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java
index 159fad8..e34fba4 100644
--- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java
+++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java
@@ -9,15 +9,15 @@ import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
-import java.util.Arrays;
-import java.util.List;
 
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.codehaus.jackson.JsonNode;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+//import org.ovirt.engine.ui.frontend.server.gwt.WebadminDynamicHostingServlet;
 
 /**
  * Renders the HTML source page for the given UI plugin.
@@ -25,34 +25,48 @@ import org.apache.commons.logging.LogFactory;
 public class PluginSourcePageServlet extends HttpServlet {
 
     private static final long serialVersionUID = 1L;
+    private static final long PATH_MAX = 512;
 
     private static Log logger = LogFactory.getLog(PluginSourcePageServlet.class);
 
     @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
         // Read plugin name as HTTP request parameter
-        String pluginName = request.getParameter("plugin"); //$NON-NLS-1$
-        if (pluginName == null) {
+        String pathInfo = request.getPathInfo();
+        String path[] = null;
+        logger.debug("Got plugin request "+pathInfo); //$NON-NLS-1$
+
+        try {
+            path = pathInfo.split("/", 3); //$NON-NLS-1$
+        } catch(Exception ex) {}
+
+        if (path == null || path.length < 2) {
+            logger.error("Missing plugin name request parameter"); //$NON-NLS-1$
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+
+        JsonNode pinfo = WebadminDynamicHostingServlet.getPluginDefinition(path[1]);
+        if (pinfo == null) {
             logger.error("Missing plugin name request parameter"); //$NON-NLS-1$
             response.sendError(HttpServletResponse.SC_BAD_REQUEST);
             return;
         }
 
         // Locate plugin code in local file system
-        // TODO hard-coded plugin location
-        File pluginCodeLocation = new File("/home/vszocs/Downloads"); //$NON-NLS-1$
-        File pluginCodeFile = new File(pluginCodeLocation, pluginName + ".js"); //$NON-NLS-1$
+        String localfile = pinfo.path("path").getTextValue() + File.separator + path[2]; //$NON-NLS-1$
+        if (!isSane(localfile)) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        File pluginCodeFile = new File(localfile);
         if (!pluginCodeFile.isFile() || !pluginCodeFile.canRead()) {
             logger.error("Cannot read plugin code: " + pluginCodeFile.getAbsolutePath()); //$NON-NLS-1$
             response.sendError(HttpServletResponse.SC_NOT_FOUND);
             return;
         }
 
-        // TODO simulate plugin dependencies
-        List<String> pluginDependencyList =
-                Arrays.asList("https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"); //$NON-NLS-1$
-
-        // Render HTML source page to the output
         response.setContentType("text/html; charset=UTF-8"); //$NON-NLS-1$
         response.setHeader("Cache-Control", "no-cache"); //$NON-NLS-1$ //$NON-NLS-2$
 
@@ -63,7 +77,7 @@ public class PluginSourcePageServlet extends HttpServlet {
             in = new BufferedReader(new InputStreamReader(new FileInputStream(pluginCodeFile), "UTF-8")); //$NON-NLS-1$
             out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), "UTF-8")); //$NON-NLS-1$
 
-            renderPluginSourcePage(in, pluginDependencyList, out);
+            copyChars(in, out);
             out.flush();
         } finally {
             if (in != null) {
@@ -72,28 +86,6 @@ public class PluginSourcePageServlet extends HttpServlet {
         }
     }
 
-    void renderPluginSourcePage(Reader pluginCodeInput, List<String> pluginDependencyList, Writer output)
-            throws IOException {
-        output.write("<!DOCTYPE html><html><head>"); //$NON-NLS-1$
-        output.write("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">"); //$NON-NLS-1$
-
-        for (String dependency : pluginDependencyList) {
-            output.write("<script type=\"text/javascript\" src=\""); //$NON-NLS-1$
-            output.write(dependency);
-            output.write("\"></script>"); //$NON-NLS-1$
-        }
-
-        output.write("</head><body>"); //$NON-NLS-1$
-        output.write("<script type=\"text/javascript\">"); //$NON-NLS-1$
-        output.write("(function( pluginApi ) {"); //$NON-NLS-1$
-
-        copyChars(pluginCodeInput, output);
-
-        output.write("}) ( parent.pluginApi );"); //$NON-NLS-1$
-        output.write("</script>"); //$NON-NLS-1$
-        output.write("</body></html>"); //$NON-NLS-1$
-    }
-
     void copyChars(Reader in, Writer out) throws IOException {
         char[] buffer = new char[4 * 1024]; // Use 4 kB buffer
         int numRead = 0;
@@ -103,4 +95,23 @@ public class PluginSourcePageServlet extends HttpServlet {
         }
     }
 
+    private static boolean isSane(String path) {
+        // Check that the path is not too long:
+        final int length = path.length();
+        if (length > PATH_MAX) {
+            logger.error("The path is " + length + " characters long, which is longer than the maximum allowed " + PATH_MAX + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+            return false;
+        }
+
+        // Check that there aren't potentially dangerous directory navigation sequences:
+        if (path.contains("..") || path.contains("//") || path.contains("./")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+            logger.error("The path contains potentially dangerous directory navigation sequences."); //$NON-NLS-1$
+            return false;
+        }
+
+        // All checks passed, the path is sane:
+        return true;
+    }
 }
+
+// vim: ts=4 sts=4 sw=4 expandtab:
diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java
index 683cb68..1d49297 100644
--- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java
+++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java
@@ -1,10 +1,21 @@
 package org.ovirt.engine.ui.frontend.server.gwt;
 
 import java.io.PrintWriter;
+import java.io.File;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Iterator;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 
 import javax.servlet.http.HttpServletRequest;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.node.ObjectNode;
+import org.codehaus.jackson.map.ObjectMapper;
 
 import org.ovirt.engine.core.common.config.Config;
 import org.ovirt.engine.core.common.queries.ConfigurationValues;
@@ -19,6 +30,8 @@ import org.ovirt.engine.core.common.queries.VdcQueryType;
 public class WebadminDynamicHostingServlet extends GwtDynamicHostPageServlet {
 
     private static final long serialVersionUID = 1L;
+    private static Map<String, JsonNode> pluginDefinitions;
+    private static Log logger = LogFactory.getLog(WebadminDynamicHostingServlet.class);
 
     @Override
     protected String getSelectorScriptName() {
@@ -36,10 +49,14 @@ public class WebadminDynamicHostingServlet extends GwtDynamicHostPageServlet {
             writeJsObject(writer, "applicationMode", appModeData); //$NON-NLS-1$
         }
 
-        StringBuilder pluginDefinitions = new StringBuilder(" var pluginDefinitions = [ "); //$NON-NLS-1$
-        pluginDefinitions.append("{ name: \"myPlugin\", url: \"/webadmin/webadmin/PluginSourcePage?plugin=myPlugin\", config: { \"foo\": 1 } }"); //$NON-NLS-1$
-        pluginDefinitions.append(" ]; "); //$NON-NLS-1$
-        writer.append(pluginDefinitions.toString());
+        // FIXME: do we load this everytime, or just once?
+        if (true) { /**pluginDefinitions == null) {**/
+            logger.debug("getPluginDefinitions: Loading plugin definitions"); //$NON-NLS-1$
+            pluginDefinitions = getPluginDefinitions();
+        }
+
+        writeJsonObject(writer, "pluginDefinitions", pluginDefinitions.values()); //$NON-NLS-1$
+
     }
 
     private Integer getApplicationMode(HttpServletRequest request) {
@@ -48,4 +65,100 @@ public class WebadminDynamicHostingServlet extends GwtDynamicHostPageServlet {
                         Config.DefaultConfigurationVersion), request);
     }
 
+    private boolean checkPluginDefinition(String filename, JsonNode def) {
+        String url, cfg;
+
+        if (!def.path("name").isTextual()) { //$NON-NLS-1$
+            logger.error(filename+":  The 'name' field must be present and must be a string."); //$NON-NLS-1$
+            return false;
+        }
+        if (!def.path("url").isTextual()) { //$NON-NLS-1$
+            logger.error(filename+":  The 'url' field must be present and must be a string."); //$NON-NLS-1$
+            return false;
+        }
+        url = def.path("url").getValueAsText(); //$NON-NLS-1$
+        if (url.startsWith("/webadmin")) { //$NON-NLS-1$
+            if (!def.path("path").isTextual()) { //$NON-NLS-1$
+                logger.error(filename+":  The 'path' field must be present when the 'url' is local and must be a string."); //$NON-NLS-1$
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Map<String, JsonNode> getPluginDefinitions() {
+        Map<String, JsonNode> defs = new HashMap<String, JsonNode>();
+        File directory;
+        File[] files;
+
+        logger.debug("getPluginDefinitions: Checking path "+Config.resolveOVirtUiPluginsPath()); //$NON-NLS-1$
+        directory = new File(Config.resolveOVirtUiPluginsPath());
+        files = directory.listFiles();
+
+        JsonNode root;
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+        // FIXME: If JACKSON-618 is resolved (ALLOW_TRAILING_COMMA), use it.
+
+        for(int i=0; i<files.length; i++) {
+            String filename = files[i].getName();
+            logger.debug("getPluginDefinitions: Checking file "+filename); //$NON-NLS-1$
+            if (!filename.endsWith(".json")) //$NON-NLS-1$
+                continue;
+
+            try {
+                root = mapper.readValue(files[i], JsonNode.class);
+            } catch(IOException ex) {
+                logger.error("getPluginDefinitions: Error reading "+filename+": "+ex); //$NON-NLS-1$ //$NON-NLS-2$
+                continue;
+            }
+            if (checkPluginDefinition(files[i].getAbsolutePath(), root)) {
+                String name = root.path("name").getValueAsText(); //$NON-NLS-1$
+                String cfg = root.path("configFile").getValueAsText(); //$NON-NLS-1$
+                if (cfg != null) {
+                    cfg = Config.resolveOVirtUiPluginConfigFile(cfg);
+                    try {
+                        JsonNode config = mapper.readValue(new File(cfg), JsonNode.class);
+                        ObjectNode on = (ObjectNode)root.with("config"); //$NON-NLS-1$
+                        for(Iterator<Entry<String,JsonNode>> it=config.getFields(); it.hasNext();) {
+                            Entry<String,JsonNode> e = it.next();
+                            on.put(e.getKey(), e.getValue());
+                        }
+                    } catch(Exception ex) {
+                        logger.error(filename+":  Error reading the plugin configuration from "+cfg+".  Exception was: "+ex); //$NON-NLS-1$ //$NON-NLS-2$
+                    }
+                }
+                defs.put(name, root);
+            }
+        }
+        return defs;
+    }
+
+    /**
+     * Return a single plugin definition given a plugin name
+     */
+    public static JsonNode getPluginDefinition(String name) {
+        JsonNode ret = null;
+        if (pluginDefinitions != null) {
+            ret = pluginDefinitions.get(name);
+        }
+        return ret;
+    }
+
+    /**
+     * Writes a string representing JavaScript object literal containing given attributes.
+     */
+    protected void writeJsonObject(PrintWriter writer, String objectName, Object obj) {
+        StringBuilder sb = new StringBuilder();
+        ObjectMapper mapper = new ObjectMapper();
+        sb.append(" var ").append(objectName).append(" = "); //$NON-NLS-1$ //$NON-NLS-2$
+        try {
+            sb.append(mapper.writeValueAsString(obj));
+        } catch(Exception ex) { }
+        sb.append(";"); //$NON-NLS-1$
+        writer.append(sb.toString());
+    }
+
+
 }
+// vim: ts=4 sts=4 sw=4 expandtab:
diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java
index a25fc75..687e801 100644
--- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java
+++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java
@@ -15,5 +15,4 @@ public class PluginEventHandler {
         // TODO call EventBus.addHandler for each extension point (event),
         // with the handler implementation using PluginManager to call plugins
     }
-
 }
diff --git a/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml b/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml
index dbf93a2..0ed4ce8 100644
--- a/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml
+++ b/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml
@@ -32,6 +32,10 @@
 		<servlet-name>PluginSourcePage</servlet-name>
 		<url-pattern>/webadmin/PluginSourcePage</url-pattern>
 	</servlet-mapping>
+	<servlet-mapping>
+		<servlet-name>PluginSourcePage</servlet-name>
+		<url-pattern>/webadmin/plugin/*</url-pattern>
+	</servlet-mapping>
 
 	<!-- Default page to serve -->
 	<welcome-file-list>
