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.
Table of Contents
- How managed storage works
- Step-by-step: policy-configurable settings
- Common mistakes
- Cross-browser variation
- Verification
- FAQ
- Can the extension write to managed storage?
- Do content scripts see managed values?
- Can policy install the extension too?
- How do I support several customers with different configurations?
- What should happen when policy values are invalid?
- Can I detect that the extension is policy-installed?
- Should policy values be cached?
- Related
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.
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.
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.
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
onChangedfor managed. Policy updates then need a browser restart. - No admin documentation. Administrators cannot guess your schema.
Cross-browser variation
- Chrome / Edge:
managed_schemawith policy via registry, profiles, JSON and cloud consoles; Edge uses its own policy paths (Microsoft\Edge). - Firefox: supports
storage.managedthrough a native manifest file of type"storage"in the managed storage directory, or through the enterprisepolicies.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
- Without policy, confirm
chrome.storage.managed.get(null)returns{}and the extension works normally. - Apply a policy file and confirm values appear on
chrome://policyunder the extension’s id without errors. - Confirm locked settings are disabled in options with an explanation.
- 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.
Related
- Defaulting and versioning an options schema — the user-settings side.
- Per-site settings and overrides — another settings layer.
- MV2 deprecation timeline and enterprise exceptions — enterprise deployment context.
- Options page configuration — the parent topic.