diff --git entity.api.php entity.api.php
index 892f03a..146ffb6 100644
--- entity.api.php
+++ entity.api.php
@@ -252,7 +252,7 @@ function entity_metadata_hook_entity_info() {
  *       having the given property value. See entity_metadata_entity_query().
  *     - required: Optionally, this may be set to TRUE, if this property is
  *       required for the creation of a new instance of its entity. See
- *       entity_metadata_entity_create().
+ *       entity_property_values_create_entity().
  *     - field: Optionally, a boolean indicating whether a property is stemming
  *       from a field.
  *     - 'property info': Optionally, may be used to specify an array of info
@@ -262,6 +262,11 @@ function entity_metadata_hook_entity_info() {
  *       info before it is used.
  *     - 'property defaults': Optionally, an array of defaults for the info of
  *       each property of the wrapped data item.
+ *     - 'auto creation': (optional) Properties of type 'struct' may specify
+ *       this callback which is used to automatically create the data structure
+ *       if necessary, e.g. as the data structure is not created yet but one of
+ *       its properties is set. See entity_metadata_field_file_callback() for
+ *       an example.
  *   - bundles: An array keyed by bundle name containing further metadata
  *     related to the bundles only. This array may contain the key 'properties'
  *     with an array of info about the bundle specific properties, structured in
diff --git entity.test entity.test
index 87527ba..aae8c4c 100644
--- entity.test
+++ entity.test
@@ -412,11 +412,9 @@ class EntityMetadataTestCase extends DrupalWebTestCase {
     $this->assertEqual("<p>The body &amp; nothing.</p>\n", $body_value->value(), "Getting processed value.");
     $this->assertEqual("The body & nothing.\n", $body_value->value(array('decode' => TRUE)), "Decoded value.");
     $this->assertEqual("<b>The body & nothing.</b>", $body_value->raw(), "Raw body returned.");
-    $info = $body_value->info();
 
     $wrapper->body->set(array('value' => "<b>The second body.</b>"));
     $this->assertEqual("<p>The second body.</p>\n", $wrapper->body->value->value(), "Setting a processed field value and reading it again.");
-    $node = $wrapper->value();
     $this->assertEqual($node->body[LANGUAGE_NONE][0]['value'], "<b>The second body.</b>", 'Update appears in the wrapped entity.');
     $this->assert(isset($node->body[LANGUAGE_NONE][0]['safe_value']), 'Formatted text has been processed.');
 
@@ -645,8 +643,8 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
   }
 
   protected function assertException($wrapper, $name, $text = NULL) {
-    $this->assertTrue(isset($wrapper->$name), 'Property ' . check_plain($name) . ' exists.');
-    $text = isset($text) ? $text : 'Getting the not set property ' . $name . ' throws exception.';
+    $this->assertTrue(isset($wrapper->$name), 'Property wrapper ' . check_plain($name) . ' exists.');
+    $text = isset($text) ? $text : 'Getting the not existing property ' . $name . ' throws exception.';
     try {
       $wrapper->$name->value();
       $this->fail($text);
@@ -656,6 +654,11 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
     }
   }
 
+  protected function assertEmpty($wrapper, $name) {
+    $this->assertTrue(isset($wrapper->$name), 'Property ' . check_plain($name) . ' exists.');
+    $this->assertTrue($wrapper->$name->value() === NULL, 'Property ' . check_plain($name) . ' is empty.');
+  }
+
   protected function assertValue($wrapper, $key) {
     $this->assertTrue($wrapper->$key->value() !== NULL, check_plain($key) . ' property returned.');
     $info = $wrapper->$key->info();
@@ -725,7 +728,7 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
       }
     }
     $this->assertException($wrapper, 'book');
-    $this->assertException($wrapper, 'source');
+    $this->assertEmpty($wrapper, 'source');
     $this->assertException($wrapper->source, 'title');
   }
 
@@ -754,7 +757,6 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
     foreach ($wrapper as $key => $value) {
       $this->assertValue($wrapper, $key);
     }
-
     // Test setting another parent using the full object.
     $wrapper->parent[] = $term_parent2;
     $this->assertEqual($wrapper->parent[1]->getIdentifier(), $term_parent2->tid, 'Term parent added.');
@@ -808,18 +810,7 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
       $this->assertValue($wrapper, $key);
     }
     // Test files.
-    $filepath = file_destination('temporary://' . $this->randomName(), FILE_EXISTS_REPLACE);
-    file_put_contents($filepath, "content");
-    $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
-    $file = new stdClass();
-    $file->uri = $filepath;
-    $file->filename = basename($file->uri);
-    $file->filemime = 'text/plain';
-    $file->uid = 1;
-    $file->timestamp = REQUEST_TIME;
-    $file->filesize = filesize($file->uri);
-    $file->status = 0;
-    file_save($file);
+    $file = $this->createFile();
 
     $wrapper = entity_metadata_wrapper('file', $file);
     foreach ($wrapper as $key => $value) {
@@ -876,6 +867,71 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Test making use of a file field.
+   */
+  function testFileFields() {
+    $file = $this->createFile();
+
+    // Create a file field.
+    $field = array(
+      'field_name' => 'field_file',
+      'type' => 'file',
+      'cardinality' => 2,
+      'settings' => array('display_field' => TRUE),
+    );
+    field_create_field($field);
+    $instance = array(
+      'field_name' => 'field_file',
+      'entity_type' => 'node',
+      'label' => 'File',
+      'bundle' => 'article',
+      'settings' => array('description_field' => TRUE),
+      'required' => FALSE,
+      'widget' => array(
+        'type' => 'file_generic',
+        'weight' => -1,
+      ),
+    );
+    field_create_instance($instance);
+
+    $node = $this->drupalCreateNode(array('type' => 'article'));
+    $wrapper = entity_metadata_wrapper('node', $node);
+
+    $wrapper->field_file[0] = array('fid' => $file->fid, 'display' => FALSE);
+    $this->assertEqual($file->filename, $wrapper->field_file[0]->file->name->value(), 'File has been specified.');
+
+    $wrapper->field_file[0]->description = 'foo';
+    $wrapper->field_file[0]->display = TRUE;
+
+    $this->assertEqual($wrapper->field_file[0]->description->value(), 'foo', 'File description has been correctly set.');
+
+    // Try saving the node and make sure the information is still there after
+    // loading the node again, thus the correct data structure has been written.
+    node_save($node);
+    $node = node_load($node->nid, NULL, TRUE);
+    $wrapper = entity_metadata_wrapper('node', $node);
+
+    $this->assertEqual($wrapper->field_file[0]->description->value(), 'foo', 'File description has been correctly set.');
+    $this->assertEqual($wrapper->field_file[0]->display->value(), TRUE, 'File display value has been correctly set.');
+
+    // Test adding a new file, the display-property has to be created
+    // automatically.
+    $wrapper->field_file[1]->file = $file;
+    node_save($node);
+    $node = node_load($node->nid, NULL, TRUE);
+    $this->assertEqual($file->fid, $wrapper->field_file[1]->file->getIdentifier(), 'New file has been added.');
+
+    // Test adding an invalid file-field item, i.e. without any file.
+    try {
+      $wrapper->field_file[] = array('description' => 'test');
+      $this->fail('Exception not thrown.');
+    }
+    catch (EntityMetadataWrapperException $e) {
+      $this->pass('Not valid file-field item has thrown an exception.');
+    }
+  }
+
+  /**
    * Creates a value for the given type.
    */
   protected function createValue($type, $info) {
@@ -927,5 +983,21 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
     entity_save('taxonomy_vocabulary', $vocab);
     return $vocab;
   }
+
+  protected function createFile() {
+    $filepath = file_destination('temporary://' . $this->randomName(), FILE_EXISTS_REPLACE);
+    file_put_contents($filepath, "content");
+    $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
+    $file = new stdClass();
+    $file->uri = $filepath;
+    $file->filename = basename($file->uri);
+    $file->filemime = 'text/plain';
+    $file->uid = 1;
+    $file->timestamp = REQUEST_TIME;
+    $file->filesize = filesize($file->uri);
+    $file->status = 0;
+    file_save($file);
+    return $file;
+  }
 }
 
diff --git includes/entity.property.inc includes/entity.property.inc
index 0955ab8..b3358d5 100644
--- includes/entity.property.inc
+++ includes/entity.property.inc
@@ -146,6 +146,12 @@ function entity_field_info_alter(&$field_info) {
     $field_info['taxonomy_term_reference']['property_type'] = 'taxonomy_term';
     $field_info['taxonomy_term_reference']['property_callbacks'][] = 'entity_metadata_field_term_reference_callback';
   }
+  if (module_exists('file')) {
+    // The callback specifies a custom data structure matching the file field
+    // items. We introduce a custom type name for this data structure.
+    $field_info['file']['property_type'] = 'field_item_file';
+    $field_info['file']['property_callbacks'][] = 'entity_metadata_field_file_callback';
+  }
 }
 
 /**
@@ -274,8 +280,8 @@ function entity_property_verbatim_get($data, array $options, $name) {
 /**
  * Sets the property to the given value. May be used as 'setter callback'.
  */
-function entity_property_verbatim_set($data, $name, $value) {
-  if (is_object($data) && $data instanceof ArrayAccess) {
+function entity_property_verbatim_set(&$data, $name, $value) {
+  if (is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) {
     $data[$name] = $value;
   }
   elseif (is_object($data)) {
diff --git includes/entity.wrapper.inc includes/entity.wrapper.inc
index 84a1df7..bc2d718 100644
--- includes/entity.wrapper.inc
+++ includes/entity.wrapper.inc
@@ -68,12 +68,12 @@ abstract class EntityMetadataWrapper {
    *     strips HTML tags and decodes HTML entities. Defaults to FALSE.
    */
   public function value(array $options = array()) {
+    if (!$this->dataAvailable()) {
+      throw new EntityMetadataWrapperException('Missing data values.');
+    }
     if (!isset($this->data) && isset($this->info['name'])) {
       $this->data = $this->info['parent']->getPropertyValue($this->info['name'], $this->info);
     }
-    elseif (!isset($this->data)) {
-      throw new EntityMetadataWrapperException('Missing data values.');
-    }
     return $this->data;
   }
 
@@ -83,6 +83,9 @@ abstract class EntityMetadataWrapper {
    * will return the unprocessed data in contrast to value().
    */
   public function raw() {
+    if (!$this->dataAvailable()) {
+      throw new EntityMetadataWrapperException('Missing data values.');
+    }
     if (isset($this->info['name']) && isset($this->info['parent'])) {
       return $this->info['parent']->getPropertyRaw($this->info['name'], $this->info);
     }
@@ -91,18 +94,13 @@ abstract class EntityMetadataWrapper {
   }
 
   /**
-   * Returns whether data is available to work with. Also if we wrap a property,
-   * it ensures the value of the property is loaded and so available.
+   * Returns whether data is available to work with.
    *
    * @return
    *   If we operate without any data FALSE, else TRUE.
    */
   protected function dataAvailable() {
-    if (!isset($this->data) && isset($this->info['name']) && $this->info['parent']->dataAvailable()) {
-      // Make sure it is loaded.
-      $this->data = $this->info['parent']->getPropertyValue($this->info['name'], $this->info);
-    }
-    return isset($this->data);
+    return isset($this->data) || (isset($this->info['parent']) && $this->info['parent']->dataAvailable());
   }
 
   /**
@@ -112,14 +110,19 @@ abstract class EntityMetadataWrapper {
     if (!$this->validate($value)) {
       throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
     }
-    if (empty($this->info['parent'])) {
-      $this->clear();
-      $this->data = $value;
-    }
-    else {
+    $this->clear();
+    $this->data = $value;
+    $this->updateParent($value);
+    return $this;
+  }
+
+  /**
+   * Updates the parent data structure of a data property with the latest data value.
+   */
+  protected function updateParent($value) {
+    if (isset($this->info['parent'])) {
       $this->info['parent']->setProperty($this->info['name'], $value);
     }
-    return $this;
   }
 
   /**
@@ -129,7 +132,7 @@ abstract class EntityMetadataWrapper {
     if (!entity_property_verify_data_type($value, $this->type)) {
       return FALSE;
     }
-    return !isset($this->info['validation callback']) || call_user_func($this->info['validation callback'], $value);
+    return !isset($this->info['validation callback']) || call_user_func($this->info['validation callback'], $value, $this->info);
   }
 
   public function __toString() {
@@ -387,20 +390,25 @@ class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAg
    * Gets the value of a property.
    */
   protected function getPropertyValue($name, &$info) {
-    if ($this->dataAvailable()) {
-      $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
-      return $info['getter callback']($this->value(), $options, $name, $this->type, $info);
+    $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
+    $data = $this->value();
+    if (!isset($data)) {
+      throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.');
     }
-    throw new EntityMetadataWrapperException('Entity property ' . check_plain($name) . " isn't set.");
+    return $info['getter callback']($data, $options, $name, $this->type, $info);
   }
 
   /**
    * Gets the raw value of a property.
    */
   protected function getPropertyRaw($name, &$info) {
-    if (!empty($info['raw getter callback']) && $this->dataAvailable()) {
+    if (!empty($info['raw getter callback'])) {
       $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE);
-      return $info['raw getter callback']($this->value(), $options, $name, $this->type, $info);
+      $data = $this->value();
+      if (!isset($data)) {
+        throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.');
+      }
+      return $info['raw getter callback']($data, $options, $name, $this->type, $info);
     }
     return $this->getPropertyValue($name, $info);
   }
@@ -410,9 +418,27 @@ class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAg
    */
   protected function setProperty($name, $value) {
     $info = $this->getPropertyInfo($name);
-    if (!empty($info['setter callback']) && $this->dataAvailable()) {
-      $info['setter callback']($this->value(), $name, $value, $this->langcode, $this->type);
-      $this->get($name)->clear();
+    if (!empty($info['setter callback'])) {
+      $data = $this->value();
+
+      // In case the data structure is not set, support simple auto-creation
+      // for arrays. Else an exception is thrown.
+      if (!isset($data)) {
+        if (!empty($this->info['auto creation']) && !($this instanceof EntityDrupalWrapper)) {
+          $data = $this->info['auto creation']($name, $this->info);
+        }
+        else {
+          throw new EntityMetadataWrapperException('Unable to set the data property ' . check_plain($name) . ' as the parent data structure is not set.');
+        }
+      }
+
+      // Invoke the setter callback for updating our data.
+      $info['setter callback']($data, $name, $value, $this->langcode, $this->type);
+
+      // If the setter has not thrown any exceptions, proceed and apply the
+      // update to the current and any parent wrappers as necessary.
+      $this->set($data);
+
       // Clear the cache of properties dependent on this value.
       foreach ($info['clear'] as $name) {
         if (isset($this->cache[$name])) {
@@ -548,8 +574,8 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
     if (!isset($this->info['bundle']) || isset($bundle)) {
       $this->info['bundle'] = $bundle;
       // Detect the bundle if not set yet and add in properties from the bundle.
-      if (!isset($this->info['bundle']) && !empty($this->entityInfo['fieldable']) && $this->dataAvailable()) {
-        list($id, $vid, $bundle) = entity_extract_ids($this->type, $this->value());
+      if (!isset($this->info['bundle']) && !empty($this->entityInfo['fieldable']) && $this->dataAvailable() && $entity = $this->value()) {
+        list($id, $vid, $bundle) = entity_extract_ids($this->type, $entity);
         $this->info['bundle'] = $bundle;
       }
       // Like entity_extract_ids() assume the entity type if no key is given.
@@ -571,7 +597,7 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
    * Returns the identifier of the wrapped entity.
    */
   public function getIdentifier() {
-    return $this->dataAvailable() ? $this->id : NULL;
+    return $this->dataAvailable() && $this->value() ? $this->id : NULL;
   }
 
   /**
@@ -588,10 +614,8 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
    * Overridden.
    */
   public function value(array $options = array()) {
-    if (!$this->dataAvailable()) {
-      throw new EntityMetadataWrapperException('Missing data values.');
-    }
-    elseif (!$this->data && $this->id !== FALSE) {
+    $this->setEntity(parent::value());
+    if (!$this->data && !empty($this->id)) {
       // Lazy load the entity if necessary.
       $return = entity_load($this->type, array($this->id));
       $this->data = reset($return);
@@ -603,24 +627,21 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
   }
 
   /**
-   * Overridden to support setting the entity by object and id.
-   */
-  protected function dataAvailable() {
-    if (!isset($this->data) && isset($this->info['name']) && parent::dataAvailable()) {
-      $this->setEntity($this->data);
-    }
-    return isset($this->data);
-  }
-
-  /**
    * Overridden to support setting the entity by either the object or the id.
    */
   public function set($value) {
+    if (!$this->validate($value)) {
+      throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
+    }
+    $previous_id = $this->id;
     // Set value, so we get the identifier and pass it to the normal setter.
+    $this->clear();
     $this->setEntity($value);
-    parent::set($this->id);
-    // The parent implemented cleared the data, so set the value again.
-    $this->setEntity($value);
+    // For entities being referenced in a data property, it's only necessary
+    // to propagate the update to the parent if the id changed.
+    if ($previous_id != $this->id) {
+      $this->updateParent($this->id);
+    }
     return $this;
   }
 
@@ -746,7 +767,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
     if (!array_key_exists($delta, $this->cache)) {
       if (!isset($delta)) {
         // The [] operator has been used so point at a new entry.
-        $delta = max(array_keys($this->data)) + 1;
+        $delta = max(array_keys(parent::value())) + 1;
       }
       if (is_numeric($delta)) {
         $info = array('parent' => $this, 'name' => $delta) + $this->info;
@@ -759,25 +780,23 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
     return $this->cache[$delta];
   }
 
-  protected function &getPropertyValue($delta) {
-    if ($this->dataAvailable() && isset($this->data[$delta])) {
-      return $this->data[$delta];
+  protected function getPropertyValue($delta) {
+    // Make use parent::value() to easily by-pass any entity-loading.
+    $data = parent::value();
+    if (isset($data[$delta])) {
+      return $data[$delta];
     }
-    throw new EntityMetadataWrapperException('There is no item with a delta of ' . check_plain($delta) . " for this list.");
   }
 
   protected function getPropertyRaw($delta) {
-    $list = $this->raw();
-    if (isset($list[$delta])) {
-      return $list[$delta];
-    }
-    throw new EntityMetadataWrapperException('List entry ' . check_plain($delta) . " isn't set.");
+    return $this->getPropertyValue($delta);
   }
 
   protected function setProperty($delta, $value) {
-    if (is_numeric($delta) && $this->dataAvailable()) {
-      $this->data[$delta] = $value;
-      $this->set($this->data);
+    $data = parent::value();
+    if (is_numeric($delta)) {
+      $data[$delta] = $value;
+      $this->set($data);
     }
   }
 
@@ -794,7 +813,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
   public function value(array $options = array()) {
     // For lists of entities fetch full entity objects before returning.
     if ($this->get(0) instanceof EntityDrupalWrapper && empty($options['identifier']) && $this->dataAvailable()) {
-      return array_values(entity_load($this->get(0)->type, $this->data));
+      return array_values(entity_load($this->get(0)->type, parent::value()));
     }
     return parent::value();
   }
@@ -815,7 +834,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
    */
   public function getIterator() {
     // In case there is no data available, just iterate over the first item.
-    return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys($this->data) : array(0));
+    return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys($this->value()) : array(0));
   }
 
   /**
@@ -826,7 +845,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
   }
 
   public function offsetExists($delta) {
-    return $this->dataAvailable() && isset($this->data[$delta]);
+    return $this->dataAvailable() && ($data = $this->value()) && isset($data[$delta]);
   }
 
   public function offsetSet($delta, $value) {
@@ -834,14 +853,22 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
   }
 
   public function offsetUnset($delta) {
-    if ($this->offsetExists($delta) && $this->dataAvailable()) {
+    if ($this->offsetExists($delta)) {
       unset($this->data[$delta]);
       $this->set($this->data);
     }
   }
 
   public function count() {
-    return $this->dataAvailable() ? count($this->data) : 0;
+    return $this->dataAvailable() ? count($this->value()) : 0;
+  }
+
+  /**
+   * Overridden to not invoke any validation callback, as those are for the
+   * list items.
+   */
+  public function validate($value) {
+    return entity_property_verify_data_type($value, $this->type);
   }
 }
 
diff --git modules/callbacks.inc modules/callbacks.inc
index e173977..1f4cc52 100644
--- modules/callbacks.inc
+++ modules/callbacks.inc
@@ -394,16 +394,10 @@ function entity_metadata_field_verbatim_get($entity, array $options, $name, $ent
   $context['field'] = field_info_field($name);
 
   $langcode = field_language($entity_type, $entity, $name, isset($options['language']) ? $options['language']->language : NULL);
-  if (!isset($entity->{$name}[$langcode])) {
-    $entity->{$name}[$langcode] = array(0 => array());
-  }
-  // To avoid loosing the reference wrap the item into an array object. That
-  // way the changes through the wrapper are automatically reflected in the
-  // entity.
   if ($context['field']['cardinality'] == 1) {
-    return new EntityMetadataArrayObject($entity->{$name}[$langcode][0]);
+    return isset($entity->{$name}[$langcode][0]) ? $entity->{$name}[$langcode][0] : NULL;
   }
-  return new EntityMetadataArrayObject($entity->{$name}[$langcode]);
+  return isset($entity->{$name}[$langcode]) ? $entity->{$name}[$langcode] : array();
 }
 
 /**
@@ -418,14 +412,15 @@ function entity_metadata_field_verbatim_set($entity, $name, $items, $langcode) {
 
 /**
  * Callback for getting the sanitized text of 'text_formatted' properties.
- * Note that $item might be an ArrayObject.
  */
-function entity_metadata_field_text_get($item, array $options, $name, $type) {
+function entity_metadata_field_text_get($item, array $options, $name, $type, $context) {
   if (!isset($item['safe_value'])) {
     // Apply input formats.
     $langcode = isset($options['language']) ? $options['language']->language : '';
     $format = isset($item['format']) ? $item['format'] : filter_default_format();
     $item['safe_value'] = check_markup($item['value'], $format, $langcode);
+    // To speed up subsequent calls, update $item with the 'safe_value'.
+    $context['parent']->set($item);
   }
   return $item['safe_value'];
 }
@@ -441,6 +436,42 @@ function entity_metadata_field_text_formats() {
 }
 
 /**
+ * Callback for getting the file entity of file fields.
+ */
+function entity_metadata_field_file_get($item) {
+  return $item['fid'];
+}
+
+/**
+ * Callback for setting the file entity of file fields.
+ */
+function entity_metadata_field_file_set(&$item, $property_name, $value) {
+  $item['fid'] = $value;
+}
+
+/**
+ * Callback for auto-creating file field $items.
+ */
+function entity_metadata_field_file_create_item($property_name, $context) {
+  // 'fid' is required, so 'file' has to be set as initial property.
+  return array('display' => $context['field']['settings']['display_default']);
+}
+
+/**
+ * Callback for validating file field $items.
+ */
+function entity_metadata_field_file_validate_item($item, $context) {
+  // File-field items require a valid file.
+  if (!isset($item['fid']) || !file_load($item['fid'])) {
+    return FALSE;
+  }
+  if (isset($context['property info']['display']) && !isset($item['display'])) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
  * Access callback for the node entity.
  *
  * This function does not implement hook_node_access(), thus it may not be
diff --git modules/field.info.inc modules/field.info.inc
index b395c68..457a8f2 100644
--- modules/field.info.inc
+++ modules/field.info.inc
@@ -102,3 +102,41 @@ function entity_metadata_field_term_reference_callback(&$info, $entity_type, $fi
   $property['options list'] = 'entity_metadata_field_options_list';
   unset($property['query callback']);
 }
+
+/**
+ * Additional callback to adapt the property info for file fields.
+ * @see entity_metadata_field_entity_property_info().
+ */
+function entity_metadata_field_file_callback(&$info, $entity_type, $field, $instance, $field_type) {
+  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
+  // Define a data structure so it's possible to deal with files and their
+  // descriptions.
+  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
+  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
+
+  // Auto-create the field $items as soon as a property is set.
+  $property['auto creation'] = 'entity_metadata_field_file_create_item';
+  $property['validation callback'] = 'entity_metadata_field_file_validate_item';
+
+  $property['property info']['file'] = array(
+    'type' => 'file',
+    'label' => t('The file.'),
+    'getter callback' => 'entity_metadata_field_file_get',
+    'setter callback' => 'entity_metadata_field_file_set',
+  );
+  if (!empty($instance['settings']['description_field'])) {
+    $property['property info']['description'] = array(
+      'type' => 'text',
+      'label' => t('The file description'),
+      'setter callback' => 'entity_property_verbatim_set',
+    );
+  }
+  if (!empty($field['settings']['display_field'])) {
+    $property['property info']['display'] = array(
+      'type' => 'boolean',
+      'label' => t('Whether the file is being displayed.'),
+      'setter callback' => 'entity_property_verbatim_set',
+    );
+  }
+  unset($property['query callback']);
+}
