Templates
A template is a GitHub repository that the dashboard can provision as the frontend for a new space. Pick a built-in template when creating a site, or connect a repo of your own to reuse it across every space in your organization.
This guide covers the three things worth knowing up front:
- What the dashboard actually does when you provision from a template.
- The preflight checklist your repo must pass — use this when authoring a custom starter.
- How content import works, and why the wrong JSON shape silently produces an empty site.
Built-in vs. custom templates
Every organization sees the same three built-in templates in the Full Stack card on the new-space wizard:
- Decoupled Components (Next.js) — the reference starter, content-aware, ships with sample landing-page paragraphs.
- Decoupled Components (Astro) — same content model, Astro flavored.
- Decoupled Minimal — no sample content, bring-your-own everything.
Custom templates live under Templates in the sidebar. They're scoped to your organization, appear in the same Full Stack starter picker, and only your users see them.
How provisioning works
When you create a space from a template, the dashboard runs in this order:
- Drupal tenant provisions on Fly.io — fresh DB, install profile (
dc_core), baseline content types. - Netlify site is created and linked to the template's GitHub repo. Environment variables are set to placeholders.
- Once Drupal is ready, the connect flow runs:
- Pushes template info into Drupal state so
/dc-configcan surface the right Vercel "deploy your own copy" button and content-import URL. - Fetches OAuth credentials from Drupal and pushes them into Netlify env vars.
- Content import (optional) — fetches the template's content JSON from GitHub and posts it to Drupal's
/api/dc-importendpoint. - Preview iframe — Drupal auto-enables iframe preview for every node bundle that exists on the tenant. No hardcoded bundle list — if your template adds a
homepagenode type, preview wires up automatically. - Puck editor — enabled when the template declares
features.supports_puck. - Netlify rebuild triggers so the new env vars take effect.
- Pushes template info into Drupal state so
The user visits /dc-config on the tenant to confirm everything is hooked up. This page reads the pushed template info and surfaces a one-click link to clone the repo to the user's own Vercel, plus a content-import fallback if the automatic import didn't run.
Private repos
The dashboard supports private repositories via a GitHub OAuth connection scoped to your organization:
- On the Templates page, click Connect GitHub.
- Authorize the OAuth app — you'll return to the dashboard with an active connection.
- Save a template pointing at a private repo — the dashboard uses the connection's access token to read
package.json, fetch content, and provision Netlify with a per-site deploy key it adds to your repo automatically.
Tokens are AES-256-GCM encrypted at rest, derived from NEXTAUTH_SECRET. We never request write scope.
Netlify handles its own private-repo access separately — if you see the first Netlify deploy fail on private repos, install the Netlify GitHub App on the repo.
Preflight checklist
If you're authoring a repo to use as a custom template, these are the gotchas worth getting right up front. The dashboard's built-in Verify button on each template card will check most of these for you.
Repo structure
Your repo must have:
-
A
package.jsonat the root with a buildablebuildscript. -
A framework declaration the dashboard recognizes —
next,astro, orother. -
For Next.js templates deploying to Netlify: a
netlify.tomland@netlify/plugin-nextjsindevDependencies. Without the plugin, Netlify treats.next/as flat static files — dynamic routes likeapp/[...slug]/page.tsxreturn 404, and catch-all paths never render.Minimal
netlify.toml:[build] command = "npm run build" publish = ".next" [[plugins]] package = "@netlify/plugin-nextjs" -
A catch-all route (
app/[...slug]/page.tsxin Next.js app router,src/pages/[...slug].astroin Astro) that resolves Drupal paths. The built-in starters are a good reference.
Environment variables
The dashboard sets these automatically on Netlify at provision time — your app should read them without any manual setup:
NEXT_PUBLIC_DRUPAL_BASE_URL(andDRUPAL_BASE_URL/PUBLIC_DRUPAL_BASE_URLfor Astro)DRUPAL_CLIENT_IDDRUPAL_CLIENT_SECRETNEXT_PUBLIC_DEMO_MODE=false(andPUBLIC_DEMO_MODEfor Astro)
You can declare additional env vars in the template's env_manifest if you need other values surfaced to users at provision time.
Content import payload
This is the most common surprise. Your content JSON file (default data/components-content.json, configurable on the template save form) must use the content-entity shape that /api/dc-import expects — not a content-type-definition shape.
The import endpoint expects:
{
"nodes": [
{
"type": "landing_page",
"title": "Welcome",
"field_paragraphs": [ ... ]
}
],
"paragraphs": [ ... ]
}
A file that instead describes content types themselves — like:
{
"model": [
{
"bundle": "homepage",
"fields": [ { "id": "hero_title", "type": "string" } ]
}
]
}
— is a model definition, not content. The dashboard will fetch it, post it, Drupal will accept the request, and your site will end up with zero content entities. If that's all your repo ships, uncheck "Import content" when saving the template, or point content_payload_path at a different file that contains actual entries.
See Content for the full shape reference.
Preview-iframe compatibility
The Drupal tenant auto-enables iframe preview for every node bundle that exists. Your frontend's catch-all route needs to render those bundle paths correctly when loaded inside an iframe — don't redirect() unknown paths to /, handle them the same way the route would during a normal page visit.
Verify preflight
On each template card there's a Verify button that runs a subset of the above checks against the repo without having to create a space:
- GitHub connection is active (for private repos).
- Repo + branch are reachable.
package.jsonexists and is parseable.- For Next.js targets —
netlify.toml+@netlify/plugin-nextjsare present. - Content payload exists at the configured path, parses as JSON, and looks like content (not a model definition).
Run Verify before creating spaces from a newly-saved template — it catches most of the "deployed fine, but the site is empty or 404s everywhere" surprises.
Related
- Getting Started — create your first space.
- Deployment — Netlify and Vercel specifics.
- Content — the import payload shape.
- Visual Editor — what
features.supports_puckbuys you.