Previews
A preview is a build of your app put up at its own address, open to the same people the app is, before anything goes live. It gets its own database (empty the first time that name is used), uses your app's real connections, and goes away on its own after two weeks if you never touch it again.
Making one#
$ wirl preview
Preview my-feature of dashboard — 12 files
https://acme--dashboard--my-feature.wirl.run
Expires 2026-10-05T09:00:00.000Z
Look at it: wirl fetch / --preview my-feature
wirl preview [dir] bundles and scans the folder exactly like wirl deploy does (the same .wirlignore, the same credential scan before a byte is uploaded), but the result never goes live. The address is <org>--<app>--<name>.wirl.run.
The name comes from --name <name>, or, absent that, your current git branch (unless it is main, master or HEAD), or else the part of your signed-in email before the @. Either way it is slugified the same way any other name is, and if org--app--name, the first part of the address, would run past a DNS label's 63-character limit, the name is shortened with a short hash on the end so two long branch names never collide.
A preview can come before the first deploy. If the app is not there yet, wirl preview makes it, exactly as wirl deploy would (--no-create refuses instead), and the app's own address answers 404 until you deploy: look at the preview, fix what needs fixing, then wirl deploy.
Asking again with the same --name replaces what that address serves: the old script and files go, a fresh build takes their place, and the database keeps whatever the last one wrote, since the name did not change.
Who can open it#
A preview is open to exactly the people who can already open the app: share, revoke or change the app's visibility and every one of its previews follows at once, because the gate reads the app's own access rule for a preview host too, with no separate list to keep in sync. A preview is never public on its own. If the app itself is open to anyone with the link, its previews are not: only your organisation, and anyone individually shared on the app, can open one, because a build an agent put up an hour ago has not been vetted for the public the way the live app has. The wirl badge never appears on a preview, whatever the app's own badge setting is.
Its own database#
Each preview gets its own env.DB, created empty the first time that name is deployed. It is never seeded from production (production holds real people's data, and a preview link is opened casually), so a page that expects rows may look blank until something writes to them. Redeploying the same name keeps the same database; deleting the preview, or letting it expire, takes the database with it.
Real connections, real keys#
A preview calls out through the app's own granted connections, with their real keys, through the same broker a live deploy uses: that is what makes a preview of a HubSpot dashboard actually show HubSpot data. A connection newly declared in the previewed wirl.json is ratified exactly as a deploy's would be: one you already hold just works, and a brand new one waits on an admin the same way. None of this is sandboxed, so a preview is exactly as trusted as the code in it: review what you are about to preview the way you would review what you are about to deploy.
Schedules, held#
Schedules declared in the previewed wirl.json are read but never fire on a clock, so nothing calls a vendor's API on a timer from a preview sitting untouched for two weeks. Fire one by hand instead:
$ wirl run poll-jobs --preview my-feature
ok in 4.1s: checked 40 postings
wirl run <schedule> --preview <name> (or the wirl_run MCP tool's preview argument) posts to the preview's own address and prints the outcome at once; the schedule has to be one the previewed wirl.json actually declares. A run started this way is answered on the spot and kept nowhere: a preview has no schedule of its own, so there is no run history for it in wirl schedules or on the Overview.
Public paths, live#
A public path declared in the manifest is live on a preview exactly as it is on the live app: the same signature check runs before your code does, and whatever it writes lands in the preview's own database, never production's. This is what lets you point a real webhook at a preview and watch it work before it ever reaches the live address.
Looking at what you built#
An agent has no browser, so wirl fetch <path> --preview <name> (or the wirl_preview_fetch MCP tool) makes one request as whoever runs it and prints the status, the content type and the body: the page source for HTML, capped at 100 KB and said so past that.
$ wirl fetch / --preview my-feature
200
content-type: text/html; charset=utf-8
<h1>Accounts at risk this month</h1>...
For a person with a browser, wirl preview open prints a link that signs the opener in for fifteen minutes, once per browser:
$ wirl preview open --name my-feature
https://acme--dashboard--my-feature.wirl.run/?__wirl_visit=…
Lasts 15 minutes and works once per browser: after the first open, that browser already holds the grant.
The link itself stops working fifteen minutes after it was printed. A browser that opened it in time holds an ordinary fifteen-minute pass from that moment, the same one any signed-in visit gets, so it can keep using the preview for up to fifteen minutes after the link has expired.
wirl logs --preview <name> (or the wirl_logs MCP tool's preview argument) reads that preview's own script instead of the live app's history, with the same --since, --errors and search flags wirl logs always takes.
Limits#
An app holds at most 5 previews at once; a sixth distinct name is refused, naming the oldest one and how to remove it. Each one expires 14 days after it was last deployed (redeploying the same name resets the clock), and is then taken the same way a deleted app's data is: its script, its files, its database and its address all go.
Moving an app to another organisation removes its previews first: a preview's address names the organisation it was made in, so it does not follow the app. If one cannot be removed just then (it is mid-deploy), the move is refused and nothing moves.
A preview takes a version number from the app's own sequence, so the numbers wirl versions lists can skip: v3, then v5, when v4 was a preview. Previews themselves are listed by wirl previews, never by wirl versions.
Deleting one#
$ wirl preview --delete my-feature
Deleted preview my-feature.
wirl preview --delete <name> (the wirl_delete_preview MCP tool, or Delete on the app's Overview page) removes it at once: script, files and database, and the address stops answering. Deleting one already gone still succeeds.
wirl previews#
Lists what an app currently holds, the same shape the wirl_list_previews MCP tool answers with:
$ wirl previews
my-feature https://acme--dashboard--my-feature.wirl.run by Dana expires in 12d