Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Forge Previewer Laravel Package

ryangjchandler/forge-previewer

Forge Previewer is a Laravel package for creating preview deployments on Laravel Forge. It helps generate temporary sites for branches or pull requests so teams can review changes in a production-like environment before merging.

View on GitHub
Deep Wiki
Context7

Getting Started

Since the package is archived (last release January 2023), treat it as a starting point to fork or copy, not a maintained dependency. Pin the exact version and vendor it if you plan to rely on it.

  1. Install and inspect the source first. Because there is no active maintenance, read src/ before wiring it up. The bulk of the logic lives in the webhook controller and a small set of jobs that talk to the Forge API.

    composer require ryangjchandler/forge-previewer
    php artisan vendor:publish --provider="Ryangjchandler\ForgePreviewer\ForgePreviewerServiceProvider"
    

    Publish the config file and check the migrations for the preview_deployments (or similarly named) table — that table is the source of truth linking a PR number to a Forge server ID.

  2. Collect the four credentials you actually need. Expect to set at minimum:

    • FORGE_API_TOKEN — a Forge API token with server + site permissions
    • FORGE_PREVIEWER_WEBHOOK_SECRET — the GitHub webhook signing secret
    • GITHUB_TOKEN (or an installed GitHub App) — to post comments and read PR metadata
    • Provider credentials (DigitalOcean/AWS/Vultr token) if the package provisions servers directly rather than through a pre-existing Forge "server provider" config

    Add these to config/forge-previewer.php as well; the config cache means any change requires php artisan config:clear.

  3. Register the webhook in GitHub. Point a repository (or organisation) webhook at your app's route (typically POST /webhooks/github or whatever the package registers) with content type application/json, the shared secret you configured, and events: pull_request and pull_request_review. Exclude it from CSRF and from auth middleware.

  4. Run a queue worker. Server provisioning blocks for minutes; the webhook handler should acknowledge fast and dispatch jobs. php artisan queue:work must be running for anything to happen after the 200.

  5. First use case — the "hello world" loop: open a PR against a test repo → the package creates a Forge server, creates a site, installs the repository on the PR branch, runs your deploy script, and comments the preview URL back on the PR. Closing the PR should tear the server down. Verify both halves before trusting it on a real repo.

Implementation Patterns

Tag everything you create, and reconcile from tags. Forge's API is tag-aware. Name servers using a deterministic convention (e.g. preview-{repo}-{pr_number}) and tag them preview. This gives you a reliable way to find and destroy orphans without trusting your own database, which will drift when webhooks are lost.

Wrap provisioning in an explicit state machine. The package's jobs are the state machine: created → provisioning → deployed → destroying. Persist each transition in the preview_deployments table. If you extend it, hook your own listeners onto the package's events (or Model::created on the deployment record) rather than editing the package's jobs — this keeps your changes mergeable against the upstream code.

Custom provision scripts are the extension point. The real leverage is the provisioning script the package runs after the server exists. Put your composer install, .env generation, php artisan migrate --seed, storage link, and queue restart in a Forge "recipe" or a script the package shells out to. Keep preview env vars (Stripe test keys, S3 bucket, mailer = log) in a dedicated config/preview.php and inject them via forge env:set-style calls — never reuse production credentials.

Idempotency in the webhook handler. GitHub redelivers webhooks. Dedupe on X-GitHub-Delivery (store in a webhook_deliveries table with a unique index) before dispatching. Otherwise you will create two servers for one PR and pay for both.

Comment the URL, don't rely on Forge's dashboard. Post a PR comment with the preview URL and destroy/rebuild instructions. Better: update a single comment (look it up by a marker string like <!-- forge-previewer -->) instead of appending one per push, so the PR thread stays readable.

Add a scheduled reconcile command. Register php artisan forge-previewer:prune (or write it yourself if the package doesn't ship one) to run hourly: list all Forge servers tagged preview, cross-reference open PRs via the GitHub API, and destroy any server whose PR is closed or gone. This is the safety net that makes the whole thing safe to run unattended and directly addresses the biggest real-world cost problem.

Multi-environment use. A common pattern is one previewer instance per repo, configured via config/forge-previewer.php with per-repo provider/size/region overrides. If the package supports only one repo, wrap it in a small "repos" config array and instantiate the client per repo yourself — you'll likely need to fork for this, which is a good argument for copying the source into app/ rather than depending on the archived package.

Testing and local development. Use gh webhook forward --repo=<owner/repo> --events=pull_request --url=http://localhost:8000/webhooks/github (or ngrok) and replay a captured PR payload with a tiny artisan command. Forge's API has no sandbox; use a dry_run flag in your own copy of the jobs that logs the server-creation payload instead of sending it.

Gotchas and Tips

The archive status is the headline risk. It was last released in January 2023. Forge's API, the forge PHP SDK, and GitHub's webhook contracts have all changed since. Expect to update the Forge SDK constraint and possibly the webhook payload handling before the package works against a current Forge account. Budget half a day for the initial "make it run" step alone; if that's too much, use Forge's own preview-deployment features or a maintained GitHub Action instead.

Webhook signature verification is not optional. If you don't set the secret, anyone can POST a pull_request payload and spin up servers on your Forge account — a direct billing vulnerability. Verify with hash_hmac('sha256', $payload, $secret) and reject on mismatch, and set the secret in GitHub, not just in .env.

Cost control is the whole game. Every preview PR is a real server. Three failure modes you will hit within the first month: (a) PRs opened from forks are closed without a webhook reaching you, (b) the webhook fires but the queue worker is down, and (c) the destruction job fails silently. All three leak servers. Mitigations: hard TTL on every server, the hourly reconcile job, and a Forge billing alert on the account.

Provisioning is slow and the webhook isn't. DigitalOcean/AWS servers take 60–180 seconds; sites and installs add more. If the webhook handler does this synchronously you'll hit GitHub's 10-second timeout and get redeliveries, which compounds with the idempotency problem above. Always queue.

Database strategy needs a decision early. Two common approaches: each preview server runs its own MySQL (slow to provision, but isolated and realistic) or previews share a staging database with per-PR schema prefixes (fast, but you must add cleanup to the destroy job or the DB grows forever). The package doesn't decide this for you; bake it into the provision script.

DNS and TLS. Forge issues certificates per site. If you generate hostnames like pr-123.preview.example.com you need a wildcard A record to your provider, and each new server needs its IP registered or you need a wildcard DNS record pointing at a load balancer. Wildcard DNS to each server IP (e.g. pr-123.preview.example.com A <server-ip>) is the simplest path; Forge's Let's Encrypt issuance will then work normally. Avoid nip.io/sslip.io for anything with cookies that need Secure.

Config caching surprises. The config file's callback/closure values (if any, e.g. for dynamic repo config) cannot be serialised by php artisan config:cache. If you see "Serialization of 'Closure' is not allowed" after enabling config cache in production, that's why.

Environment variables on the preview site should set APP_ENV=preview, APP_DEBUG=false, a log channel that doesn't fill the disk, MAIL_MAILER=log, and a queue driver that won't try to reach production Redis. Miss these and previews will email real users or write into production storage.

Fork, don't wait. Given the archive status, the pragmatic move is to copy src/ into app/Support/ForgePreviewer/ (or a private fork) so you can patch the Forge SDK calls as the API evolves. Keep the public surface — webhook route, job names, deployment model — intact so your team's mental model matches the docs and any future maintainer can diff against upstream.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo