diff --git entity.api.php entity.api.php
index a4b1a17..9322626 100644
--- entity.api.php
+++ entity.api.php
@@ -250,9 +250,12 @@ function entity_metadata_hook_entity_info() {
  *       callback which can be used to retrieve the raw, unprocessed value.
  *     - bundle: If the property is an entity, you may specify the bundle of the
  *       retrieved entity. Optional.
- *     - 'options list': Optionally, a callback that returns a list of key value
- *       pairs for the property. The callback has to return an array as
+ *     - 'options list': Optionally, a callback that returns a list of possible
+ *       values for the property. The callback has to return an array as
  *       used by hook_options_list().
+ *       Note that it is possible to return a different set of options depending
+ *       whether they are used in read or in write context. See
+ *       EntityMetadataWrapper::optionsList() for more details on that.
  *     - 'access callback': An optional access callback to allow for checking
  *       'view' and 'edit' access for the described property. If no callback
  *       is specified, a 'setter permission' may be specified instead.
diff --git entity.test entity.test
index 6a0870d..bd23c08 100644
--- entity.test
+++ entity.test
@@ -473,6 +473,14 @@ class EntityMetadataTestCase extends DrupalWebTestCase {
     $wrapper->delete();
     $return = node_load($wrapper->getIdentifier());
     $this->assertFalse($return, "Node has been successfully deleted.");
+
+    // Test labels.
+    $user = $this->drupalCreateUser();
+    user_save($user, array('roles' => array()));
+    $wrapper->author = $user->uid;
+    $this->assertEqual($wrapper->label(), $node->title, 'Entity label returned.');
+    $this->assertEqual($wrapper->author->roles[0]->label(), t('authenticated user'), 'Label from options list returned');
+    $this->assertEqual($wrapper->author->roles->label(), t('authenticated user'), 'Label for a list from options list returned');
   }
 
   /**
@@ -877,8 +885,7 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
       foreach (entity_metadata_wrapper($entity_type) as $name => $wrapper) {
         $info = $wrapper->info();
         if (!empty($info['setter callback'])) {
-          $info += array('type' => 'text');
-          $values[$name] = $this->createValue($info['type'], $info);
+          $values[$name] = $this->createValue($wrapper);
         }
       }
       $entity = entity_property_values_create_entity($entity_type, $values)->value();
@@ -1093,26 +1100,27 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
   }
 
   /**
-   * Creates a value for the given type.
+   * Creates a value for the given property.
    */
-  protected function createValue($type, $info) {
+  protected function createValue($wrapper) {
     if (!isset($this->node)) {
       $this->node = $this->drupalCreateNode(array('type' => 'page'));
       $this->user = $this->drupalCreateUser();
       $this->taxonomy_vocabulary = $this->createVocab();
     }
 
-    if (isset($info['options list'])) {
-      $options = array_keys($info['options list']());
-      return entity_property_list_extract_type($type) ? array(reset($options)) : reset($options);
+    if ($options = $wrapper->optionsList()) {
+      $options = entity_property_options_flatten($options);
+      return $wrapper instanceof EntityListWrapper ? array(key($options)) : key($options);
     }
 
     // For mail addresses properly pass an mail address.
+    $info = $wrapper->info();
     if ($info['name'] == 'mail') {
       return 'webmaster@example.com';
     }
 
-    switch ($type) {
+    switch ($wrapper->type()) {
       case 'decimal':
       case 'integer':
       case 'duration':
@@ -1131,7 +1139,7 @@ class EntityMetadataIntegrationTestCase extends DrupalWebTestCase {
         return array();
 
       default:
-        return $this->$type;
+        return $this->{$wrapper->type()};
     }
   }
 
diff --git includes/entity.property.inc includes/entity.property.inc
index 8961bfb..0203d36 100644
--- includes/entity.property.inc
+++ includes/entity.property.inc
@@ -349,6 +349,25 @@ function entity_property_create_array($property_name, $context) {
 }
 
 /**
+ * Flattens the given options in single dimensional array.
+ * We don't depend on options module, so we cannot use options_array_flatten().
+ *
+ * @see options_array_flatten()
+ */
+function entity_property_options_flatten($options) {
+  $result = array();
+  foreach ($options as $key => $value) {
+    if (is_array($value)) {
+      $result += $value;
+    }
+    else {
+      $result[$key] = $value;
+    }
+  }
+  return $result;
+}
+
+/**
  * Defines info for the properties of the text_formatted data structure.
  */
 function entity_property_text_formatted_info() {
diff --git includes/entity.wrapper.inc includes/entity.wrapper.inc
index 4b159d2..8804a71 100644
--- includes/entity.wrapper.inc
+++ includes/entity.wrapper.inc
@@ -150,20 +150,44 @@ abstract class EntityMetadataWrapper {
   }
 
   /**
-   * Returns the options list specifying possible key value pairs for the
-   * property, if defined.
+   * Returns the options list specifying possible values for the property, if
+   * defined.
+   *
+   * @param $op
+   *   (optional) One of 'edit' or 'view'. In case the list of possible values
+   *   a user could set for a property differs to the list of values a property
+   *   could have, $op determines which options should be returned. Defaults to
+   *   'edit'.
+   *   E.g. all possible roles a user could have include the anonymous and the
+   *   authenticated user roles, while those roles cannot be added to a user
+   *   account. So their options would be included for 'view', but for 'edit'
+   *   not.
    *
    * @return
    *   An array as used by hook_options_list() or FALSE.
    */
-  public function optionsList() {
+  public function optionsList($op = 'edit') {
     if (isset($this->info['options list']) && is_callable($this->info['options list']) && isset($this->info['name'])) {
-      return call_user_func($this->info['options list'], $this->info['name'], $this->info);
+      return call_user_func($this->info['options list'], $this->info['name'], $this->info, $op);
     }
     return FALSE;
   }
 
   /**
+   * Returns the label for the currently set property value if there is one
+   * available, i.e. if an options list has been specified.
+   */
+  public function label() {
+    if ($options = $this->optionsList('view')) {
+      $options = entity_property_options_flatten($options);
+      $value = $this->value();
+      if (is_scalar($value) && isset($options[$value])) {
+        return $options[$value];
+      }
+    }
+  }
+
+  /**
    * Determines whether the given user has access to view or edit this property.
    * Apart from relying on access metadata of properties, this takes into
    * account information about entity level access, if available:
@@ -754,6 +778,15 @@ class EntityDrupalWrapper extends EntityStructureWrapper {
   }
 
   /**
+   * Returns the entity label.
+   *
+   * @see entity_label()
+   */
+  public function label() {
+    return entity_label($this->type, $this->value());
+  }
+
+  /**
    * Prepare for serializiation.
    */
   public function __sleep() {
@@ -913,6 +946,29 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega
   public function validate($value) {
     return entity_property_verify_data_type($value, $this->type);
   }
+
+  /**
+   * Returns the label for the list of set values if available.
+   */
+  public function label() {
+    if ($options = $this->optionsList('view')) {
+      $options = entity_property_options_flatten($options);
+      $labels = array_intersect_key($options, array_flip(parent::value()));
+    }
+    else {
+      // Get each label on its own, e.g. to support getting labels of a list
+      // of entities.
+      $labels = array();
+      foreach ($this as $key => $property) {
+        $label = $property->label();
+        if (!$label) {
+          return NULL;
+        }
+        $labels[] = $label;
+      }
+    }
+    return isset($labels) ? implode(', ', $labels) : NULL;
+  }
 }
 
 /**
diff --git modules/callbacks.inc modules/callbacks.inc
index 8f0aeb8..037480b 100644
--- modules/callbacks.inc
+++ modules/callbacks.inc
@@ -322,15 +322,17 @@ function entity_metadata_user_set_properties($account, $name, $value) {
   }
 }
 
-
 /**
- * Options list callback for user roles.
+ * Options list callback returning all user roles.
  */
-function entity_metadata_user_roles() {
-  return user_roles(TRUE);
+function entity_metadata_user_roles($property_name = 'roles', $info = array(), $op = 'edit') {
+  $roles = user_roles();
+  if ($op == 'edit') {
+    unset($roles[DRUPAL_AUTHENTICATED_RID], $roles[DRUPAL_ANONYMOUS_RID]);
+  }
+  return $roles;
 }
 
-
 /**
  * Callback defining an options list for language properties.
  */
@@ -440,10 +442,10 @@ function entity_metadata_field_text_get($item, array $options, $name, $type, $co
 }
 
 /**
- * Defines the list of formats available for the current user.
+ * Defines the list of all available text formats.
  */
 function entity_metadata_field_text_formats() {
-  foreach (filter_formats($GLOBALS['user']) as $key => $format) {
+  foreach (filter_formats() as $key => $format) {
     $formats[$key] = $format->name;
   }
   return $formats;
