diff --git a/i18n/en.po b/i18n/en.po
index 6402bd623407c747d8f624e682e29f5bdc123396_aTE4bi9lbi5wbw==..a729d4376e9660a2b595a3ba4ec3ef70aef3e910_aTE4bi9lbi5wbw== 100644
--- a/i18n/en.po
+++ b/i18n/en.po
@@ -9,3 +9,7 @@
 
 msgid "clone"
 msgstr ""
+
+#, python-format
+msgid "clone of entity #%d created"
+msgstr ""
diff --git a/i18n/fr.po b/i18n/fr.po
index 6402bd623407c747d8f624e682e29f5bdc123396_aTE4bi9mci5wbw==..a729d4376e9660a2b595a3ba4ec3ef70aef3e910_aTE4bi9mci5wbw== 100644
--- a/i18n/fr.po
+++ b/i18n/fr.po
@@ -9,3 +9,7 @@
 
 msgid "clone"
 msgstr "cloner"
+
+#, python-format
+msgid "clone of entity #%d created"
+msgstr "un clone de l'entité #%d a été crée"
diff --git a/test/test_compound.py b/test/test_compound.py
index 6402bd623407c747d8f624e682e29f5bdc123396_dGVzdC90ZXN0X2NvbXBvdW5kLnB5..a729d4376e9660a2b595a3ba4ec3ef70aef3e910_dGVzdC90ZXN0X2NvbXBvdW5kLnB5 100644
--- a/test/test_compound.py
+++ b/test/test_compound.py
@@ -340,5 +340,16 @@
                                                  rset=entity.as_rset())
             self.assertIsInstance(action, CloneAction)
 
+    @staticmethod
+    def _clone_setup(cnx):
+        """Setup a graph of entities to be cloned."""
+        agent = cnx.create_entity('Agent', name=u'bob')
+        cnx.create_entity('OnlineAccount', reverse_account=agent)
+        bio = cnx.create_entity('Biography', reverse_biography=agent)
+        cnx.create_entity('Event', reverse_event=bio)
+        cnx.create_entity('Anecdote', reverse_event=bio)
+        cnx.create_entity('Anecdote', reverse_event=bio, narrated_by=agent)
+        return agent
+
     def test_clone_hook(self):
         with self.admin_access.repo_cnx() as cnx:
@@ -343,11 +354,6 @@
     def test_clone_hook(self):
         with self.admin_access.repo_cnx() as cnx:
-            agent = cnx.create_entity('Agent', name=u'bob')
-            cnx.create_entity('OnlineAccount', reverse_account=agent)
-            bio = cnx.create_entity('Biography', reverse_biography=agent)
-            cnx.create_entity('Event', reverse_event=bio)
-            cnx.create_entity('Anecdote', reverse_event=bio)
-            cnx.create_entity('Anecdote', reverse_event=bio, narrated_by=agent)
+            self._clone_setup(cnx)
             cnx.commit()
         with self.new_access(u'georges').repo_cnx() as cnx:
             bob = cnx.find('Agent', name=u'bob').one()
@@ -360,6 +366,17 @@
                 '            B event E, E is Event, B event A, A narrated_by X')
             self.assertTrue(rset)
 
+    def test_clone_controller(self):
+        with self.admin_access.repo_cnx() as cnx:
+            original_eid = self._clone_setup(cnx).eid
+            cnx.commit()
+        with self.new_access(u'georges').web_request() as req:
+            req.form['eid'] = original_eid
+            self.app_handle_request(req, 'compound.clone')
+            rset = req.execute(
+                'Any X WHERE X clone_of O, O eid %s' % original_eid)
+            self.assertTrue(rset)
+
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
diff --git a/views.py b/views.py
index 6402bd623407c747d8f624e682e29f5bdc123396_dmlld3MucHk=..a729d4376e9660a2b595a3ba4ec3ef70aef3e910_dmlld3MucHk= 100644
--- a/views.py
+++ b/views.py
@@ -15,6 +15,9 @@
 # with this program. If not, see <http://www.gnu.org/licenses/>.
 """cubicweb-compound views/forms/actions/components for web ui"""
 
-from cubicweb.predicates import one_line_rset, adaptable, has_permission
+from cubicweb.web import Redirect
+from cubicweb.predicates import (one_line_rset, adaptable, has_permission,
+                                 match_form_params)
+from cubicweb.web.controller import Controller
 from cubicweb.web.views.actions import CopyAction
 
@@ -19,5 +22,7 @@
 from cubicweb.web.views.actions import CopyAction
 
+from cubes.compound.entities import copy_entity
+
 
 _ = unicode
 
@@ -36,3 +41,22 @@
 
 
 CopyAction.__select__ &= ~adaptable('IClonable')
+
+
+class CloneController(Controller):
+    """Controller handling cloning of the original entity (with `eid` passed
+    in form parameters). Redirects to the cloned entity primary view.
+    """
+    __regid__ = 'compound.clone'
+    __select__ = Controller.__select__ & match_form_params('eid')
+
+    def publish(self, rset=None):
+        eid = int(self._cw.form['eid'])
+        original = self._cw.entity_from_eid(eid)
+        iclone = original.cw_adapt_to('IClonable')
+        rtype = (iclone.rtype if iclone.role == 'object'
+                 else 'reverse_' + iclone.rtype)
+        kwargs = {rtype: eid}
+        clone = copy_entity(original, **kwargs)
+        msg = self._cw._('clone of entity #%d created' % eid)
+        raise Redirect(clone.absolute_url(__message=msg))