diff --git a/src/Collections/Nodules.js b/src/Collections/Nodules.js
index 3c1a92e..534f7b0 100644
--- a/src/Collections/Nodules.js
+++ b/src/Collections/Nodules.js
@@ -25,6 +25,20 @@ class Nodules {
     }
   }
 
+  addNewJoinNodule = props => {
+    try {
+      const newJoinNodule = new JoinNodule({
+        id: props.id || uuid(),
+        label: props.label,
+        tables: props.tables,
+        joinBy: props.joinBy
+      })
+      this.collection.push(newJoinNodule)
+    } catch (err) {
+      console.error(err)
+    }
+  }
+
   removeById = id => {
     const indexToRemove = this.collection.findIndex(n => n.id === id)
     if (indexToRemove > -1) this.collection.splice(indexToRemove, 1)
diff --git a/src/Controllers/CreateNoduleController.js b/src/Controllers/CreateNoduleController.js
index 29317cb..2641106 100644
--- a/src/Controllers/CreateNoduleController.js
+++ b/src/Controllers/CreateNoduleController.js
@@ -23,6 +23,23 @@ class CreateNoduleController {
     document.dispatchEvent(this.updatedNodulesEvent)
   }
 
+  addNewJoinNodule = props => {
+    const { label, tablesToImportByLabel, baseTableLabel, joinParams } = props
+    const tables = tablesToImportByLabel.map(label => {
+      return this.tables.getTableByLabel(label)
+    })
+
+    this.nodules.addNewJoinNodule({
+      label,
+      tables,
+      joinBy: {
+        baseTableLabel,
+        joinParams
+      }
+    })
+    document.dispatchEvent(this.updatedNodulesEvent)
+  }
+
   deleteNodule = id => {
     this.nodules.removeById(id)
     document.dispatchEvent(this.updatedNodulesEvent)
diff --git a/src/views/CreateNodule/CreateJoinNoduleForm.js b/src/views/CreateNodule/CreateJoinNoduleForm.js
new file mode 100644
index 0000000..df36f37
--- /dev/null
+++ b/src/views/CreateNodule/CreateJoinNoduleForm.js
@@ -0,0 +1,94 @@
+import React, { Component } from 'react'
+import { Input, Dropdown, Button, Icon, List } from 'semantic-ui-react'
+import './CreateNodule.css'
+
+import Tables from '../../Collections/Tables'
+
+class CreateJoinNoduleForm extends Component {
+  constructor () {
+    super()
+    this.tables = new Tables()
+
+    this.state = {
+      bastTableLabel: '',
+      joinParams: [],
+      tables: this.tables.getCollectionProps()
+    }
+
+    this.foreignTableInput = React.createRef()
+    this.primaryTableKeyInput = React.createRef()
+    this.matchingKeyInput = React.createRef()
+    document.addEventListener('updateTables', this.updateTableList)
+  }
+
+  addJoinParam = () => {
+    let joinParams = this.state.joinParams || []
+
+    const foreignTable = this.foreignTableInput.current.inputRef.current.value
+    const primaryTableKey = this.primaryTableKeyInput.current.inputRef.current.value
+    const matchingKey = this.matchingKeyInput.current.inputRef.current.value
+
+    if (foreignTable && matchingKey && primaryTableKey)
+      joinParams.push({ foreignTable, primaryTableKey, matchingKey })
+
+    this.setState({ joinParams: joinParams })
+  }
+
+  handleChange = (e, value) => {
+    this.setState({ bastTableLabel: value.value })
+  }
+
+  getBaseTableDropDownOptions = () => {
+    const { tables } = this.state
+
+    const options = tables.map(t => {
+      return { key: t.label, text: t.label, value: t.label }
+    })
+
+    return options
+  }
+
+  updateTableList = () => {
+    this.setState({tables: this.tables.getCollectionProps()})
+  }
+
+  renderJoinParams = () => {
+    const { joinParams, bastTableLabel } = this.state
+
+    const joinParamsElements = joinParams.map(p => {
+      return