diff --git project.inc project.inc
index 94b5b57..6717b5e 100644
--- project.inc
+++ project.inc
@@ -129,6 +129,50 @@ function project_project_form($node, $form_state) {
   // concerned, but the value shows up in the $node->project array for
   // validation and submission as far as FAPI is concerned.
   $form['project_node']['project'] = array('#tree' => TRUE);
+
+  // Setup sandbox checkbox for new projects
+  if (!isset($node->nid)) {
+    // Check permissions and set sandbox value and form element status
+    if (user_access('create sandbox projects') && !user_access('create full projects')) {
+      // Force sandbox, disable checkbox
+      $is_sandbox = isset($node->project['sandbox']) ? $node->project['sandbox'] : TRUE;
+      $disabled = TRUE;
+    }
+    elseif (!user_access('create sandbox projects') && user_access('create full projects')) {
+      // Force full project, disable checkbox
+      $is_sandbox = isset($node->project['sandbox']) ? $node->project['sandbox'] : FALSE;
+      $disabled = TRUE;
+    }
+    elseif ((user_access('create sandbox projects') && user_access('create full projects')) || user_access('administer projects')) {
+      // User has full permissions, allow checkbox, use default sandbox value
+      $is_sandbox = variable_get('project_sandbox_default', FALSE);
+      $disabled = FALSE;
+    }
+    $form['project_node']['project']['sandbox'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Sandbox'),
+      '#default_value' => $is_sandbox,
+      '#description' => '', // @TODO need a good generic description of sandboxes
+      '#disabled' => $disabled,
+    );
+  }
+  else {
+    $is_sandbox = $node->project['sandbox'];
+    $form['project_node']['project']['sandbox'] = array(
+      '#type' => 'value',
+      '#value' => $is_sandbox,
+    );
+    $form['project_node']['project']['sandbox_help'] = array(
+      '#type' => 'markup',
+      '#value' => t('This project is currently a sandbox.'),
+      '#prefix' => '<div>', // required to show up in fieldset
+      '#suffix' => '</div>',
+    );
+    if (user_access('administer projects')) {
+      $form['project_node']['project']['sandbox_help']['#value'] .= ' '. t('<a href="!url">Promote this project</a>', array('!url' => url("node/$node->nid/edit/promote")));  
+    }
+  }
+
   $form['project_node']['project']['uri'] = array(
     '#type' => 'textfield',
     '#title' => t('Short project name'),
@@ -136,7 +180,11 @@ function project_project_form($node, $form_state) {
     '#size' => 40,
     '#maxlength' => 50,
     '#description' => (variable_get('project_enable_alias', TRUE)) ? t('This will be used to generate a /project/&lt;shortname&gt;/ URL for your project.') : '',
-    '#required' => TRUE,
+    // Only mark this item required if the project is not a sandbox, and it has
+    // already been created, since project.js will mark it as required
+    // automatically if it's a new project. This is safe to mark as not required
+    // since project_project_validate() checks that this value is not empty.
+    '#required' => (!$is_sandbox && isset($node->nid)) ? TRUE : FALSE,
   );
   // Updating of the uri has been disabled.
   if (!variable_get('project_allow_uri_update', TRUE)) {
@@ -155,6 +203,13 @@ function project_project_form($node, $form_state) {
     }
   }
 
+  // If project is a sandbox, and it is an existing node, and we are
+  // auto-generating the short name, hide the short name field
+  if (variable_get('project_sandbox_numeric_shortname', FALSE) && $is_sandbox && isset($node->nid)) {
+    $form['project_node']['project']['uri']['#disabled'] = TRUE;
+  }
+  drupal_add_js(array('project' => array('project_sandbox_numeric_shortname' => variable_get('project_sandbox_numeric_shortname', FALSE))), 'setting');
+
   $form['project_node']['body_field'] = node_body_field($node, t('Full description'), 1);
 
   $form['project'] = array(
@@ -244,33 +299,8 @@ function project_project_validate(&$node) {
   }
 
   // Validate uri.
-  if (empty($node->project['uri'])) {
-    form_set_error('project][uri', t('A short project name is required.'));
-  }
-  else {
-    // Make sure uri only includes valid characters
-    if (!preg_match('/^[a-zA-Z0-9_-]+$/', $node->project['uri'])) {
-      form_set_error('project][uri', t('Please only use alphanumerical characters for the project name.'));
-    }
-
-    // Make sure uri isn't already in use, or reserved.  Includes all X from
-    // project/issues/X paths used in project_issues module
-    $reserved_names = array('user', 'issues', 'releases', 'rss', 'subscribe-mail', 'search', 'add', 'update_project', 'statistics', 'comments', 'autocomplete', 'cvs', 'developers', 'usage');
-    if (project_use_taxonomy()) {
-      $terms = taxonomy_get_tree(_project_get_vid());
-      foreach ($terms as $i => $term) {
-        if ($term->depth == 0) {
-          $reserved_names[] = strtolower($term->name);
-        }
-      }
-    }
-    if (in_array(strtolower($node->project['uri']), $reserved_names)) {
-      form_set_error('project][uri', t('This project name is reserved.'));
-    }
-    $existing_nid = project_get_nid_from_uri($node->project['uri']);
-    if (!empty($existing_nid) && $existing_nid != $node->nid) {
-      form_set_error('project][uri', t('This project name is already in use.'));
-    }
+  if ($error = project_validate_project_shortname($node)) {
+    form_set_error('shortname', $error);
   }
 
   // Make sure all URL fields actually contain URLs.
@@ -427,14 +457,20 @@ function project_project_nodeapi(&$node, $op, $arg) {
 }
 
 function project_project_insert($node) {
-  db_query("INSERT INTO {project_projects} (nid, uri, homepage, changelog, cvs, demo, screenshots, documentation, license) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->project['uri'], $node->project['homepage'], $node->project['changelog'], $node->project['cvs'], $node->project['demo'], $node->project['screenshots'], $node->project['documentation'], $node->project['license']);
+  if (variable_get('project_sandbox_numeric_shortname', FALSE) && $node->project['sandbox']) {
+    $node->project['uri'] = $node->nid;
+  }
+  db_query("INSERT INTO {project_projects} (nid, uri, homepage, changelog, cvs, demo, screenshots, documentation, license, sandbox) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", $node->nid, $node->project['uri'], $node->project['homepage'], $node->project['changelog'], $node->project['cvs'], $node->project['demo'], $node->project['screenshots'], $node->project['documentation'], $node->project['license'], $node->project['sandbox']);
 //  project_release_scan_directory($node->project['uri']);
   $perms = array_fill_keys(array_keys(project_permission_load()), 1);
   project_maintainer_save($node->nid, $node->uid, $perms);
 }
 
 function project_project_update($node) {
-  db_query("UPDATE {project_projects} SET uri = '%s', homepage = '%s', changelog = '%s', cvs = '%s', demo = '%s', screenshots = '%s', documentation = '%s', license = '%s' WHERE nid = %d", $node->project['uri'], $node->project['homepage'], $node->project['changelog'], $node->project['cvs'], $node->project['demo'], $node->project['screenshots'], $node->project['documentation'], $node->project['license'], $node->nid);
+  if (variable_get('project_sandbox_numeric_shortname', FALSE) && $node->project['sandbox']) {
+    $node->project['uri'] = $node->nid;
+  }
+  db_query("UPDATE {project_projects} SET uri = '%s', homepage = '%s', changelog = '%s', cvs = '%s', demo = '%s', screenshots = '%s', documentation = '%s', license = '%s', sandbox = %d WHERE nid = %d", $node->project['uri'], $node->project['homepage'], $node->project['changelog'], $node->project['cvs'], $node->project['demo'], $node->project['screenshots'], $node->project['documentation'], $node->project['license'], $node->project['sandbox'], $node->nid);
 //  project_release_scan_directory($node->project['uri']);
   $perms = array_fill_keys(array_keys(project_permission_load()), 1);
   project_maintainer_save($node->nid, $node->uid, $perms);
@@ -561,3 +597,86 @@ function project_get_project_link_info($node = NULL) {
   return $all_links;
 }
 
+/**
+ * Page callback for promote subtab.
+ */
+function project_promote_project_page($node) {
+  project_project_set_breadcrumb($node);
+  drupal_set_title(check_plain($node->title));
+  return drupal_get_form('project_promote_project_form', $node);
+}
+
+/**
+ * Form callback for project promote form.
+ */
+function project_promote_project_form($form_state, $project) {
+  $form = array();
+
+  $form['help'] = array(
+    '#type' => 'markup',
+    '#value' => t('You are about to promote the project %project_name to a full project. This action is not reversible.', array('%project_name' => $project->title)),
+  );
+
+  $form['sandbox'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Sandbox'),
+    '#default_value' => $project->project['sandbox'],
+    '#description' => '', // @TODO need a good generic description of sandboxes
+  );
+
+  if (variable_get('project_sandbox_numeric_shortname', FALSE) && $project->project['sandbox']) {
+    $form['shortname'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Short project name'),
+      '#default_value' => isset($project->project['uri']) ? $project->project['uri'] : NULL,
+      '#size' => 40,
+      '#maxlength' => 50,
+      '#description' => (variable_get('project_enable_alias', TRUE)) ? t('This will be used to generate a /project/&lt;shortname&gt;/ URL for your project.') : '',
+      '#required' => TRUE,
+    );
+  }
+  if (!variable_get('project_allow_uri_update', TRUE)) {
+      $form['shortname']['#description'] .= ' '. t('You may not edit this value after the project has been promoted.');
+  }
+
+  $form['pid'] = array('#type' => 'value', '#value' => $project->nid);
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#weight' => 45,
+  );
+
+  return $form;
+}
+
+function project_promote_project_form_validate($form, &$form_state) {
+  if ($form_state['values']['sandbox'] == 0) {
+    // Only validate the short name if it is relevant, and included in the
+    // original form.
+    if (variable_get('project_sandbox_numeric_shortname', FALSE) && isset($form_state['values']['shortname'])) {
+      $project = node_load($form_state['values']['pid']);
+      $project->project['sandbox'] = 0;
+      $project->project['uri'] = $form_state['values']['shortname'];
+      if ($error = project_validate_project_shortname($project)) {
+        form_set_error('shortname', $error);
+      }
+    }
+  }
+}
+
+function project_promote_project_form_submit($form, &$form_state) {
+  // Only act on the form, if we are promoting the project from a sandbox
+  if ($form_state['values']['sandbox'] == 0) {
+    $project = node_load($form_state['values']['pid']);
+    $project->project['sandbox'] = 0;
+
+    // Only update the shortname if it is relevant, and included in the original
+    // form.
+    if (variable_get('project_sandbox_numeric_shortname', FALSE) && isset($form_state['values']['shortname'])) {
+      $project->project['uri'] = $form_state['values']['shortname'];
+    }
+    node_save($project);
+    drupal_set_message(t('The project %project_name has been promoted to a full project.', array('%project_name' => $project->title)));
+    $form_state['redirect'] = 'node/'. $project->nid;
+  }
+}
diff --git project.install project.install
index 45c5eaa..8937170 100644
--- project.install
+++ project.install
@@ -98,6 +98,12 @@ function project_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
+      'sandbox' => array(
+        'description' => 'Indicate whether or not this project is sandboxed.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('nid'),
     'indexes' => array(
@@ -227,3 +233,31 @@ function project_update_6002() {
 
   return $ret;
 }
+
+/**
+ * Add {project_projects}.sandbox column and update permissions.
+ */
+function project_update_6003() {
+  $ret = array();
+
+  // Add the new sandbox column to the project_projects table.
+  $field = array(
+    'description' => 'Indicate whether or not this project is sandboxed.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  db_add_field($ret, 'project_projects', 'sandbox', $field);
+
+  // Update permissions from 'maintain projects' to 'create full projects'
+  $results = db_query("SELECT * FROM {permission}");
+  while ($row = db_fetch_object($results)) {
+    if (strpos($row->perm, 'maintain projects') !== FALSE) {
+       db_query('DELETE FROM {permission} WHERE rid = %d', $row->rid);
+      $perms = str_replace('maintain projects', 'create full projects', $row->perm);
+      db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $row->rid, $perms);
+    }
+  }
+
+  return $ret;
+}
diff --git project.js project.js
index 6660a48..5de6f70 100644
--- project.js
+++ project.js
@@ -41,3 +41,31 @@ Drupal.projectSetTaxonomy = function (tid) {
     }
   });
 }
+
+Drupal.behaviors.projectSandboxShortname = function (context) {
+
+  $('div#edit-project-sandbox-wrapper input:not(.projectSandboxShortname-processed)', context).addClass('projectSandboxShortname-processed').each(function() {
+    // Add required markup to field label
+    Drupal.projectMarkUriRequired();
+    // Only toggle the field and required label if numeric short name is turned on
+    if (Drupal.settings.project.project_sandbox_numeric_shortname) {
+      $(this).click(function () {
+        if (this.checked) {
+          $('div#edit-project-uri-wrapper').hide();
+        }
+        else {
+          $('div#edit-project-uri-wrapper').show();
+        }
+      });
+      // Set the default value when loading the page
+      if (this.checked) {
+        $('div#edit-project-uri-wrapper').hide();
+      }
+    }
+  });
+}
+
+Drupal.projectMarkUriRequired = function() {
+  var required = Drupal.t('This field is required.')
+  $('div#edit-project-uri-wrapper label').append('<span title="' + required + '" class="form-required">*</span>');
+}
diff --git project.module project.module
index c548364..e70b295 100644
--- project.module
+++ project.module
@@ -168,7 +168,8 @@ function project_node_info() {
 function project_perm() {
   $perms = array(
     'administer projects',
-    'maintain projects',
+    'create sandbox projects',
+    'create full projects',
     'access projects',
     'access own projects',
     'delete any projects',
@@ -350,6 +351,20 @@ function project_settings_form() {
       '#default_value' => variable_get('project_solr_project_release_api_tids_alias', 'drupal_core'),
     );
   }
+
+  $form['sandbox'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Sandbox settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['sandbox']['project_sandbox_numeric_shortname'] = array(
+    '#title' => t('Auto generate short name for sandboxes'),
+    '#type' => 'checkbox',
+    '#default_value' => variable_get('project_sandbox_numeric_shortname', FALSE),
+    '#description' => t('If checked, projects marked as sandboxes will be have their shortname automatically generated using a unique numeric identifier.'),
+  );
+
+
   return system_settings_form($form);
 }
 
@@ -542,6 +557,15 @@ function project_menu() {
     'access arguments' => array('update', 1),
     'weight' => -5, 'type' => MENU_DEFAULT_LOCAL_TASK,
   );
+  $items['node/%project_edit_project/edit/promote'] = array(
+    'title' => 'Promote',
+    'page callback' => 'project_promote_project_page',
+    'page arguments' => array(1),
+    'access callback' => 'project_promote_project_access',
+    'access arguments' => array(1),
+    'file' => 'project.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
   return $items;
 }
 
@@ -604,6 +628,16 @@ function project_edit_project_load($nid) {
 }
 
 /**
+ * Menu access callback for the promote tab at node/%project_node/edit/promote.
+ *
+ * @param  $project
+ *   The project object to check access against.
+ */
+function project_promote_project_access($project) {
+  return (user_access('administer projects') && $project->project['sandbox']);
+}
+
+/**
  * See if the current user has the given permission on a given project.
  *
  * @param $project
@@ -628,7 +662,7 @@ function project_user_access($project, $permission) {
      return TRUE;
   }
 
-  if (user_access('maintain projects')) {
+  if (user_access('create sandbox projects') || user_access('create full projects')) {
     // Project owners are treated as super users and can always access.
     if ($user->uid == $project_obj->uid) {
        return TRUE;
@@ -741,7 +775,7 @@ function project_project_access($op, $node, $account) {
       }
       break;
     case 'create':
-      if ($account->uid && user_access('maintain projects')) {
+      if ($account->uid && (user_access('create sandbox projects') || user_access('create full projects'))) {
         // Since this CVS access checking is non-standard, we need to
         // special-case uid 1 to always allow everything.
         if ($account->uid != 1 && module_exists('cvs') && variable_get('cvs_restrict_project_creation', 1)) {
@@ -1503,3 +1537,46 @@ function project_token_values($type = 'all', $object = NULL) {
   }
 }
 
+/**
+ * @param  $node
+ *   A project node with relevant fields pre-filled
+ * @return array
+ *   An array of errors.
+ */
+function project_validate_project_shortname(&$node) {
+  if (empty($node->project['uri'])) {
+    if (variable_get('project_sandbox_numeric_shortname', FALSE) && $node->project['sandbox']) {
+      $node->project['uri'] = '';
+    }
+    else {
+      return t('A short project name is required.');
+    }
+  }
+  else {
+    // Make sure uri only includes valid characters
+    if (!preg_match('/^[a-zA-Z0-9_-]+$/', $node->project['uri'])) {
+      return t('Please only use alphanumerical characters for the project name.');
+    }
+
+    // Make sure uri isn't already in use, or reserved.  Includes all X from
+    // project/issues/X paths used in project_issues module
+    $reserved_names = array('user', 'issues', 'releases', 'rss', 'subscribe-mail', 'search', 'add', 'update_project', 'statistics', 'comments', 'autocomplete', 'cvs', 'developers', 'usage');
+    if (project_use_taxonomy()) {
+      $terms = taxonomy_get_tree(_project_get_vid());
+      foreach ($terms as $i => $term) {
+        if ($term->depth == 0) {
+          $reserved_names[] = strtolower($term->name);
+        }
+      }
+    }
+    if (in_array(strtolower($node->project['uri']), $reserved_names)) {
+      return t('This project name is reserved.');
+    }
+    $existing_nid = project_get_nid_from_uri($node->project['uri']);
+    if (!empty($existing_nid) && $existing_nid != $node->nid) {
+      return t('This project name is already in use.');
+    }
+  }
+
+  return FALSE;
+}
diff --git project.test project.test
index 7e616fd..0c95155 100644
--- project.test
+++ project.test
@@ -13,7 +13,7 @@ class ProjectWebTestCase extends DrupalWebTestCase {
     // this ugly call_user_func_array().
     call_user_func_array(array($this, 'parent::setUp'), $modules);
 
-    $perms = array('maintain projects', 'access user profiles', 'access projects');
+    $perms = array('create full projects', 'access user profiles', 'access projects');
 
     $this->owner = $this->drupalCreateUser($perms);
     $this->drupalLogin($this->owner);
@@ -123,7 +123,7 @@ class ProjectTestCase extends ProjectWebTestCase {
   function setUp() {
     parent::setUp('path');
 
-    $maintain_user = $this->drupalCreateUser(array('maintain projects'));
+    $maintain_user = $this->drupalCreateUser(array('create full projects'));
     $this->drupalLogin($maintain_user);
   }
 
@@ -216,7 +216,7 @@ class ProjectDrupalOrgWebTestCase extends ProjectWebTestCase {
     $this->admin_user = $this->drupalCreateUser(array(
       'access projects',
       'administer projects',
-      'maintain projects',
+      'create full projects',
       'access project issues',
       'create project issues',
       'administer content types',
diff --git views/project.views.inc views/project.views.inc
index b9c6f98..2a466e6 100644
--- views/project.views.inc
+++ views/project.views.inc
@@ -189,6 +189,26 @@ function project_views_data() {
     ),
   );
 
+  // sandbox
+  $data['project_projects']['sandbox'] = array(
+    'title' => t('Sandbox'),
+    'help' => t('Indicate whether or not this project is a sandbox.'),
+    'field' => array(
+      'group' => t('Project'),
+      'handler' => 'views_handler_field_boolean',
+      'click sortable' => TRUE,
+     ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_boolean_operator ',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_numeric',
+    ),
+  );
+
   return $data;
 }
 
