diff --git a/package.json b/package.json
index 83916a98..a1c42b59 100644
--- a/package.json
+++ b/package.json
@@ -111,8 +111,9 @@
     "react-addons-css-transition-group": "^15.3.1",
     "react-datetime": "^2.6.0",
     "react-portal": "^2.2.1",
-    "react-toolbox": "^1.2.1",
     "react-simple-dnd": "^0.1.2",
+    "react-toolbox": "^1.2.1",
+    "react-waypoint": "^3.1.3",
     "selection-position": "^1.0.0",
     "semaphore": "^1.0.5",
     "slate": "^0.13.6"
diff --git a/src/actions/config.js b/src/actions/config.js
index 8b286142..9500df2f 100644
--- a/src/actions/config.js
+++ b/src/actions/config.js
@@ -1,8 +1,6 @@
 import yaml from 'js-yaml';
-import _ from 'lodash';
 import { currentBackend } from '../backends/backend';
 import { authenticate } from '../actions/auth';
-import * as publishModes from '../constants/publishModes';
 import * as MediaProxy from '../valueObjects/MediaProxy';
 
 export const CONFIG_REQUEST = 'CONFIG_REQUEST';
@@ -72,19 +70,5 @@ function parseConfig(data) {
     }
   }
 
-  if (!('publish_mode' in config) || _.values(publishModes).indexOf(config.publish_mode) === -1) {
-    // Make sure there is a publish workflow mode set
-    config.publish_mode = publishModes.SIMPLE;
-  }
-
-  if (!('public_folder' in config)) {
-    // Make sure there is a public folder
-    config.public_folder = config.media_folder;
-  }
-
-  if (config.public_folder.charAt(0) !== '/') {
-    config.public_folder = '/' + config.public_folder;
-  }
-
   return config;
 }
diff --git a/src/actions/entries.js b/src/actions/entries.js
index 83cb5c65..bf5d5b46 100644
--- a/src/actions/entries.js
+++ b/src/actions/entries.js
@@ -1,5 +1,6 @@
 import { currentBackend } from '../backends/backend';
-import { getMedia } from '../reducers';
+import { getIntegrationProvider } from '../integrations';
+import { getMedia, selectIntegration } from '../reducers';
 
 /*
  * Contant Declarations
@@ -21,6 +22,9 @@ export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
 export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
 export const ENTRY_PERSIST_FAILURE = 'ENTRY_PERSIST_FAILURE';
 
+export const SEARCH_ENTRIES_REQUEST = 'SEARCH_ENTRIES_REQUEST';
+export const SEARCH_ENTRIES_SUCCESS = 'SEARCH_ENTRIES_SUCCESS';
+export const SEARCH_ENTRIES_FAILURE = 'SEARCH_ENTRIES_FAILURE';
 
 /*
  * Simple Action Creators (Internal)
@@ -61,7 +65,7 @@ export function entriesLoaded(collection, entries, pagination) {
     payload: {
       collection: collection.get('name'),
       entries: entries,
-      pages: pagination
+      page: pagination
     }
   };
 }
@@ -110,6 +114,34 @@ export function emmptyDraftCreated(entry) {
   };
 }
 
+export function searchingEntries(searchTerm) {
+  return {
+    type: SEARCH_ENTRIES_REQUEST,
+    payload: { searchTerm }
+  };
+}
+
+export function SearchSuccess(searchTerm, entries, page) {
+  return {
+    type: SEARCH_ENTRIES_SUCCESS,
+    payload: {
+      searchTerm,
+      entries,
+      page
+    }
+  };
+}
+
+export function SearchFailure(searchTerm, error) {
+  return {
+    type: SEARCH_ENTRIES_FAILURE,
+    payload: {
+      searchTerm,
+      error
+    }
+  };
+}
+
 /*
  * Exported simple Action Creators
  */
@@ -136,25 +168,30 @@ export function changeDraft(entry) {
 /*
  * Exported Thunk Action Creators
  */
-export function loadEntry(collection, slug) {
+
+export function loadEntry(entry, collection, slug) {
   return (dispatch, getState) => {
     const state = getState();
     const backend = currentBackend(state.config);
-
     dispatch(entryLoading(collection, slug));
-    backend.entry(collection, slug)
-      .then((entry) => dispatch(entryLoaded(collection, entry)));
+    let getPromise;
+    if (entry && entry.get('path')) {
+      getPromise = backend.getEntry(entry.get('collection'), entry.get('slug'), entry.get('path'));
+    } else {
+      getPromise = backend.lookupEntry(collection, slug);
+    }
+    return getPromise.then((loadedEntry) => dispatch(entryLoaded(collection, loadedEntry)));
   };
 }
 
-export function loadEntries(collection) {
+export function loadEntries(collection, page = 0) {
   return (dispatch, getState) => {
     if (collection.get('isFetching')) { return; }
     const state = getState();
-    const backend = currentBackend(state.config);
-
+    const integration = selectIntegration(state, collection.get('name'), 'listEntries');
+    const provider = integration ? getIntegrationProvider(state.integrations, integration) : currentBackend(state.config);
     dispatch(entriesLoading(collection));
-    backend.entries(collection).then(
+    provider.listEntries(collection, page).then(
       (response) => dispatch(entriesLoaded(collection, response.entries, response.pagination)),
       (error) => dispatch(entriesFailed(collection, error))
     );
@@ -184,3 +221,19 @@ export function persistEntry(collection, entry) {
     );
   };
 }
+
+export function searchEntries(searchTerm, page = 0) {
+  return (dispatch, getState) => {
+    const state = getState();
+    let collections = state.collections.keySeq().toArray();
+    collections = collections.filter(collection => selectIntegration(state, collection, 'search'));
+    const integration = selectIntegration(state, collections[0], 'search');
+    if (!integration) console.warn('There isn\'t a search integration configured.');
+    const provider = integration ? getIntegrationProvider(state.integrations, integration) : currentBackend(state.config);
+    dispatch(searchingEntries(searchTerm));
+    provider.search(collections, searchTerm, page).then(
+      (response) => dispatch(SearchSuccess(searchTerm, response.entries, response.pagination)),
+      (error) => dispatch(SearchFailure(searchTerm, error))
+    );
+  };
+}
diff --git a/src/actions/findbar.js b/src/actions/findbar.js
index b726ec58..4abfd3db 100644
--- a/src/actions/findbar.js
+++ b/src/actions/findbar.js
@@ -31,7 +31,7 @@ export function runCommand(commandName, payload) {
         window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');
         break;
       case SEARCH:
-        history.push('/search');
+        history.push(`/search/${payload.searchTerm}`);
         break;
     }
     dispatch(run(commandName, payload));
diff --git a/src/backends/backend.js b/src/backends/backend.js
index 1cb9bc42..6c6bb778 100644
--- a/src/backends/backend.js
+++ b/src/backends/backend.js
@@ -17,6 +17,25 @@ class LocalStorageAuthStore {
   }
 }
 
+const slugFormatter = (template, entryData) => {
+  var date = new Date();
+  return template.replace(/\{\{([^\}]+)\}\}/g, function(_, name) {
+    switch (name) {
+      case 'year':
+        return date.getFullYear();
+      case 'month':
+        return ('0' + (date.getMonth() + 1)).slice(-2);
+      case 'day':
+        return ('0' + date.getDate()).slice(-2);
+      case 'slug':
+        const identifier = entryData.get('title', entryData.get('path'));
+        return identifier.trim().toLowerCase().replace(/[^a-z0-9\.\-\_]+/gi, '-');
+      default:
+        return entryData.get(name);
+    }
+  });
+};
+
 class Backend {
   constructor(implementation, authStore = null) {
     this.implementation = implementation;
@@ -46,7 +65,7 @@ class Backend {
     });
   }
 
-  entries(collection, page, perPage) {
+  listEntries(collection, page, perPage) {
     return this.implementation.entries(collection, page, perPage).then((response) => {
       return {
         pagination: response.pagination,
@@ -55,8 +74,15 @@ class Backend {
     });
   }
 
-  entry(collection, slug) {
-    return this.implementation.entry(collection, slug).then(this.entryWithFormat(collection));
+  // We have the file path. Fetch and parse the file.
+  getEntry(collection, slug, path) {
+    return this.implementation.getEntry(collection, slug, path).then(this.entryWithFormat(collection));
+  }
+
+  // Will fetch the whole list of files from GitHub and load each file, then looks up for entry.
+  // (Files are persisted in local storage - only expensive on the first run for each file).
+  lookupEntry(collection, slug) {
+    return this.implementation.lookupEntry(collection, slug).then(this.entryWithFormat(collection));
   }
 
   newEntry(collection) {
@@ -87,24 +113,6 @@ class Backend {
     return this.implementation.unpublishedEntry(collection, slug).then(this.entryWithFormat(collection));
   }
 
-  slugFormatter(template, entry) {
-    var date = new Date();
-    return template.replace(/\{\{([^\}]+)\}\}/g, function(_, name) {
-      switch (name) {
-        case 'year':
-          return date.getFullYear();
-        case 'month':
-          return ('0' + (date.getMonth() + 1)).slice(-2);
-        case 'day':
-          return ('0' + date.getDate()).slice(-2);
-        case 'slug':
-          return entry.getIn(['data', 'title']).trim().toLowerCase().replace(/[^a-z0-9\.\-\_]+/gi, '-');
-        default:
-          return entry.getIn(['data', name]);
-      }
-    });
-  }
-
   persistEntry(config, collection, entryDraft, MediaFiles, options) {
     const newEntry = entryDraft.getIn(['entry', 'newRecord']) || false;
 
@@ -116,7 +124,7 @@ class Backend {
     const entryData = entryDraft.getIn(['entry', 'data']).toJS();
     let entryObj;
     if (newEntry) {
-      const slug = this.slugFormatter(collection.get('slug'), entryDraft.get('entry'));
+      const slug = slugFormatter(collection.get('slug'), entryDraft.getIn(['entry', 'data']));
       entryObj = {
         path: `${collection.get('folder')}/${slug}.md`,
         slug: slug,
@@ -172,11 +180,11 @@ export function resolveBackend(config) {
 
   switch (name) {
     case 'test-repo':
-      return new Backend(new TestRepoBackend(config), authStore);
+      return new Backend(new TestRepoBackend(config, slugFormatter), authStore);
     case 'github':
-      return new Backend(new GitHubBackend(config), authStore);
+      return new Backend(new GitHubBackend(config, slugFormatter), authStore);
     case 'netlify-git':
-      return new Backend(new NetlifyGitBackend(config), authStore);
+      return new Backend(new NetlifyGitBackend(config, slugFormatter), authStore);
     default:
       throw `Backend not found: ${name}`;
   }
diff --git a/src/backends/github/implementation.js b/src/backends/github/implementation.js
index 2d270261..cdb94c86 100644
--- a/src/backends/github/implementation.js
+++ b/src/backends/github/implementation.js
@@ -38,7 +38,7 @@ export default class GitHub {
       files.map((file) => {
         promises.push(new Promise((resolve, reject) => {
           return sem.take(() => this.api.readFile(file.path, file.sha).then((data) => {
-            resolve(createEntry(file.path, file.path.split('/').pop().replace(/\.[^\.]+$/, ''), data));
+            resolve(createEntry(collection.get('name'), file.path.split('/').pop().replace(/\.[^\.]+$/, ''), file.path, { raw: data }));
             sem.leave();
           }).catch((err) => {
             sem.leave();
@@ -53,12 +53,19 @@ export default class GitHub {
     }));
   }
 
-  entry(collection, slug) {
+
+  // Will fetch the entire list of entries from github.
+  lookupEntry(collection, slug) {
     return this.entries(collection).then((response) => (
       response.entries.filter((entry) => entry.slug === slug)[0]
     ));
   }
 
+  // Fetches a single entry.
+  getEntry(collection, slug, path) {
+    return this.api.readFile(path).then(data => createEntry(collection, slug, path, { raw: data }));
+  }
+
   persistEntry(entry, mediaFiles = [], options = {}) {
     return this.api.persistFiles(entry, mediaFiles, options);
   }
@@ -72,7 +79,7 @@ export default class GitHub {
           const contentKey = branch.ref.split('refs/heads/cms/').pop();
           return sem.take(() => this.api.readUnpublishedBranchFile(contentKey).then((data) => {
             const entryPath = data.metaData.objects.entry;
-            const entry = createEntry(entryPath, entryPath.split('/').pop().replace(/\.[^\.]+$/, ''), data.file);
+            const entry = createEntry('draft', entryPath.split('/').pop().replace(/\.[^\.]+$/, ''), entryPath, { raw: data.file });
             entry.metaData = data.metaData;
             resolve(entry);
             sem.leave();
diff --git a/src/backends/netlify-git/AuthenticationPage.js b/src/backends/netlify-git/AuthenticationPage.js
index 28333d33..0840a332 100644
--- a/src/backends/netlify-git/AuthenticationPage.js
+++ b/src/backends/netlify-git/AuthenticationPage.js
@@ -19,7 +19,6 @@ export default class AuthenticationPage extends React.Component {
         'Authorization': 'Basic ' + btoa(`${email}:${password}`)
       }
     }).then((response) => {
-      console.log(response);
       if (response.ok) {
         return response.json().then((data) => {
           this.props.onLogin(Object.assign({ email }, data));
diff --git a/src/backends/netlify-git/implementation.js b/src/backends/netlify-git/implementation.js
index cf7f21ab..589b1b83 100644
--- a/src/backends/netlify-git/implementation.js
+++ b/src/backends/netlify-git/implementation.js
@@ -35,7 +35,7 @@ export default class NetlifyGit {
       files.map((file) => {
         promises.push(new Promise((resolve, reject) => {
           return sem.take(() => this.api.readFile(file.path, file.sha).then((data) => {
-            resolve(createEntry(file.path, file.path.split('/').pop().replace(/\.[^\.]+$/, ''), data));
+            resolve(createEntry(collection.get('name'), file.path.split('/').pop().replace(/\.[^\.]+$/, ''), file.path, { raw: data }));
             sem.leave();
           }).catch((err) => {
             sem.leave();
@@ -50,7 +50,7 @@ export default class NetlifyGit {
     }));
   }
 
-  entry(collection, slug) {
+  lookupEntry(collection, slug) {
     return this.entries(collection).then((response) => (
       response.entries.filter((entry) => entry.slug === slug)[0]
     ));
diff --git a/src/backends/test-repo/implementation.js b/src/backends/test-repo/implementation.js
index b8cf56cc..fc3462d7 100644
--- a/src/backends/test-repo/implementation.js
+++ b/src/backends/test-repo/implementation.js
@@ -29,7 +29,7 @@ export default class TestRepo {
     const folder = collection.get('folder');
     if (folder) {
       for (var path in window.repoFiles[folder]) {
-        entries.push(createEntry(folder + '/' + path, getSlug(path), window.repoFiles[folder][path].content));
+        entries.push(createEntry(collection.get('name'), getSlug(path), folder + '/' + path, { raw: window.repoFiles[folder][path].content }));
       }
     }
 
@@ -39,7 +39,7 @@ export default class TestRepo {
     });
   }
 
-  entry(collection, slug) {
+  lookupEntry(collection, slug) {
     return this.entries(collection).then((response) => (
       response.entries.filter((entry) => entry.slug === slug)[0]
     ));
diff --git a/src/components/ControlPane.js b/src/components/ControlPane.js
index fc39041e..ef764b0a 100644
--- a/src/components/ControlPane.js
+++ b/src/components/ControlPane.js
@@ -6,11 +6,13 @@ export default class ControlPane extends React.Component {
   controlFor(field) {
     const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
     const widget = resolveWidget(field.get('widget'));
+    const value = entry.getIn(['data', field.get('name')]);
+    if (!value) return null;
     return <div className="cms-control">
       <label>{field.get('label')}</label>
       {React.createElement(widget.control, {
         field: field,
-        value: entry.getIn(['data', field.get('name')]),
+        value: value,
         onChange: (value) => onChange(entry.setIn(['data', field.get('name')], value)),
         onAddMedia: onAddMedia,
         onRemoveMedia: onRemoveMedia,
diff --git a/src/components/EntryListing.js b/src/components/EntryListing.js
index 91239dc3..09fe5407 100644
--- a/src/components/EntryListing.js
+++ b/src/components/EntryListing.js
@@ -1,6 +1,8 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import { Map } from 'immutable';
 import Bricks from 'bricks.js';
+import Waypoint from 'react-waypoint';
 import history from '../routing/history';
 import Cards from './Cards';
 import _ from 'lodash';
@@ -23,6 +25,7 @@ export default class EntryListing extends React.Component {
     };
 
     this.updateBricks = _.throttle(this.updateBricks.bind(this), 30);
+    this.handleLoadMore = this.handleLoadMore.bind(this);
   }
 
   componentDidMount() {
@@ -58,7 +61,6 @@ export default class EntryListing extends React.Component {
   }
 
   cardFor(collection, entry, link) {
-    //const { entry, getMedia, onChange, onAddMedia, onRemoveMedia } = this.props;
     const cartType = collection.getIn(['card', 'type']) || 'alltype';
     const card = Cards[cartType] || Cards._unknown;
     return React.createElement(card, {
@@ -72,23 +74,47 @@ export default class EntryListing extends React.Component {
     });
   }
 
-  render() {
-    const { collection, entries } = this.props;
-    const name = collection.get('name');
+  handleLoadMore() {
+    this.props.onPaginate(this.props.page + 1);
+  }
 
+  renderCards = () => {
+    const { collections, entries } = this.props;
+    if (Map.isMap(collections)) {
+      const collectionName = collections.get('name');
+      return entries.map((entry) => {
+        const path = `/collections/${collectionName}/entries/${entry.get('slug')}`;
+        return this.cardFor(collections, entry, path);
+      });
+    } else {
+      return entries.map((entry) => {
+        const collection = collections.filter(collection => collection.get('name') === entry.get('collection')).first();
+        const path = `/collections/${collection.get('name')}/entries/${entry.get('slug')}`;
+        return this.cardFor(collection, entry, path);
+      });
+    }
+  };
+
+  render() {
+    const { children } = this.props;
+    const cards = this.renderCards();
     return <div>
-      <h1>Listing {name}</h1>
+      <h1>{children}</h1>
       <div ref={(c) => this._entries = c}>
-        {entries.map((entry) => {
-          const path = `/collections/${name}/entries/${entry.get('slug')}`;
-          return this.cardFor(collection, entry, path);
-        })}
+        {cards}
+        <Waypoint onEnter={this.handleLoadMore} />
       </div>
     </div>;
   }
 }
 
 EntryListing.propTypes = {
-  collection: ImmutablePropTypes.map.isRequired,
+  children: PropTypes.node.isRequired,
+  collections: PropTypes.oneOfType([
+    ImmutablePropTypes.map,
+    ImmutablePropTypes.iterable
+  ]).isRequired,
   entries: ImmutablePropTypes.list,
+  onPaginate: PropTypes.func.isRequired,
+  page: PropTypes.number,
 };
diff --git a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js b/src/components/Widgets/MarkdownControlElements/RawEditor/index.js
index 6aad3750..e8cacbcb 100644
--- a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js
+++ b/src/components/Widgets/MarkdownControlElements/RawEditor/index.js
@@ -2,8 +2,6 @@ import React, { PropTypes } from 'react';
 import { Editor, Plain, Mark } from 'slate';
 import Prism from 'prismjs';
 import marks from './prismMarkdown';
-import styles from './index.css';
-
 
 Prism.languages.markdown = Prism.languages.extend('markup', {});
 Prism.languages.insertBefore('markdown', 'prolog', marks);
@@ -75,7 +73,6 @@ const SCHEMA = {
 class RawEditor extends React.Component {
   constructor(props) {
     super(props);
-
     const content = props.value ? Plain.deserialize(props.value) : Plain.deserialize('');
 
     this.state = {
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js
index d7af9d66..c285ed61 100644
--- a/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js
+++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/index.js
@@ -8,7 +8,7 @@ import { DEFAULT_NODE, SCHEMA } from './schema';
 import { getNodes, getSyntaxes, getPlugins } from '../../richText';
 import StylesMenu from './StylesMenu';
 import BlockTypesMenu from './BlockTypesMenu';
-import styles from './index.css';
+//import styles from './index.css';
 
 /**
  * Slate Render Configuration
diff --git a/src/components/Widgets/MarkdownControlElements/plugins.js b/src/components/Widgets/MarkdownControlElements/plugins.js
index 86c9a111..b8e4da6a 100644
--- a/src/components/Widgets/MarkdownControlElements/plugins.js
+++ b/src/components/Widgets/MarkdownControlElements/plugins.js
@@ -17,7 +17,7 @@ const EditorComponent = Record({
 });
 
 
-class Plugin extends Component {
+class Plugin extends Component { // eslint-disable-line
   static propTypes = {
     children: PropTypes.element.isRequired
   };
diff --git a/src/containers/CollectionPage.js b/src/containers/CollectionPage.js
index 7a218866..3c161adf 100644
--- a/src/containers/CollectionPage.js
+++ b/src/containers/CollectionPage.js
@@ -9,6 +9,7 @@ import styles from './CollectionPage.css';
 import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC';
 
 class DashboardPage extends React.Component {
+
   static propTypes = {
     collection: ImmutablePropTypes.map.isRequired,
     collections: ImmutablePropTypes.orderedMap.isRequired,
@@ -30,16 +31,21 @@ class DashboardPage extends React.Component {
     }
   }
 
+  handleLoadMore = (page) => {
+    const { collection, dispatch } = this.props;
+    dispatch(loadEntries(collection, page));
+  };
+
   render() {
-    const { collections, collection, entries } = this.props;
+    const { collections, collection, page, entries } = this.props;
     if (collections == null) {
       return <h1>No collections defined in your config.yml</h1>;
     }
-
-
     return <div className={styles.root}>
       {entries ?
-        <EntryListing collection={collection} entries={entries}/>
+        <EntryListing collections={collection} entries={entries} page={page} onPaginate={this.handleLoadMore}>
+          Listing {collection.get('name')}
+        </EntryListing>
         :
         <Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
       }
@@ -58,9 +64,11 @@ function mapStateToProps(state, ownProps) {
   const { collections } = state;
   const { name, slug } = ownProps.params;
   const collection = name ? collections.get(name) : collections.first();
+  const page = state.entries.getIn(['pages', collection.get('name'), 'page']);
+
   const entries = selectEntries(state, collection.get('name'));
 
-  return { slug, collection, collections, entries };
+  return { slug, collection, collections, page, entries };
 }
 
 export default connect(mapStateToProps)(DashboardPage);
diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js
index dd79f2f0..dd903b2e 100644
--- a/src/containers/EntryPage.js
+++ b/src/containers/EntryPage.js
@@ -33,18 +33,18 @@ class EntryPage extends React.Component {
   };
 
   componentDidMount() {
-    if (!this.props.newEntry) {
-      this.props.loadEntry(this.props.collection, this.props.slug);
+    const { entry, collection, slug } = this.props;
 
-      this.createDraft(this.props.entry);
-    } else {
+    if (this.props.newEntry) {
       this.props.createEmptyDraft(this.props.collection);
+    } else {
+      this.props.loadEntry(entry, collection, slug);
+      this.createDraft(entry);
     }
   }
 
   componentWillReceiveProps(nextProps) {
     if (this.props.entry === nextProps.entry) return;
-
     if (nextProps.entry && !nextProps.entry.get('isFetching')) {
       this.createDraft(nextProps.entry);
     } else if (nextProps.newEntry) {
@@ -86,6 +86,13 @@ class EntryPage extends React.Component {
   }
 }
 
+
+/*
+ * Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff,
+ * We delegate it to a Higher Order Component
+ */
+EntryPage = EntryPageHOC(EntryPage);
+
 function mapStateToProps(state, ownProps) {
   const { collections, entryDraft } = state;
   const collection = collections.get(ownProps.params.name);
@@ -96,12 +103,6 @@ function mapStateToProps(state, ownProps) {
   return { collection, collections, newEntry, entryDraft, boundGetMedia, slug, entry };
 }
 
-/*
- * Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff,
- * We delegate it to a Higher Order Component
- */
-EntryPage = EntryPageHOC(EntryPage);
-
 export default connect(
   mapStateToProps,
   {
diff --git a/src/containers/SearchPage.js b/src/containers/SearchPage.js
index db042948..7d503991 100644
--- a/src/containers/SearchPage.js
+++ b/src/containers/SearchPage.js
@@ -1,12 +1,64 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
+import { selectSearchedEntries } from '../reducers';
+import { searchEntries } from '../actions/entries';
+import { Loader } from '../components/UI';
+import EntryListing from '../components/EntryListing';
+import styles from './CollectionPage.css';
 
 class SearchPage extends React.Component {
+
+  static propTypes = {
+    isFetching: PropTypes.bool,
+    searchEntries: PropTypes.func.isRequired,
+    searchTerm: PropTypes.string.isRequired,
+    entries: ImmutablePropTypes.list
+  };
+
+  componentDidMount() {
+    const { searchTerm, searchEntries } = this.props;
+    searchEntries(searchTerm);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (this.props.searchTerm === nextProps.searchTerm) return;
+    const { searchEntries } = this.props;
+    searchEntries(nextProps.searchTerm);
+  }
+
+  handleLoadMore = (page) => {
+    const { searchTerm, searchEntries } = this.props;
+    searchEntries(searchTerm, page);
+  };
+
   render() {
-    return <div>
-      <h1>Search</h1>
+    const { collections, searchTerm, entries, isFetching, page } = this.props;
+    return <div className={styles.root}>
+      {(isFetching === true || !entries) ?
+        <Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
+        :
+        <EntryListing collections={collections} entries={entries} page={page} onPaginate={this.handleLoadMore}>
+          Results for “{searchTerm}”
+        </EntryListing>
+      }
     </div>;
   }
 }
 
-export default connect()(SearchPage);
+
+function mapStateToProps(state, ownProps) {
+  const isFetching = state.entries.getIn(['search', 'isFetching']);
+  const page = state.entries.getIn(['search', 'page']);
+  const entries = selectSearchedEntries(state);
+  const collections = state.collections.toIndexedSeq();
+  const searchTerm = ownProps.params && ownProps.params.searchTerm;
+
+  return { isFetching, page, collections, entries, searchTerm };
+}
+
+
+export default connect(
+  mapStateToProps,
+  { searchEntries }
+)(SearchPage);
diff --git a/src/integrations/index.js b/src/integrations/index.js
new file mode 100644
index 00000000..8a5b604e
--- /dev/null
+++ b/src/integrations/index.js
@@ -0,0 +1,28 @@
+import Algolia from './providers/algolia/implementation';
+import { Map } from 'immutable';
+
+export function resolveIntegrations(interationsConfig) {
+  let integrationInstances = Map({});
+  interationsConfig.get('providers').forEach((providerData, providerName) => {
+    switch (providerName) {
+      case 'algolia':
+        integrationInstances = integrationInstances.set('algolia', new Algolia(providerData));
+        break;
+    }
+  });
+  return integrationInstances;
+}
+
+
+export const getIntegrationProvider = (function() {
+  let integrations = null;
+
+  return (interationsConfig, provider) => {
+    if (integrations) {
+      return integrations.get(provider);
+    } else {
+      integrations = resolveIntegrations(interationsConfig);
+      return integrations.get(provider);
+    }
+  };
+})();
diff --git a/src/integrations/providers/algolia/implementation.js b/src/integrations/providers/algolia/implementation.js
new file mode 100644
index 00000000..0fd7f517
--- /dev/null
+++ b/src/integrations/providers/algolia/implementation.js
@@ -0,0 +1,123 @@
+import { createEntry } from '../../../valueObjects/Entry';
+import _ from 'lodash';
+
+function getSlug(path) {
+  const m = path.match(/([^\/]+?)(\.[^\/\.]+)?$/);
+  return m && m[1];
+}
+
+export default class Algolia {
+  constructor(config) {
+    this.config = config;
+    if (config.get('applicationID') == null ||
+        config.get('apiKey') == null) {
+      throw 'The Algolia search integration needs the credentials (applicationID and apiKey) in the integration configuration.';
+    }
+
+    this.applicationID = config.get('applicationID');
+    this.apiKey = config.get('apiKey');
+    this.searchURL = `https://${this.applicationID}-dsn.algolia.net/1`;
+
+    this.entriesCache = {
+      collection: null,
+      page: null,
+      entries: []
+    };
+  }
+
+  requestHeaders(headers = {}) {
+    return {
+      'X-Algolia-API-Key': this.apiKey,
+      'X-Algolia-Application-Id': this.applicationID,
+      'Content-Type': 'application/json',
+      ...headers
+    };
+  }
+
+  parseJsonResponse(response) {
+    return response.json().then((json) => {
+      if (!response.ok) {
+        return Promise.reject(json);
+      }
+
+      return json;
+    });
+  }
+
+  urlFor(path, options) {
+    const params = [];
+    if (options.params) {
+      for (const key in options.params) {
+        params.push(`${key}=${encodeURIComponent(options.params[key])}`);
+      }
+    }
+    if (params.length) {
+      path += `?${params.join('&')}`;
+    }
+    return path;
+  }
+
+  request(path, options = {}) {
+    const headers = this.requestHeaders(options.headers || {});
+    const url = this.urlFor(path, options);
+    return fetch(url, { ...options, headers: headers }).then((response) => {
+      const contentType = response.headers.get('Content-Type');
+      if (contentType && contentType.match(/json/)) {
+        return this.parseJsonResponse(response);
+      }
+
+      return response.text();
+    });
+  }
+
+  search(collections, searchTerm, page) {
+    const searchCollections = collections.map(collection => (
+      { indexName: collection, params: `query=${searchTerm}&page=${page}` }
+    ));
+
+    return this.request(`${this.searchURL}/indexes/*/queries`, {
+      method: 'POST',
+      body: JSON.stringify({ requests: searchCollections })
+    }).then(response => {
+      const entries = response.results.map((result, index) => result.hits.map(hit => {
+        const slug = hit.slug || getSlug(hit.path);
+        return createEntry(collections[index], slug, hit.path, { data: hit.data, partial: true });
+      }));
+
+      return { entries: _.flatten(entries), pagination: page };
+    });
+  }
+
+  searchBy(field, collection, query) {
+    return this.request(`${this.searchURL}/indexes/${collection}`, {
+      params: {
+        restrictSearchableAttributes: field,
+        query
+      }
+    });
+  }
+
+  listEntries(collection, page) {
+    if (this.entriesCache.collection === collection && this.entriesCache.page === page) {
+      return Promise.resolve({ page: this.entriesCache.page, entries: this.entriesCache.entries });
+    } else {
+      return this.request(`${this.searchURL}/indexes/${collection.get('name')}`, {
+        params: { page }
+      }).then(response => {
+        const entries = response.hits.map(hit => {
+          const slug = hit.slug || getSlug(hit.path);
+          return createEntry(collection.get('name'), slug, hit.path, { data: hit.data, partial: true });
+        });
+        this.entriesCache = { collection, page, entries };
+        return { entries, pagination: response.page };
+      });
+    }
+  }
+
+  getEntry(collection, slug) {
+    return this.searchBy('slug', collection.get('name'), slug).then((response) => {
+      const entry = response.hits.filter((hit) => hit.slug === slug)[0];
+      return createEntry(collection.get('name'), slug, entry.path, { data: entry.data, partial: true });
+    });
+  }
+}
diff --git a/src/reducers/config.js b/src/reducers/config.js
index 088b014c..15985a75 100644
--- a/src/reducers/config.js
+++ b/src/reducers/config.js
@@ -1,12 +1,28 @@
 import Immutable from 'immutable';
+import _ from 'lodash';
+import * as publishModes from '../constants/publishModes';
 import { CONFIG_REQUEST, CONFIG_SUCCESS, CONFIG_FAILURE } from '../actions/config';
 
+const defaults = {
+  publish_mode: publishModes.SIMPLE
+};
+
+const applyDefaults = (config) => {
+  // Make sure there is a public folder
+  _.set(defaults,
+        'public_folder',
+        config.media_folder.charAt(0) === '/' ? config.media_folder : '/' + config.media_folder);
+
+  return _.defaultsDeep(config, defaults);
+};
+
 const config = (state = null, action) => {
   switch (action.type) {
     case CONFIG_REQUEST:
       return Immutable.Map({ isFetching: true });
     case CONFIG_SUCCESS:
-      return Immutable.fromJS(action.payload);
+      const config = applyDefaults(action.payload);
+      return Immutable.fromJS(config);
     case CONFIG_FAILURE:
       return Immutable.Map({ error: action.payload.toString() });
     default:
diff --git a/src/reducers/entries.js b/src/reducers/entries.js
index 6ae5e554..a609fa80 100644
--- a/src/reducers/entries.js
+++ b/src/reducers/entries.js
@@ -1,8 +1,10 @@
 import { Map, List, fromJS } from 'immutable';
 import {
-  ENTRY_REQUEST, ENTRY_SUCCESS, ENTRIES_REQUEST, ENTRIES_SUCCESS
+  ENTRY_REQUEST, ENTRY_SUCCESS, ENTRIES_REQUEST, ENTRIES_SUCCESS, SEARCH_ENTRIES_REQUEST, SEARCH_ENTRIES_SUCCESS
 } from '../actions/entries';
 
+let collection, loadedEntries, page, searchTerm;
+
 const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
   switch (action.type) {
     case ENTRY_REQUEST:
@@ -18,14 +20,45 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
       return state.setIn(['pages', action.payload.collection, 'isFetching'], true);
 
     case ENTRIES_SUCCESS:
-      const { collection, entries, pages } = action.payload;
+      collection = action.payload.collection;
+      loadedEntries = action.payload.entries;
+      page = action.payload.page;
       return state.withMutations((map) => {
-        entries.forEach((entry) => (
+        loadedEntries.forEach((entry) => (
           map.setIn(['entities', `${collection}.${entry.slug}`], fromJS(entry).set('isFetching', false))
         ));
+
+        const ids = List(loadedEntries.map((entry) => entry.slug));
+
         map.setIn(['pages', collection], Map({
-          ...pages,
-          ids: List(entries.map((entry) => entry.slug))
+          page: page,
+          ids: page === 0 ? ids : map.getIn(['pages', collection, 'ids'], List()).concat(ids)
+        }));
+      });
+
+    case SEARCH_ENTRIES_REQUEST:
+      if (action.payload.searchTerm !== state.getIn(['search', 'term'])) {
+        return state.withMutations((map) => {
+          map.setIn(['search', 'isFetching'], true);
+          map.setIn(['search', 'term'], action.payload.searchTerm);
+        });
+      } else {
+        return state;
+      }
+
+    case SEARCH_ENTRIES_SUCCESS:
+      loadedEntries = action.payload.entries;
+      page = action.payload.page;
+      searchTerm = action.payload.searchTerm;
+      return state.withMutations((map) => {
+        loadedEntries.forEach((entry) => (
+          map.setIn(['entities', `${entry.collection}.${entry.slug}`], fromJS(entry).set('isFetching', false))
+        ));
+        const ids = List(loadedEntries.map(entry => ({ collection: entry.collection, slug: entry.slug })));
+        map.set('search', Map({
+          page: page,
+          term: searchTerm,
+          ids: page === 0 ? ids : map.getIn(['search', 'ids'], List()).concat(ids)
         }));
       });
 
@@ -43,4 +76,9 @@ export const selectEntries = (state, collection) => {
   return slugs && slugs.map((slug) => selectEntry(state, collection, slug));
 };
 
+export const selectSearchedEntries = (state) => {
+  const searchItems = state.getIn(['search', 'ids']);
+  return searchItems && searchItems.map(({ collection, slug }) => selectEntry(state, collection, slug));
+};
+
 export default entries;
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 62615bdc..1430bc13 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,8 +1,9 @@
 import auth from './auth';
 import config from './config';
 import editor from './editor';
+import integrations, * as fromIntegrations from './integrations';
 import entries, * as fromEntries  from './entries';
-import editorialWorkflow, * as fromEditorialWorkflow  from './editorialWorkflow';
+import editorialWorkflow, * as fromEditorialWorkflow from './editorialWorkflow';
 import entryDraft from './entryDraft';
 import collections from './collections';
 import medias, * as fromMedias from './medias';
@@ -11,6 +12,7 @@ const reducers = {
   auth,
   config,
   collections,
+  integrations,
   editor,
   entries,
   editorialWorkflow,
@@ -29,11 +31,17 @@ export const selectEntry = (state, collection, slug) =>
 export const selectEntries = (state, collection) =>
   fromEntries.selectEntries(state.entries, collection);
 
+export const selectSearchedEntries = (state) =>
+  fromEntries.selectSearchedEntries(state.entries);
+
 export const selectUnpublishedEntry = (state, status, slug) =>
   fromEditorialWorkflow.selectUnpublishedEntry(state.editorialWorkflow, status, slug);
 
 export const selectUnpublishedEntries = (state, status) =>
   fromEditorialWorkflow.selectUnpublishedEntries(state.editorialWorkflow, status);
 
+export const selectIntegration = (state, collection, hook) =>
+  fromIntegrations.selectIntegration(state.integrations, collection, hook);
+
 export const getMedia = (state, path) =>
   fromMedias.getMedia(state.medias, path);
diff --git a/src/reducers/integrations.js b/src/reducers/integrations.js
new file mode 100644
index 00000000..efff00b6
--- /dev/null
+++ b/src/reducers/integrations.js
@@ -0,0 +1,29 @@
+import { fromJS } from 'immutable';
+import { CONFIG_SUCCESS } from '../actions/config';
+
+const integrations = (state = null, action) => {
+  switch (action.type) {
+    case CONFIG_SUCCESS:
+      const integrations = action.payload.integrations || [];
+      const newState = integrations.reduce((acc, integration) => {
+        const { hooks, collections, provider, ...providerData } = integration;
+        acc.providers[provider] = { ...providerData };
+        collections.forEach(collection => {
+          hooks.forEach(hook => {
+            acc.hooks[collection] ? acc.hooks[collection][hook] = provider : acc.hooks[collection] = { [hook]: provider };
+          });
+        });
+        return acc;
+      }, { providers:{}, hooks: {} });
+      return fromJS(newState);
+    default:
+      return state;
+  }
+};
+
+export const selectIntegration = (state, collection, hook) => {
+  return state.getIn(['hooks', collection, hook], false);
+};
+
+
+export default integrations;
diff --git a/src/routing/routes.js b/src/routing/routes.js
index 6dc9f55e..9c83a2a0 100644
--- a/src/routing/routes.js
+++ b/src/routing/routes.js
@@ -13,7 +13,7 @@ export default (
     <Route path="/collections/:name/entries/new" component={EntryPage} newRecord />
     <Route path="/collections/:name/entries/:slug" component={EntryPage} />
     <Route path="/editorialworkflow/:name/:status/:slug" component={EntryPage} unpublishedEntry />
-    <Route path="/search" component={SearchPage}/>
+    <Route path="/search/:searchTerm" component={SearchPage}/>
     <Route path="*" component={NotFoundPage}/>
   </Route>
 );
diff --git a/src/valueObjects/Entry.js b/src/valueObjects/Entry.js
index ab247a6c..ba000b24 100644
--- a/src/valueObjects/Entry.js
+++ b/src/valueObjects/Entry.js
@@ -1,9 +1,11 @@
-export function createEntry(path = '', slug = '', raw = '') {
+export function createEntry(collection, slug = '', path = '', options = {}) {
   const returnObj = {};
-  returnObj.path = path;
+  returnObj.collection = collection;
   returnObj.slug = slug;
-  returnObj.raw = raw;
-  returnObj.data = {};
-  returnObj.metaData = {};
+  returnObj.path = path;
+  returnObj.partial = options.partial || false;
+  returnObj.raw = options.raw || '';
+  returnObj.data = options.data || {};
+  returnObj.metaData = options.metaData || null;
   return returnObj;
 }