Stagepass

Migrate from 1.x to 2.0

Every breaking change in 2.0 with what changed and what to do.

loader 2.3.0cli 2.2.1macOS

Before you start

Version 2.0 changes defaults for security. Sites that keep their 1.x script URLs keep working until you change them. Update the CLI, then the loader and the modules, and work through the list below.

Terminal
$ npm install -g @stagepass/cli@2
$ stagepass --version
Head Code
<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>

1. CORS is no longer allow-all by default

Before (1.x / early security WIP): new links could behave permissively, or defaults included broad wildcards.

After (2.0): new stagepass link projects use an origin allowlist. Allow-all is only via explicit opt-in:

Terminal
$ stagepass link --allow-all-origins   # emergency / migration only (prints a warning)

What to do: Prefer listing your live hosts:

Terminal
$ stagepass link --origin my-project.webflow.io --origin www.example.com
$ stagepass link --no-allow-all-origins

Legacy Caddy blocks migrated by the CLI keep allowAllOrigins enabled for compatibility (timestamped backup under ~/.stagepass/). Switch them off after adding --origin entries.

2. *.webflow.io / *.webflow.com are no longer default

Before: default CORS allowlist included https://*.webflow.io and https://*.webflow.com.

After: defaults are local only (https://*.sp, localhost, 127.0.0.1) plus per-project --origin values.

What to do: Add each staging/live host you need:

Terminal
$ stagepass link --origin my-project.webflow.io

An explicit --origin *.webflow.io is still accepted but prints a warning (any Webflow user's site would then be allowed). Prefer the exact subdomain.

3. Dotfiles, .git, and node_modules return 404

Before: Caddy could serve sensitive paths under the project root.

After: Any path segment starting with ., plus node_modules and common key/cert/db/log extensions, always return 404 (including OPTIONS), even with --allow-all-origins.

What to do: Do not rely on serving .env, .git, or node_modules over .sp. Keep secrets out of the web root.

Before: Some unsafe folder-derived or injectable names could reach the Caddyfile.

After: Domains must be DNS-safe labels ending in .sp. Folder names are slugified when the domain argument is omitted; injection attempts are rejected.

What to do: Use a clean domain (my-project.sp). If link slugifies your folder name, use the printed domain going forward.

5. Production console is no longer muted globally

Before: Without a Stagepass session, the loader replaced console.log / warn / error / … with no-ops in production.

After: Only Stagepass' own sp.log / sp.warn / sp.error output is gated on an active session. The global console is left alone.

What to do: If you still want the old mute, opt in on the loader tag:

HTML
<script
  src="https://cdn.jsdelivr.net/npm/@stagepass/loader@2/dist/loader.min.js"
  data-stagepass-mute-console="true"
></script>

6. ?stagepass= activation expires (default 12 h)

Before: localStorage.stagepass_domain stored a permanent plain string.

After: The session is a JSON object with an expiry (default 12 hours). Legacy/malformed/expired values are cleared. ?stagepass=off / 0 / false still disconnects.

What to do: Re-activate with ?stagepass=my-project.sp after expiry. Optional lifetime:

HTML
<script
  src="https://cdn.jsdelivr.net/npm/@stagepass/loader@2/dist/loader.min.js"
  data-stagepass-session-hours="24"
></script>

7. data-stagepass-cachebust="true" no longer timestamps every production view

Before: In production, data-stagepass-cachebust="true" appended ?_cb=<Date.now()> on every page load.

After: Production cache busting uses a version string:

  • data-stagepass-version on the loader tag, and/or
  • data-stagepass-v on an individual asset

Both append ?v=<version> (URL-encoded; existing query/hash preserved). Per-request ?_cb=<timestamp> applies only while a Stagepass session is swapping to local assets.

What to do:

HTML
<script
  src="https://cdn.jsdelivr.net/npm/@stagepass/loader@2/dist/loader.min.js"
  data-stagepass-version="2026-11-01"
></script>
<script
  data-stagepass
  data-src="https://cdn.example.com/app.js"
  data-stagepass-v="2026-11-01"
></script>

Bump the version when you publish new production assets.

8. Injected scripts execute in insertion order

Before: Dynamically created scripts often ran with the browser default async = true, so order was not guaranteed.

After: The loader and @stagepass/modules inject set script.async = false (unless the original/async: true opted into async). Classic scripts keep document order.

What to do: Usually nothing. If you relied on races, fix load-order assumptions in your own code.

9. splog, spwarn, sperror are removed

Before: Deprecated globals aliased sp.log / sp.warn / sp.error.

After: Only sp / stagepass / window.stagepass logging APIs remain.

What to do: Replace:

JavaScript
// before
splog('hello');
spwarn('careful');
sperror('fail');

// after
sp.log('hello');
sp.warn('careful');
sp.error('fail');

Automatic Caddyfile upgrade

On stagepass link, reload, and start, legacy Caddyfile blocks (no # stagepass-block: v2 marker) are upgraded automatically:

  1. A timestamped backup is written once: ~/.stagepass/Caddyfile.bak-YYYYMMDD-HHMMSS
  2. Blocks are rewritten with path blocking + the version marker
  3. allowAllOrigins is enabled for migrated blocks so existing Webflow / live-domain embedding keeps working
  4. A short notice recommends tightening CORS

Recommended after migration:

Terminal
$ stagepass link --origin www.example.com
$ stagepass link --no-allow-all-origins

Hand-written content outside # START: / # END: markers is left untouched. Unparsable legacy blocks are left unchanged with a warning. Migration is idempotent (second run does nothing and creates no new backup).