diff --git a/includes/admin.settings.inc b/includes/admin.settings.inc
index 7b15f1a..74590ad 100644
--- a/includes/admin.settings.inc
+++ b/includes/admin.settings.inc
@@ -53,16 +53,17 @@ function signup_settings_form() {
   );
 
   $form['adv_settings']['signup_form_location'] = array(
-    '#title' => t('Location of the signup form'),
+    '#title' => t('Location of the signup control'),
     '#type' => 'radios',
     '#options' => array(
-      'node' => t('Included on each node'),
-      'tab' => t('Under the %signups tab', array('%signups' => t('Signups'))),
-      'none' => t('Do not display signup form'),
+      'node' => t('Include a form on each node'),
+      'link' => t('Include a one-click link'),
+      'tab' => t('Form under the %signups tab', array('%signups' => t('Signups'))),
+      'none' => t('Do not display any signup control'),
     ),
     '#default_value' => variable_get('signup_form_location', 'node'),
     '#description' => t('On every signup-enabled node, users with permission to
- sign up can be presented with a form. This setting controls where this form should be displayed: either directly on the node itself, on a separate tab, or not at all.'),
+ sign up can be presented with a form, a link, or a tag. This setting controls what and where it should be displayed: either directly on the node itself as a form or link, on a separate tab as a form, or not at all.'),
     '#prefix' => '<div class="signup-form-location-radios">',
     '#suffix' => '</div>',
   );
diff --git a/includes/node_output.inc b/includes/node_output.inc
index 759f6ce..d190d63 100644
--- a/includes/node_output.inc
+++ b/includes/node_output.inc
@@ -89,18 +89,39 @@ function _signup_current_user_signup($node, $type = 'node') {
       // An authenticated user.
 
       // See if the user is already signed up for this node.
+      $url = 'node/' . $node->nid . '/signup';
       $signup = db_fetch_object(db_query("SELECT sl.*, n.title, u.name, u.mail FROM {signup_log} sl INNER JOIN {node} n ON sl.nid = n.nid INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.uid = %d AND sl.nid = %d", $user->uid, $node->nid));
       if (empty($signup)) {
         // Not yet signed up
         if (user_access('sign up for content')) {
           // User has permission to do so, so give them the form.
           module_load_include('inc', 'signup', 'includes/signup_form');
-          $output .= drupal_get_form('signup_form', $node, 'auth', $fieldset);
+          if ($type == 'node') {
+            $output .= drupal_get_form('signup_form', $node, 'auth', $fieldset);
+          }
+          elseif ($type == 'link') {
+            $path = drupal_get_path('module', 'signup');
+            drupal_add_js("$path/js/signup_link.js");
+            // Generate token.
+            $token = drupal_get_token($url);
+            $signup_url = url($url, array('query' => array('token' => $token)));
+            $output .= theme('signup_link', $node, $signup_url, t('Signup'));
+          }
         }
       }
       else {
         // Already signed up, display their info.
-        $output .= _signup_render_signup_edit_form($signup, $type);
+        if ($type == 'node') {
+          $output .= _signup_render_signup_edit_form($signup, $type);
+        }
+        elseif ($type == 'link') {
+          $path = drupal_get_path('module', 'signup');
+          drupal_add_js("$path/js/signup_link.js");
+          // Generate token.
+          $token = drupal_get_token($url);
+          $signup_url = url($url, array('query' => array('token' => $token)));
+          $output .= theme('signup_link', $node, $signup_url, t('Cancel signup'));
+        }
       }
     }
   }
diff --git a/includes/signup_form.inc b/includes/signup_form.inc
index ce49d8d..4b07656 100644
--- a/includes/signup_form.inc
+++ b/includes/signup_form.inc
@@ -196,3 +196,41 @@ function signup_node_admin_add_user_page($node) {
   return $output;
 }
 
+/**
+ *
+ */
+function signup_link_signup($node) {
+  global $user;
+  // @todo do I need to check user_access('sign up for content') or does _signup_menu_access check that/can?
+  // Check for token.
+  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'node/' . $node->nid . '/signup')) {
+    return drupal_access_denied();
+  }
+  $js = FALSE;
+  if (isset($_GET['js']) && intval($_GET['js']) == 1) {
+    $js = TRUE;
+  }
+  // Sign-up or cancel.
+  $signup = db_fetch_object(db_query("SELECT sl.*, n.title, u.name, u.mail FROM {signup_log} sl INNER JOIN {node} n ON sl.nid = n.nid INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.uid = %d AND sl.nid = %d", $user->uid, $node->nid));
+  if (empty($signup)) {
+    // Mock signup_form.
+    $signup_form = array(
+      'nid' => $node->nid,
+      'uid' => $user->uid,
+    );
+    $result = signup_sign_up_user($signup_form, !$js);
+    if ($result !== FALSE) {
+      $message = t('Cancel signup');
+    }
+  }
+  else {
+    signup_cancel_signup($signup->sid, !$js);
+    $message = t('Signup');
+  }
+  if ($js) {
+    drupal_json($message);
+    return;
+  }
+  // We weren't invoked via JS so set a message and return to the node.
+  drupal_goto('node/' . $node->nid);
+}
\ No newline at end of file
diff --git a/signup.module b/signup.module
index 00dfce9..617e559 100644
--- a/signup.module
+++ b/signup.module
@@ -156,6 +156,14 @@ function signup_theme() {
         'view' => NULL,
       ),
     ),
+    'signup_link' => array(
+      'file' => 'node.inc',
+      'path' => drupal_get_path('module', 'signup') .'/includes',
+      'arguments' => array(
+        'node' => NULL,
+        'signup_url' => NULL,
+      ),
+    ),
   );
 }
 
@@ -406,6 +414,16 @@ function signup_menu() {
     'file' => 'broadcast.inc',
     'file path' => $path,
   );
+  $items['node/%node/signup'] = array(
+    'title' => 'Signup link',
+    'page callback' => 'signup_link_signup',
+    'page arguments' => array(1),
+    'access callback' => '_signup_menu_access',
+    'access arguments' => array(1, 'link'),
+    'type' => MENU_CALLBACK,
+    'file' => 'signup_form.inc',
+    'file path' => $path,
+  );
 
   return $items;
 }
@@ -499,6 +517,10 @@ function _signup_menu_access($node, $menu_type = 'node') {
       $admin = _signup_menu_access($node, 'admin');
       $email = _signup_menu_access($node, 'broadcast');
       return $signup || $list || $admin || $email;
+
+    case 'link':
+      $location = variable_get('signup_form_location', 'node');
+      return $location == 'link' ? TRUE : FALSE;
   }
 }
 
@@ -801,8 +823,8 @@ function signup_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
         if ($info_location == 'node' || $list_location == 'embed-view') {
           module_load_include('inc', 'signup', 'includes/node_output');
         }
-        if ($info_location == 'node') {
-          $signup_info = _signup_node_output($node);
+        if ($info_location == 'node' || $info_location == 'link') {
+          $signup_info = _signup_node_output($node, $info_location);
           if (!empty($signup_info)) {
             if (module_exists('content')) {
               // Due to a bug in CCK (http://drupal.org/node/363456), we need
diff --git a/theme/node.inc b/theme/node.inc
index 1c3c4b9..54db682 100644
--- a/theme/node.inc
+++ b/theme/node.inc
@@ -58,6 +58,22 @@ function theme_signup_node_output_header($node) {
   return '<a name="signup"></a>';
 }
 
+/**
+ * Return HTML for the dynamic signup link.
+ *
+ * @param $node
+ *   The fully loaded node object.
+ * @param $signup_url
+ *   Signup link URL with token.
+ *
+ * @return
+ *   HTML to display.
+ *
+ */
+function theme_signup_link($node, $signup_url, $signup_text) {
+  return '<a href="' . $signup_url . '" class="signup-link">' . $signup_text . '</a>';
+}
+
 /*
  * Return HTML desired when displaying a node title along with the signup date.
  */
