[Google Apps Script](https://developers.google.com/apps-script/overview.md.txt) is a cloud-based scripting
environment included with Google Workspace as a core service. It is available
to all users by default. Within a Google Workspace organization, users
can build scripts ranging from spreadsheet macros to full integrations, such as
[Google Workspace add-ons](https://developers.google.com/workspace/add-ons).

Because Apps Script projects automatically create
Google Cloud projects, administrators benefit from understanding the lifecycle and
management of these projects. This guide explains how default
Cloud projects work, how to audit and manage them, and how to establish
formal governance for Apps Script projects in your organization.

## Types of Google Cloud projects used by Apps Script

Every Apps Script project uses Google Cloud to manage
authorization, [advanced services](https://developers.google.com/apps-script/guides/services/advanced.md.txt), and other project
settings. To configure and manage these settings, every
Apps Script project has an associated Google Cloud project:

- **Default Cloud projects** : Apps Script creates a [default Cloud project](https://developers.google.com/apps-script/guides/cloud-platform-projects.md.txt#default_google_cloud_projects) automatically when a user creates and runs a script project. Default Cloud projects work well for everyday or single-user scripts.
- **Standard Cloud projects** : Users can create and manage their own [standard Cloud project](https://developers.google.com/apps-script/guides/cloud-platform-projects.md.txt#standard_google_cloud_projects) for complex, commercial-grade, or published applications.

> [!NOTE]
> **Note:** Script projects created before April 8, 2019 might use a default Cloud project that you can open directly in the Google Cloud console. To open the project, go to the script project settings and click the project number.

This guide focuses on governing the default Cloud projects that
Apps Script creates automatically.

## About default Cloud projects

Default Cloud projects created by Apps Script are hidden
by default. Most users cannot locate, view, or update them in the
Google Cloud console. Only administrators and domain users with the
`resourcemanager.projects.list` permission on the parenting Google Cloud
folder can view and configure default projects. To grant these permissions, see
[Assign permissions for Google Cloud projects](https://developers.google.com/apps-script/guides/admin/assign-cloud-permissions.md.txt).

Each default Cloud project has an Identity and Access Management (IAM)
policy with a single entry. That entry is a Google service account that acts as
the owner of the project. This service account is always
`appsdev-apps-dev-script-auth@system.gserviceaccount.com`.

For Google Workspace organizations, default Cloud projects reside in
the following folder path within the
[Resource Manager hierarchy](https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy):

`Organization root > system-gsuite > apps-script`
![Default Cloud projects in the Resource Manager hierarchy](https://developers.google.com/static/apps-script/images/screenshots/extensions_apps_script.png) **Figure 1.** Default Cloud projects in the Resource Manager hierarchy.

> [!CAUTION]
> **Caution:** Don't delete the `system-gsuite` or `apps-script` folders. If you delete these folders, Apps Script cannot create default Cloud projects and scripts fail to execute.

For non-Google Workspace accounts, default Cloud projects are
unparented in the Resource Manager hierarchy. For more details about viewing
these folders and projects in the Google Cloud console, see
[View or edit Google Cloud projects](https://developers.google.com/apps-script/guides/admin/view-cloud-projects.md.txt) and
[Default Cloud projects](https://developers.google.com/apps-script/guides/cloud-platform-projects.md.txt#default_google_cloud_projects).

## Common sources of default Cloud projects

Standalone Apps Script projects create default
Cloud projects automatically. However, most default
Cloud projects in an organization originate from
[container-bound scripts](https://developers.google.com/apps-script/guides/bound.md.txt). Bound scripts are attached to a Google
Workspace file, such as a Google Sheets spreadsheet. Users access them by
clicking **Extensions** \>
**Apps Script**. Bound scripts are commonly used for macros,
reporting, and workflow automation.
![Default Cloud projects created from bound scripts](https://developers.google.com/static/apps-script/images/screenshots/sample_apps_script_project.png) **Figure 2.** Default Cloud projects created from bound scripts.

When someone in your organization develops a useful bound script, other users
often make copies of that spreadsheet for their own tasks. For example, a user
might share a script that generates a weekly status report. Each copy of the
spreadsheet includes a copy of the bound script. When a user runs the script in
their copy, Apps Script creates a new default
Cloud project. As a result, the number of default
Cloud projects can grow quickly to thousands of projects in large
organizations. The default Cloud project is only created when the user
first invokes the script.

## Audit default Cloud projects

You can audit default Cloud projects across your organization using the
`gcloud` command-line tool, the Cloud Resource Manager API, the
Drive audit log, or an automated monitoring script.

### List projects using the `gcloud` command-line tool

Default projects reside in the
`Organization root > system-gsuite > apps-script` folder hierarchy. You can use
the `gcloud` command-line tool to list all projects that have the `apps-script`
folder as their parent:

1. In the Google Cloud console, go to **IAM \& Admin** \> **Manage Resources** . Locate the folder ID of the `apps-script` folder under `system-gsuite`. For step-by-step instructions, see [View or edit default
   Cloud projects](https://developers.google.com/apps-script/guides/admin/view-cloud-projects#view-or-edit-default-cloud-projects).
2. Run the following command in Cloud Shell, replacing `FOLDER_ID` with your
   `apps-script` folder ID:

   ```bash
   gcloud projects list --filter='parent.id:FOLDER_ID'
   ```

The command output includes the `NAME` of each project. This name matches the
name of the associated Apps Script project unless someone renamed
the script after creation. Default project IDs begin with `sys-`.

### List projects using the Cloud Resource Manager API

You can also query the [Cloud Resource Manager API](https://cloud.google.com/resource-manager/docs) to
list all default Cloud projects in your organization. Filter your
query by projects located within the
`Organization root > system-gsuite > apps-script` parent folder. Alternatively,
filter by projects where the IAM policy owner is the service account
`appsdev-apps-dev-script-auth@system.gserviceaccount.com`.

### List projects using the Drive audit log

Standalone Apps Script projects are stored as files in
Google Drive. You can inspect project creation events in the
[Drive audit log](https://admin.google.com/ac/reporting/audit/drive) in the Google Admin console.
This log helps you identify who in your organization creates standalone
Apps Script projects. For step-by-step filtering instructions,
see [View Apps Script audit logs](https://developers.google.com/apps-script/guides/admin/monitor-use.md.txt#view-audit-logs).

### Automate detection of new default projects

You can combine the Cloud Resource Manager API with the Reports API to
review newly created default Cloud projects automatically. This
approach correlates new projects with Drive file creation and
OAuth token audit events that occurred within 30 seconds of project creation.

Running this check on an hourly schedule lets you notify administrators by email
whenever a user triggers the creation of a new default Cloud project.
The notification can include the following details:

- The email address of the user who triggered the event.
- The names and IDs of the associated files, such as a standalone script or a Sheets spreadsheet with a bound script.
- The OAuth client IDs and granted OAuth scopes.

For a complete implementation, see the
[Sample script: Detect new default Cloud projects](https://developers.google.com/apps-script/guides/admin/project-governance.md.txt#sample-script)
section in this guide.

## Manage default Cloud projects

Use the following controls to manage the creation and lifecycle of default
Cloud projects in your organization.

### Prevent creation of new default projects

When users have access to Apps Script, you cannot prevent
Apps Script from creating default Cloud projects when
users create and run new scripts. To prevent users from creating default
Cloud projects, you must turn off the Apps Script
service for those users, organizational units (OUs), or groups.

### Turn off Apps Script for users

Google Workspace administrators can [turn Apps Script on
or off for users](https://support.google.com/a/answer/15100049) by OU or group without turning off
Google Drive. For more information, see
[Turn off Apps Script](https://developers.google.com/apps-script/guides/admin/monitor-use#turn-Apps Script).

Before turning off Apps Script for users, evaluate how the change
affects the following groups:

1. **Users of internal tools powered by Apps Script**: Users can no longer run internal tools when the underlying script executes under the user's own credentials. Affected tools include Google Sheets macros, reporting spreadsheets, and internal web apps. Scripts bound to Sheets spreadsheets are typically the largest source of default Cloud projects in an organization and are the most frequently affected.
2. **Creators and editors of internal tools**: Users can no longer open or edit their scripts in the Apps Script editor. In addition, if a user authors an Apps Script web app that executes under the author's credentials, other users can no longer run that web app.

> [!NOTE]
> **Note:** Turning off Apps Script for users does not affect published add-ons powered by Apps Script. This exception applies to both public third-party add-ons and internally published add-ons.

### Delete default projects

Default Cloud projects are deleted under the following conditions:

- **User deletion or project switch**: When a user deletes their script project, deletes the file to which the script is bound, or switches the script to use a different Cloud project, Apps Script deletes the associated default Cloud project and its settings.
- **Administrator deletion** : Administrators with project deletion permissions can shut down a specific default Cloud project to stop script executions. For instructions, see [Turn off a specific Apps Script project](https://developers.google.com/apps-script/guides/admin/monitor-use.md.txt#turn-specific).
- **Automatic inactivity cleanup**: Apps Script automatically deletes default Cloud projects when their associated scripts have not run for 180 days or more.

> [!NOTE]
> **Note:** If a user runs an Apps Script project after its default Cloud project was deleted, Apps Script automatically creates a new default Cloud project so that the script can run again.

## Governance recommendations

Consider the following best practices to establish governance for internal
Apps Script projects in your Google Workspace organization:

- **Require a naming convention for standalone projects** : Establish a policy that requires users to include a clear identifier, such as `Apps Script`, in the name of standalone Apps Script projects. This practice helps IT teams distinguish script projects from other Cloud projects.
- **Monitor and enforce OAuth scope policies** : Monitor OAuth grants in the Admin console or use a third-party monitoring tool that checks OAuth access granted by users. Enforce policies based on users, groups, and requested OAuth scopes. Configure alerts or automatic token revocation when non-compliant scopes are granted. For instructions, see [Monitor \& restrict data access](https://developers.google.com/apps-script/guides/admin/monitor-restrict-oauth-scopes.md.txt).
- **Audit and allowlist external URLs for `UrlFetchApp`** : Scripts can use the
  [`UrlFetchApp`](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app.md.txt) service to make HTTP requests to external
  endpoints. These requests could export data from Gmail or
  Drive outside your organization. Configure an allowlist of
  approved external domains in the Admin console. For details,
  see [Control access to external domains](https://developers.google.com/apps-script/guides/admin/monitor-use.md.txt#control-access) and
  [Allow only certain external connections for Apps Script and
  Sheets](https://support.google.com/a/answer/13686736).

  > [!IMPORTANT]
  > **Important:** Many scripts use `UrlFetchApp` to call Google APIs that don't have built-in Apps Script services, such as fetching data from a Firebase database. Consider allowlisting trusted Google API domains, such as `googleapis.com` and `firebaseio.com`, to avoid breaking scripts that access Google services.

- **Appoint an Apps Script governance team**: Designate a team
  to oversee internal script development and lifecycle management:

  - **Establish review policies** : Define which internal projects are permitted by default and which require security review before deployment. For example, require reviews for scripts that send or retrieve external data using [`UrlFetchApp`](https://developers.google.com/apps-script/reference/url-fetch.md.txt) or [JDBC](https://developers.google.com/apps-script/guides/jdbc.md.txt), or scripts that request high-risk OAuth scopes. Require developers to declare all external URLs, databases, and scopes for approval.
  - **Manage project ownership and lifecycle**: Audit projects created by employees who leave the organization. Regularly check in with active project owners to confirm whether existing scripts are still needed.
  - **Publish internal guidance**: Share governance policies on an internal site and offer support channels, such as office hours or a Google Chat space, for developer questions.
  - **Promote approved internal solutions** : Catalog approved scripts on an internal directory and allowlist vetted solutions for internal distribution as published [add-ons](https://developers.google.com/workspace/add-ons) or [Google Chat apps](https://developers.google.com/workspace/chat).

## Sample script: Detect new default Cloud projects

The following sample script uses the Cloud Resource Manager API and the
Reports API (`AdminReports` advanced service) to identify default
Cloud projects created within a configurable time window, such as the
past 60 minutes. The script emails a report containing correlated
Drive file creation and edit events and OAuth token authorization
events that occurred within 30 seconds of project creation.

### Before you begin

To run this sample script, complete the following setup steps:

1. Create a new standalone Apps Script project and associate it with a [standard Cloud project](https://developers.google.com/apps-script/guides/cloud-platform-projects.md.txt#standard_google_cloud_projects).
2. In the Google Cloud console, enable the **Cloud Resource Manager API** and the **Admin SDK API** in your standard Cloud project.
3. In the Apps Script editor, enable the **Admin SDK API** (`reports_v1`) [advanced service](https://developers.google.com/apps-script/guides/services/advanced.md.txt) with the identifier `AdminReports`.
4. Copy the following `appsscript.json` manifest and `main.gs` code into your script project. Replace `<your-email-here@your-email.com>` with the recipient email address for the report.
5. Set up a [time-driven installable trigger](https://developers.google.com/apps-script/guides/triggers/installable.md.txt) to run `getNewProjects` at your chosen interval, such as every 60 minutes.

### `appsscript.json`

```json
{
  "timeZone": "America/New_York",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "AdminReports",
        "version": "reports_v1",
        "serviceId": "admin"
      }
    ]
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/admin.reports.audit.readonly",
    "https://www.googleapis.com/auth/script.send_mail"
  ]
}
```

### `main.gs`

```javascript
// Example code for listing and reporting on default Apps Script Cloud projects.
// Sample code only - not to be used for production use cases.
//
// Setup:
// - Must use a standard Google Cloud project (not auto-generated)
// - Must enable Cloud Resource Manager API in the Google Cloud console
// - Must enable Admin SDK API in the Google Cloud console
// - Must add Admin SDK API (reports_v1) as an Apps Script advanced service
//   named "AdminReports"
// - Must set up a time-driven trigger to run getNewProjects() every
//   TIME_WINDOW_MINUTES minutes.

// How far back to look for new Cloud projects created by Apps Script (minutes).
const TIME_WINDOW_MINUTES = 60;

// Recipient email address for the governance report.
const report_email = '<your-email-here@your-email.com>';

function getNewProjects() {
  const url =
      'https://cloudresourcemanager.googleapis.com/v1/projects' +
      '?filter=projectId:sys-*';
  let email_body = '<html><body>';
  const options = {
    method: 'GET',
  };
  const res = restCall(url, options);
  if (!res.projects || !res.projects.length) {
    console.log('No Cloud projects found for your organization!');
    return;
  }
  let num_autocreated_projects = 0;
  for (let p = 0; p < res.projects.length; p++) {
    const project = res.projects[p];
    const project_id = project.projectId;
    const project_create_time = project.createTime;
    const project_create_dt = new Date(project_create_time);
    const proj = getIamPolicy(project_id);
    const now = new Date();
    const time_window = new Date(now.getTime() - (TIME_WINDOW_MINUTES * 60000));
    console.log('Checking project %d of %d', p + 1, res.projects.length);

    // Check if the Cloud project was created by Apps Script automatically.
    const defaultOwner =
        'serviceAccount:appsdev-apps-dev-script-auth@system.gserviceaccount.com';
    if (proj.bindings && proj.bindings[0].members[0] === defaultOwner) {
      // Check if the project was created within the configured time window.
      if (project_create_dt >= time_window) {
        num_autocreated_projects++;
        email_body +=
            '<div><b>Project details:</b><br>' +
            JSON.stringify(project) +
            '<br><br>';
        const drive_events = getRelatedDriveEvents(project_create_dt);
        const token_events = getRelatedTokenEvents(project_create_dt);
        if (drive_events.length || token_events.length) {
          for (let dei = 0; dei < drive_events.length; dei++) {
            const activity = drive_events[dei];
            email_body +=
                '<div style="margin-left:25px;">' +
                '<b>Possibly related Drive create/edit event from</b> ' +
                activity.actor.email +
                ' at ' +
                activity.id.time +
                '. Details: ' +
                JSON.stringify(activity.events[0].parameters) +
                '</div><br>';
          }
          for (let tei = 0; tei < token_events.length; tei++) {
            const activity = token_events[tei];
            email_body +=
                '<div style="margin-left:25px;">' +
                '<b>Possibly related token auth event from</b> ' +
                activity.actor.email +
                ' at ' +
                activity.id.time +
                '. Details: ' +
                JSON.stringify(activity.events[0].parameters) +
                '</div><br>';
          }
        } else {
          email_body +=
              '<div style="margin-left:25px;">' +
              'No related Drive or Token events found for this project' +
              '</div>';
        }
        email_body += '</div><br>';
      }
    }
  }
  email_body += '</body></html>';
  if (num_autocreated_projects === 0) {
    console.log(
        'Did not find any recently created Cloud projects from Apps Script'
    );
    return;
  }
  const email_opts = {
    htmlBody: email_body,
    noReply: true,
  };
  MailApp.sendEmail(
      report_email,
      'New autocreated Cloud projects',
      '',
      email_opts
  );
  console.log(email_body);
}

function getIamPolicy(projectId) {
  const baseUrl = 'https://cloudresourcemanager.googleapis.com/v1/projects';
  const url = `${baseUrl}/${projectId}:getIamPolicy`;
  const options = {
    method: 'POST',
  };
  return restCall(url, options);
}

function restCall(url, options) {
  options.headers = {
    'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`,
    'Content-Type': 'application/json',
  };
  let obj = {};
  do {
    const res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() > 300) {
      return obj;
    }
    const data = JSON.parse(res.getContentText());
    options.pageToken = data.nextPageToken;
    obj = {...obj, ...data};
  } while (options.pageToken);
  return obj;
}

function getRelatedDriveEvents(event_dt) {
  // New files (such as copied scripts).
  const create_events = getRelatedEventsByAppAndEventName(
      'drive',
      'create',
      event_dt
  );
  // Edited files (such as spreadsheets updated when a bound script first runs).
  const edit_events = getRelatedEventsByAppAndEventName(
      'drive',
      'edit',
      event_dt
  );
  if (edit_events.length) {
    for (let i = 0; i < edit_events.length; i++) {
      create_events.push(edit_events[i]);
    }
  }
  return create_events;
}

function getRelatedTokenEvents(event_dt) {
  // OAuth tokens granted.
  return getRelatedEventsByAppAndEventName('token', 'authorize', event_dt);
}

function getRelatedDriveEventsByApp(event_name, event_dt) {
  return getRelatedEventsByAppAndEventName('drive', event_name, event_dt);
}

function getRelatedTokenEventsByApp(event_name, event_dt) {
  return getRelatedEventsByAppAndEventName('token', event_name, event_dt);
}

function getRelatedEventsByAppAndEventName(
    application_name,
    event_name,
    event_dt
) {
  // Retrieve matching audit events within 30 seconds of project creation.
  const results = [];
  const start_dt = new Date(event_dt.getTime() - 30000);
  const end_dt = new Date(event_dt.getTime() + 30000);
  const userKey = 'all';
  const optionalArgs = {
    eventName: event_name,
    maxResults: 50,
    startTime: start_dt.toISOString(),
    endTime: end_dt.toISOString(),
  };
  const response = AdminReports.Activities.list(
      userKey,
      application_name,
      optionalArgs
  );
  const activities = response.items;
  if (activities && activities.length > 0) {
    for (let i = 0; i < activities.length; i++) {
      results.push(activities[i]);
    }
  }
  return results;
}
```

## Related resources

- [Google Cloud projects in Apps Script](https://developers.google.com/apps-script/guides/cloud-platform-projects.md.txt)
- [Assign permissions for Google Cloud projects](https://developers.google.com/apps-script/guides/admin/assign-cloud-permissions.md.txt)
- [View or edit Google Cloud projects](https://developers.google.com/apps-script/guides/admin/view-cloud-projects.md.txt)
- [Monitor and control Apps Script use](https://developers.google.com/apps-script/guides/admin/monitor-use.md.txt)
- [Monitor \& restrict data access](https://developers.google.com/apps-script/guides/admin/monitor-restrict-oauth-scopes.md.txt)