For district IT
Setup guide
SafeYT has no server. Deploying it means three things: publish the extension, force-install it to student Chromebooks, and give it a policy. A pilot takes an afternoon.
This page is the short version. The full deployment guide on GitHub has every detail, including using SafeYT next to a whole-web filter. Admin console paths are as of 2026; if one has moved, type the setting name into the search box on the Chrome settings page.
Fastest path
Install from the Chrome Web Store
SafeYT is being published on the Chrome Web Store, so most schools will not need to build or host anything. Two steps:
- In Google Admin, add SafeYT to the student OU by its Chrome Web Store ID and set it to Force install + pin to browser toolbar (as in step 3).
- Paste the managed policy JSON into Policy for extensions (step 4), then set the required Chrome policies.
Chrome Web Store listing: coming soon. The link and extension ID will appear here once the listing is approved.
The store build has the media gate but not the stream-URL lockdown: the Chrome Web Store does not accept the blocking network permission that the lockdown needs, so the popup shows "Store build: media gate only". If your threat model includes students running their own scripts against YouTube's player, or you want the lockdown as a second layer, use the self-hosted build in steps 2 and 3 instead.
Step 1
Requirements
- Chromebooks enrolled in your Google Workspace for Education domain, with students signing in to managed accounts.
- Google Admin console access with rights to manage Chrome apps, extensions and settings for the student organizational units (OUs).
- A pilot OU: one class or a few staff test accounts. Start there.
- Optional: somewhere to host one JSON file over HTTPS (a GitHub repository works), so curriculum staff can edit channel lists without touching Admin.
Step 2
Publish the extension
Get the source from GitHub, then run npm run build && npm test (Node, no dependencies). Pick one way to publish:
- Your own Chrome Web Store copy, unlisted or private. Run
npm run pack-storeand uploadstore/dist/safeyt-store-<version>.zipin the Chrome Web Store developer dashboard, with visibility Private (your domain only) or Unlisted. Note the extension ID. Like the public listing, this build has no stream-URL lockdown. - Self-hosted CRX. Run
node scripts/pack-crx.js --key path/to/district-key.pem --out safeyt.crx. It signs a CRX3 file and prints the extension ID. Host the.crxand an update manifest XML on an HTTPS server you control, and use that update URL in Admin.
The extension ID is derived from the signing key. The packer creates e2e/dev-key.pem by default for testing; for production, generate a separate key, keep it somewhere safe and use it for every release. A new key means a new ID, and every device would need the new one.
Step 3
Force-install and pin for the student OU
- Admin console: Devices > Chrome > Apps & extensions > Users & browsers.
- Select the student organizational unit (the pilot OU first).
- Click + and choose Add Chrome app or extension by ID (or from Chrome Web Store, or Add from custom app with your update URL for a self-hosted CRX).
- Set Installation policy to Force install + pin to browser toolbar, so students and teachers can always open "What SafeYT does on this device".
- Save. Chromebooks pick up the change within a few minutes, or right away after
chrome://policy> Reload policies.
Force install also means students cannot disable or remove SafeYT, its options page turns read-only, and Chrome grants it the blocking network permission that powers the stream-URL lockdown.
Step 4
Paste the managed policy JSON
With SafeYT selected in the same panel, find Policy for extensions and paste the policy. Google Admin wraps each field in a {"Value": ...} object. The simplest setup points at a file you host (step 5) and pins only what Admin must control:
{
"policyUrl": { "Value": "https://raw.githubusercontent.com/your-district/safeyt-policy/main/policy.json" },
"enforcement": { "Value": { "mediaGate": true, "streamLockdown": true } },
"blockShorts": { "Value": true },
"schedule": { "Value": { "days": [1, 2, 3, 4, 5], "start": "08:00", "end": "15:30", "tz": "America/Chicago", "outsideHours": "same" } }
}
Every field is optional; anything left out comes from the bundled default. Fields set here override the hosted file. To set lists inline instead, without a hosted file:
{
"mode": { "Value": "strict" },
"allowChannels": { "Value": ["@AmoebaSisters", "@khanacademy", "@crashcourse", "@3blue1brown", "@HeimlersHistory", "Mr. Smith Science"] },
"blockChannels": { "Value": ["@MrBeast", "@MrBeastGaming", "@IShowSpeed", "@KaiCenat", "@Jynxzi"] },
"blockShorts": { "Value": true },
"hideComments": { "Value": true },
"hideRecommendations": { "Value": "offchannel" },
"dailyEntertainmentMinutes": { "Value": 15 },
"schedule": { "Value": { "days": [1, 2, 3, 4, 5], "start": "08:00", "end": "15:30", "tz": "America/Chicago", "outsideHours": "off" } },
"showReasons": { "Value": true },
"enforcement": { "Value": { "mediaGate": true, "streamLockdown": true } }
}
Lists replace the bundled lists rather than adding to them. To add a few channels, start from the bundled policy/default.json (49 learning channels, 22 entertainment channels). Channel entries can be a handle (@khanacademy), a channel ID or an exact channel name, which is handy for a teacher's own channel.
Step 5
Host the policy JSON
A hosted policy file lets a curriculum lead update channel lists without touching Admin. SafeYT fetches it at startup and every 15 minutes, keeps the last good copy, and keeps using that copy if the server is down.
- GitHub. Create a repository, commit
policy.json(a copy ofpolicy/default.json) and use its raw address:https://raw.githubusercontent.com/<org>/<repo>/main/policy.json. Turn on branch protection and pull requests so every change is reviewed and recorded. - District web server. Any address that returns the JSON works. Send
Content-Type: application/jsonand allow caching of a few minutes at most.
The address must use HTTPS. SafeYT sends no cookies, identifiers or student data with the request, and a hosted file cannot change policyUrl itself. To test a file before publishing, paste it into the extension's options page on an unmanaged test profile and click Validate.
Step 6
Required Chrome policies
SafeYT defends against scripts inside a web page. It cannot defend against tools that run with more privilege than a page, or against a session it is not part of. Set these on the student OU before rollout (most are under Devices > Chrome > Settings), then check them on a pilot device at chrome://policy.
| Policy and value | Attack it closes |
|---|---|
URLBlocklist: javascript://* | Bookmarklets and javascript: typed in the address bar, the only way to run script in a page without tools. |
DeveloperToolsAvailability: 2 | The DevTools console, network overrides, editing the extension's worker or storage, and extension developer mode. |
ExtensionInstallBlocklist: * | A student-installed extension that injects script, proxies video or hides SafeYT's effect. Allow only what you approve. |
ExtensionInstallForcelist / ExtensionSettings | Removing or disabling SafeYT. Also grants the blocking permission that turns the stream-URL lockdown on. |
IncognitoModeAvailability: 1 | An Incognito window, where extensions do not run by default. |
DeviceGuestModeEnabled: false | A guest session with no force-installed extensions. |
ArcEnabled: false, or block the YouTube apps | The Android YouTube app, which plays anything. |
VirtualMachinesAllowed / CrostiniAllowed: false | A Linux browser outside Chrome policy. |
DeviceBlockDevmode: true | ChromeOS developer mode: a root shell or booting another OS. |
Sign-in restriction: *@yourdistrict.org | A personal Google account with no school policy and no SafeYT. |
TaskManagerEndProcessEnabled: false | Ending SafeYT's process to drop the lockdown until it restarts. |
| Network or DNS filter | Alternative YouTube front-ends (Invidious, Piped) that never touch YouTube's video servers. |
ForceYouTubeRestrict: off or moderate | Not an attack: strict Restricted Mode hides the learning videos SafeYT is meant to let through. |
The full table in DEPLOYMENT.md gives the Admin console location for each row and a few optional extras.
Step 7
Verify on one device
Sign in to a pilot Chromebook with a student test account and click the pinned SafeYT icon. The popup must show:
- Media gate: Active
- Stream-URL lockdown: Active. "Unavailable (extension not policy-installed)" means the extension was not force-installed; check step 3. With the Chrome Web Store build it reads "Unavailable. Store build: media gate only", which is expected.
- Policy source naming your Admin policy or hosted file.
Then try two videos:
- Open any MrBeast video link. You should land on SafeYT's block page ("MrBeast is on your school's blocked channel list") without the YouTube page ever loading.
- Open an Amoeba Sisters video. A new video may show "Checking this video..." for about a second, then it plays.
Also open a youtube.com/shorts/ link (Shorts page) and youtube.com/@MrBeast (channel blocked). If the popup ever shows "Verification failing" during normal use, check that nothing on the network blocks the Chromebook from reaching youtube.com.
Step 8
Roll out, and edit lists later
- Policy reviewed by a curriculum lead: the allow list covers the subjects you teach, and the block list matches your community.
- Schedule time zone, days and times match the bell schedule.
- Pilot OU for a week. Ask teachers to report wrong blocks with the reason shown on the block screen.
- Every required policy in step 6 set on the student OU and checked at
chrome://policy. - Media gate and lockdown both Active on a pilot device; a learning video plays and a MrBeast video does not.
- Chrome's YouTube restricted mode set to off or moderate.
- Privacy documents attached to the district's data privacy agreement: privacy exhibit, data statement, accessibility statement, and for New York the Ed Law 2-d package.
- Students and families told what SafeYT does.
- Expand to all student OUs.
Editing lists later
With a hosted file, edit policy.json (through a pull request, if it lives on GitHub). Every Chromebook picks up the change at its next 15-minute refresh (plus any caching on the host), no Admin visit needed. With inline Admin fields, edit Policy for extensions and save; devices update within minutes. Validate new channel handles with node scripts/resolve-channels.js @handle before publishing them, and re-check the whole list each term with npm run check-channels.