diff --git a/src/applications/files/controller/PhabricatorFileEditController.php b/src/applications/files/controller/PhabricatorFileEditController.php
--- a/src/applications/files/controller/PhabricatorFileEditController.php
+++ b/src/applications/files/controller/PhabricatorFileEditController.php
@@ -26,11 +26,17 @@
     }
 
     $title = pht('Edit %s', $file->getName());
+    $file_name = $file->getName();
     $view_uri = '/'.$file->getMonogram();
-
+    $error_name = true;
     $validation_exception = null;
+
     if ($request->isFormPost()) {
       $can_view = $request->getStr('canView');
+      $file_name = $request->getStr('name');
+      $errors = array();
+
+      $type_name = PhabricatorFileTransaction::TYPE_NAME;
 
       $xactions = array();
 
@@ -38,6 +44,10 @@
         ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
         ->setNewValue($can_view);
 
+      $xactions[] = id(new PhabricatorFileTransaction())
+        ->setTransactionType(PhabricatorFileTransaction::TYPE_NAME)
+        ->setNewValue($file_name);
+
       $editor = id(new PhabricatorFileEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
@@ -48,6 +58,7 @@
         return id(new AphrontRedirectResponse())->setURI($view_uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
+        $error_name = $ex->getShortMessage($type_name);
 
         $file->setViewPolicy($can_view);
       }
@@ -61,6 +72,12 @@
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
+       ->appendChild(
+        id(new AphrontFormTextControl())
+        ->setName('name')
+        ->setValue($file_name)
+        ->setLabel(pht('Name'))
+        ->setError($error_name))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
diff --git a/src/applications/files/editor/PhabricatorFileEditor.php b/src/applications/files/editor/PhabricatorFileEditor.php
--- a/src/applications/files/editor/PhabricatorFileEditor.php
+++ b/src/applications/files/editor/PhabricatorFileEditor.php
@@ -17,16 +17,30 @@
     $types[] = PhabricatorTransactions::TYPE_COMMENT;
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
 
+    $types[] = PhabricatorFileTransaction::TYPE_NAME;
+
     return $types;
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {}
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorFileTransaction::TYPE_NAME:
+        return $object->getName();
+    }
+  }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {}
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorFileTransaction::TYPE_NAME:
+        return $xaction->getNewValue();
+    }
+  }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
@@ -36,6 +50,9 @@
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         $object->setViewPolicy($xaction->getNewValue());
         break;
+      case PhabricatorFileTransaction::TYPE_NAME:
+        $object->setName($xaction->getNewValue());
+        break;
     }
   }
 
@@ -97,4 +114,34 @@
     return false;
   }
 
+  protected function validateTransaction(
+    PhabricatorLiskDAO $object,
+    $type,
+    array $xactions) {
+
+    $errors = parent::validateTransaction($object, $type, $xactions);
+
+    switch ($type) {
+      case PhabricatorFileTransaction::TYPE_NAME:
+        $missing = $this->validateIsEmptyTextField(
+          $object->getName(),
+          $xactions);
+
+        if ($missing) {
+          $error = new PhabricatorApplicationTransactionValidationError(
+            $type,
+            pht('Required'),
+            pht('File name is required.'),
+            nonempty(last($xactions), null));
+
+          $error->setIsMissingFieldError(true);
+          $errors[] = $error;
+        }
+        break;
+    }
+
+    return $errors;
+  }
+
+
 }
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -209,7 +209,7 @@
 
     $file = id(new PhabricatorFile())->loadOneWhere(
       'name = %s AND contentHash = %s LIMIT 1',
-      self::normalizeFileName(idx($params, 'name')),
+      idx($params, 'name'),
       self::hashFileContent($data));
 
     if (!$file) {
@@ -693,6 +693,7 @@
 
   private function getCDNURI($token) {
     $name = phutil_escape_uri($this->getName());
+    $name = self::normalizeFileName($name);
 
     $parts = array();
     $parts[] = 'file';
@@ -1210,7 +1211,6 @@
    */
   private function readPropertiesFromParameters(array $params) {
     $file_name = idx($params, 'name');
-    $file_name = self::normalizeFileName($file_name);
     $this->setName($file_name);
 
     $author_phid = idx($params, 'authorPHID');
diff --git a/src/applications/files/storage/PhabricatorFileTransaction.php b/src/applications/files/storage/PhabricatorFileTransaction.php
--- a/src/applications/files/storage/PhabricatorFileTransaction.php
+++ b/src/applications/files/storage/PhabricatorFileTransaction.php
@@ -3,6 +3,8 @@
 final class PhabricatorFileTransaction
   extends PhabricatorApplicationTransaction {
 
+  const TYPE_NAME  = 'file:name';
+
   public function getApplicationName() {
     return 'file';
   }
@@ -15,4 +17,69 @@
     return new PhabricatorFileTransactionComment();
   }
 
+  public function getTitle() {
+    $author_phid = $this->getAuthorPHID();
+
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_NAME:
+        return pht(
+          '%s updated the name for this file from "%s" to "%s".',
+          $this->renderHandleLink($author_phid),
+          $old,
+          $new);
+        break;
+    }
+
+    return parent::getTitle();
+  }
+
+  public function getTitleForFeed() {
+    $author_phid = $this->getAuthorPHID();
+    $object_phid = $this->getObjectPHID();
+
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $type = $this->getTransactionType();
+    switch ($type) {
+      case self::TYPE_NAME:
+        return pht(
+          '%s updated the name of %s from "%s" to "%s".',
+          $this->renderHandleLink($author_phid),
+          $this->renderHandleLink($object_phid),
+          $old,
+          $new);
+        break;
+      }
+
+    return parent::getTitleForFeed();
+  }
+
+  public function getIcon() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_NAME:
+        return 'fa-pencil';
+    }
+
+    return parent::getIcon();
+  }
+
+
+  public function getColor() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_NAME:
+        return PhabricatorTransactions::COLOR_BLUE;
+    }
+
+    return parent::getColor();
+  }
 }