diff --git a/editableviews.info b/editableviews.info
index 5257966..7cf381a 100644
--- a/editableviews.info
+++ b/editableviews.info
@@ -9,6 +9,7 @@ core = 7.x
 files[] = editableviews_plugin_style_row_edit_table.inc
 files[] = handlers/editableviews_handler_field_field_edit.inc
 files[] = handlers/editableviews_handler_field_node_title_edit.inc
+files[] = handlers/editableviews_handler_field_entity_metadata_property.inc
 
 files[] = tests/editableviews.test
 
diff --git a/editableviews.views.inc b/editableviews.views.inc
index 6702a65..7d4482e 100644
--- a/editableviews.views.inc
+++ b/editableviews.views.inc
@@ -74,6 +74,24 @@ function editableviews_views_data_alter(&$data) {
     $data[$table][$new_field_name]['field']['handler'] = 'editableviews_handler_field_field_edit';
   } // foreach field_info_fields()
 
+  // Editable field for entity metadata properties. Only properties with a
+  // defined 'setter callback' may be used.
+  // We define a single pseudofield, and set the particular property at the
+  // handler options level, as depending on metadata properties here is quite
+  // likely weirdly circular.
+  foreach ($entity_info as $entity_type => $entity_type_info) {
+    if (!empty($entity_type_info['base table']) && isset($data[$entity_type_info['base table']])) {
+      $data[$entity_type_info['base table']]['metadata_property_editable'] = array(
+        'title' => t("Entity metadata property (editable)"),
+        'help' => t("Editable field for entity metadata properties that have setter callbacks defined."),
+        'field' => array(
+          'real field' => $entity_type_info['entity keys']['id'],
+          'handler' => 'editableviews_handler_field_entity_metadata_property',
+        ),
+      );
+    }
+  }
+
   // Node title editable field.
   $data['node']['title_editable'] = array(
     'title' => $data['node']['title']['title'] . ' ' . t('(editable)'),
@@ -81,9 +99,6 @@ function editableviews_views_data_alter(&$data) {
   );
   $data['node']['title_editable']['field'] = $data['node']['title']['field'];
   $data['node']['title_editable']['field']['handler'] = 'editableviews_handler_field_node_title_edit';
-  // This is needed by insert_form_elements(). Outside of FieldAPI fields, this
-  // is simply the key of the element that our handler adds to the form.
-  $data['node']['title_editable']['field']['field_name'] = 'title';
 
   // Define the direction of entityreference reverse relationships.
   // This is set by entityreference module in the magic callback on the field,
diff --git a/editableviews_plugin_style_row_edit_table.inc b/editableviews_plugin_style_row_edit_table.inc
index cdd3d4b..67fabc2 100644
--- a/editableviews_plugin_style_row_edit_table.inc
+++ b/editableviews_plugin_style_row_edit_table.inc
@@ -155,7 +155,7 @@ class editableviews_plugin_style_row_edit_table extends views_plugin_style_table
 
     foreach ($edit_field_handlers as $handler_id => $handler) {
       //dsm($handler, "field handler $handler_id");
-      $edit_fields[$handler_id] = $handler->definition['field_name'];
+      $edit_fields[$handler_id] = $handler->field_name();
 
       // Build an array of handlers grouped by relationship ID.
       // This is for the form builder to only work on the handlers that are
@@ -240,7 +240,7 @@ class editableviews_plugin_style_row_edit_table extends views_plugin_style_table
         list($entity_type, $entity_id) = $result_indexes_reverse[$field_handler_relationship_id][$index];
         if (!is_numeric($entity_id)) {
           $entity = $result_entities[$entity_type][$entity_id];
-          $entity->editableviews_exposed_fields[] = $handler->definition['field_name'];
+          $entity->editableviews_exposed_fields[] = $handler->field_name();
         }
 
         // Build a lookup array from index and field handler to the entity type
diff --git a/handlers/editableviews_handler_field_entity_metadata_property.inc b/handlers/editableviews_handler_field_entity_metadata_property.inc
new file mode 100644
index 0000000..c28d280
--- /dev/null
+++ b/handlers/editableviews_handler_field_entity_metadata_property.inc
@@ -0,0 +1,269 @@
+<?php
+
+/**
+ * Field handler for editing an entity metadata property.
+ *
+ * This allows editing of Entity API metadata properties, as defined in
+ * hook_entity_property_info(). To qualify, a property have a 'setter callback'
+ * defined.
+ */
+class editableviews_handler_field_entity_metadata_property extends views_handler_field {
+
+  /**
+   * Boolean to indicate to the style plugin that this field is editable.
+   */
+  public $editable = TRUE;
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['property'] = array('default' => NULL);
+
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+
+    $table_data = views_fetch_data($this->table);
+    $entity_info = entity_get_info($table_data['table']['entity type']);
+    $entity_property_info = entity_get_property_info($table_data['table']['entity type']);
+
+    // Create an array of grouped options.
+    $options = array();
+
+    // Common properties.
+    $label_common = t('Common');
+    $options[$label_common] = array();
+    if (isset($entity_info['entity keys']['bundle'])) {
+      // Entity API makes the bundle entity key settable, presumably for
+      // creating new entities. That's really not going to fly here.
+      // (No need to check it exists before we attempt to unset it.)
+      unset($entity_property_info['properties'][$entity_info['entity keys']['bundle']]);
+    }
+
+    foreach ($entity_property_info['properties'] as $property_name => $property_info) {
+      if (empty($property_info['setter callback'])) {
+        // We can't do anything with a property that has no information about
+        // how to set it.
+        continue;
+      }
+
+      if (!empty($property_info['field'])) {
+        // FieldAPI fields have their own handler that does a far better job
+        // (with widgets, etc) than this can do.
+        continue;
+      }
+
+      $options[$label_common][$property_name] = $property_info['label'];
+    }
+
+    // Bundle-specific properties.
+    foreach ($entity_property_info['bundles'] as $bundle_name => $bundle_info) {
+      $bundle_label = $entity_info['bundles'][$bundle_name]['label'];
+      $options[$bundle_label] = array();
+      foreach ($bundle_info['properties'] as $property_name => $property_info) {
+        if (empty($property_info['setter callback'])) {
+          // We can't do anything with a property that has no information about
+          // how to set it.
+          continue;
+        }
+
+        if (!empty($property_info['field'])) {
+          // FieldAPI fields have their own handler that does a far better job
+          // (with widgets, etc) than this can do.
+          continue;
+        }
+
+        $options[$bundle_label][$property_name] = $property_info['label'];
+      }
+    }
+
+    $form['property'] = array(
+      '#type' => 'select',
+      '#title' => t('Metadata property'),
+      '#options' => $options,
+      '#description' => t('Select the property to edit with this field. (Only properties that define how they may be set on an entity are available. Be sure to ensure the property applies to all entities the View will show.)'),
+      '#default_value' => $this->options['property'],
+    );
+  }
+
+  /**
+   * Return the name of the entity property this handler controls.
+   */
+  function field_name() {
+    return $this->options['property'];
+  }
+
+  /**
+   * Render the field.
+   *
+   * Override this as otherwise we'd just output the entity ID all the time.
+   *
+   * @param $values
+   *   The values retrieved from the database.
+   */
+  function render($values) {
+    // Don't return anything. We don't know the entity we're on at this point.
+    // TODO: split up insert_form_elements() to do all the joining up of data
+    // before parent::render_fields() is called, and set the entity on the
+    // handlers. This would allow us to output the value of the property here.
+    return '';
+  }
+
+  /**
+   * Add the edit form for the field.
+   */
+  function edit_form($entity_type, $entity, &$element, &$form_state) {
+    // Something weird in Views admin UI causes us to come here (because the
+    // style plugin gets rendered) without the options set on this handler. This
+    // then causes an AJAX error because further down we access a metadata
+    // wrapper with no property. I have no time to go chasing this right now, so
+    // for now, just bail here if we're not properly set up. Doing this appears
+    // to have no adverse or visible side effects.
+    if (empty($this->options['property'])) {
+      return;
+    }
+
+    $wrapper = entity_metadata_wrapper($entity_type, $entity);
+
+    // Get the info for the property we are working with. We only need to get
+    // this once.
+    if (!isset($this->property_info)) {
+      $this->property_info = $wrapper->getPropertyInfo($this->options['property']);
+    }
+
+    // Special case: if the property has an 'options list' defined, we can show
+    // a select form element of options.
+    if (isset($this->property_info['options list'])) {
+      $this->edit_form_element_select($element, $form_state, $wrapper);
+      return;
+    }
+
+    // The type of form element we add depends on the type of the property.
+    // This is just a best guess.
+    // TODO: add an option to override this?
+    switch ($this->property_info['type']) {
+      case 'boolean':
+        $this->edit_form_element_checkbox($element, $form_state, $wrapper);
+        return;
+      default:
+        $this->edit_form_element_textfield($element, $form_state, $wrapper);
+        return;
+    }
+  }
+
+  /**
+   * Create a textfield element.
+   *
+   * @param &$element
+   *  The element to alter.
+   * @param &$form_state
+   *  The form state.
+   * @param $wrapper
+   *  The wrapper for the entity whose property is to be shown in the element.
+   */
+  function edit_form_element_textfield(&$element, &$form_state, $wrapper) {
+    // Just do the same thing as node_content_form().
+    $element[$this->options['property']] = array(
+      '#type' => 'textfield',
+      '#title' => check_plain($this->property_info['label']),
+      '#required' => !empty($this->property_info['required']),
+      // The value might not be set in the case where we're on a non-required
+      // relationship with empty data. TODO???
+      '#default_value' => $wrapper->{$this->options['property']}->raw(),
+      //'#size' => $this->options['textfield_size'],
+      '#maxlength' => 255,
+    );
+  }
+
+  /**
+   * Create a select element.
+   *
+   * @param &$element
+   *  The element to alter.
+   * @param &$form_state
+   *  The form state.
+   * @param $wrapper
+   *  The wrapper for the entity whose property is to be shown in the element.
+   */
+  function edit_form_element_select(&$element, &$form_state, $wrapper) {
+    // Just do the same thing as node_content_form().
+    $element[$this->options['property']] = array(
+      '#type' => 'select',
+      '#title' => check_plain($this->property_info['label']),
+      '#required' => !empty($this->property_info['required']),
+      '#options' => $wrapper->{$this->options['property']}->optionsList(),
+      // The value might not be set in the case where we're on a non-required
+      // relationship with empty data. TODO???
+      '#default_value' => $wrapper->{$this->options['property']}->raw(),
+    );
+  }
+
+  /**
+   * Create a checkbox element.
+   *
+   * @param &$element
+   *  The element to alter.
+   * @param &$form_state
+   *  The form state.
+   * @param $wrapper
+   *  The wrapper for the entity whose property is to be shown in the element.
+   */
+  function edit_form_element_checkbox(&$element, &$form_state, $wrapper) {
+    $element[$this->options['property']] = array(
+      '#type' => 'checkbox',
+      '#title' => check_plain($this->property_info['label']),
+      '#required' => !empty($this->property_info['required']),
+      // The value might not be set in the case where we're on a non-required
+      // relationship with empty data. TODO???
+      '#default_value' => $wrapper->{$this->options['property']}->raw(),
+    );
+  }
+
+  /**
+   * Helper to get the value out of the form element.
+   */
+  function get_element_value($element, $form_state) {
+    // The element's '#parents' property gets us nearly all the way there.
+    $parents = $element['#parents'];
+    $parents[] = $this->options['property'];
+
+    $value = drupal_array_get_nested_value($form_state['values'], $parents);
+
+    return $value;
+  }
+
+  /**
+   * Handle the form validation for this field's form element.
+   */
+  function edit_form_validate($entity_type, $entity, &$element, &$form_state) {
+    // Wrappers don't have a way of testing the waters to validate, but we can
+    // attempt to set the property and catch an exception.
+    try {
+      // Get the value out of the form state.
+      $value = $this->get_element_value($element, $form_state);
+
+      // Set it on the wrapper, and stand back!
+      $wrapper = entity_metadata_wrapper($entity_type, $entity);
+      $wrapper->{$this->options['property']}->set($value);
+    }
+    catch (EntityMetadataWrapperException $e) {
+      // TODO: the exception message from Entity API is not that end-user-friendly.
+      form_error($element[$this->options['property']], $e->getMessage());
+    }
+  }
+
+  /**
+   * Handle the form submission for this field's form element.
+   */
+  function edit_form_submit($entity_type, $entity, &$element, &$form_state) {
+    // Get the value out of the form state.
+    $value = $this->get_element_value($element, $form_state);
+    // We can set this on the wrapper with inpunity, as the validate step
+    // already caught any exception this might throw.
+    $wrapper = entity_metadata_wrapper($entity_type, $entity);
+    $wrapper->{$this->options['property']}->set($value);
+  }
+
+}
diff --git a/handlers/editableviews_handler_field_field_edit.inc b/handlers/editableviews_handler_field_field_edit.inc
index c2d039b..8b01fc3 100755
--- a/handlers/editableviews_handler_field_field_edit.inc
+++ b/handlers/editableviews_handler_field_field_edit.inc
@@ -65,6 +65,13 @@ class editableviews_handler_field_field_edit extends views_handler_field_field {
   }
 
   /**
+   * Return the name of the entity property this handler controls.
+   */
+  function field_name() {
+    return $this->definition['field_name'];
+  }
+
+  /**
    * Add the form element for this handler's field to the form.
    *
    * We break this out into the handler to allow other handlers that work with
diff --git a/handlers/editableviews_handler_field_node_title_edit.inc b/handlers/editableviews_handler_field_node_title_edit.inc
index dae0516..ca3c242 100755
--- a/handlers/editableviews_handler_field_node_title_edit.inc
+++ b/handlers/editableviews_handler_field_node_title_edit.inc
@@ -34,6 +34,13 @@ class editableviews_handler_field_node_title_edit extends views_handler_field_no
   }
 
   /**
+   * Return the name of the entity property this handler controls.
+   */
+  function field_name() {
+    return 'title';
+  }
+
+  /**
    * Return the edit form for the field.
    */
   function edit_form($entity_type, $entity, &$element, &$form_state) {
diff --git a/tests/editableviews.test b/tests/editableviews.test
index 4b2cbda..e393f56 100644
--- a/tests/editableviews.test
+++ b/tests/editableviews.test
@@ -152,6 +152,68 @@ class EditableViewsBasicWebTestCase extends EditableViewsBaseWebTestCase {
     $this->assertEqual($first_node_wrapper->field_test_editable_options->value(), $new_value_field_test_editable_options, t("The node's field value is set to the new value by saving the view."));
   }
 
+  /**
+   * Test basic node view 'test_editable_views_node_properties'.
+   */
+  function testNodePropertiesView() {
+    $first_node_title = $this->randomName();
+    $first_node = $this->drupalCreateNode(array(
+      'title' => $first_node_title,
+      'type'  => 'editable_views_test_node',
+    ));
+    $second_node_title = $this->randomName();
+    $second_node = $this->drupalCreateNode(array(
+      'title' => $second_node_title,
+      'type'  => 'editable_views_test_node',
+    ));
+
+    $property_names = array(
+      'status',
+      'promote',
+      'sticky',
+    );
+
+    // An array of form element names, keyed first by node id, and then by
+    // property name.
+    $editable_element_names = array(
+      $first_node->nid => array(
+        'status'    => "node[{$first_node->nid}][status]",
+        'promote'   => "node[{$first_node->nid}][promote]",
+        'sticky'    => "node[{$first_node->nid}][sticky]",
+      ),
+      $second_node->nid => array(
+        'status'    => "node[{$second_node->nid}][status]",
+        'promote'   => "node[{$second_node->nid}][promote]",
+        'sticky'    => "node[{$second_node->nid}][sticky]",
+      ),
+    );
+
+    // Look at the node properties editable view.
+    $this->drupalGet('test-editable-views-node-properties');
+
+    // Check the field editable elements are shown in the view form.
+    foreach ($property_names as $property_name) {
+      $this->assertFieldByName($editable_element_names[$first_node->nid][$property_name], NULL, t("View shows an edit form for the first node's $property_name field."));
+
+      $this->assertFieldByName($editable_element_names[$second_node->nid][$property_name], NULL, t("View shows an edit form for the second node's $property_name field."));
+    }
+
+    // Save changes to the first node's properties.
+    $edit = array(
+      "node[{$first_node->nid}][status]"  => 0,
+      "node[{$first_node->nid}][promote]" => 1,
+      "node[{$first_node->nid}][sticky]"  => 1,
+    );
+    $this->drupalPost('test-editable-views-node-properties', $edit, t('Save'));
+
+    entity_get_controller('node')->resetCache();
+    $first_node = node_load($first_node->nid);
+
+    $this->assertEqual($first_node->status, FALSE, t("The node's 'status' property is set to the new value by saving the view."));
+    $this->assertEqual($first_node->promote, TRUE, t("The node's 'status' property is set to the new value by saving the view."));
+    $this->assertEqual($first_node->sticky, TRUE, t("The node's 'status' property is set to the new value by saving the view."));
+  }
+
 }
 
 /**
diff --git a/tests/editableviews_test_feature/editableviews_test_feature.info b/tests/editableviews_test_feature/editableviews_test_feature.info
index 990b2cb..c8e3ff7 100644
--- a/tests/editableviews_test_feature/editableviews_test_feature.info
+++ b/tests/editableviews_test_feature/editableviews_test_feature.info
@@ -23,6 +23,7 @@ features[field_instance][] = node-editable_views_test_referencing-field_test_nod
 features[node][] = editable_views_test_node
 features[node][] = editable_views_test_referencing
 features[views_view][] = test_editable_views_node_fields
+features[views_view][] = test_editable_views_node_properties
 features[views_view][] = test_editable_views_node_referenced_backwards_title
 features[views_view][] = test_editable_views_node_referenced_forwards_title
 features[views_view][] = test_editable_views_node_title
diff --git a/tests/editableviews_test_feature/editableviews_test_feature.views_default.inc b/tests/editableviews_test_feature/editableviews_test_feature.views_default.inc
index 38a4f13..fb6fffd 100644
--- a/tests/editableviews_test_feature/editableviews_test_feature.views_default.inc
+++ b/tests/editableviews_test_feature/editableviews_test_feature.views_default.inc
@@ -71,6 +71,70 @@ function editableviews_test_feature_views_default_views() {
   $export['test_editable_views_node_fields'] = $view;
 
   $view = new view();
+  $view->name = 'test_editable_views_node_properties';
+  $view->description = '';
+  $view->tag = 'default';
+  $view->base_table = 'node';
+  $view->human_name = 'test_editable_views_node_properties';
+  $view->core = 7;
+  $view->api_version = '3.0';
+  $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+  /* Display: Master */
+  $handler = $view->new_display('default', 'Master', 'default');
+  $handler->display->display_options['title'] = 'Test editable views node properties';
+  $handler->display->display_options['use_more_always'] = FALSE;
+  $handler->display->display_options['access']['type'] = 'perm';
+  $handler->display->display_options['cache']['type'] = 'none';
+  $handler->display->display_options['query']['type'] = 'views_query';
+  $handler->display->display_options['exposed_form']['type'] = 'basic';
+  $handler->display->display_options['pager']['type'] = 'some';
+  $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+  $handler->display->display_options['style_plugin'] = 'editableviews_table';
+  /* Field: Content: Title */
+  $handler->display->display_options['fields']['title']['id'] = 'title';
+  $handler->display->display_options['fields']['title']['table'] = 'node';
+  $handler->display->display_options['fields']['title']['field'] = 'title';
+  $handler->display->display_options['fields']['title']['label'] = '';
+  $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+  $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+  /* Field: Content: Entity metadata property (editable) */
+  $handler->display->display_options['fields']['metadata_property_editable']['id'] = 'metadata_property_editable';
+  $handler->display->display_options['fields']['metadata_property_editable']['table'] = 'node';
+  $handler->display->display_options['fields']['metadata_property_editable']['field'] = 'metadata_property_editable';
+  $handler->display->display_options['fields']['metadata_property_editable']['label'] = 'Status';
+  $handler->display->display_options['fields']['metadata_property_editable']['property'] = 'status';
+  /* Field: Content: Entity metadata property (editable) */
+  $handler->display->display_options['fields']['metadata_property_editable_1']['id'] = 'metadata_property_editable_1';
+  $handler->display->display_options['fields']['metadata_property_editable_1']['table'] = 'node';
+  $handler->display->display_options['fields']['metadata_property_editable_1']['field'] = 'metadata_property_editable';
+  $handler->display->display_options['fields']['metadata_property_editable_1']['label'] = 'Promote';
+  $handler->display->display_options['fields']['metadata_property_editable_1']['property'] = 'promote';
+  /* Field: Content: Entity metadata property (editable) */
+  $handler->display->display_options['fields']['metadata_property_editable_2']['id'] = 'metadata_property_editable_2';
+  $handler->display->display_options['fields']['metadata_property_editable_2']['table'] = 'node';
+  $handler->display->display_options['fields']['metadata_property_editable_2']['field'] = 'metadata_property_editable';
+  $handler->display->display_options['fields']['metadata_property_editable_2']['label'] = 'Sticky';
+  $handler->display->display_options['fields']['metadata_property_editable_2']['property'] = 'sticky';
+  /* Sort criterion: Content: Post date */
+  $handler->display->display_options['sorts']['created']['id'] = 'created';
+  $handler->display->display_options['sorts']['created']['table'] = 'node';
+  $handler->display->display_options['sorts']['created']['field'] = 'created';
+  $handler->display->display_options['sorts']['created']['order'] = 'DESC';
+  /* Filter criterion: Content: Type */
+  $handler->display->display_options['filters']['type']['id'] = 'type';
+  $handler->display->display_options['filters']['type']['table'] = 'node';
+  $handler->display->display_options['filters']['type']['field'] = 'type';
+  $handler->display->display_options['filters']['type']['value'] = array(
+    'editable_views_test_node' => 'editable_views_test_node',
+  );
+
+  /* Display: Page */
+  $handler = $view->new_display('page', 'Page', 'page');
+  $handler->display->display_options['path'] = 'test-editable-views-node-properties';
+  $export['test_editable_views_node_properties'] = $view;
+
+  $view = new view();
   $view->name = 'test_editable_views_node_referenced_backwards_title';
   $view->description = '';
   $view->tag = 'default';
