diff --git a/__tests__/src/actions/companionWindow.test.js b/__tests__/src/actions/companionWindow.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd28ad7cd4482b1d8cb511f80e699de1fd1f18f7
--- /dev/null
+++ b/__tests__/src/actions/companionWindow.test.js
@@ -0,0 +1,53 @@
+import * as actions from '../../../src/state/actions';
+import ActionTypes from '../../../src/state/actions/action-types';
+
+describe('companionWindow actions', () => {
+  describe('addCompanionWindow', () => {
+    it('should return correct action object', () => {
+      const payload = {
+        content: 'info',
+        position: 'right',
+        foo: 'bar',
+      };
+      const action = actions.addCompanionWindow(payload);
+      expect(action.type).toBe(ActionTypes.ADD_COMPANION_WINDOW);
+      expect(action.payload).toEqual(payload);
+    });
+
+    it('should set the correct default values', () => {
+      const payload = {};
+      const defaults = { foo: 'bar' };
+      const action = actions.addCompanionWindow(payload, defaults);
+      expect(action.payload.foo).toBe('bar');
+    });
+
+    it('should generate a new companionWindow ID', () => {
+      const payload = {};
+
+      expect(actions.addCompanionWindow(payload).id).toEqual(
+        expect.stringMatching(/^cw-\w+-\w+/),
+      );
+    });
+  });
+
+  describe('updateCompanionWindow', () => {
+    it('should return correct action object', () => {
+      const payload = {
+        content: 'info',
+        position: 'right',
+      };
+      const action = actions.updateCompanionWindow('cw-123', payload);
+      expect(action.type).toBe(ActionTypes.UPDATE_COMPANION_WINDOW);
+      expect(action.id).toBe('cw-123');
+      expect(action.payload).toEqual(payload);
+    });
+  });
+
+  describe('removeCompanionWindow', () => {
+    it('should return correct action object', () => {
+      const action = actions.removeCompanionWindow('cw-123');
+      expect(action.type).toBe(ActionTypes.REMOVE_COMPANION_WINDOW);
+      expect(action.id).toBe('cw-123');
+    });
+  });
+});
diff --git a/__tests__/src/actions/window.test.js b/__tests__/src/actions/window.test.js
index 868fbf52c06131dac741954f7bbd50e610adf4f8..2dde5c7c11ba78e4f83b9e9cc8e93b369f3dbf30 100644
--- a/__tests__/src/actions/window.test.js
+++ b/__tests__/src/actions/window.test.js
@@ -15,6 +15,7 @@ describe('window actions', () => {
           id: 'helloworld',
           canvasIndex: 1,
           collectionIndex: 0,
+          companionWindowIds: [],
           manifestId: null,
           rangeId: null,
           thumbnailNavigationPosition: 'bottom',
@@ -26,6 +27,20 @@ describe('window actions', () => {
       expect(actions.addWindow(options)).toEqual(expectedAction);
     });
   });
+
+  describe('updateWindow', () => {
+    it('should return correct action object', () => {
+      const payload = {
+        foo: 1,
+        bar: 2,
+      };
+      const action = actions.updateWindow('window-123', payload);
+      expect(action.type).toBe(ActionTypes.UPDATE_WINDOW);
+      expect(action.id).toBe('window-123');
+      expect(action.payload).toEqual(payload);
+    });
+  });
+
   describe('removeWindow', () => {
     it('removes the window and returns windowId', () => {
       const id = 'abc123';
@@ -85,36 +100,43 @@ describe('window actions', () => {
     });
   });
 
-  describe('setWindowCompanionWindow', () => {
-    it('returns the appropriate action type', () => {
-      const windowId = 'abc123';
-      const panelType = 'info';
-      const position = 'right';
-      const expectedAction = {
-        type: ActionTypes.SET_WINDOW_COMPANION_WINDOW,
-        windowId,
-        panelType,
-        position,
-      };
-      expect(actions.setWindowCompanionWindow(windowId, 'info', 'right')).toEqual(expectedAction);
-    });
-  });
-
   describe('popOutCompanionWindow', () => {
     it('returns a thunk which dispatches the appropriate actions', () => {
-      const mockDispatch = jest.fn();
+      const mockState = {
+        windows: {
+          abc123: {
+            companionWindowIds: ['cw-1'],
+          },
+        },
+      };
+      const mockDispatch = jest.fn(() => ({ id: 'cw-1' }));
+      const mockGetState = jest.fn(() => mockState);
       const windowId = 'abc123';
       const panelType = 'info';
       const position = 'right';
       const thunk = actions.popOutCompanionWindow(windowId, panelType, position);
 
       expect(typeof thunk).toEqual('function');
-      thunk(mockDispatch);
-      expect(mockDispatch).toHaveBeenCalledTimes(2);
-      expect(mockDispatch).toHaveBeenCalledWith({
-        type: ActionTypes.SET_WINDOW_COMPANION_WINDOW, windowId, panelType, position,
+      thunk(mockDispatch, mockGetState);
+      expect(mockDispatch).toHaveBeenCalledTimes(4);
+
+      expect(mockDispatch).toHaveBeenNthCalledWith(1, {
+        type: ActionTypes.REMOVE_COMPANION_WINDOW,
+        id: 'cw-1',
       });
-      expect(mockDispatch).toHaveBeenCalledWith({
+
+      const addCompanionWindowAction = mockDispatch.mock.calls[1][0];
+      expect(addCompanionWindowAction.type).toBe(ActionTypes.ADD_COMPANION_WINDOW);
+      expect(addCompanionWindowAction.payload).toEqual({ content: 'info', position: 'right' });
+      expect(addCompanionWindowAction.id.startsWith('cw-')).toBe(true);
+
+      expect(mockDispatch).toHaveBeenNthCalledWith(3, {
+        type: ActionTypes.UPDATE_WINDOW,
+        id: 'abc123',
+        payload: { companionWindowIds: ['cw-1'] },
+      });
+
+      expect(mockDispatch).toHaveBeenNthCalledWith(4, {
         type: ActionTypes.TOGGLE_WINDOW_SIDE_BAR_PANEL, windowId, panelType: 'closed',
       });
     });
diff --git a/__tests__/src/components/CompanionWindow.test.js b/__tests__/src/components/CompanionWindow.test.js
index 02dd89f090918704881adafc1e07f7626abec71f..f155abd8ef11cdfcad8ea38296e1f6fee67137bb 100644
--- a/__tests__/src/components/CompanionWindow.test.js
+++ b/__tests__/src/components/CompanionWindow.test.js
@@ -7,8 +7,10 @@ import WindowSideBarInfoPanel from '../../../src/containers/WindowSideBarInfoPan
 function createWrapper(props) {
   return shallow(
     <CompanionWindow
-      windowId="abc123"
+      id="abc123"
+      windowId="x"
       classes={{}}
+      companionWindow={{}}
       position="right"
       {...props}
     />,
@@ -20,7 +22,7 @@ describe('CompanionWindow', () => {
 
   describe('when the panelContent is set to "info"', () => {
     it('renders the WindowSideBarInfoPanel', () => {
-      companionWindow = createWrapper({ panelContent: 'info' });
+      companionWindow = createWrapper({ content: 'info' });
       expect(companionWindow.find(WindowSideBarInfoPanel).length).toBe(1);
     });
   });
@@ -33,16 +35,16 @@ describe('CompanionWindow', () => {
   });
 
   describe('when the close companion window button is clicked', () => {
-    it('triggers the closeCompanionWindow prop with the appropriate args', () => {
-      const closeCompanionWindowEvent = jest.fn();
+    it('triggers the onCloseClick prop with the appropriate args', () => {
+      const removeCompanionWindowEvent = jest.fn();
       companionWindow = createWrapper({
-        closeCompanionWindow: closeCompanionWindowEvent,
+        onCloseClick: removeCompanionWindowEvent,
       });
 
       const closeButton = companionWindow.find('WithStyles(IconButton)[aria-label="closeCompanionWindow"]');
       closeButton.simulate('click');
-      expect(closeCompanionWindowEvent).toHaveBeenCalledTimes(1);
-      expect(closeCompanionWindowEvent).toHaveBeenCalledWith('abc123', null, 'right');
+      expect(removeCompanionWindowEvent).toHaveBeenCalledTimes(1);
+      expect(removeCompanionWindowEvent).toHaveBeenCalledWith('x', 'abc123');
     });
   });
 });
diff --git a/__tests__/src/components/WindowMiddleContent.test.js b/__tests__/src/components/WindowMiddleContent.test.js
index 34e4bdbf9e1097bae55c96e9e522bc6a4dc713b8..64da2c84b34af25fa14cb51b23809348ef418e74 100644
--- a/__tests__/src/components/WindowMiddleContent.test.js
+++ b/__tests__/src/components/WindowMiddleContent.test.js
@@ -5,24 +5,34 @@ import CompanionWindow from '../../../src/containers/CompanionWindow';
 import WindowSideBar from '../../../src/containers/WindowSideBar';
 import WindowViewer from '../../../src/containers/WindowViewer';
 
+/** create wrapper */
+function createWrapper(props) {
+  return shallow(
+    <WindowMiddleContent
+      companionWindowIds={['cw1', 'cw-2']}
+      window={{ id: 'window-1' }}
+      manifest={{}}
+      {...props}
+    />,
+  );
+}
+
 describe('WindowMiddleContent', () => {
-  let wrapper;
-  let manifest;
   it('should render outer element', () => {
-    wrapper = shallow(<WindowMiddleContent window={window} />);
+    const wrapper = createWrapper();
     expect(wrapper.find('.mirador-window-middle-content')).toHaveLength(1);
   });
-  it('should render <CompanionWindow>', () => {
-    wrapper = shallow(<WindowMiddleContent window={window} />);
-    expect(wrapper.find(CompanionWindow)).toHaveLength(1);
+  it('should render all <CompanionWindow> components', () => {
+    const wrapper = createWrapper();
+    expect(wrapper.find(CompanionWindow)).toHaveLength(2);
   });
   it('should render <WindowSideBar>', () => {
-    wrapper = shallow(<WindowMiddleContent window={window} />);
+    const wrapper = createWrapper();
     expect(wrapper.find(WindowSideBar)).toHaveLength(1);
   });
   it('should render <WindowViewer> if manifest is present', () => {
-    manifest = { id: 456, isFetching: false };
-    wrapper = shallow(<WindowMiddleContent window={window} manifest={manifest} />);
+    const manifest = { id: 456, isFetching: false };
+    const wrapper = createWrapper({ manifest });
     expect(wrapper.find(WindowViewer)).toHaveLength(1);
   });
 });
diff --git a/__tests__/src/components/WindowTopBar.test.js b/__tests__/src/components/WindowTopBar.test.js
index e482f635c53eeb19ff208026f008458e76c9ef3d..4fe237023b1c07600e28eb782d73d9eea2941d25 100644
--- a/__tests__/src/components/WindowTopBar.test.js
+++ b/__tests__/src/components/WindowTopBar.test.js
@@ -20,7 +20,7 @@ function createWrapper(props) {
       windowId="xyz"
       classes={{}}
       t={str => str}
-      removeWindow={() => {}}
+      closeWindow={() => {}}
       toggleWindowSideBar={() => {}}
       {...props}
     />,
@@ -67,8 +67,8 @@ describe('WindowTopBar', () => {
   });
 
   it('passes correct props to <Button/>', () => {
-    const removeWindow = jest.fn();
-    const wrapper = createWrapper({ removeWindow });
-    expect(wrapper.find(IconButton).last().props().onClick).toBe(removeWindow);
+    const closeWindow = jest.fn();
+    const wrapper = createWrapper({ closeWindow });
+    expect(wrapper.find(IconButton).last().props().onClick).toBe(closeWindow);
   });
 });
diff --git a/__tests__/src/reducers/companionWindows.test.js b/__tests__/src/reducers/companionWindows.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..68775833910068ef0252fdf95084980790c37c1a
--- /dev/null
+++ b/__tests__/src/reducers/companionWindows.test.js
@@ -0,0 +1,63 @@
+import { companionWindowsReducer } from '../../../src/state/reducers/companionWindows';
+import ActionTypes from '../../../src/state/actions/action-types';
+
+describe('companionWindowsReducer', () => {
+  describe('ADD_COMPANION_WINDOW', () => {
+    it('adds a new companion window', () => {
+      const action = {
+        type: ActionTypes.ADD_COMPANION_WINDOW,
+        id: 'abc123',
+        payload: { content: 'info', position: 'right' },
+      };
+      const beforeState = {};
+      const expectedState = {
+        abc123: {
+          position: 'right',
+          content: 'info',
+        },
+      };
+      expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState);
+    });
+  });
+
+  describe('UPDATE_COMPANION_WINDOW', () => {
+    it('updates an existing companion window', () => {
+      const action = {
+        type: ActionTypes.UPDATE_COMPANION_WINDOW,
+        id: 'abc123',
+        payload: { content: 'canvases', foo: 'bar' },
+      };
+      const beforeState = {
+        abc123: {
+          position: 'right',
+          content: 'info',
+        },
+      };
+      const expectedState = {
+        abc123: {
+          position: 'right',
+          content: 'canvases',
+          foo: 'bar',
+        },
+      };
+      expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState);
+    });
+  });
+
+  describe('REMOVE_COMPANION_WINDOW', () => {
+    it('should remove a companion window', () => {
+      const action = {
+        type: ActionTypes.REMOVE_COMPANION_WINDOW,
+        id: 'abc123',
+      };
+      const beforeState = {
+        abc123: {
+          position: 'right',
+          content: 'info',
+        },
+      };
+      const expectedState = {};
+      expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState);
+    });
+  });
+});
diff --git a/__tests__/src/reducers/windows.test.js b/__tests__/src/reducers/windows.test.js
index 758cb5d9287014286e354b7c4b023c1925419513..e8beeabbbf756d5feda29645d26af0751986b2a8 100644
--- a/__tests__/src/reducers/windows.test.js
+++ b/__tests__/src/reducers/windows.test.js
@@ -121,42 +121,6 @@ describe('windows reducer', () => {
     });
   });
 
-  describe('SET_WINDOW_COMPANION_WINDOW', () => {
-    it('sets the given type under the given position when no companion window exists', () => {
-      const action = {
-        type: ActionTypes.SET_WINDOW_COMPANION_WINDOW,
-        windowId: 'abc123',
-        position: 'right',
-        panelType: 'info',
-      };
-      const before = {
-        abc123: {},
-      };
-      const after = {
-        abc123: { companionWindows: { right: 'info' } },
-      };
-
-      expect(windowsReducer(before, action)).toEqual(after);
-    });
-
-    it('overwrites the given position and sets the new type when a companion window in the same position exists', () => {
-      const action = {
-        type: ActionTypes.SET_WINDOW_COMPANION_WINDOW,
-        windowId: 'abc123',
-        position: 'right',
-        panelType: 'info',
-      };
-      const before = {
-        abc123: { companionWindows: { right: 'canvas_navigation' } },
-      };
-      const after = {
-        abc123: { companionWindows: { right: 'info' } },
-      };
-
-      expect(windowsReducer(before, action)).toEqual(after);
-    });
-  });
-
   it('should handle NEXT_CANVAS', () => {
     expect(windowsReducer({
       abc123: {
@@ -230,4 +194,28 @@ describe('windows reducer', () => {
       },
     });
   });
+
+  describe('UPDATE_WINDOW', () => {
+    it('updates an existing window', () => {
+      const action = {
+        type: ActionTypes.UPDATE_WINDOW,
+        id: 'abc123',
+        payload: { foo: 11, baz: 33 },
+      };
+      const beforeState = {
+        abc123: {
+          foo: 1,
+          bar: 2,
+        },
+      };
+      const expectedState = {
+        abc123: {
+          foo: 11,
+          bar: 2,
+          baz: 33,
+        },
+      };
+      expect(windowsReducer(beforeState, action)).toEqual(expectedState);
+    });
+  });
 });
diff --git a/__tests__/src/selectors/index.test.js b/__tests__/src/selectors/index.test.js
index 216d0efbd8d0a902722895314c7e082cb2ea3aba..f3f04a75fe416ca6898e2b79dcceae4866b885fd 100644
--- a/__tests__/src/selectors/index.test.js
+++ b/__tests__/src/selectors/index.test.js
@@ -323,17 +323,20 @@ describe('getIdAndLabelOfCanvases', () => {
 
 describe('getCompanionWindowForPosition', () => {
   const state = {
-    windows: { a: { companionWindows: { right: 'info' } } },
+    companionWindows: {
+      abc: { id: 'abc', windowId: 'a', position: 'right' },
+      xyz: { id: 'xyz', windowId: 'b', position: 'bottom' },
+    },
   };
 
   it('the companion window type based on the given position', () => {
     const received = getCompanionWindowForPosition(state, 'a', 'right');
 
-    expect(received).toEqual('info');
+    expect(received.id).toEqual('abc');
   });
 
   it('returns undefined if the given window does not exist', () => {
-    const received = getCompanionWindowForPosition(state, 'b', 'right');
+    const received = getCompanionWindowForPosition(state, 'c', 'right');
 
     expect(received).toBeUndefined();
   });
diff --git a/package.json b/package.json
index 3378f26d956f2ebc84d388b8874de98a545c6ee0..156228a973f2ae817a969b06a9d7f2224c32a88c 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
     "deepmerge": "^3.1.0",
     "dompurify": "^1.0.9",
     "i18next": "^14.0.1",
+    "immutable": "^4.0.0-rc.12",
     "intersection-observer": "^0.5.1",
     "lodash": "^4.17.11",
     "manifesto.js": "^3.0.9",
diff --git a/src/components/CompanionWindow.js b/src/components/CompanionWindow.js
index 2f73e48ef0db99c4a0d5e7ec1ecfd21b86563013..b26f2f84ce1b900d64c531b9dca218a779650b55 100644
--- a/src/components/CompanionWindow.js
+++ b/src/components/CompanionWindow.js
@@ -17,8 +17,9 @@ class CompanionWindow extends Component {
    * @return React Component
    */
   activePanelComponent() {
-    const { windowId, panelContent } = this.props;
-    switch (panelContent) {
+    const { content, windowId } = this.props;
+
+    switch (content) {
       case 'info':
         return <WindowSideBarInfoPanel windowId={windowId} />;
       case 'canvas_navigation':
@@ -34,8 +35,9 @@ class CompanionWindow extends Component {
    */
   render() {
     const {
-      classes, closeCompanionWindow, isDisplayed, position, t, windowId,
+      classes, id, onCloseClick, isDisplayed, position, t, windowId,
     } = this.props;
+
     return (
       <Paper
         className={[classes.root, ns(`companion-window-${position}`)].join(' ')}
@@ -46,7 +48,7 @@ class CompanionWindow extends Component {
         <IconButton
           aria-label={t('closeCompanionWindow')}
           className={classes.closeButton}
-          onClick={() => { closeCompanionWindow(windowId, null, position); }}
+          onClick={() => { onCloseClick(windowId, id); }}
         >
           <CloseIcon />
         </IconButton>
@@ -57,18 +59,20 @@ class CompanionWindow extends Component {
 
 CompanionWindow.propTypes = {
   classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types,
-  closeCompanionWindow: PropTypes.func,
+  content: PropTypes.string,
+  id: PropTypes.string.isRequired,
+  onCloseClick: PropTypes.func,
+  position: PropTypes.string,
   isDisplayed: PropTypes.bool,
-  panelContent: PropTypes.string,
-  position: PropTypes.string.isRequired,
   t: PropTypes.func,
   windowId: PropTypes.string.isRequired,
 };
 
 CompanionWindow.defaultProps = {
-  closeCompanionWindow: () => {},
-  panelContent: null,
+  content: null,
+  onCloseClick: () => {},
   isDisplayed: false,
+  position: null,
   t: key => key,
 };
 
diff --git a/src/components/WindowMiddleContent.js b/src/components/WindowMiddleContent.js
index 8b6604f1f3344e81e7acf514ece1bfeac6e23198..b2817e6b68d508c5898c51b06e0fcde6ea68fc3d 100644
--- a/src/components/WindowMiddleContent.js
+++ b/src/components/WindowMiddleContent.js
@@ -32,18 +32,19 @@ class WindowMiddleContent extends Component {
    * Render the component
    */
   render() {
-    const { window } = this.props;
+    const { companionWindowIds, window } = this.props;
     return (
       <div className={ns('window-middle-content')}>
         <WindowSideBar windowId={window.id} />
         {this.renderViewer()}
-        <CompanionWindow windowId={window.id} position="right" />
+        { companionWindowIds.map(id => <CompanionWindow key={id} id={id} windowId={window.id} />) }
       </div>
     );
   }
 }
 
 WindowMiddleContent.propTypes = {
+  companionWindowIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
   window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
   manifest: PropTypes.object, // eslint-disable-line react/forbid-prop-types
 };
diff --git a/src/components/WindowTopBar.js b/src/components/WindowTopBar.js
index 7e5969cd85d2d4602eb03e57c90cba13db76d4a8..846fe95780dc0d91dd256a410a856a66bacc5f2c 100644
--- a/src/components/WindowTopBar.js
+++ b/src/components/WindowTopBar.js
@@ -24,7 +24,7 @@ class WindowTopBar extends Component {
    */
   render() {
     const {
-      removeWindow, windowId, classes, toggleWindowSideBar, t, manifestTitle,
+      closeWindow, windowId, classes, toggleWindowSideBar, t, manifestTitle,
     } = this.props;
     return (
       <AppBar position="relative">
@@ -46,7 +46,7 @@ class WindowTopBar extends Component {
             color="inherit"
             className={ns('window-close')}
             aria-label={t('closeWindow')}
-            onClick={removeWindow}
+            onClick={closeWindow}
           >
             <CloseIcon />
           </IconButton>
@@ -58,7 +58,7 @@ class WindowTopBar extends Component {
 
 WindowTopBar.propTypes = {
   manifestTitle: PropTypes.string,
-  removeWindow: PropTypes.func.isRequired,
+  closeWindow: PropTypes.func.isRequired,
   windowId: PropTypes.string.isRequired,
   classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
   toggleWindowSideBar: PropTypes.func.isRequired,
diff --git a/src/containers/CompanionWindow.js b/src/containers/CompanionWindow.js
index ac5536d50546367b63818ec0eb4d5c7b341f5917..0572435bf5a9c4fd7bc012dcdf47cacbc4b96d60 100644
--- a/src/containers/CompanionWindow.js
+++ b/src/containers/CompanionWindow.js
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
 import { withNamespaces } from 'react-i18next';
 import * as actions from '../state/actions';
 import miradorWithPlugins from '../lib/miradorWithPlugins';
-import { getCompanionWindowForPosition } from '../state/selectors';
 import CompanionWindow from '../components/CompanionWindow';
 
 /**
@@ -11,13 +10,14 @@ import CompanionWindow from '../components/CompanionWindow';
  * @memberof CompanionWindow
  * @private
  */
-const mapStateToProps = (state, { windowId, position }) => {
-  const companionWindowForPosition = getCompanionWindowForPosition(state, windowId, position);
+const mapStateToProps = (state, { id }) => {
+  const companionWindow = state.companionWindows[id];
 
   return {
-    isDisplayed: (companionWindowForPosition
-                  && companionWindowForPosition.length > 0),
-    panelContent: companionWindowForPosition,
+    ...companionWindow,
+    isDisplayed: (companionWindow
+                  && companionWindow.content
+                  && companionWindow.content.length > 0),
   };
 };
 
@@ -27,7 +27,7 @@ const mapStateToProps = (state, { windowId, position }) => {
  * @private
  */
 const mapDispatchToProps = {
-  closeCompanionWindow: actions.setWindowCompanionWindow,
+  onCloseClick: actions.closeCompanionWindow,
 };
 
 const enhance = compose(
diff --git a/src/containers/WindowMiddleContent.js b/src/containers/WindowMiddleContent.js
index f843d9da3b98a3ffbe5eac68a397f1b441d72ca9..9ca434593329cc514e680d4a05b0be79b0fc5860 100644
--- a/src/containers/WindowMiddleContent.js
+++ b/src/containers/WindowMiddleContent.js
@@ -1,10 +1,16 @@
 import { compose } from 'redux';
 import { connect } from 'react-redux';
+import { getCompantionWindowIds } from '../state/selectors';
 import miradorWithPlugins from '../lib/miradorWithPlugins';
 import WindowMiddleContent from '../components/WindowMiddleContent';
 
+/** */
+const mapStateToProps = (state, { window }) => ({
+  companionWindowIds: getCompantionWindowIds(state, window.id),
+});
+
 const enhance = compose(
-  connect(null, null),
+  connect(mapStateToProps, null),
   miradorWithPlugins,
   // further HOC go here
 );
diff --git a/src/containers/WindowTopBar.js b/src/containers/WindowTopBar.js
index 1d193b101f5f425ea21d5de7a37248d3509ab0b4..191ebb59ca3256d1f782028fc4f3583a9c30f3fb 100644
--- a/src/containers/WindowTopBar.js
+++ b/src/containers/WindowTopBar.js
@@ -17,7 +17,7 @@ const mapStateToProps = (state, { windowId }) => ({
  * @private
  */
 const mapDispatchToProps = (dispatch, { windowId }) => ({
-  removeWindow: () => dispatch(actions.removeWindow(windowId)),
+  closeWindow: () => dispatch(actions.closeWindow(windowId)),
   toggleWindowSideBar: () => dispatch(actions.toggleWindowSideBar(windowId)),
 });
 
diff --git a/src/state/actions/action-types.js b/src/state/actions/action-types.js
index f68f33db70258a6b4f98f4d1ca8f1c008b55b8ed..117eda9052691186eea6ead2d20257607cc8c702 100644
--- a/src/state/actions/action-types.js
+++ b/src/state/actions/action-types.js
@@ -1,4 +1,9 @@
 const ActionTypes = {
+  ADD_COMPANION_WINDOW: 'ADD_COMPANION_WINDOW',
+  UPDATE_COMPANION_WINDOW: 'UPDATE_COMPANION_WINDOW',
+  REMOVE_COMPANION_WINDOW: 'REMOVE_COMPANION_WINDOW',
+  UPDATE_WINDOW: 'UPDATE_WINDOW',
+
   FOCUS_WINDOW: 'FOCUS_WINDOW',
   SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN',
   ADD_MANIFEST: 'ADD_MANIFEST',
@@ -15,7 +20,6 @@ const ActionTypes = {
   SET_WINDOW_THUMBNAIL_POSITION: 'SET_WINDOW_THUMBNAIL_POSITION',
   SET_WINDOW_VIEW_TYPE: 'SET_WINDOW_VIEW_TYPE',
   SET_WORKSPACE_ADD_VISIBILITY: 'SET_WORKSPACE_ADD_VISIBILITY',
-  SET_WINDOW_COMPANION_WINDOW: 'SET_WINDOW_COMPANION_WINDOW',
   TOGGLE_WINDOW_SIDE_BAR: 'TOGGLE_WINDOW_SIDE_BAR',
   TOGGLE_WINDOW_SIDE_BAR_PANEL: 'TOGGLE_WINDOW_SIDE_BAR_PANEL',
   TOGGLE_ZOOM_CONTROLS: 'TOGGLE_ZOOM_CONTROLS',
diff --git a/src/state/actions/companionWindow.js b/src/state/actions/companionWindow.js
new file mode 100644
index 0000000000000000000000000000000000000000..43b27cab8db26afe77da928cf4f6b659c32cf2eb
--- /dev/null
+++ b/src/state/actions/companionWindow.js
@@ -0,0 +1,26 @@
+import uuid from 'uuid/v4';
+import ActionTypes from './action-types';
+
+const defaultProps = {
+  content: null,
+  position: null,
+};
+
+/** */
+export function addCompanionWindow(payload, defaults = defaultProps) {
+  return {
+    type: ActionTypes.ADD_COMPANION_WINDOW,
+    id: `cw-${uuid()}`,
+    payload: { ...defaults, ...payload },
+  };
+}
+
+/** */
+export function updateCompanionWindow(id, payload) {
+  return { type: ActionTypes.UPDATE_COMPANION_WINDOW, id, payload };
+}
+
+/** */
+export function removeCompanionWindow(id) {
+  return { type: ActionTypes.REMOVE_COMPANION_WINDOW, id };
+}
diff --git a/src/state/actions/index.js b/src/state/actions/index.js
index 95a4dc0828066a301e77d29932c410e7c0563498..465e1fd28e821c29b4f49679fa3b1520fb3bbd02 100644
--- a/src/state/actions/index.js
+++ b/src/state/actions/index.js
@@ -2,6 +2,7 @@
  * Action Creators for Mirador
  * @namespace ActionCreators
  */
+export * from './companionWindow';
 export * from './config';
 export * from './window';
 export * from './manifest';
diff --git a/src/state/actions/window.js b/src/state/actions/window.js
index 33b4c1b6f599adaa9e3247cc026c8611bcd427f3..afb5c766f61eaea98e0ec3b6c4edca3861826e80 100644
--- a/src/state/actions/window.js
+++ b/src/state/actions/window.js
@@ -1,5 +1,6 @@
 import uuid from 'uuid/v4';
 import ActionTypes from './action-types';
+import { addCompanionWindow, removeCompanionWindow } from './companionWindow';
 
 /**
  * focusWindow - action creator
@@ -26,12 +27,18 @@ export function addWindow(options) {
     rangeId: null,
     thumbnailNavigationPosition: 'bottom', // bottom by default in settings.js
     xywh: [0, 0, 400, 400],
+    companionWindowIds: [],
     rotation: null,
     view: 'single',
   };
   return { type: ActionTypes.ADD_WINDOW, window: { ...defaultOptions, ...options } };
 }
 
+/** */
+export function updateWindow(id, payload) {
+  return { type: ActionTypes.UPDATE_WINDOW, id, payload };
+}
+
 /**
  * removeWindow - action creator
  *
@@ -52,25 +59,6 @@ export function toggleWindowSideBar(windowId) {
   return { type: ActionTypes.TOGGLE_WINDOW_SIDE_BAR, windowId };
 }
 
-/**
- * setWindowCompanionWindow - action creator
- *
- * @param  {String} windowId
- * @param  {String} panelType The type of panel content to be rendered
- *                            in the companion window (e.g. info, canvas_navigation)
- * @param  {String} position The position of the companion window to
- *                           set content for (e.g. right, bottom)
- * @memberof ActionCreators
- */
-export function setWindowCompanionWindow(windowId, panelType, position) {
-  return {
-    type: ActionTypes.SET_WINDOW_COMPANION_WINDOW,
-    windowId,
-    panelType,
-    position,
-  };
-}
-
 /**
  * toggleWindowSideBarPanel - action creator
  *
@@ -93,10 +81,40 @@ export function toggleWindowSideBarPanel(windowId, panelType) {
  * @memberof ActionCreators
  */
 export function popOutCompanionWindow(windowId, panelType, position) {
-  return ((dispatch) => {
-    dispatch(setWindowCompanionWindow(windowId, panelType, position));
+  return (dispatch, getState) => {
+    const { companionWindowIds } = getState().windows[windowId];
+    companionWindowIds.map(id => dispatch(removeCompanionWindow(id)));
+
+    const action = dispatch(addCompanionWindow({ content: panelType, position }));
+
+    const companionWindowId = action.id;
+    dispatch(updateWindow(windowId, { companionWindowIds: [companionWindowId] }));
+
     dispatch(toggleWindowSideBarPanel(windowId, 'closed'));
-  });
+  };
+}
+
+/**
+* Clean up state and remove window
+*/
+export function closeWindow(windowId) {
+  return (dispatch, getState) => {
+    const { companionWindowIds } = getState().windows[windowId];
+    companionWindowIds.map(id => dispatch(removeCompanionWindow(id)));
+    dispatch(removeWindow(windowId));
+  };
+}
+
+/**
+* Close companion window and remove reference from window
+*/
+export function closeCompanionWindow(windowId, companionWindowId) {
+  return (dispatch, getState) => {
+    dispatch(removeCompanionWindow(companionWindowId));
+    const companionWindowIds = getState().windows[windowId].companionWindowIds
+      .filter(id => id !== companionWindowId);
+    dispatch(updateWindow(windowId, { companionWindowIds }));
+  };
 }
 
 /**
diff --git a/src/state/reducers/companionWindows.js b/src/state/reducers/companionWindows.js
new file mode 100644
index 0000000000000000000000000000000000000000..c5cfc2dcc685b5570d1563974c423124591fcaf9
--- /dev/null
+++ b/src/state/reducers/companionWindows.js
@@ -0,0 +1,21 @@
+import {
+  removeIn, setIn, updateIn, merge,
+} from 'immutable';
+import ActionTypes from '../actions/action-types';
+
+/** */
+export function companionWindowsReducer(state = {}, action) {
+  switch (action.type) {
+    case ActionTypes.ADD_COMPANION_WINDOW:
+      return setIn(state, [action.id], action.payload);
+
+    case ActionTypes.UPDATE_COMPANION_WINDOW:
+      return updateIn(state, [action.id], orig => merge(orig, action.payload));
+
+    case ActionTypes.REMOVE_COMPANION_WINDOW:
+      return removeIn(state, [action.id]);
+
+    default:
+      return state;
+  }
+}
diff --git a/src/state/reducers/index.js b/src/state/reducers/index.js
index 7a0c19af469d2af7c7fe22909ff64ecca525531a..478ac4467e511e258bce3edd51967b9d7496cc10 100644
--- a/src/state/reducers/index.js
+++ b/src/state/reducers/index.js
@@ -1,3 +1,4 @@
+export * from './companionWindows';
 export * from './workspace';
 export * from './windows';
 export * from './manifests';
diff --git a/src/state/reducers/rootReducer.js b/src/state/reducers/rootReducer.js
index 895fe4dbd06d9c3e98b28479eb09dfa669a585eb..927420a4fad8f8429abb6c7dbbb41da45f9e4675 100644
--- a/src/state/reducers/rootReducer.js
+++ b/src/state/reducers/rootReducer.js
@@ -1,5 +1,6 @@
 import { combineReducers } from 'redux';
 import {
+  companionWindowsReducer,
   configReducer,
   infoResponsesReducer,
   manifestsReducer,
@@ -15,6 +16,7 @@ import {
  */
 export default function createRootReducer(pluginReducers) {
   return combineReducers({
+    companionWindows: companionWindowsReducer,
     workspace: workspaceReducer,
     windows: windowsReducer,
     manifests: manifestsReducer,
diff --git a/src/state/reducers/windows.js b/src/state/reducers/windows.js
index 2f3bb2a3e7898070a27e23dbdd5a2829c30d9ed8..7a1739f8e481d9380bd89d709ea843be247a481c 100644
--- a/src/state/reducers/windows.js
+++ b/src/state/reducers/windows.js
@@ -1,3 +1,4 @@
+import { updateIn, merge } from 'immutable';
 import ActionTypes from '../actions/action-types';
 
 /**
@@ -7,6 +8,10 @@ export const windowsReducer = (state = {}, action) => {
   switch (action.type) {
     case ActionTypes.ADD_WINDOW:
       return { ...state, [action.window.id]: action.window };
+
+    case ActionTypes.UPDATE_WINDOW:
+      return updateIn(state, [action.id], orig => merge(orig, action.payload));
+
     case ActionTypes.REMOVE_WINDOW:
       return Object.keys(state).reduce((object, key) => {
         if (key !== action.windowId) {
@@ -50,17 +55,6 @@ export const windowsReducer = (state = {}, action) => {
           ),
         },
       };
-    case ActionTypes.SET_WINDOW_COMPANION_WINDOW:
-      return {
-        ...state,
-        [action.windowId]: {
-          ...state[action.windowId],
-          companionWindows: {
-            ...state[action.windowId].companionWindows,
-            [action.position]: action.panelType,
-          },
-        },
-      };
     case ActionTypes.NEXT_CANVAS:
       return setCanvasIndex(state, action.windowId, currentIndex => currentIndex + 1);
     case ActionTypes.PREVIOUS_CANVAS:
diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js
index 557d5d3b4946455dad56fcb12a34cfd07ad6965e..e7b4565c634581f94ddc3ff0caf2e9f251c2dacd 100644
--- a/src/state/selectors/index.js
+++ b/src/state/selectors/index.js
@@ -188,7 +188,16 @@ export function getCanvasDescription(canvas) {
 * @return {String}
 */
 export function getCompanionWindowForPosition(state, windowId, position) {
-  return state.windows[windowId]
-    && state.windows[windowId].companionWindows
-    && state.windows[windowId].companionWindows[position];
+  return Object.values((state.companionWindows || [])).find(cw => (
+    cw.windowId === windowId && cw.position === position
+  ));
+}
+
+/**
+* Return compantion window ids from a window
+* @param {String} windowId
+* @return {Array}
+*/
+export function getCompantionWindowIds(state, windowId) {
+  return state.windows[windowId].companionWindowIds;
 }