Until v1.18.0, Forge enforced a hard rule: anything outside Published returns 404. That is the right default. It prevents draft leaks. It means you can hand an agent full publish access without worrying that unfinished content reaches your audience.
The problem is that "share before you publish" is a legitimate need. A client needs to approve copy. A colleague needs to review a technical post before it goes live. You want to double-check how something renders at the real URL with real CSS.
Forge solves this without weakening the lifecycle guarantee.
How it works
App.GeneratePreviewToken(prefix, slug) returns a signed URL parameter — a compact HMAC token bound to the content prefix and slug, with a configurable expiry (default 12 hours).
The full preview URL looks like this:
https://yoursite.com/posts/my-draft-post?preview=<token>
On each request, Forge checks for the preview query parameter. If it is present and valid for that slug, the published-only filter is bypassed — but only for that request, and only for that slug. Every other URL on the site is unaffected.
The bypass applies to Draft and Scheduled content. Archived content does not get a bypass — archiving is intentional and final.
Generating a preview URL
From the CLI:
forge preview posts my-draft-post
Prints the full URL with token to stdout. The token is valid for 12 hours by default.
From MCP (Admin role):
create_preview_url prefix="posts" slug="my-draft-post"
Returns the full URL. An agent can generate and share it as part of a review workflow — create draft, generate preview URL, send to reviewer, publish after approval.
Sharing with reviewers
The URL can be shared with anyone. No Forge account required. No login. The token is self-validating: Forge verifies the HMAC signature inline on every request using Config.Secret as the signing key.
The reviewer sees the page exactly as it will look when published — the real URL, the real template, the real CSS.
Configuring TTL
The default is 12 hours. Change it in forge.config:
preview_token_expiry = 24h
Or in code:
forge.New(db, forge.Config{
PreviewTokenExpiry: 24 * time.Hour,
})
Accepted formats: Go duration strings (1h, 30m, 24h). If not set, the default is 12 hours.
What does not change
The published-only filter still applies to everything that is not a valid preview request. A draft without a token is still a 404. A token for my-draft-post does not grant access to any other slug. An expired token is rejected.
The invariant holds: only Published content is visible by default. Preview tokens are a narrowly scoped, time-limited exception — not a bypass of the model.
forge v1.18.0, forge-mcp v1.8.0, forge-cli v0.5.0.
*See Content lifecycle for how Draft, Scheduled, Published, and Archived states work.*