diff --git a/src/components/Interactive.js b/src/components/Interactive.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ba219db463f3dc15c3ff14fea76442992a3a822
--- /dev/null
+++ b/src/components/Interactive.js
@@ -0,0 +1,6 @@
+import React from "react";
+import { Alert } from "react-bootstrap";
+
+export default function Interactive() {
+  return <Alert variant="info">This service is under development!</Alert>;
+}
diff --git a/src/components/Paginate.js b/src/components/Paginate.js
index 935de29fad040b5ff9d7812ba743ca37ab51bdf0..92fa6088915807973835757d2d20b7c4a7bd1f52 100644
--- a/src/components/Paginate.js
+++ b/src/components/Paginate.js
@@ -1,15 +1,33 @@
 import React from "react";
 import { Pagination } from "react-bootstrap";
 
-export default function Paginate() {
+export default function Paginate(props) {
+  const { currentPage, getNewPage, numAdjacent, numPages } = props;
+
+  console.log(props);
+
+  const numBelow = Math.min(numAdjacent, currentPage-1);
+  const belowAdjacent = [...Array(numBelow).keys()].map((key) => {
+    const itemKey = `page_below_${key}`;
+    return (<Pagination.Item key={itemKey}>{currentPage - numBelow + key}</Pagination.Item>)
+  });
+  const prefix = belowAdjacent.length >= numAdjacent ? (<><Pagination.Item>{1}</Pagination.Item><Pagination.Ellipsis disabled/>{belowAdjacent}</>) : (<>{belowAdjacent}</>);
+
+  const numAbove = Math.min(numAdjacent, numPages-currentPage);
+  const aboveAdjacent = [...Array(numAbove).keys()].map((key) => {
+    const itemKey = `page_above_${key}`;
+    return (<Pagination.Item key={itemKey}>{currentPage + key + 1}</Pagination.Item>)
+  });
+  const suffix = aboveAdjacent.length >= numAdjacent ? (<>{aboveAdjacent}<Pagination.Ellipsis disabled/><Pagination.Item>{numPages}</Pagination.Item></>) : (<>{aboveAdjacent}</>);
+
   return (
-    <Pagination>
-      <Pagination.First />
-      <Pagination.Prev />
-      <Pagination.Item active>{1}</Pagination.Item>
-      <Pagination.Ellipsis />
-      <Pagination.Next />
-      <Pagination.Last />
+    <Pagination onClick={getNewPage} size="lg">
+      {prefix}
+      <Pagination.Item active >{currentPage}</Pagination.Item>
+      {suffix}
     </Pagination>
   );
 }
+
+const pagination_fields = ["requested_page", "pages"];
+export { pagination_fields };
diff --git a/src/components/Rucio.js b/src/components/Rucio.js
new file mode 100644
index 0000000000000000000000000000000000000000..6dd2613edf49f20b5644027270190debe7f9ff91
--- /dev/null
+++ b/src/components/Rucio.js
@@ -0,0 +1,17 @@
+import React from "react";
+import { Alert } from "react-bootstrap";
+
+export default function Rucio() {
+  return (
+    <Alert variant="warning">
+      <p>You will leave ESAP GUI and be redirected to</p>
+      <a
+        target="_blank"
+        rel="noopener noreferrer"
+        href="https://escape-dios-dl.cern.ch/ui/"
+      >
+        Rucio Web UI
+      </a>
+    </Alert>
+  );
+}
diff --git a/src/components/archives/ArchiveDetails.js b/src/components/archives/ArchiveDetails.js
index fa5e2759f7f3762805d243068fb24a3203407bfa..9ac3f575b9c86d70dadaf4a61da50d5b033ffa68 100644
--- a/src/components/archives/ArchiveDetails.js
+++ b/src/components/archives/ArchiveDetails.js
@@ -1,23 +1,23 @@
 import React, { useContext } from "react";
 import { useParams } from "react-router-dom";
 import { GlobalContext } from "../../contexts/GlobalContext";
-import { Container, Row, Col, Card, Table, Alert, Image } from "react-bootstrap";
+import {
+  Container,
+  Row,
+  Col,
+  Card,
+  Table,
+  Alert,
+  Image,
+} from "react-bootstrap";
 import DataProductCategories from "./DataProductCategories";
 
 export default function ArchiveDetails(props) {
   const { uri } = useParams();
   console.log(uri);
-  const { archives, defaultConfigName, setConfigName } = useContext(GlobalContext);
+  const { archives } = useContext(GlobalContext);
   if (!archives) return null;
 
-  /* This is a nasty hack. There must be a better way! */
-  if (uri.search("zooniverse") >= 0){
-    setConfigName("zooniverse");
-  }
-  else{
-    setConfigName(defaultConfigName);
-  }
-
   const archive = archives.find((archive) => archive.uri === uri);
   if (!archive) return <Alert variant="danger">No Archive found!</Alert>;
   console.log(archive);
@@ -46,15 +46,19 @@ export default function ArchiveDetails(props) {
                   </tbody>
                 </Table>
                 <Card>
-                <Image
-                  className="mx-auto d-block"
-                  src={archive.thumbnail}
-                  alt=""
-                  rounded
-                  fluid
-                />
-                <Card.Title className="text-center h4 pt-3">{archive.short_description}</Card.Title>
-                <Card.Text className="text-justify m-3">{archive.long_description}</Card.Text>
+                  <Image
+                    className="mx-auto d-block"
+                    src={archive.thumbnail}
+                    alt=""
+                    rounded
+                    fluid
+                  />
+                  <Card.Title className="text-center h4 pt-3">
+                    {archive.short_description}
+                  </Card.Title>
+                  <Card.Text className="text-justify m-3">
+                    {archive.long_description}
+                  </Card.Text>
                 </Card>
               </Card.Body>
             </Card>
diff --git a/src/components/archives/Archives.js b/src/components/archives/Archives.js
index a53113a9c2846f1adcf2410c40ca3bf47dda8fa0..f2148c055ddd5cfab06cfe00ec15b63f65f5be7c 100644
--- a/src/components/archives/Archives.js
+++ b/src/components/archives/Archives.js
@@ -1,23 +1,19 @@
 import React, { useContext } from "react";
-import { CardColumns} from "react-bootstrap";
+import { CardColumns } from "react-bootstrap";
 import ArchiveCard from "./ArchiveCard";
 import { GlobalContext } from "../../contexts/GlobalContext";
 
 export function Archives() {
-  const { archives, defaultConfigName, setConfigName} = useContext(GlobalContext);
+  const { archives } = useContext(GlobalContext);
   if (!archives) return null;
 
-  setConfigName(defaultConfigName);
-
   console.log("archives: ", { archives });
   return (
     <CardColumns className="row-2">
-            {archives.map((archive) => {
-              let key = "archive-" + archive.id;
-              return (
-                  <ArchiveCard key={key} archive={archive} />
-              );
-            })}
+      {archives.map((archive) => {
+        let key = "archive-" + archive.id;
+        return <ArchiveCard key={key} archive={archive} />;
+      })}
     </CardColumns>
   );
 }
diff --git a/src/components/archives/DataProductCategories.js b/src/components/archives/DataProductCategories.js
index 3846156353758d54215d96e93c8d37161a02f970..d7f4d71f5be66d7bcf2a123e35784e95ad746f56 100644
--- a/src/components/archives/DataProductCategories.js
+++ b/src/components/archives/DataProductCategories.js
@@ -1,12 +1,6 @@
 import React, { useState, useContext, useEffect } from "react";
 import { NavLink } from "react-router-dom";
-import {
-  ListGroup,
-  Card,
-  Button,
-  Row,
-  Col,
-} from "react-bootstrap";
+import { ListGroup, Card, Button, Row, Col } from "react-bootstrap";
 import { GlobalContext } from "../../contexts/GlobalContext";
 import axios from "axios";
 
@@ -37,13 +31,15 @@ export default function DataProductCategories({ archive }) {
           {categories
             .filter((category) => archive.datasets.includes(category.uri))
             .map((category) => {
-              console.log(category);
+              console.log("category:", category);
               let button;
               let extra_info;
               let documentation;
               if (category.service_connector) {
+                let query_url = `${category.archive_uri_derived}/query`;
+                console.log("query_url:", query_url);
                 button = (
-                  <Button as={NavLink} variant="outline-info" to="/query">
+                  <Button as={NavLink} variant="outline-info" to={query_url}>
                     Browse Catalog & Run Queries
                   </Button>
                 );
diff --git a/src/components/query/QueryCatalogs.js b/src/components/query/QueryCatalogs.js
index c5709c393b687fb1cf5c41bd34fbbb0466296052..913d2aabf0450eb7cbfba1350a6b2bd375782360 100644
--- a/src/components/query/QueryCatalogs.js
+++ b/src/components/query/QueryCatalogs.js
@@ -1,4 +1,5 @@
 import React, { useContext, useEffect } from "react";
+import { useParams } from "react-router-dom";
 import axios from "axios";
 import { Container } from "react-bootstrap";
 import Form from "react-jsonschema-form";
@@ -13,17 +14,43 @@ export default function QueryCatalogs() {
   //  "catalogquery": "querystring",
   //  "status": "fetching|fechted",
   //  "results": null}
-  const { queryMap, formData, setFormData } = useContext(QueryContext);
-  const { config, api_host } = useContext(GlobalContext);
+  const { queryMap, formData, setFormData, page } = useContext(QueryContext);
+  const { config, api_host, setConfigName } = useContext(GlobalContext);
+  const { uri } = useParams();
+  console.log(uri);
 
-  console.log(config.query_schema);
+  // set ConfigName for archive
   useEffect(() => {
+    switch (uri) {
+      case "adex":
+        setConfigName("adex");
+        break;
+      case "apertif":
+        setConfigName("apertif");
+        break;
+      case "zooniverse":
+        setConfigName("zooniverse");
+        break;
+      case "astron_vo":
+        setConfigName("astron_vo");
+        break;
+      case "lofar":
+        setConfigName("lofar");
+        break;
+      default:
+        setConfigName("esap_ivoa");
+    }
+    return () => {
+      console.log("cleaned up");
+      setConfigName("esap_ivoa");
+    };
+  }, [uri]);
+
+  useEffect(() => {
+    console.log(config.query_schema);
     if (!formData) return;
-    let catalogs = config.query_schema.properties.catalog.enum.filter(
-      (name) => name !== "all"
-    );
-    let gui = config.query_schema.name;
-    const queries = parseQueryForm(gui, formData, catalogs);
+    const gui = config.query_schema.name;
+    const queries = parseQueryForm(gui, formData, page);
 
     // Ideally query for each catalog is sent to ESAP API Gateway, and query results is returned
     // This is under development in the backend at the moment
@@ -35,7 +62,7 @@ export default function QueryCatalogs() {
         status: "fetching",
         results: null,
       });
-      let url = api_host + "query/query/?" + query.esapquery;
+      const url = api_host + "query/query/?" + query.esapquery;
       axios
         .get(url)
         .then((queryResponse) => {
@@ -55,7 +82,7 @@ export default function QueryCatalogs() {
           });
         });
     });
-  }, [formData]);
+  }, [formData, page]);
 
   function formTemplate({ TitleField, properties, title, description }) {
     return (
@@ -78,7 +105,7 @@ export default function QueryCatalogs() {
 
   console.log("queryMap", Array.from(queryMap.values()));
 
-  const uiSchemaProp = config.ui_schema ? { uiSchema : config.ui_schema } : {};
+  const uiSchemaProp = config.ui_schema ? { uiSchema: config.ui_schema } : {};
   return (
     <Container fluid>
       <Form
diff --git a/src/components/query/QueryIVOARegistry.js b/src/components/query/QueryIVOARegistry.js
new file mode 100644
index 0000000000000000000000000000000000000000..c373f93c039dd2b6eeb538483efd98d140df9848
--- /dev/null
+++ b/src/components/query/QueryIVOARegistry.js
@@ -0,0 +1,145 @@
+import React, { useContext, useEffect } from "react";
+import { useParams } from "react-router-dom";
+import axios from "axios";
+import { Container, Button } from "react-bootstrap";
+import Form from "react-jsonschema-form";
+import { GlobalContext } from "../../contexts/GlobalContext";
+import { QueryContext } from "../../contexts/QueryContext";
+import QueryResults from "./QueryResults";
+import parseQueryForm from "../../utils/form/parseQueryForm";
+
+export default function QueryIVOARegistry() {
+  // queryMap is a map of dictionaries, where each dictionary consists of
+  // {"catalog": "catalogname",
+  //  "catalogquery": "querystring",
+  //  "status": "fetching|fechted",
+  //  "results": null}
+  const { queryMap, formData, setFormData } = useContext(QueryContext);
+  const { config, api_host, setConfigName } = useContext(GlobalContext);
+  const { uri } = useParams();
+  console.log("uri:", uri);
+
+  // set ConfigName for archive
+  useEffect(() => {
+    switch (uri) {
+      case "adex":
+        setConfigName("adex");
+        break;
+      case "apertif":
+        setConfigName("apertif");
+        break;
+      case "zooniverse":
+        setConfigName("zooniverse");
+        break;
+      case "astron_vo":
+        setConfigName("astron_vo");
+        break;
+      case "lofar":
+        setConfigName("lofar");
+        break;
+      default:
+        setConfigName("esap_ivoa");
+    }
+    return () => {
+      console.log("Set configuration back to default!");
+      setConfigName("esap_ivoa");
+    };
+  }, [uri]);
+
+  useEffect(() => {
+    console.log(config.query_schema);
+    if (!formData) return;
+    console.log("formData:", formData);
+    let gui = config.query_schema.name;
+    const queries = parseQueryForm(gui, formData);
+
+    // Ideally query for each catalog is sent to ESAP API Gateway, and query results is returned
+    // This is under development in the backend at the moment
+    queryMap.clear();
+    queries.forEach((query) => {
+      queryMap.set(query.catalog, {
+        catalog: query.catalog,
+        esapquery: query.esapquery,
+        status: "fetching",
+        results: null,
+      });
+      let url = api_host + "query/" + query.esapquery;
+      axios
+        .get(url)
+        .then((queryResponse) => {
+          queryMap.set(query.catalog, {
+            catalog: query.catalog,
+            esapquery: query.esapquery,
+            status: "fetched",
+            results: queryResponse.data,
+          });
+        })
+        .catch(() => {
+          queryMap.set(query.catalog, {
+            catalog: query.catalog,
+            esapquery: query.esapquery,
+            status: "error",
+            results: null,
+          });
+        });
+    });
+  }, [formData]);
+
+  function formTemplate({ TitleField, properties, title, description }) {
+    return (
+      <div>
+        <TitleField title={title} />
+        <div className="row">
+          {properties.map((prop) => (
+            <div
+              className="col-lg-2 col-md-4 col-sm-6 col-xs-12"
+              key={prop.content.key}
+            >
+              {prop.content}
+            </div>
+          ))}
+        </div>
+        {description}
+      </div>
+    );
+  }
+
+  console.log("queryMap:", Array.from(queryMap.values()));
+
+  const uiSchemaProp = config.ui_schema ? { uiSchema: config.ui_schema } : {};
+  console.log("UI Schema props:", uiSchemaProp);
+
+  return (
+    <Container fluid>
+      <Form
+        schema={config.query_schema}
+        ObjectFieldTemplate={formTemplate}
+        formData={formData}
+        onSubmit={({ formData }) => setFormData(formData)}
+        {...uiSchemaProp}
+      >
+        <div>
+          <Button type="submit">Get Registry Services</Button>
+        </div>
+      </Form>
+      {Array.from(queryMap.keys()).map((catalog) => {
+        console.log("catalog:", catalog);
+        const details = queryMap.get(catalog);
+        console.log("Details:", details);
+        console.log("Results:", details.results);
+        let catalogName =
+          config.query_schema.properties.catalog.enumNames[
+            config.query_schema.properties.catalog.enum.findIndex(
+              (name) => name === catalog
+            )
+          ];
+        return (
+          <div key={catalog} className="mt-3">
+            <h4>List of registries</h4>
+            <QueryResults catalog={catalog} />
+          </div>
+        );
+      })}
+    </Container>
+  );
+}
diff --git a/src/components/query/QueryResults.js b/src/components/query/QueryResults.js
index 9e0af9ea6da48474688580b5c83745b8f85668db..edc9542aed74a5a35a07649a4e78e932fe3c9c94 100644
--- a/src/components/query/QueryResults.js
+++ b/src/components/query/QueryResults.js
@@ -2,6 +2,7 @@ import React from "react";
 import ApertifResults from "./ApertifResults";
 import ASTRONVOResults from "./ASTRONVOResults";
 import ZooniverseResults from "./ZooniverseResults";
+import VORegistryResults from "./VORegistryResults";
 
 export default function QueryResults({ catalog }) {
   switch (catalog) {
@@ -12,7 +13,9 @@ export default function QueryResults({ catalog }) {
     case "zooniverse_projects":
       return <ZooniverseResults catalog={catalog} />;
     case "zooniverse_workflows":
-        return <ZooniverseResults catalog={catalog} />;
+      return <ZooniverseResults catalog={catalog} />;
+    case "vo_reg":
+      return <VORegistryResults catalog={catalog} />;
     default:
       return null;
   }
diff --git a/src/components/query/VORegistryResults.js b/src/components/query/VORegistryResults.js
new file mode 100644
index 0000000000000000000000000000000000000000..828a9ae7670a24aa25c01f894bb8cc982cc28e84
--- /dev/null
+++ b/src/components/query/VORegistryResults.js
@@ -0,0 +1,96 @@
+import React, { useContext, useEffect, useState } from "react";
+import { Table, Alert, InputGroup } from "react-bootstrap";
+import { QueryContext } from "../../contexts/QueryContext";
+import LoadingSpinner from "../LoadingSpinner";
+import Paginate from "../Paginate";
+import { IVOAContext } from "../../contexts/IVOAContext";
+
+export default function VORegistryResults({ catalog }) {
+  const { queryMap } = useContext(QueryContext);
+  const { registryList, add, remove } = useContext(IVOAContext);
+  // const [checkAll, setCheckAll] = useState("");
+
+  useEffect(() => {
+    console.log("RegistryList:", registryList);
+  }, [registryList]);
+
+  // useEffect(() => {
+  //   console.log("checkAll:", checkAll);
+  // }, [checkAll]);
+
+  if (!queryMap) return null;
+  console.log("VOReg queryMap:", queryMap.get(catalog));
+
+  if (queryMap.get(catalog).status === "fetched") {
+    if (queryMap.get(catalog).results.results.length === 0)
+      return <Alert variant="warning">No matching results found!</Alert>;
+    console.log("VO Registry results:", queryMap.get(catalog).results.results);
+
+    return (
+      <>
+        <Table className="mt-3" responsive>
+          <thead>
+            <tr className="bg-light">
+              <th>
+                <InputGroup>
+                  <InputGroup.Checkbox
+                  // onChange={(event) => {
+                  //   setCheckAll(event.target.checked);
+                  //   event.target.checked
+                  //     ? queryMap
+                  //         .get(catalog)
+                  //         .results.results.map((result) => {
+                  //           add(result.access_url);
+                  //         })
+                  //     : queryMap
+                  //         .get(catalog)
+                  //         .results.results.map((result) => {
+                  //           remove(result.access_url);
+                  //         });
+                  // }}
+                  />
+                </InputGroup>
+              </th>
+              <th>Name</th>
+              <th>Access URL</th>
+              <th>Waveband</th>
+              <th>Title</th>
+              <th>Service Type</th>
+              <th>Content Types</th>
+            </tr>
+          </thead>
+          <tbody>
+            {queryMap.get(catalog).results.results.map((result) => {
+              return (
+                <tr key={result.short_name}>
+                  <th>
+                    <InputGroup>
+                      <InputGroup.Checkbox
+                        // checked={checkAll}
+                        onChange={(event) => {
+                          console.log(event.target.checked);
+                          event.target.checked
+                            ? add(result.access_url)
+                            : remove(result.access_url);
+                        }}
+                      />
+                    </InputGroup>
+                  </th>
+                  <td>{result.short_name}</td>
+                  <td>{result.access_url}</td>
+                  <td>{result.waveband}</td>
+                  <td>{result.title}</td>
+                  <td>{result.service_type}</td>
+                  <td>{result.content_types}</td>
+                </tr>
+              );
+            })}
+          </tbody>
+        </Table>
+        <Paginate />
+      </>
+    );
+  } else {
+    return <LoadingSpinner />;
+  }
+}
diff --git a/src/components/query/ZooniverseResults.js b/src/components/query/ZooniverseResults.js
index baa1a98239cda58b005ae39c9e20bd703a08244d..f8814e4c5c3ab1cbc3c802750534604d9aa26591 100644
--- a/src/components/query/ZooniverseResults.js
+++ b/src/components/query/ZooniverseResults.js
@@ -1,8 +1,8 @@
-import React, { useContext } from "react";
+import React, { useContext, useState} from "react";
 import { Table, Alert } from "react-bootstrap";
 import { QueryContext } from "../../contexts/QueryContext";
 import LoadingSpinner from "../LoadingSpinner";
-import Paginate from "../Paginate";
+import Paginate, { pagination_fields } from "../Paginate";
 
 const DATETIME_OPTIONS = {
   year: 'numeric',
@@ -16,14 +16,13 @@ const DATETIME_OPTIONS = {
 };
 
 Object.isObject = function(obj) {
-    return obj && obj.constructor === this || false;
+    return (obj && obj.constructor === this) || false;
 };
 
 function renderArray(array, currentReactKey=""){
   return array.map((element, index) => {
     const updatedReactKey = `${currentReactKey}_${index}`;
     const separator = index < array.length - 1 ? ", " : "";
-    // console.log([index, array.length, separator]);
     return renderIfCompound(element, updatedReactKey, separator);
   });
 }
@@ -50,10 +49,16 @@ function renderIfCompound(element, currentReactKey="", separatorForPod="", float
     return renderObject(element, currentReactKey, separatorForPod);
   } else if (typeof element === "boolean") {
     return JSON.stringify(element) + separatorForPod
-  } else if (typeof element === "float") {
-    return element.toFixed(floatPrecision) + separatorForPod
+  } else if (Number.isInteger(element)){
+    return element.toString() + separatorForPod;
+  } else {
+    try{
+      return element.toFixed(floatPrecision) + separatorForPod;
+    }
+    catch(err){
+      return  `${element}` + separatorForPod
+    }
   }
-  return  `${element}` + separatorForPod
 }
 
 function titleCase(string) {
@@ -64,17 +69,28 @@ function titleCase(string) {
    return sentence.join(" ");
    }
 
-function ZooniverseProjectResults(queryMap){
-  let date_formatter=new Intl.DateTimeFormat("default", DATETIME_OPTIONS);
-  let result = queryMap.get("zooniverse_projects").results.query_results[0];
-  let mandatory_fields = ["launch_date", "created_at", "live", "updated_at", "project_id", "display_name", "slug"];
-  let remaining_fields = Object.keys(result).filter(key => !mandatory_fields.includes(key));
-  let remaining_headers = remaining_fields.map((field) => {
-    let title=titleCase(field.replace("_", " "));
+function newPageCallback(setPage){
+  return (args) => {
+      if(args.target){
+        setPage(parseFloat(args.target.text));
+      }
+  };
+}
+
+function ZooniverseProjectResults(context){
+  const { queryMap, page, setPage }  = context;
+  const date_formatter=new Intl.DateTimeFormat("default", DATETIME_OPTIONS);
+  const result = queryMap.get("zooniverse_projects").results.query_results[0];
+  const numPages = result.pages;
+  const mandatory_fields = ["launch_date", "created_at", "live", "updated_at", "project_id", "display_name", "slug"];
+  const remaining_fields = Object.keys(result).filter(key => !(mandatory_fields.includes(key) || pagination_fields.includes(key)));
+  const remaining_headers = remaining_fields.map((field) => {
+    const title=titleCase(field.replace("_", " "));
     return (<th key={`project_header_${field}`}>{title}</th>);
   });
     return (
       <>
+      <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/>
         <Table className="mt-3" responsive>
           <thead>
             <tr className="bg-light">
@@ -95,12 +111,12 @@ function ZooniverseProjectResults(queryMap){
           </thead>
           <tbody>
             {queryMap.get("zooniverse_projects").results.query_results.map((result) => {
-              let launch_date = result.launch_date ? date_formatter.format(new Date(result.launch_date)) : "Not Launched";
-              let created_at = date_formatter.format(new Date(result.created_at));
-              let updated_at = date_formatter.format(new Date(result.updated_at));
-              let live = result.live ? "Yes" : "No"
-              let remaining_cells = remaining_fields.map((field) => {
-                let reactKey = `project_${result.project_id}_${field}`;
+              const launch_date = result.launch_date ? date_formatter.format(new Date(result.launch_date)) : "Not Launched";
+              const created_at = date_formatter.format(new Date(result.created_at));
+              const updated_at = date_formatter.format(new Date(result.updated_at));
+              const live = result.live ? "Yes" : "No"
+              const remaining_cells = remaining_fields.map((field) => {
+                const reactKey = `project_${result.project_id}_${field}`;
                 return (<td key={reactKey}>{renderIfCompound(result[field], reactKey)}</td>);
               });
               return (
@@ -123,24 +139,27 @@ function ZooniverseProjectResults(queryMap){
             })}
           </tbody>
         </Table>
-        {/* <Paginate /> */}
+        <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/>
       </>
     );
 
 }
 
-function ZooniverseWorkflowResults(queryMap){
+function ZooniverseWorkflowResults(context){
+  const { queryMap, page, setPage }  = context;
   let date_formatter=new Intl.DateTimeFormat("default", DATETIME_OPTIONS);
   let result = queryMap.get("zooniverse_workflows").results.query_results[0];
-  let result_workflow = result.workflows[0]
+  let result_workflow = result.workflows[0];
+  const numPages = result.pages;
   let mandatory_fields = ["created_at", "updated_at", "workflow_id", "display_name"];
-  let remaining_fields = Object.keys(result_workflow).filter(key => !mandatory_fields.includes(key));
+  let remaining_fields = Object.keys(result_workflow).filter(key => !(mandatory_fields.includes(key) || pagination_fields.includes(key)));
   let remaining_headers = remaining_fields.map((field) => {
     let title=titleCase(field.replace("_", " "));
     return (<th key={`project_header_${field}`}>{title}</th>);
   });
     return (
       <>
+      <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/>
       {queryMap.get("zooniverse_workflows").results.query_results.map((project) => {
         return (<div key={project.project_id}>
         <h4>{project.display_name}</h4>
@@ -186,24 +205,24 @@ function ZooniverseWorkflowResults(queryMap){
               })}
             </tbody>
           </Table>
-          {/* <Paginate /> */}
           </div>);
         })}
+      <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/>
       </>
     );
 }
 
 export default function ZooniverseResults({ catalog }) {
-  const { queryMap } = useContext(QueryContext);
-  if (!queryMap) return null;
-  if (queryMap.get(catalog).status === "fetched") {
-    if (queryMap.get(catalog).results.query_results.length === 0)
+  const context = useContext(QueryContext);
+  if (!context.queryMap) return null;
+  if (context.queryMap.get(catalog).status === "fetched") {
+    if (context.queryMap.get(catalog).results.query_results.length === 0)
       return <Alert variant="warning">No matching results found!</Alert>;
     else if (catalog === "zooniverse_projects"){
-      return ZooniverseProjectResults(queryMap);
+      return ZooniverseProjectResults(context);
     }
     else if (catalog === "zooniverse_workflows"){
-      return ZooniverseWorkflowResults(queryMap);
+      return ZooniverseWorkflowResults(context);
     }
     else {
       return <Alert variant="warning">Unrecognised Zooniverse Catalog!</Alert>;
diff --git a/src/contexts/BasketContext.js b/src/contexts/BasketContext.js
index 5f262b3f779ff45c30ff6574337af1408027d846..fd948e80ea70b9170e8d7bae68e5505de430d580 100644
--- a/src/contexts/BasketContext.js
+++ b/src/contexts/BasketContext.js
@@ -3,18 +3,18 @@ import Databasket from "../components/basket/databasket";
 import Addtobasket from "../components/basket/addtobasket";
 import BasketContext from "../contexts/BasketContext";
 
-const BasketContext = createContext();
-const initialDatasets = ["First Dataset 1", "Just another dataset"];
+export const BasketContext = createContext();
 
-function mydatasets() {
-  const [datasets, setDatasets] = useState(initialDatasets);
+export function BasketContextProvider({ children }) {
+  const [datasets, setDatasets] = useState([]);
 
-  function handleAddDataset(Dataset) {
-    setDatasets([...datasets, Dataset]);
+  function handleAddDataset(dataset) {
+    setDatasets([...datasets, dataset]);
   }
 
-  function handleRemoveDataset(index) {
+  function handleRemoveDataset(dataset) {
     const copy = [...datasets];
+    const index = copy.findIndex((ds) => ds === dataset);
     copy.splice(index, 1);
     setDatasets(copy);
   }
@@ -22,15 +22,7 @@ function mydatasets() {
     <BasketContext.Provider
       value={{ datasets, add: handleAddDataset, remove: handleRemoveDataset }}
     >
-      <div className="App">
-        <header className="App-header">
-          <h2>DataBasket App</h2>
-          <Addtobasket />
-          <Databasket />
-        </header>
-      </div>
+      {children}
     </BasketContext.Provider>
   );
 }
-
-export default BasketContext;
diff --git a/src/contexts/GlobalContext.js b/src/contexts/GlobalContext.js
index 0b828c476758051005a753ec2cca6c2594127260..e18b2eb607345a265f8060ccbf209a52e6d63753 100644
--- a/src/contexts/GlobalContext.js
+++ b/src/contexts/GlobalContext.js
@@ -8,17 +8,16 @@ export function GlobalContextProvider({ children }) {
   console.log("ASTRON ESAP version 14 aug 2020");
   const api_host =
     process.env.NODE_ENV === "development"
-      ? "http://sdc.astron.nl:15671/esap-api/"
+      ? "http://localhost:15671/esap-api/"
       : "/esap-api/";
 
   const [config, setConfig] = useState();
-  const [configName, setConfigName] = useState("esap_config");
-  const [defaultConfigName, setDefaultConfigName] = useState("esap_config");
+  const [configName, setConfigName] = useState("esap_ivoa");
 
   useEffect(() => {
-    let configNameString=""
-    if (configName){
-      configNameString=`?name=${configName}`
+    let configNameString = "";
+    if (configName) {
+      configNameString = `?name=${configName}`;
     }
     axios
       .get(api_host + "query/configuration" + configNameString)
@@ -63,7 +62,7 @@ export function GlobalContextProvider({ children }) {
         archives,
         handleLogin,
         handleLogout,
-        setConfigName
+        setConfigName,
       }}
     >
       {children}
diff --git a/src/contexts/IVOAContext.js b/src/contexts/IVOAContext.js
new file mode 100644
index 0000000000000000000000000000000000000000..eba8707e13090945b095b033a21600474fd60d29
--- /dev/null
+++ b/src/contexts/IVOAContext.js
@@ -0,0 +1,28 @@
+import React, { createContext, useState } from "react";
+
+export const IVOAContext = createContext();
+
+export function IVOAContextProvider({ children }) {
+  const [registryList, setRegistryList] = useState([]);
+  function handleAddRegistry(access_url) {
+    setRegistryList([...registryList, access_url]);
+  }
+
+  function handleRemoveRegistry(access_url) {
+    const copy = [...registryList];
+    const index = copy.findIndex((url) => url === access_url);
+    copy.splice(index, 1);
+    setRegistryList(copy);
+  }
+  return (
+    <IVOAContext.Provider
+      value={{
+        registryList,
+        add: handleAddRegistry,
+        remove: handleRemoveRegistry,
+      }}
+    >
+      {children}
+    </IVOAContext.Provider>
+  );
+}
diff --git a/src/contexts/QueryContext.js b/src/contexts/QueryContext.js
index 91725aff18f08ab567f787c966367783cf1b6594..e0ad5419511ddddacc8a575a820f7ae199e5a56e 100644
--- a/src/contexts/QueryContext.js
+++ b/src/contexts/QueryContext.js
@@ -6,9 +6,12 @@ export const QueryContext = createContext();
 export function QueryContextProvider({ children }) {
   const queryMap = useMap();
   const [formData, setFormData] = useState();
+  const [page, setPage] = useState(1);
   return (
     <QueryContext.Provider
       value={{
+        page,
+        setPage,
         queryMap,
         formData,
         setFormData,
diff --git a/src/routes/Routes.js b/src/routes/Routes.js
index e28032c493e06f280a9c7d9067f4091b3785911c..5cea8e2796c348ba82c3f4e7cccb50a87a2ced3d 100644
--- a/src/routes/Routes.js
+++ b/src/routes/Routes.js
@@ -4,9 +4,13 @@ import { Archives } from "../components/archives/Archives";
 import ArchiveDetails from "../components/archives/ArchiveDetails";
 import { GlobalContext } from "../contexts/GlobalContext";
 import QueryCatalogs from "../components/query/QueryCatalogs";
+import QueryIVOARegistry from "../components/query/QueryIVOARegistry";
 import { BrowserRouter as Router } from "react-router-dom";
 import NavBar from "../components/NavBar";
 import { QueryContextProvider } from "../contexts/QueryContext";
+import Rucio from "../components/Rucio";
+import Interactive from "../components/Interactive";
+import { IVOAContextProvider } from "../contexts/IVOAContext";
 
 export default function Routes() {
   const { config, handleLogin, handleLogout } = useContext(GlobalContext);
@@ -22,14 +26,27 @@ export default function Routes() {
         <Route exact path="/archives">
           <Archives />
         </Route>
-        <Route path="/query">
+        <Route exact path="/query">
           <QueryContextProvider>
-            <QueryCatalogs />
+            <IVOAContextProvider>
+              <QueryIVOARegistry />
+            </IVOAContextProvider>
           </QueryContextProvider>
         </Route>
+        <Route exact path="/rucio">
+          <Rucio />
+        </Route>
+        <Route exact path="/interactive">
+          <Interactive />
+        </Route>
         <Route exact path="/login" component={handleLogin} />
         <Route exact path="/logout" component={handleLogout} />
-        <Route path="/archives/:uri" component={ArchiveDetails} />
+        <Route exact path="/archives/:uri" component={ArchiveDetails} />
+        <Route exact path="/archives/:uri/query">
+          <QueryContextProvider>
+            <QueryCatalogs />
+          </QueryContextProvider>
+        </Route>
       </Switch>
     </Router>
   );
diff --git a/src/utils/form/parseADEXForm.js b/src/utils/form/parseADEXForm.js
index c4f17bd8a42aa373f5cd0652ba8e2353c099f662..d2f9460dc23fc6ea74995f1f051a881a8590194f 100644
--- a/src/utils/form/parseADEXForm.js
+++ b/src/utils/form/parseADEXForm.js
@@ -1,4 +1,5 @@
-export default function ParseADEXForm(formData, catalogs) {
+export default function ParseADEXForm(formData) {
+  let catalogs = ["apertif", "astron_vo"];
   let queries = [];
   // queries is an array of dictionaries, where each dictionary consists of
   // {"catalog": "catalogname",
diff --git a/src/utils/form/parseASTRONVOForm.js b/src/utils/form/parseASTRONVOForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..8724fc5aabc2c2c9d2a6cda65687d5f6d5b78b1d
--- /dev/null
+++ b/src/utils/form/parseASTRONVOForm.js
@@ -0,0 +1,29 @@
+export default function ParseASTRONVOForm(formData) {
+  let queries = [];
+  // queries is an array of dictionaries, where each dictionary consists of
+  // {"catalog": "catalogname",
+  //  "esapquery": "querystring"}
+  let query = "";
+  let formInput = Object.entries(formData);
+  console.log(formInput);
+
+  for (let [key, value] of formInput) {
+    console.log(`${key}: ${value}`);
+    if (value && value !== "all" && key !== "catalog") {
+      query += `${`${query}` ? "&" : ""}` + key + "=" + value;
+    }
+  }
+  console.log("Query:", query);
+  // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname",
+  //  "catalogquery": "querystring",
+  //  "status": "null|fetching|fetched",
+  //  "results": null}
+  let catalog = formInput.find(([key]) => key === "catalog")[1];
+  let esapquery = query + `${`${query}` ? "&" : ""}archive_uri=` + catalog;
+  queries.push({
+    catalog: catalog,
+    esapquery: esapquery,
+  });
+  console.log("Queries:", queries);
+  return queries;
+}
diff --git a/src/utils/form/parseApertifForm.js b/src/utils/form/parseApertifForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a764050acb24deea951cb6778e8e023e62364c6
--- /dev/null
+++ b/src/utils/form/parseApertifForm.js
@@ -0,0 +1,29 @@
+export default function ParseApertifForm(formData) {
+  let queries = [];
+  // queries is an array of dictionaries, where each dictionary consists of
+  // {"catalog": "catalogname",
+  //  "esapquery": "querystring"}
+  let query = "";
+  let formInput = Object.entries(formData);
+  console.log("formInput:", formInput);
+
+  for (let [key, value] of formInput) {
+    console.log(`${key}: ${value}`);
+    if (value && value !== "all" && key !== "catalog") {
+      query += `${`${query}` ? "&" : ""}` + key + "=" + value;
+    }
+  }
+  console.log("Query:", query);
+  // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname",
+  //  "catalogquery": "querystring",
+  //  "status": "null|fetching|fetched",
+  //  "results": null}
+  let catalog = formInput.find(([key]) => key === "catalog")[1];
+  let esapquery = query + `${`${query}` ? "&" : ""}archive_uri=` + catalog;
+  queries.push({
+    catalog: catalog,
+    esapquery: esapquery,
+  });
+  console.log("Queries:", queries);
+  return queries;
+}
diff --git a/src/utils/form/parseIVOAForm.js b/src/utils/form/parseIVOAForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..640227870591c5f3dbbcb864355cc7240c5fb2df
--- /dev/null
+++ b/src/utils/form/parseIVOAForm.js
@@ -0,0 +1,36 @@
+export default function ParseIVOAForm(formData) {
+  let queries = [];
+  // queries is an array of dictionaries, where each dictionary consists of
+  // {"catalog": "catalogname",
+  //  "esapquery": "querystring"}
+  let query = "";
+  let formInput = Object.entries(formData);
+  console.log(formInput);
+
+  // IVOA query consists of multiple steps
+  // Step 1: get list of registry services
+
+  for (let [key, value] of formInput) {
+    console.log(`${key}: ${value}`);
+    if (value && value !== "all" && key !== "catalog") {
+      query += `${`${query}` ? "&" : ""}` + key + "=" + value;
+    }
+  }
+  console.log("Query:", query);
+  // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname",
+  //  "catalogquery": "querystring",
+  //  "status": "null|fetching|fetched",
+  //  "results": null}
+  let catalog = formInput.find(([key]) => key === "catalog")[1];
+
+  let esapquery =
+    "get-services/?" + query + `${`${query}` ? "&" : ""}dataset_uri=` + catalog;
+
+  queries.push({
+    catalog: catalog,
+    esapquery: esapquery,
+  });
+
+  console.log("Queries:", queries);
+  return queries;
+}
diff --git a/src/utils/form/parseLOFARForm.js b/src/utils/form/parseLOFARForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..badc2b8512cc5c64457384b6e23c3b435f4eb1d8
--- /dev/null
+++ b/src/utils/form/parseLOFARForm.js
@@ -0,0 +1,29 @@
+export default function ParseLOFARForm(formData) {
+  let queries = [];
+  // queries is an array of dictionaries, where each dictionary consists of
+  // {"catalog": "catalogname",
+  //  "esapquery": "querystring"}
+  let query = "";
+  let formInput = Object.entries(formData);
+  console.log(formInput);
+
+  for (let [key, value] of formInput) {
+    console.log(`${key}: ${value}`);
+    if (value && value !== "all" && key !== "catalog") {
+      query += `${`${query}` ? "&" : ""}` + key + "=" + value;
+    }
+  }
+  console.log("Query:", query);
+  // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname",
+  //  "catalogquery": "querystring",
+  //  "status": "null|fetching|fetched",
+  //  "results": null}
+  let catalog = formInput.find(([key]) => key === "catalog")[1];
+  let esapquery = query + `${`${query}` ? "&" : ""}archive_uri=` + catalog;
+  queries.push({
+    catalog: catalog,
+    esapquery: esapquery,
+  });
+  console.log("Queries:", queries);
+  return queries;
+}
diff --git a/src/utils/form/parseQueryForm.js b/src/utils/form/parseQueryForm.js
index 21f9315428e7af25435b27b52d64b99f2cde7adb..ecf4e15093d67ab8c6142f82e434ee406d4128d9 100644
--- a/src/utils/form/parseQueryForm.js
+++ b/src/utils/form/parseQueryForm.js
@@ -1,12 +1,24 @@
 import parseADEXForm from "./parseADEXForm";
 import parseZooniverseForm from "./parseZooniverseForm";
+import parseLOFARForm from "./parseLOFARForm";
+import parseIVOAForm from "./parseIVOAForm";
+import parseApertifForm from "./parseApertifForm";
+import parseASTRONVOForm from "./parseASTRONVOForm";
 
-export default function parseQueryForm(gui, formData, catalogs) {
+export default function parseQueryForm(gui, formData, page) {
   switch (gui) {
     case "adex":
-      return parseADEXForm(formData, catalogs);
+      return parseADEXForm(formData);
     case "zooniverse":
-      return parseZooniverseForm(formData, catalogs);
+      return parseZooniverseForm(formData, page);
+    case "lofar":
+      return parseLOFARForm(formData);
+    case "apertif":
+      return parseApertifForm(formData);
+    case "astron_vo":
+      return parseASTRONVOForm(formData);
+    case "ivoa":
+      return parseIVOAForm(formData);
     default:
       return null;
   }
diff --git a/src/utils/form/parseZooniverseForm.js b/src/utils/form/parseZooniverseForm.js
index c4fa8ceabc4b7fbbaab45686ea5328dad23fa783..50284bb4c8ab1e2db35e4cd2b3446662b477e284 100644
--- a/src/utils/form/parseZooniverseForm.js
+++ b/src/utils/form/parseZooniverseForm.js
@@ -1,4 +1,5 @@
-export default function parseZooniverseForm(formData, catalogs) {
+export default function parseZooniverseForm(formData, page) {
+  let catalogs = ["zooniverse_projects", "zooniverse_workflows"];
   let queries = [];
   // queries is an array of dictionaries, where each dictionary consists of
   // {"catalog": "catalogname",
@@ -23,7 +24,11 @@ export default function parseZooniverseForm(formData, catalogs) {
   if (catalog === "all") {
     console.log(`Catalogs: ${catalogs}`);
     catalogs.map((catalog) => {
-      let esapquery = [query, "archive_uri=zooniverse", `catalog=${catalog}`].join("&");
+      let esapquery = [
+        query,
+        "archive_uri=zooniverse",
+        `catalog=${catalog}`,
+      ].join("&");
 
       queries.push({
         catalog: catalog,
@@ -32,11 +37,15 @@ export default function parseZooniverseForm(formData, catalogs) {
       return null;
     });
   } else {
-    let esapquery = [query, "archive_uri=zooniverse", `catalog=${catalog}`].join("&");
+    let esapquery = [
+      query,
+      "archive_uri=zooniverse",
+      `catalog=${catalog}`,
+    ].join("&");
 
     queries.push({
       catalog: catalog,
-      esapquery: esapquery,
+      esapquery: esapquery + `&page=${page}`,
     });
   }
   console.log("Queries:", queries);