diff --git a/api-samples/userScripts/README.md b/api-samples/userScripts/README.md
new file mode 100644
index 0000000000..4f5d83b813
--- /dev/null
+++ b/api-samples/userScripts/README.md
@@ -0,0 +1,29 @@
+# chrome.userScripts API
+
+This sample demonstrates using the [`chrome.userScripts`](https://developer.chrome.com/docs/extensions/reference/scripting/) API to inject JavaScript into web pages.
+
+## Overview
+
+Clicking this extension's action icon opens an options page.
+
+
+
+## Running this extension
+
+1. Clone this repository.
+2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
+3. Click the extension's action icon to open the options page.
+4. Once a user script has been configured, visit https://example.com/.
+
+## Features
+
+This sample allows you to inject the following:
+
+- Files
+- Arbitrary code
+
+## Implementation Notes
+
+The User Scripts API requires users to enabled developer mode. We check for this by attempting to access `chrome.userScripts`, which throws an error on property access if it is disabled.
+
+When a change is made on the options page, use the `chrome.userScripts` API to update the user script registration.
diff --git a/api-samples/userScripts/manifest.json b/api-samples/userScripts/manifest.json
new file mode 100644
index 0000000000..5d6cb66d1c
--- /dev/null
+++ b/api-samples/userScripts/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "User Scripts API Demo",
+ "version": "1.0",
+ "manifest_version": 3,
+ "minimum_chrome_version": "120",
+ "description": "Uses the chrome.userScripts API to inject JavaScript into web pages.",
+ "background": {
+ "service_worker": "sw.js"
+ },
+ "permissions": ["storage", "userScripts"],
+ "host_permissions": ["https://example.com/*"],
+ "action": {},
+ "options_ui": {
+ "page": "options.html",
+ "open_in_tab": false
+ }
+}
diff --git a/api-samples/userScripts/options.css b/api-samples/userScripts/options.css
new file mode 100644
index 0000000000..15be65e7f9
--- /dev/null
+++ b/api-samples/userScripts/options.css
@@ -0,0 +1,61 @@
+/*
+Copyright 2023 Google LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+html {
+ padding: 0 10px;
+}
+
+#warning {
+ display: none;
+ margin-bottom: 30px;
+}
+
+label {
+ display: flex;
+ align-items: center;
+}
+
+label input {
+ margin-right: 10px;
+}
+
+textarea {
+ resize: none;
+ width: calc(100% - 35px);
+ border: 2px solid black;
+ background: rgb(34, 34, 34);
+ padding: 15px;
+ color: white;
+}
+
+textarea:focus {
+ border: 2px solid grey;
+ outline: none;
+}
+
+button {
+ margin: 20px 0;
+}
+
+/* Hide custom script textarea by default */
+#custom-script-wrapper {
+ display: none;
+}
+
+/* Only show custom script textarea when custom type is selected */
+form:has(input[name='type'][value='custom']:checked) #custom-script-wrapper {
+ display: block;
+}
diff --git a/api-samples/userScripts/options.html b/api-samples/userScripts/options.html
new file mode 100644
index 0000000000..c95bde435b
--- /dev/null
+++ b/api-samples/userScripts/options.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ User Scripts API Demo
+
+
+
+
+
+
+ ⚠️ To use the User Scripts API, you need to first enable developer mode
+ at chrome://extensions.
+
+
+
+
diff --git a/api-samples/userScripts/options.js b/api-samples/userScripts/options.js
new file mode 100644
index 0000000000..5fa3a2a6ef
--- /dev/null
+++ b/api-samples/userScripts/options.js
@@ -0,0 +1,100 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+const USER_SCRIPT_ID = 'default';
+const SAVE_BUTTON_ID = 'save-button';
+
+const FORM_ID = 'settings-form';
+const FORM = document.getElementById(FORM_ID);
+
+const TYPE_INPUT_NAME = 'type';
+const SCRIPT_TEXTAREA_NAME = 'custom-script';
+
+/**
+ * Checks if the user has developer mode enabled, which is required to use the
+ * User Scripts API.
+ *
+ * @returns If the chrome.userScripts API is available.
+ */
+function isUserScriptsAvailable() {
+ try {
+ // Property access which throws if developer mode is not enabled.
+ chrome.userScripts;
+ return true;
+ } catch {
+ // Not available, so hide UI and show error.
+ document.getElementById('warning').style.display = 'block';
+ FORM.style.display = 'none';
+ return false;
+ }
+}
+
+async function updateUi() {
+ if (!isUserScriptsAvailable()) return;
+
+ // Access settings from storage with default values.
+ const { type, script } = await chrome.storage.local.get({
+ type: 'file',
+ script: "alert('hi');"
+ });
+
+ // Update UI with current values.
+ FORM.elements[TYPE_INPUT_NAME].value = type;
+ FORM.elements[SCRIPT_TEXTAREA_NAME].value = script;
+}
+
+async function onSave() {
+ if (!isUserScriptsAvailable()) return;
+
+ // Get values from form.
+ const type = FORM.elements[TYPE_INPUT_NAME].value;
+ const script = FORM.elements[SCRIPT_TEXTAREA_NAME].value;
+
+ // Save to storage.
+ chrome.storage.local.set({
+ type,
+ script
+ });
+
+ const existingScripts = await chrome.userScripts.getScripts({
+ ids: [USER_SCRIPT_ID]
+ });
+
+ if (existingScripts.length > 0) {
+ // Update existing script.
+ await chrome.userScripts.update([
+ {
+ id: USER_SCRIPT_ID,
+ matches: ['https://example.com/*'],
+ js: type === 'file' ? [{ file: 'user-script.js' }] : [{ code: script }]
+ }
+ ]);
+ } else {
+ // Register new script.
+ await chrome.userScripts.register([
+ {
+ id: USER_SCRIPT_ID,
+ matches: ['https://example.com/*'],
+ js: type === 'file' ? [{ file: 'user-script.js' }] : [{ code: script }]
+ }
+ ]);
+ }
+}
+
+// Update UI immediately, and on any storage changes.
+updateUi();
+chrome.storage.local.onChanged.addListener(updateUi);
+
+// Register listener for save button click.
+document.getElementById(SAVE_BUTTON_ID).addEventListener('click', onSave);
diff --git a/api-samples/userScripts/screenshot.png b/api-samples/userScripts/screenshot.png
new file mode 100644
index 0000000000..b709887b4e
Binary files /dev/null and b/api-samples/userScripts/screenshot.png differ
diff --git a/api-samples/userScripts/sw.js b/api-samples/userScripts/sw.js
new file mode 100644
index 0000000000..0771a9dd68
--- /dev/null
+++ b/api-samples/userScripts/sw.js
@@ -0,0 +1,23 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+chrome.runtime.onInstalled.addListener(({ reason }) => {
+ if (reason == chrome.runtime.OnInstalledReason.INSTALL) {
+ chrome.runtime.openOptionsPage();
+ }
+});
+
+chrome.action.onClicked.addListener(() => {
+ chrome.runtime.openOptionsPage();
+});
diff --git a/api-samples/userScripts/user-script.js b/api-samples/userScripts/user-script.js
new file mode 100644
index 0000000000..c83fdbd691
--- /dev/null
+++ b/api-samples/userScripts/user-script.js
@@ -0,0 +1,15 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+alert('Hello World!');