Deploy a UnfoldCMS-Powered Site on Netlify

Host your front end on Netlify, keep UnfoldCMS on any PHP host, and rebuild on every publish

Deploy a UnfoldCMS-Powered Site on Netlify

Netlify won't run UnfoldCMS itself — it's a PHP app, and Netlify builds and serves JavaScript front ends. What Netlify does brilliantly is host the site: keep the CMS on a small PHP host, deploy your Astro, Next.js, or SvelteKit front end to Netlify, and rebuild automatically the moment an editor hits publish. This guide wires that up end to end, including the Netlify build hook and the signed CMS webhook that fires it. See also: the Vercel version of this guide and the Cloudflare Pages version.

TL;DR: UnfoldCMS stays on your PHP host (any cheap VPS or shared host). Your front end deploys to Netlify and fetches published content from /api/v1/posts at build time. You create a Netlify build hook (a URL that triggers a build), then register it as an outgoing webhook in UnfoldCMS — HMAC-SHA256 signed, fires on post publish, update, and delete, every delivery attempt logged. Publish in the CMS, Netlify rebuilds, live in a minute or two. No GraphQL, no SDK — plain fetch().


How Does the Architecture Work?

The CMS and the front end live in different places and talk over HTTPS. That's the whole trick: Netlify hosts your fast static (or SSR) front end, while UnfoldCMS runs on any PHP host and acts as the content backend. "Deploy UnfoldCMS on Netlify" is the wrong mental model — you deploy the site on Netlify.

  • UnfoldCMS runs on a PHP host (VPS or shared hosting with PHP 8.3 + MySQL). Editors log in here; content lives here. The admin is a Laravel app and never touches Netlify.
  • Your front end (Astro / Next.js / SvelteKit) runs on Netlify. It pulls content from the CMS API during the build.
  • The link between them is a Netlify build hook, triggered by the CMS's signed publish webhook.

Not sure Netlify is the right host? The Cloudflare Pages vs Netlify vs Vercel comparison breaks down the trade-offs for exactly this setup.


What You Need Before Starting

Four things, and most readers already have three of them. Budget about 20 minutes for the whole walkthrough — the longest part is waiting for Netlify's first build to finish.

  1. UnfoldCMS running on a PHP host, with at least one published post.
  2. A front-end project (Astro, Next.js, or SvelteKit) in a Git repo (GitHub, GitLab, or Bitbucket).
  3. A free Netlify account.
  4. Your CMS base URL handy, e.g. https://cms.yoursite.com.

If your front end doesn't fetch from the CMS yet, start with the Astro integration guide or the Next.js integration guide, then come back here for the hosting piece.


Step 1: Connect the Repo and Set the Build

In the Netlify dashboard, go to Add new site → Import an existing project, pick your Git provider, and select the repo. Netlify detects most frameworks and pre-fills the build command and publish directory. Then add one environment variable — your CMS base URL — and the build can fetch content.

Under Site configuration → Environment variables, add:

CMS_URL=https://cms.yoursite.com

Your build pulls published content from the public API — no auth needed for reads:

// runs during the Netlify build
const res = await fetch(`${process.env.CMS_URL}/api/v1/posts`);
const { data } = (await res.json()).data; // { success, message, data: { data: [...] } }

Every /api/v1/* response uses the same envelope: { success, message, data }. The post list is paginated inside data, which is why the snippet unwraps twice.

Rather than clicking through the UI, you can pin the build settings in a netlify.toml at the repo root — it's versioned with your code and survives dashboard changes:

# netlify.toml
[build]
  command = "astro build"        # or "next build" / "vite build" for SvelteKit
  publish = "dist"               # ".next" for Next.js, "build" for SvelteKit adapters

[build.environment]
  NODE_VERSION = "20"

Deploy once. You now have a CMS-sourced site on Netlify's CDN. The remaining problem: it only updates when you push a commit. Editors don't push commits — that's what the build hook fixes.


Step 2: Create a Netlify Build Hook

A build hook is a unique URL that starts a production build whenever something POSTs to it. In Site configuration → Build & deploy → Build hooks, click Add build hook, name it "CMS publish", and pick your production branch (usually main). Netlify gives you a URL like:

https://api.netlify.com/build_hooks/665f1a2b3c4d5e6f7a8b9c0d

Copy it. Any POST to this URL kicks off a fresh build that re-fetches your CMS content. Treat it like a password — there's no signature check on Netlify's side, so anyone holding the URL can trigger builds.


Step 3: Register the Hook as an UnfoldCMS Webhook

UnfoldCMS ships outgoing HMAC-SHA256-signed webhooks, managed via the admin API at /api/v1/admin/webhooks. A subscription holds a target URL, the events it cares about, and a signing secret. Content events — post publish, update, and delete — fire a signed POST, and every delivery attempt is logged so you can see exactly what was sent and whether it landed.

You have two ways to connect the webhook to the build hook:

Option A — direct (simplest). Register the Netlify build hook URL itself as the webhook target. Netlify rebuilds on any POST, so this works immediately. Trade-off: nothing verifies the CMS signature, so a leaked hook URL means anyone can burn your build minutes.

Option B — verified relay (recommended). Put a small Netlify Function in front that checks the signature, then calls the build hook:

// netlify/functions/cms-deploy.ts
import crypto from 'node:crypto';

export default async (req: Request): Promise<Response> => {
  const raw = await req.text();
  // UnfoldCMS signs with spatie/laravel-webhook-server — header "Signature",
  // value hash_hmac('sha256', rawBody, secret).
  const signature = req.headers.get('signature') ?? '';
  const expected = crypto
    .createHmac('sha256', process.env.CMS_WEBHOOK_SECRET!)
    .update(raw)
    .digest('hex');

  const ok =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!ok) return new Response('Bad signature', { status: 401 });

  // Payload: { event, occurred_at, data: { id, title, slug, url, posted_at } }
  await fetch(process.env.NETLIFY_BUILD_HOOK_URL!, { method: 'POST' });
  return Response.json({ deploying: true });
};

export const config = { path: '/api/cms-deploy' };

Set CMS_WEBHOOK_SECRET and NETLIFY_BUILD_HOOK_URL as environment variables on the site, then register the subscription against the admin API with a Sanctum admin token:

curl -X POST https://cms.yoursite.com/api/v1/admin/webhooks \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yoursite.netlify.app/api/cms-deploy",
    "events": ["post.published"]
  }'

For Option A, swap the url for the raw build hook URL. Either way, use the test endpoint — POST /api/v1/admin/webhooks/{id}/test — to fire a sample delivery and confirm a Netlify build starts.


Step 4: Verify the Rebuild Flow End to End

Don't trust the wiring until you've watched it work once. The full loop — publish in the CMS, webhook fires, build hook triggers, Netlify rebuilds, new content is live — should take one to three minutes, and every stage leaves a trace you can check.

  1. Publish a new post (or edit an existing one) in the UnfoldCMS admin.
  2. Check the webhook delivery log in the CMS — you should see a logged attempt with a 2xx response. Failed deliveries are logged too, with the response that came back.
  3. Open Deploys in the Netlify dashboard — a new build should appear within seconds, tagged as triggered by your build hook.
  4. Wait for the build to go green, then load the site. The new post is there.

If the build starts but the post is missing, the build fetched before checking the right endpoint — confirm CMS_URL is set in the Netlify environment, not just locally. If no build starts at all, the delivery log tells you which side failed: a 401 means the relay rejected the signature (secret mismatch), a timeout means Netlify never got the POST.

Want to poke at the API before touching your own install? Try the live demo — its /api/v1/posts endpoint is public, so you can point a test build at it for free.


Optional: Redirects and Caching Headers

Netlify reads redirect and header rules straight from your repo, which pairs nicely with a CMS-driven site — old slugs keep working and your assets get long cache lifetimes without any server config.

For redirects, either drop a _redirects file in your publish directory or add rules to netlify.toml:

# netlify.toml
[[redirects]]
  from = "/old-blog/:slug"
  to = "/blog/:slug"
  status = 301

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

Because the HTML is regenerated on every build-hook deploy, you can cache hashed assets forever and leave HTML at Netlify's defaults — the content refreshes when an editor publishes, not when a cache expires.

Here's the whole flow at a glance:

Stage Where it runs Triggered by
Editor publishes a post UnfoldCMS (PHP host) A human clicking Publish
Signed webhook POST CMS → Netlify post.published (also fires on update/delete)
Build hook Netlify The webhook (direct or via relay function)
Build re-fetches /api/v1/* Netlify build container The build hook
CDN serves fresh HTML Netlify edge Successful deploy

Frequently Asked Questions

Can I host UnfoldCMS itself on Netlify?

No. UnfoldCMS is a PHP/Laravel app that needs PHP 8.3 and MySQL; Netlify builds and serves JavaScript sites and functions. Host the CMS on any PHP-capable server — a $5–10/month VPS is plenty — and deploy only your front end to Netlify.

Which CMS events can trigger a Netlify rebuild?

Content events: post publish, update, and delete. Subscribe to the ones you need per webhook — most sites want all three, so edits and removals rebuild the site just like new posts do. Each delivery attempt is logged in the CMS for debugging.

Do I need the relay function, or is the direct build hook enough?

Direct works fine for getting started — Netlify rebuilds on any POST. The relay function adds HMAC signature verification, so a leaked hook URL can't be used to trigger builds and drain your build minutes. Use the relay for production sites.

Is there an npm SDK for fetching UnfoldCMS content?

No, and you don't need one. The API is plain REST: fetch() against /api/v1/posts, /api/v1/pages, /api/v1/categories, and friends, each returning a { success, message, data } envelope. The full endpoint list is in the API docs.


Where to Go From Here

You now have a Netlify-hosted site that rebuilds itself whenever content changes. For the data-fetching layer, read the Astro integration guide or the Next.js integration guide. Deploying to a different edge host instead? The Vercel guide and the Cloudflare Pages guide follow the same three-step shape.

UnfoldCMS is a one-time license, self-hosted, with the full public API and outgoing webhooks in every tier — see pricing. Your content stays on a host you control; Netlify just serves the fast front of it.

Sources & notes: Webhook signing follows the spatie/laravel-webhook-server convention (header Signature, hex HMAC-SHA256 of the raw body). Netlify build hooks and netlify.toml syntax per Netlify's build configuration docs. API endpoints and the response envelope are documented in the UnfoldCMS docs.

Related: Astro integration · Deploy on Vercel · Pages vs Netlify vs Vercel for headless CMS hosting

Comments (0)

Leave a Comment

Please log in to leave a comment.

Don't have an account? Register here

No comments yet. Be the first to share your thoughts!