diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index e2120f71909cc711027c791bd0573437bdb04adb..9e8480010a95f50380a6751361e99bb47420151d 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -1263,3 +1263,22 @@ img.avatar {
   border-bottom: 2px solid #ddd;
   border-bottom-color: rgba(0,0,0,0.1);
 }
+
+.freetext-files {
+    padding: 10px;
+    margin-top: 10px;
+    border: 1px dotted #ddd;
+    border-radius: 4px;
+    background-color: #f5f5f5;
+}
+.freetext-files .file {
+    margin-right: 10px;
+    display: inline-block;
+    width: 48%;
+    padding-top: 0.2em;
+}
+.freetext-files .title {
+    font-weight: bold;
+    margin-bottom: 0.3em;
+    font-size: 1.1em;
+}
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 8fedabe4c8158ae3917826e6e5956f93e764c302..39e73162b9c675f154a554ca3f11abb9bc7dd9e2 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -681,15 +681,19 @@ class DynamicFormField extends VerySimpleModel {
     function setConfiguration($vars, &$errors=array()) {
         $config = array();
         foreach ($this->getConfigurationForm($vars)->getFields() as $name=>$field) {
-            $config[$name] = $field->to_php($field->getClean());
+            $config[$name] = $field->to_config($field->getClean());
             $errors = array_merge($errors, $field->errors());
         }
-        if (count($errors) === 0)
-            $this->set('configuration', JsonDataEncoder::encode($config));
 
+        if (count($errors))
+            return false;
+
+        // See if field impl. need to save or override anything
+        $config = $this->getImpl()->to_config($config);
+        $this->set('configuration', JsonDataEncoder::encode($config));
         $this->set('hint', Format::sanitize($vars['hint']));
 
-        return count($errors) === 0;
+        return true;
     }
 
     function isDeletable() {
@@ -902,14 +906,26 @@ class DynamicFormField extends VerySimpleModel {
     }
 
     function delete() {
-        // Don't really delete form fields as that will screw up the data
+        // Don't really delete form fields with data as that will screw up the data
         // model. Instead, just drop the association with the form which
         // will give the appearance of deletion. Not deleting means that
         // the field will continue to exist on form entries it may already
         // have answers on, but since it isn't associated with the form, it
         // won't be available for new form submittals.
         $this->set('form_id', 0);
-        $this->save();
+
+        $impl = $this->getImpl();
+
+        // Trigger db_clean so the field can do house cleaning
+        $impl->db_cleanup(true);
+
+        // Short-circuit deletion if the field has data.
+        if ($impl->hasData())
+            return $this->save();
+
+        // Delete the field for realz
+        parent::delete();
+
     }
 
     function save($refetch=false) {
diff --git a/include/class.forms.php b/include/class.forms.php
index 59ea16f7b47d6a08f080ab2c1b61abc3bd24f3fa..f5f3ce86f0df34a6f6e3a0609d6ca64c81bb1e0a 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -554,6 +554,18 @@ class FormField {
         return $value;
     }
 
+    /**
+     *
+     * to_config
+     *
+     * Transform the data from the value to config form (as determined by
+     * field).  By default to_php is used at the base level
+     *
+     */
+    function to_config($value) {
+        return $this->to_php($value);
+    }
+
     /**
      * to_database
      *
@@ -596,7 +608,7 @@ class FormField {
      * cleaned as well. This hook allows fields to participate when the data
      * for a field is cleaned up.
      */
-    function db_cleanup() {
+    function db_cleanup($field=false) {
     }
 
     /**
@@ -2392,6 +2404,14 @@ class FileUploadField extends FormField {
         return JsonDataParser::decode($value);
     }
 
+    function to_config($value) {
+
+        if ($value && is_array($value))
+            $value = array_values($value);
+
+        return $value;
+    }
+
     function display($value) {
         $links = array();
         foreach ($this->getFiles() as $f) {
@@ -2410,7 +2430,7 @@ class FileUploadField extends FormField {
         return implode(', ', $files);
     }
 
-    function db_cleanup() {
+    function db_cleanup($field=false) {
         // Delete associated attachments from the database, if any
         $this->getFiles();
         if (isset($this->attachments)) {
@@ -3100,6 +3120,7 @@ class FileUploadWidget extends Widget {
         $mimetypes = array_filter($config['__mimetypes'],
             function($t) { return strpos($t, '/') !== false; }
         );
+        $maxfilesize = ($config['size'] ?: 1048576) / 1048576;
         $files = $F = array();
         $new = array_fill_keys($this->field->getClean(), 1);
         foreach ($attachments as $f) {
@@ -3142,7 +3163,7 @@ class FileUploadWidget extends Widget {
           allowedfiletypes: <?php echo JsonDataEncoder::encode(
             $mimetypes); ?>,
           maxfiles: <?php echo $config['max'] ?: 20; ?>,
-          maxfilesize: <?php echo ($config['size'] ?: 1048576) / 1048576; ?>,
+          maxfilesize: <?php echo $maxfilesize; ?>,
           name: '<?php echo $name; ?>[]',
           files: <?php echo JsonDataEncoder::encode($files); ?>
         });});
@@ -3187,6 +3208,7 @@ class FileUploadError extends Exception {}
 
 class FreeTextField extends FormField {
     static $widget = 'FreeTextWidget';
+    protected $attachments;
 
     function getConfigurationOptions() {
         return array(
@@ -3195,6 +3217,12 @@ class FreeTextField extends FormField {
                 'label'=>__('Content'), 'required'=>true, 'default'=>'',
                 'hint'=>__('Free text shown in the form, such as a disclaimer'),
             )),
+            'attachments' => new FileUploadField(array(
+                'id'=>'attach',
+                'label' => __('Attachments'),
+                'name'=>'files',
+                'configuration' => array('extensions'=>'')
+            )),
         );
     }
 
@@ -3205,12 +3233,49 @@ class FreeTextField extends FormField {
     function isBlockLevel() {
         return true;
     }
+
+    /* utils */
+
+    function to_config($config) {
+
+        $keepers = array();
+        if ($config && isset($config['attachments']))
+            foreach ($config['attachments'] as $fid)
+                $keepers[] = $fid;
+
+        $this->getAttachments()->keepOnlyFileIds($keepers);
+
+        return $config;
+    }
+
+    function db_cleanup($field=false) {
+
+        if ($field && $this->getFiles())
+            $this->getAttachments()->deleteAll();
+    }
+
+    function getAttachments() {
+
+        if (!isset($this->attachments))
+            $this->attachments = GenericAttachments::forIdAndType($this->get('id'), 'I');
+
+        return $this->attachments;
+    }
+
+    function getFiles() {
+
+        if (!($attachments = $this->getAttachments()))
+            return array();
+
+        return $attachments->all();
+    }
+
 }
 
 class FreeTextWidget extends Widget {
     function render($options=array()) {
         $config = $this->field->getConfiguration();
-        ?><div class=""><?php
+        ?><div class="thread-body" style="padding:0"><?php
         if ($label = $this->field->getLocal('label')) { ?>
             <h3><?php
             echo Format::htmlchars($label);
@@ -3225,6 +3290,21 @@ class FreeTextWidget extends Widget {
             echo Format::viewableImages($config['content']); ?></div>
         </div>
         <?php
+        if (($attachments=$this->field->getFiles())) { ?>
+            <section class="freetext-files">
+            <div class="title"><?php echo __('Related Resources'); ?></div>
+            <?php foreach ($attachments as $attach) { ?>
+                <div class="file">
+                <a href="<?php echo $attach->file->getDownloadUrl(); ?>"
+                    target="_blank" download="<?php echo $attach->file->getDownloadUrl();
+                    ?>" class="truncate no-pjax">
+                    <i class="icon-file"></i>
+                    <?php echo Format::htmlchars($attach->getFilename()); ?>
+                </a>
+                </div>
+            <?php } ?>
+        </section>
+        <?php }
     }
 }
 
diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php
index bcfc9ada252471feb406527b69b274ff945e183f..7a23a4546e734458d2ec14c491da0db26788e105 100644
--- a/include/client/templates/dynamic-form.tmpl.php
+++ b/include/client/templates/dynamic-form.tmpl.php
@@ -18,7 +18,7 @@
             continue;
         ?>
         <tr>
-            <td colspan="2" style="padding-top:8px;">
+            <td colspan="2" style="padding-top:10px;">
             <?php if (!$field->isBlockLevel()) { ?>
                 <label for="<?php echo $field->getFormName(); ?>"><span class="<?php
                     if ($field->isRequiredForUsers()) echo 'required'; ?>">
diff --git a/scp/css/scp.css b/scp/css/scp.css
index ad7c976ade5c91a161e2b1e345801a253f5fa85d..2015ba20d0cf0cdacdfb0c14d5eb18aeaebf80ff 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -2404,3 +2404,22 @@ td.indented {
     width: calc(100% - 95px);
     line-height: 1.4em;
 }
+
+.freetext-files {
+    padding: 10px;
+    margin-top: 10px;
+    border: 1px dotted #ddd;
+    border-radius: 4px;
+    background-color: #f5f5f5;
+}
+.freetext-files .file {
+    margin-right: 10px;
+    display: inline-block;
+    width: 48%;
+    padding-top: 0.2em;
+}
+.freetext-files .title {
+    font-weight: bold;
+    margin-bottom: 0.3em;
+    font-size: 1.1em;
+}