Configuring Extensions with Enterprise Managed Storage

Let administrators configure an MV3 extension through policy: managed_schema, chrome.storage.managed, deploying values via Windows registry, macOS profiles and Linux JSON, locking settings in the UI, and Firefox support.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

An IT department deploys your extension to 5,000 machines and needs it pre-configured: the company’s API endpoint, single sign-on enabled, telemetry off, one feature disabled by policy. Asking every employee to open the options page is not an option, and neither is shipping a custom build per customer. Chrome’s managed storage lets administrators push configuration through the same enterprise policy channels they use for the browser itself; the extension reads it from chrome.storage.managed, a read-only area. This guide defines a schema, reads and applies policy values, and shows how they reach machines on each OS. It belongs to options page configuration.

How managed storage works

The extension declares a JSON schema in its manifest (storage.managed_schema) describing the settings administrators may set. Administrators then deliver values through policy: Group Policy or the registry on Windows, configuration profiles on macOS, JSON files on Linux, or a cloud management console such as Chrome Enterprise or an MDM. Chrome validates the values against the schema and exposes them in chrome.storage.managed — readable from the service worker, extension pages and content scripts, but not writable by the extension. Values update when policy refreshes, firing storage.onChanged with areaName === "managed". For users without policy, the area is simply empty, so the extension must treat managed values as an optional top layer above user settings.

From administrator to extensionThe administrator sets values in a management console or OS policy; Chrome receives the policy, validates it against the extension's managed_schema, and exposes it read-only in chrome.storage.managed, firing onChanged when it updates.Admin console / OS policyregistry, profile, JSONChrome policy3rdparty.extensions.<id>Validatemanaged_schemaexposed to the extensionstorage.managedread-onlyonChanged(managed)on policy refreshEffective settingsmanaged > user > defaults
The extension declares what may be set; administrators set it; Chrome validates and delivers.

Step-by-step: policy-configurable settings

1. Declare a managed schema

1// manifest.json
2{ "storage": { "managed_schema": "managed-schema.json" } }
 1{
 2  "type": "object",
 3  "properties": {
 4    "apiBaseUrl":      { "type": "string", "description": "Company API endpoint" },
 5    "ssoEnabled":      { "type": "boolean" },
 6    "telemetry":       { "type": "boolean" },
 7    "disabledFeatures":{ "type": "array", "items": { "type": "string" } },
 8    "lockedSettings":  { "type": "array", "items": { "type": "string" } }
 9  }
10}

Execution context: the manifest and a JSON schema file in the package. Chrome supports a subset of JSON Schema: type, properties, items, enum, minimum/maximum, pattern and $ref among others. Values that fail validation are dropped and reported on chrome://policy. Keep property names stable — administrators write them into policy files that live for years.

2. Read managed values defensively

1// shared/managed.js
2export async function getManaged() {
3  try {
4    return await chrome.storage.managed.get(null);      // {} when no policy is set
5  } catch {
6    return {};                                           // engines without managed storage
7  }
8}

Execution context: the service worker, extension pages or content scripts. On unmanaged machines the result is an empty object. Some engines throw if no schema or policy store exists; catching keeps the extension working everywhere. Never assume a managed value exists — every use needs a fallback.

Setting precedence with managed storageHow defaults, user global settings, per-site overrides and managed policy values combine, and whether the user can change each in the UI.LayerSourceUser can changeManaged (locked)AdministratorNo — shown disabledManaged (default)AdministratorYes, if not lockedSite overridesUserYesUser globalsUserYesDefaultsCodeVia settings
Policy sits on top; locked settings are shown but not editable.

3. Merge policy into effective settings

 1export async function effectiveSettings() {
 2  const [user, managed] = await Promise.all([chrome.storage.sync.get(null), getManaged()]);
 3  const locked = new Set(managed.lockedSettings ?? []);
 4  const result = { ...DEFAULTS };
 5  for (const k of Object.keys(DEFAULTS)) {
 6    if (k in managed && locked.has(k)) result[k] = managed[k];        // enforced
 7    else if (k in user) result[k] = user[k];                          // user's choice
 8    else if (k in managed) result[k] = managed[k];                    // admin-provided default
 9  }
10  return { ...result, _locked: [...locked] };
11}

Execution context: a shared module. Distinguishing enforced values (listed in lockedSettings) from recommended defaults gives administrators both tools: some settings they must control, others they only want to pre-set. Non-setting policies — an API endpoint, a feature kill list — are read directly where they are used.

4. Lock controls in the options page

1// options.js
2const eff = await effectiveSettings();
3for (const input of form.querySelectorAll("[name]")) {
4  if (eff._locked.includes(input.name)) {
5    input.disabled = true;
6    input.setAttribute("aria-describedby", "managed-note");
7    input.closest("label")?.append(Object.assign(document.createElement("span"), { className: "managed-badge", textContent: "Managed by your organization" }));
8  }
9}

Execution context: the options page. Users should see which settings their organisation controls instead of wondering why a toggle has no effect. A disabled control with a short explanation — and a link to contact IT if your audience needs it — is the expected enterprise pattern.

A policy change reaching an open options pageThe administrator turns telemetry off and locks it; Chrome refreshes policy; storage.onChanged fires with area managed in the worker and the open options page; the worker stops telemetry and the page disables the toggle.PolicyService workerOptions pageonChanged(managed: telemetry=false)stop telemetryonChanged(managed: lockedSettings)disable toggle …
Policy changes apply live, like any other storage change.

5. Deliver policy on each platform

 1// Linux: /etc/opt/chrome/policies/managed/readable.json
 2{
 3  "3rdparty": {
 4    "extensions": {
 5      "abcdefghijklmnopabcdefghijklmnop": {
 6        "apiBaseUrl": "https://readable.corp.example",
 7        "ssoEnabled": true,
 8        "telemetry": false,
 9        "lockedSettings": ["telemetry"]
10      }
11    }
12  }
13}

Execution context: an administrator’s policy file. On Windows, the same values go under HKLM\Software\Policies\Google\Chrome\3rdparty\extensions\<id>\policy (or Group Policy); on macOS, in a configuration profile for com.google.Chrome.extensions.<id>; cloud consoles provide a JSON editor per extension. Publish these snippets in your admin documentation with your real extension id — administrators copy them verbatim.

6. React to policy changes

1// sw.js
2chrome.storage.onChanged.addListener((changes, area) => {
3  if (area !== "managed") return;
4  if ("apiBaseUrl" in changes) resetApiClient();
5  if ("telemetry" in changes && changes.telemetry.newValue === false) stopTelemetry();
6  if ("disabledFeatures" in changes) applyFeatureKillList(changes.disabledFeatures.newValue ?? []);
7});

Execution context: the service worker, at the top level. Chrome refreshes policy periodically and when administrators push changes; the extension should apply new values without requiring a restart. Settings that affect privacy — telemetry, data collection — should take effect immediately when turned off.

7. Document and test the schema

Ship an administrator guide listing every property, its type, effect and an example per platform. Test by writing a policy file on a Linux or macOS test machine (or using the --policy testing flags where available), then checking chrome://policy for the extension’s values and validation errors and confirming the extension behaves accordingly.

Common mistakes

  • Assuming managed values exist. Most users have no policy; always fall back.
  • Changing property names. Breaks every administrator’s deployed policy.
  • Silent enforcement. Users need to see that a setting is managed.
  • Ignoring onChanged for managed. Policy updates then need a browser restart.
  • No admin documentation. Administrators cannot guess your schema.

Cross-browser variation

  • Chrome / Edge: managed_schema with policy via registry, profiles, JSON and cloud consoles; Edge uses its own policy paths (Microsoft\Edge).
  • Firefox: supports storage.managed through a native manifest file of type "storage" in the managed storage directory, or through the enterprise policies.json “3rdparty” section.
  • Safari: no managed storage for web extensions; configure the containing app through MDM-managed app configuration and pass values via native messaging.

Verification

  1. Without policy, confirm chrome.storage.managed.get(null) returns {} and the extension works normally.
  2. Apply a policy file and confirm values appear on chrome://policy under the extension’s id without errors.
  3. Confirm locked settings are disabled in options with an explanation.
  4. Change the policy and confirm the extension applies it without restart (use “Reload policies” on chrome://policy).

FAQ

Can the extension write to managed storage?

No. It is read-only for the extension; only policy writes it.

Do content scripts see managed values?

Yes, chrome.storage.managed is readable from content scripts.

Can policy install the extension too?

Yes — ExtensionInstallForcelist or ExtensionSettings force-installs it; managed storage configures it. See publishing private and enterprise extensions.

How do I support several customers with different configurations?

That is exactly what managed storage is for: one published extension, and each organisation’s administrators supply their own values. Avoid per-customer builds; they multiply review, release and support work.

What should happen when policy values are invalid?

Chrome drops values that fail schema validation and shows the error on chrome://policy. Your code should still validate semantic correctness — a syntactically valid but unreachable apiBaseUrl, for example — and report it in the options page so users can tell IT.

Can I detect that the extension is policy-installed?

chrome.management.getSelf() returns installType: "admin" for policy installs. Use it to adjust behaviour, such as skipping consumer onboarding or upsell prompts.

Should policy values be cached?

No need — chrome.storage.managed reads are fast and always current. Cache only derived objects, such as an API client built from apiBaseUrl, and rebuild them on change.

Other MV3 Architecture & Extension Lifecycle Resources