diff --git a/api-samples/storage/stylizr/README.md b/api-samples/storage/stylizr/README.md
new file mode 100644
index 00000000..a3ee7c7c
--- /dev/null
+++ b/api-samples/storage/stylizr/README.md
@@ -0,0 +1,13 @@
+# chrome.storage - Stylizr
+
+A sample that demonstrates how to use the [`chrome.storage`](https://developer.chrome.com/docs/extensions/reference/storage/) API.
+
+## Overview
+
+In this sample, the `chrome.storage` API stores a custom style string that will be applied to the active page when the extension's action icon is clicked.
+
+## 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 see the effect.
diff --git a/api-samples/storage/stylizr/icon.png b/api-samples/storage/stylizr/icon.png
new file mode 100644
index 00000000..30098be0
Binary files /dev/null and b/api-samples/storage/stylizr/icon.png differ
diff --git a/api-samples/storage/stylizr/manifest.json b/api-samples/storage/stylizr/manifest.json
new file mode 100644
index 00000000..0479ecf0
--- /dev/null
+++ b/api-samples/storage/stylizr/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "Stylizr",
+ "description": "Demonstrates how to use the chrome.storage API.",
+ "version": "1.0",
+ "permissions": ["activeTab", "storage", "scripting"],
+ "options_page": "options.html",
+ "action": {
+ "default_icon": "icon.png",
+ "default_title": "Stylize!",
+ "default_popup": "popup.html"
+ },
+ "manifest_version": 3
+}
diff --git a/api-samples/storage/stylizr/options.html b/api-samples/storage/stylizr/options.html
new file mode 100644
index 00000000..4e4a9f01
--- /dev/null
+++ b/api-samples/storage/stylizr/options.html
@@ -0,0 +1,51 @@
+
+
+
+ Stylizr
+
+
+
+
+
+
Stylizr Instructions
+
+
+
Write CSS in this textarea and save
+
Navigate to some page
+
Click the browser action icon
+
Hey presto! CSS is injected!
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api-samples/storage/stylizr/options.js b/api-samples/storage/stylizr/options.js
new file mode 100644
index 00000000..a4c814cf
--- /dev/null
+++ b/api-samples/storage/stylizr/options.js
@@ -0,0 +1,56 @@
+// Store CSS data in the "local" storage area.
+const storage = chrome.storage.local;
+
+// Get at the DOM controls used in the sample.
+const resetButton = document.querySelector('button.reset');
+const submitButton = document.querySelector('button.submit');
+const textarea = document.querySelector('textarea');
+
+// Load any CSS that may have previously been saved.
+loadChanges();
+
+submitButton.addEventListener('click', saveChanges);
+resetButton.addEventListener('click', reset);
+
+async function saveChanges() {
+ // Get the current CSS snippet from the form.
+ const cssCode = textarea.value;
+ // Check that there's some code there.
+ if (!cssCode) {
+ message('Error: No CSS specified');
+ return;
+ }
+ // Save it using the Chrome extension storage API.
+ await storage.set({ css: cssCode });
+ message('Settings saved');
+}
+
+function loadChanges() {
+ storage.get('css', function (items) {
+ // To avoid checking items.css we could specify storage.get({css: ''}) to
+ // return a default value of '' if there is no css value yet.
+ if (items.css) {
+ textarea.value = items.css;
+ message('Loaded saved CSS.');
+ }
+ });
+}
+
+async function reset() {
+ // Remove the saved value from storage. storage.clear would achieve the same
+ // thing.
+ await storage.remove('css');
+ message('Reset stored CSS');
+ // Refresh the text area.
+ textarea.value = '';
+}
+
+let messageClearTimer;
+function message(msg) {
+ clearTimeout(messageClearTimer);
+ const message = document.querySelector('.message');
+ message.innerText = msg;
+ messageClearTimer = setTimeout(function () {
+ message.innerText = '';
+ }, 3000);
+}
diff --git a/api-samples/storage/stylizr/popup.html b/api-samples/storage/stylizr/popup.html
new file mode 100644
index 00000000..a799a5a1
--- /dev/null
+++ b/api-samples/storage/stylizr/popup.html
@@ -0,0 +1,17 @@
+
+
+
+ Stylizr
+
+
+
+
+
+
+
+
diff --git a/api-samples/storage/stylizr/popup.js b/api-samples/storage/stylizr/popup.js
new file mode 100644
index 00000000..6c45abcb
--- /dev/null
+++ b/api-samples/storage/stylizr/popup.js
@@ -0,0 +1,39 @@
+// Store CSS data in the "local" storage area.
+const storage = chrome.storage.local;
+
+const message = document.querySelector('#message');
+
+// Check if there is CSS specified.
+async function run() {
+ const items = await storage.get('css');
+ if (items.css) {
+ const [currentTab] = await chrome.tabs.query({
+ active: true,
+ currentWindow: true
+ });
+ try {
+ await chrome.scripting.insertCSS({
+ css: items.css,
+ target: {
+ tabId: currentTab.id
+ }
+ });
+ message.innerText = 'Injected style!';
+ } catch (e) {
+ console.error(e);
+ message.innerText = 'Injection failed. Are you on a special page?';
+ }
+ } else {
+ const optionsUrl = chrome.runtime.getURL('options.html');
+ const optionsPageLink = document.createElement('a');
+ optionsPageLink.target = '_blank';
+ optionsPageLink.href = optionsUrl;
+ optionsPageLink.textContent = 'options page';
+ message.innerText = '';
+ message.appendChild(document.createTextNode('Set a style in the '));
+ message.appendChild(optionsPageLink);
+ message.appendChild(document.createTextNode(' first.'));
+ }
+}
+
+run();