Modules: inject, cookies and maintenance
Optional helpers on top of the loader: sp.inject, sp.cookies and sp.maintenance.
Load the modules
Modules need the loader. Add the loader first, then the modules you use, or all.min.js for all three.
<script src="https://cdn.jsdelivr.net/npm/@stagepass/loader@2/dist/loader.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stagepass/modules@2/dist/inject.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stagepass/modules@2/dist/cookies.min.js"></script>$ npm install @stagepass/loader@2 @stagepass/modules@2Modules depend on the Loader at runtime (they use sp.vars / stagepass.vars). The loader script must be loaded before any module script. Without the loader, inject and cookies still attach their APIs and warn, and maintenance does nothing. For npm users, @stagepass/modules lists @stagepass/loader as a peer dependency: install the loader when you install modules.
Available Modules
Injector Module (inject.min.js)
- Programmatic asset injection API
- Smart source resolution (local vs production)
- Flexible positioning and deduplication
- Include as a separate script tag after
loader.min.js
Cookies Module (cookies.min.js)
- Lightweight cookie utility helpers with optional consent categories (no consent manager)
- Read, write, delete, existence checks, and list helpers
- Available as
sp.cookies.*andsp.cookie.* - Include as a separate script tag after
loader.min.js
Maintenance Module (maintenance.min.js)
- Opt in overlay for visitors while the site is under maintenance
- Developers with a session still see the real page
- Available as
sp.maintenance.* - Include as a separate script tag after
loader.min.js
Injector: Programmatic Injection API
When the Injector module is loaded, use sp.inject() (preferred). stagepass.inject() and window.stagepass.inject() remain available as aliases:
// Single injection
await sp.inject({
src: 'https://cdn.example.com/lib.js',
stagepass: true, // Enable local swapping when active
localPath: 'lib.js', // Optional: local file path (defaults to filename from src)
id: 'my-library', // Optional: for deduplication
position: 'head', // 'head' | 'body-start' | 'body-end' | { target: '#id', action: 'before' | 'after' }
type: 'script', // Optional: 'script' | 'style' (auto-detected from extension)
version: '1.4.0', // Optional: in production, append ?v=<version> for cache busting
async: false, // Optional: default false (keeps script order)
defer: true, // Optional: default true (unless async is true)
attributes: { // Optional: additional HTML attributes
crossorigin: 'anonymous'
}
});
// Batch injection (array)
await sp.inject([
{ src: 'https://cdn.example.com/lib1.js', stagepass: true, localPath: 'lib1.js', version: '1.4.0' },
{ src: 'https://cdn.example.com/lib2.css', stagepass: true, localPath: 'lib2.css', type: 'style', version: '1.4.0' }
]);
// Batch injection ({ scripts, styles })
await sp.inject({
scripts: [{ src: 'https://cdn.example.com/lib1.js', stagepass: true, path: 'lib1.js', version: '1.4.0' }],
styles: [{ src: 'https://cdn.example.com/lib2.css', stagepass: true, localPath: 'lib2.css', version: '1.4.0' }]
});path is an alias for localPath. Deprecated cacheBust no longer appends production timestamps; use version instead.
Integrity, nonce and allowed hosts
Optional options for stricter sites. Reserved attributes cannot bypass the host allowlist.
await sp.inject({
src: 'https://cdn.example.com/lib.js',
integrity: 'sha384-abc', // passed through as SRI hash
crossorigin: 'anonymous', // passed through as CORS mode
nonce: 'csp-nonce', // CSP nonce
allowedHosts: ['cdn.example.com'] // other hosts are skipped with a warning
});Cookies: Utility API
When the Cookies module is loaded, use sp.cookies (singular alias sp.cookie):
sp.cookies.bake('token', 'abc123', { days: 7, path: '/' });
sp.cookies.eat('token'); // 'abc123'
sp.cookies.has('token'); // true
sp.cookies.list(); // { token: 'abc123', ... }
sp.cookies.throw('token', { path: '/' });API reference:
sp.cookies.bake(name, value, options?)Creates/updates a cookie.sp.cookies.eat(name, options?)Reads a cookie value. Returnsnullwhen not found.sp.cookies.throw(name, options?)Deletes a cookie (writesMax-Age=0+ expired date).sp.cookies.has(name)Boolean existence check.sp.cookies.list(options?)Returns all visible cookies as an object map.sp.cookies.throwAll({ category? })Deletes the cookies this helper knows, optionally only one category.sp.cookies.jar()Returns a copy of the cookies this helper knows.sp.cookies.crumbs(prefix)Returns the known cookie names that start withprefix.
bake options:
days?: number- Expiration in days.maxAge?: number- Expiration in seconds (Max-Age).path?: string- Cookie path (default:/).domain?: string- Cookie domain.secure?: boolean- Defaults totrueon HTTPS pages.sameSite?: 'Lax' | 'Strict' | 'None'- Default:Lax.encode?: boolean- URL-encode key/value (default:true).json?: boolean- Store value as JSON string.category?: string- Remembers the category. The write waits in a queue while consent rejects it.
eat / list options:
decode?: boolean- URL-decode values (default:true).json?: boolean- AttemptJSON.parsefor values.
throw options:
path?: string(default:/)domain?: stringsecure?: booleansameSite?: 'Lax' | 'Strict' | 'None'
JSON example:
sp.cookies.bake('profile', { id: 7, role: 'editor' }, { json: true, days: 30 });
sp.cookies.eat('profile', { json: true }); // { id: 7, role: 'editor' }Categories and consent
sp.consent is a small hook for your own consent banner. By default every category is allowed. Writes in a rejected category wait in a queue until you call sp.consent.update().
sp.cookies.bake('analytics_id', 'x', { category: 'analytics', days: 30 });
sp.cookies.throwAll({ category: 'analytics' });
sp.cookies.jar(); // copy of known cookies
sp.cookies.crumbs('analytics_'); // matching known names
sp.consent.set((category) => category !== 'marketing');
sp.cookies.bake('ad', '1', { category: 'marketing' }); // queued
sp.consent.set(() => true);
sp.consent.update(); // flushes the queueNotes and limitations:
- JavaScript cannot read or delete
HttpOnlycookies. - To reliably delete a cookie, use the same
path/domainyou used when writing it. - If
sameSite: 'None'is used, the module enforcesSecure=true(browser requirement). - This module provides cookie storage helpers only (bake/eat/throw). It does not implement consent management.
sp.consentonly gates writes, it is not a consent manager or a legal compliance solution. - Cookie attribute values are validated against cookie injection.
Maintenance overlay
A full page overlay for visitors while the site is under maintenance. It is off unless you set data-stagepass-maintenance="true" on its script tag or enable it in code.
<script
src="https://cdn.jsdelivr.net/npm/@stagepass/modules@2/dist/maintenance.min.js"
data-stagepass-maintenance="true"
data-stagepass-maintenance-title="We will be back soon"
data-stagepass-maintenance-text="Please check again shortly."
></script>sp.maintenance.enable({ title: 'We will be back soon', text: 'Please check again shortly.' });
sp.maintenance.isActive(); // true
sp.maintenance.disable();- Visitors without a session see the overlay. Title and text are set as plain text.
- With a session or
?stagepass=debugyou see the real page and a small badge. - It follows
prefers-color-schemeand loads no external resources.
TypeScript types
Add one entry per module your site loads, so a module that is not there is a type error:
{ "compilerOptions": { "types": ["@stagepass/loader", "@stagepass/modules/cookies"] } }| Entry | Adds |
|---|---|
@stagepass/modules/inject | sp.inject |
@stagepass/modules/cookies | sp.cookies, sp.cookie, sp.consent |
@stagepass/modules/maintenance | sp.maintenance |
@stagepass/modules | All of the above |
Module members are optional, because a page may not load the file. Guard your calls, for example if (sp.cookies) { … }. stagepass new and stagepass upgrade set these entries for you. See Declared modules.
Injector details
sp.inject()returns a promise and accepts one item, an array, or{ scripts: [...], styles: [...] }.positiondefaults tobody-end.localPathalso works aspath. Without it, the file name fromsrcis used.versionappends?v=<value>in production.cacheBustis deprecated and no longer adds a timestamp.- Scripts keep insertion order unless you pass
async: true.