diff --git a/actions/CMakeLists.txt b/actions/CMakeLists.txt
index fe50a9c8053e4bb8ad55d8e98937412a4253dd1a..7a5c3f164fad0733fae888cae9049d60171c95d6 100644
--- a/actions/CMakeLists.txt
+++ b/actions/CMakeLists.txt
@@ -6,3 +6,4 @@ pm_action_init()
 pm_action(pm-build-model actions)
 pm_action(pm-build-rawmodel actions)
 pm_action(pm-help actions)
+pm_action(pm-build-sidechains actions)
diff --git a/actions/doc/index.rst b/actions/doc/index.rst
index 4820610c7519c44d285f2b8e76ce9fb87c6f667a..7172c2b922065318f026fdfec493532173244b6f 100644
--- a/actions/doc/index.rst
+++ b/actions/doc/index.rst
@@ -113,4 +113,40 @@ Possible exit codes of the action:
 - 3: failed to perform modelling (internal error)
 - 4: failed to write results to file
 - other non-zero: failure in argument checking
-  (see :class:`promod3.core.pm3argparse.PM3ArgumentParser`)
\ No newline at end of file
+  (see :class:`promod3.core.pm3argparse.PM3ArgumentParser`)
+
+
+Sidechain Modelling
+--------------------------------------------------------------------------------
+
+You can run (re-)construct the sidechains in a model from the command line.
+
+.. code-block:: console
+
+  $ pm build-sidechains [-h] [-o <FILENAME>] [-k] [-n] [-r] in_file
+
+Example usage:
+
+.. code-block:: console
+
+  $ pm build-sidechains input.pdb
+
+This reads a structure stored in in.pdb, strips all sidechains, 
+detects and models disulfid bonds and reconstructs all sidechains with the 
+flexible rotamer model. The result is stored as :file:`out.pdb`. 
+The output filename can be controlled with the ``-o`` flag.
+
+Several flags control the modelling behaviour:
+
+.. option:: -k, --keep-sidechains 
+
+  Keep existing sidechains.
+
+.. option:: -n, --no-disulfids
+
+  Do not build disulfid bonds before sidechain optimization
+
+.. option:: -r, --rigid-rotamers 
+
+  Do not use rotamers with subrotamers
+
diff --git a/actions/pm-build-sidechains b/actions/pm-build-sidechains
new file mode 100644
index 0000000000000000000000000000000000000000..f67a32c3ac5b335c417b41835e78bc82dcae96b1
--- /dev/null
+++ b/actions/pm-build-sidechains
@@ -0,0 +1,62 @@
+'''
+Automatically (re-)construct sidechains in a protein model.
+
+Example usage:
+  pm build-sidechains in.pdb 
+    This reads a structure stored in in.pdb, strips all sidechains, 
+    detects and models disulfid bonds and reconstructs all sidechains with the 
+    flexible rotamer model. The result is stored as out.pdb
+'''
+
+import os
+import ost
+from ost import io, settings
+from promod3 import modelling
+from promod3.core import pm3argparse, helper
+
+# make sure we see output when passing '-h'
+ost.PushVerbosityLevel(2)
+
+# parse command line
+parser = pm3argparse.PM3ArgumentParser(__doc__, action=True)
+
+parser.add_argument('in_file', type=str, help='File to load coordinates ' +
+                                              '(PDB format)')
+
+parser.add_argument('-o', '--out-file', metavar='<FILENAME>', type=str,
+                    default='out.pdb', help='File to store coordinates ' +
+                                            '(default: %(default)s)')
+
+parser.add_argument('-k', '--keep-sidechains', action='store_true',
+                    help='Keep existing sidechains')
+
+parser.add_argument('-n', '--no-disulfids', action='store_true',
+                    help='Do not build disulfid bonds before sidechain ' + 
+                         'optimization')
+
+parser.add_argument('-r', '--rigid-rotamers', action='store_true',
+                    help='Do not use rotamers with subrotamers')
+
+opts = parser.Parse()
+
+ost.PushVerbosityLevel(3)
+
+if not os.path.isfile(opts.in_file):
+    helper.MsgErrorAndExit("Failed to load input file '%s'." % opts.in_file, 4)
+
+prot = io.LoadPDB(opts.in_file)
+rotamer_model = "frm"
+if opts.rigid_rotamers:
+    rotamer_model = "rrm"
+
+# do sidechain modelling
+modelling.ReconstructSidechains(prot, keep_sidechains=opts.keep_sidechains,
+                                build_disulfids=opts.no_disulfids==False, 
+                                rotamer_model=rotamer_model)
+
+# output
+ost.PopVerbosityLevel()
+io.SavePDB(prot, opts.out_file)
+if not os.path.isfile(opts.out_file):
+    helper.MsgErrorAndExit("Failed to write model file '%s'." % opts.model_file, 4)
+