diff --git a/packages/netlify-cms-core/scripts/load-extensions.js b/packages/netlify-cms-core/scripts/load-extensions.js
index 7f15dda4..f711c78a 100644
--- a/packages/netlify-cms-core/scripts/load-extensions.js
+++ b/packages/netlify-cms-core/scripts/load-extensions.js
@@ -4,13 +4,13 @@ import { GitLabBackend } from 'netlify-cms-backend-gitlab';
 import { GitGatewayBackend } from 'netlify-cms-backend-git-gateway';
 import { TestBackend } from 'netlify-cms-backend-test';
 import { BooleanControl } from 'netlify-cms-widget-boolean';
-import { StringControl, StringPreview } from 'netlify-cms-widget-string';
 import { DateControl, DatePreview } from 'netlify-cms-widget-date';
 import { DateTimeControl, DateTimePreview } from 'netlify-cms-widget-datetime';
+import { FileControl, FilePreview } from 'netlify-cms-widget-file';
+import { ImageControl, ImagePreview } from 'netlify-cms-widget-image';
+import { StringControl, StringPreview } from 'netlify-cms-widget-string';
 // import { NumberControl, NumberPreview } from 'netlify-cms-widget-number';
 // import { TextControl, TextPreview } from 'netlify-cms-widget-text';
-// import { ImageControl, ImagePreview } from 'netlify-cms-widget-image';
-// import { FileControl, FilePreview } from 'netlify-cms-widget-file';
 // import { SelectControl, SelectPreview } from 'netlify-cms-widget-select';
 // import { MarkdownControl, MarkdownPreview } from 'netlify-cms-widget-markdown';
 // import { ListControl, ListPreview } from 'netlify-cms-widget-list';
@@ -25,13 +25,13 @@ registerBackend('test-repo', TestBackend);
 registerWidget('boolean', BooleanControl);
 registerWidget('date', DateControl, DatePreview);
 registerWidget('datetime', DateTimeControl, DateTimePreview);
+registerWidget('file', FileControl, FilePreview);
+registerWidget('image', ImageControl, ImagePreview);
 registerWidget('string', StringControl, StringPreview);
 // registerWidget('text', TextControl, TextPreview);
 // registerWidget('number', NumberControl, NumberPreview);
 // registerWidget('list', ListControl, ListPreview);
 // registerWidget('markdown', MarkdownControl, MarkdownPreview);
-// registerWidget('image', ImageControl, ImagePreview);
-// registerWidget('file', FileControl, FilePreview);
 // registerWidget('select', SelectControl, SelectPreview);
 // registerWidget('object', ObjectControl, ObjectPreview);
 // registerWidget('relation', RelationControl, RelationPreview);
diff --git a/packages/netlify-cms-core/src/components/EditorWidgets/index.js b/packages/netlify-cms-core/src/components/EditorWidgets/index.js
index 823275a2..dade5aa3 100644
--- a/packages/netlify-cms-core/src/components/EditorWidgets/index.js
+++ b/packages/netlify-cms-core/src/components/EditorWidgets/index.js
@@ -5,10 +5,6 @@ import NumberControl from './Number/NumberControl';
 import NumberPreview from './Number/NumberPreview';
 import TextControl from './Text/TextControl';
 import TextPreview from './Text/TextPreview';
-import ImageControl from './Image/ImageControl';
-import ImagePreview from './Image/ImagePreview';
-import FileControl from './File/FileControl';
-import FilePreview from './File/FilePreview';
 import SelectControl from './Select/SelectControl';
 import SelectPreview from './Select/SelectPreview';
 import MarkdownControl from './Markdown/MarkdownControl';
@@ -24,8 +20,6 @@ registerWidget('text', TextControl, TextPreview);
 registerWidget('number', NumberControl, NumberPreview);
 registerWidget('list', ListControl, ListPreview);
 registerWidget('markdown', MarkdownControl, MarkdownPreview);
-registerWidget('image', ImageControl, ImagePreview);
-registerWidget('file', FileControl, FilePreview);
 registerWidget('select', SelectControl, SelectPreview);
 registerWidget('object', ObjectControl, ObjectPreview);
 registerWidget('relation', RelationControl, RelationPreview);
diff --git a/packages/netlify-cms-core/src/lib/textHelper.js b/packages/netlify-cms-core/src/lib/textHelper.js
index 9b4d69e4..07d24ff7 100644
--- a/packages/netlify-cms-core/src/lib/textHelper.js
+++ b/packages/netlify-cms-core/src/lib/textHelper.js
@@ -1,10 +1,3 @@
-export function truncateMiddle(string = "", size) {
-  if (string.length <= size) {
-    return string;
-  }
-  return `${ string.substring(0, size / 2) }\u2026${ string.substring(string.length - size / 2 + 1, string.length) }`;
-}
-
 export function stringToRGB(str) {
   if (!str) return "000000";
   let hash = 0;
diff --git a/packages/netlify-cms-ui-default/src/styles.js b/packages/netlify-cms-ui-default/src/styles.js
index 1e11dc00..d2548792 100644
--- a/packages/netlify-cms-ui-default/src/styles.js
+++ b/packages/netlify-cms-ui-default/src/styles.js
@@ -214,10 +214,15 @@ const components = {
     border-top: 6px solid currentColor;
     border-radius: 2px;
   `,
+  textBadge: css`
+    ${textBadge};
+    color: ${colors.infoText};
+    background-color: ${colors.infoBackground};
+  `,
   textBadgeSuccess: css`
     ${textBadge};
-    color: ${colorsRaw.green};
-    background-color: ${colorsRaw.greenLight};
+    color: ${colors.successText};
+    background-color: ${colors.successBackground};
   `,
   textBadgeDanger: css`
     ${textBadge};
diff --git a/packages/netlify-cms-widget-datetime/src/index.js b/packages/netlify-cms-widget-datetime/src/index.js
index d6750c61..6607d356 100644
--- a/packages/netlify-cms-widget-datetime/src/index.js
+++ b/packages/netlify-cms-widget-datetime/src/index.js
@@ -1,2 +1,2 @@
 export DateTimeControl from './DateTimeControl';
-export DateTimePreview from 'netlify-cms-widget-date';
+export { DatePreview as DateTimePreview } from 'netlify-cms-widget-date';
diff --git a/packages/netlify-cms-widget-file/package.json b/packages/netlify-cms-widget-file/package.json
new file mode 100644
index 00000000..01160d75
--- /dev/null
+++ b/packages/netlify-cms-widget-file/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "netlify-cms-widget-file",
+  "description": "Widget for uploading files in Netlify CMS.",
+  "version": "2.0.0-alpha.0",
+  "main": "dist/netlify-cms-widget-file.js",
+  "license": "MIT",
+  "keywords": [
+    "netlify",
+    "netlify-cms",
+    "widget",
+    "file",
+    "upload",
+    "file-upload"
+  ],
+  "sideEffects": false,
+  "scripts": {
+    "watch": "webpack -w",
+    "build": "webpack"
+  },
+  "dependencies": {
+    "uuid": "^3.3.2"
+  },
+  "devDependencies": {
+    "webpack": "^4.16.1",
+    "webpack-cli": "^3.1.0"
+  },
+  "peerDependencies": {
+    "netlify-cms-ui-default": "^2.0.0-alpha.0",
+    "prop-types": "^15.5.10",
+    "react": "^16.4.1",
+    "react-emotion": "^9.2.6",
+    "react-immutable-proptypes": "^2.1.0"
+  }
+}
diff --git a/packages/netlify-cms-widget-file/src/FilePreview.js b/packages/netlify-cms-widget-file/src/FilePreview.js
new file mode 100644
index 00000000..c2c2743f
--- /dev/null
+++ b/packages/netlify-cms-widget-file/src/FilePreview.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { WidgetPreviewContainer } from 'netlify-cms-ui-default';
+
+const FilePreview = ({ value, getAsset }) => (
+  <WidgetPreviewContainer>
+    { value ? <a href={getAsset(value)}>{ value }</a> : null}
+  </WidgetPreviewContainer>
+);
+
+FilePreview.propTypes = {
+  getAsset: PropTypes.func.isRequired,
+  value: PropTypes.node,
+};
+
+export default FilePreview;
diff --git a/packages/netlify-cms-widget-file/src/index.js b/packages/netlify-cms-widget-file/src/index.js
new file mode 100644
index 00000000..68141579
--- /dev/null
+++ b/packages/netlify-cms-widget-file/src/index.js
@@ -0,0 +1,5 @@
+import withFileControl from './withFileControl';
+
+export { withFileControl };
+export const FileControl = withFileControl();
+export FilePreview from './FilePreview';
diff --git a/packages/netlify-cms-widget-file/src/withFileControl.js b/packages/netlify-cms-widget-file/src/withFileControl.js
new file mode 100644
index 00000000..91455f47
--- /dev/null
+++ b/packages/netlify-cms-widget-file/src/withFileControl.js
@@ -0,0 +1,167 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import styled from 'react-emotion';
+import uuid from 'uuid/v4';
+import { lengths, components, buttons } from 'netlify-cms-ui-default';
+
+const MAX_DISPLAY_LENGTH = 50;
+
+const FileContent = styled.div`
+  display: flex;
+`
+
+const ImageWrapper = styled.div`
+  width: 155px;
+  height: 100px;
+  margin-right: 20px;
+`
+
+const Image = styled.img`
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  border-radius: ${lengths.borderRadius};
+`
+
+const FileInfo = styled.div`
+  button:not(:first-child) {
+    margin-top: 12px;
+  }
+`
+
+const FileName = styled.span`
+  display: block;
+  font-size: 16px;
+  margin-bottom: 20px;
+`
+
+const FileWidgetButton = styled.button`
+  ${buttons.button};
+  ${components.textBadge};
+  display: block;
+`
+
+const FileWidgetButtonRemove = styled.button`
+  ${buttons.button};
+  ${components.textBadgeDanger};
+  display: block;
+`
+
+export default function withFileControl({ forImage } = {}) {
+  return class extends React.Component {
+    static propTypes = {
+      field: PropTypes.object.isRequired,
+      getAsset: PropTypes.func.isRequired,
+      mediaPaths: ImmutablePropTypes.map.isRequired,
+      onAddAsset: PropTypes.func.isRequired,
+      onChange: PropTypes.func.isRequired,
+      onRemoveInsertedMedia: PropTypes.func.isRequired,
+      onOpenMediaLibrary: PropTypes.func.isRequired,
+      classNameWrapper: PropTypes.string.isRequired,
+      value: PropTypes.node,
+    };
+
+    static defaultProps = {
+      value: '',
+    };
+
+    constructor(props) {
+      super(props);
+      this.controlID = uuid();
+    }
+
+    shouldComponentUpdate(nextProps) {
+      /**
+       * Always update if the value changes.
+       */
+      if (this.props.value !== nextProps.value) {
+        return true;
+      }
+
+      /**
+       * If there is a media path for this control in the state object, and that
+       * path is different than the value in `nextProps`, update.
+       */
+      const mediaPath = nextProps.mediaPaths.get(this.controlID);
+      if (mediaPath && (nextProps.value !== mediaPath)) {
+        return true;
+      }
+
+      return false;
+    }
+
+    componentWillReceiveProps(nextProps) {
+      const { mediaPaths, value, onRemoveInsertedMedia, onChange } = nextProps;
+      const mediaPath = mediaPaths.get(this.controlID);
+      if (mediaPath && mediaPath !== value) {
+        onChange(mediaPath);
+      } else if (mediaPath && mediaPath === value) {
+        onRemoveInsertedMedia(this.controlID);
+      }
+    }
+
+    handleChange = e => {
+      const { field, onOpenMediaLibrary } = this.props;
+      e.preventDefault();
+      return onOpenMediaLibrary({
+        controlID: this.controlID,
+        forImage,
+        privateUpload: field.get('private'),
+      });
+    };
+
+    handleRemove = e => {
+      e.preventDefault();
+      return this.props.onChange('');
+    };
+
+    renderFileName = () => {
+      const { value, classNameWrapper } = this.props;
+      const size = MAX_DISPLAY_LENGTH;
+      if (!value || value.length <= size) {
+        return value;
+      }
+      return `${ value.substring(0, size / 2) }\u2026${ value.substring(value.length - size / 2 + 1, value.length) }`;
+    };
+
+    renderSelection = (subject) => {
+      const fileName = this.renderFileName();
+      const { getAsset, value } = this.props;
+      return (
+        <FileContent>
+          { forImage ? <ImageWrapper><Image src={getAsset(value)}/></ImageWrapper> : null }
+          <FileInfo>
+            <FileName>{fileName}</FileName>
+            <FileWidgetButton onClick={this.handleChange}>
+              Choose different {subject}
+            </FileWidgetButton>
+            <FileWidgetButtonRemove onClick={this.handleRemove}>
+              Remove {subject}
+            </FileWidgetButtonRemove>
+          </FileInfo>
+        </FileContent>
+      );
+    };
+
+    renderNoSelection = (subject, article) => (
+      <FileWidgetButton onClick={this.handleChange}>
+        Choose {article} {subject}
+      </FileWidgetButton>
+    );
+
+    render() {
+      const { value, classNameWrapper } = this.props;
+      const subject = forImage ? 'image' : 'file';
+      const article = forImage ? 'an' : 'a';
+
+      return (
+        <div className={classNameWrapper}>
+          <span>
+            { value ? this.renderSelection(subject) : this.renderNoSelection(subject, article) }
+          </span>
+        </div>
+      );
+    }
+  }
+};
diff --git a/packages/netlify-cms-widget-file/webpack.config.js b/packages/netlify-cms-widget-file/webpack.config.js
new file mode 100644
index 00000000..42edd361
--- /dev/null
+++ b/packages/netlify-cms-widget-file/webpack.config.js
@@ -0,0 +1,3 @@
+const { getConfig } = require('../../scripts/webpack.js');
+
+module.exports = getConfig();
diff --git a/packages/netlify-cms-widget-image/package.json b/packages/netlify-cms-widget-image/package.json
new file mode 100644
index 00000000..63442799
--- /dev/null
+++ b/packages/netlify-cms-widget-image/package.json
@@ -0,0 +1,36 @@
+{
+  "name": "netlify-cms-widget-image",
+  "description": "Widget for uploading images in Netlify CMS.",
+  "version": "2.0.0-alpha.0",
+  "main": "dist/netlify-cms-widget-image.js",
+  "license": "MIT",
+  "keywords": [
+    "netlify",
+    "netlify-cms",
+    "widget",
+    "image",
+    "upload",
+    "image-upload"
+  ],
+  "sideEffects": false,
+  "scripts": {
+    "watch": "webpack -w",
+    "build": "webpack"
+  },
+  "dependencies": {
+    "netlify-cms-widget-file": "^2.0.0-alpha.0"
+  },
+  "devDependencies": {
+    "webpack": "^4.16.1",
+    "webpack-cli": "^3.1.0"
+  },
+  "peerDependencies": {
+    "netlify-cms-ui-default": "^2.0.0-alpha.0",
+    "prop-types": "^15.5.10",
+    "react": "^16.4.1",
+    "react-emotion": "^9.2.6"
+  },
+  "localExternals": [
+    "netlify-cms-widget-file"
+  ]
+}
diff --git a/packages/netlify-cms-widget-image/src/ImagePreview.js b/packages/netlify-cms-widget-image/src/ImagePreview.js
new file mode 100644
index 00000000..f588f47d
--- /dev/null
+++ b/packages/netlify-cms-widget-image/src/ImagePreview.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'react-emotion';
+import { WidgetPreviewContainer } from 'netlify-cms-ui-default';
+
+const Image = styled.img`
+  max-width: 100%;
+  height: auto;
+`
+
+const ImagePreview = ({ value, getAsset }) => (
+  <WidgetPreviewContainer>
+    { value ? <Image src={getAsset(value)} role="presentation"/> : null}
+  </WidgetPreviewContainer>
+);
+
+ImagePreview.propTypes = {
+  getAsset: PropTypes.func.isRequired,
+  value: PropTypes.node,
+};
+
+export default ImagePreview;
diff --git a/packages/netlify-cms-widget-image/src/index.js b/packages/netlify-cms-widget-image/src/index.js
new file mode 100644
index 00000000..b437a158
--- /dev/null
+++ b/packages/netlify-cms-widget-image/src/index.js
@@ -0,0 +1,4 @@
+import { withFileControl } from 'netlify-cms-widget-file';
+
+export const ImageControl = withFileControl({ forImage: true });
+export ImagePreview from './ImagePreview';
diff --git a/packages/netlify-cms-widget-image/webpack.config.js b/packages/netlify-cms-widget-image/webpack.config.js
new file mode 100644
index 00000000..42edd361
--- /dev/null
+++ b/packages/netlify-cms-widget-image/webpack.config.js
@@ -0,0 +1,3 @@
+const { getConfig } = require('../../scripts/webpack.js');
+
+module.exports = getConfig();