Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions apps/docs/content/docs/compute/deploy-button.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
title: Deploy Button
description: Add a Deploy with Prisma button to your repository — one click copies your project into the user's GitHub account, provisions a Prisma Postgres database, and deploys it on Prisma Compute.
url: /compute/deploy-button
metaTitle: Deploy Button | Prisma Compute
metaDescription: Let anyone deploy your TypeScript project in one click. The Deploy with Prisma button clones your public GitHub repository, provisions Prisma Postgres, and deploys on Prisma Compute.
---

The Deploy Button lets anyone deploy your project in one click. Clicking it opens the Prisma Console, where the user picks a GitHub account and a region — Prisma then copies your repository's code into a new repository they own, creates a project with a [Prisma Postgres](/postgres) database, and starts the first deployment on [Prisma Compute](/compute).

Add it to your README, a blog post, or your own site:

[![Deploy with Prisma](/img/deploy-button.svg)](https://console.prisma.io/new/clone?repository-url=https%3A%2F%2Fgithub.qkg1.top%2Fprisma%2Fcompute-deploy-example&utm_source=docs)

:::note
The Deploy Button is in early access. It supports public GitHub repositories; support for private repositories is planned.
:::

## Make your repository deployable

Any public GitHub repository works with the Deploy Button when it meets this minimal contract:

- **Public GitHub repository.** The button deploys the repository's default branch.
- **A `prisma.compute.json` file at the repository root.** This is the same file Prisma Compute reads for any connected repository — it names the app and sets its HTTP port. Don't set a region in it; the person deploying picks the region, and it's used for both the database and the app. See [configuration](/compute/configuration).
- **A TypeScript project with a `package.json` and a lockfile** (`bun.lock`, `package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`) at the root. If it builds on Prisma Compute, it deploys.

That's it — no registration, API key, or approval process. A minimal working example you can copy is [`prisma/compute-deploy-example`](https://github.com/prisma/compute-deploy-example), whose README carries its own Deploy Button.

A minimal `prisma.compute.json`:

```json
{
"name": "my-app",
"port": 3000
}
```

## How it works

1. **Click the button.** The user lands on the clone page in the Prisma Console, which always shows exactly what will be deployed: your repository's name and the resolved commit, linked back to the source on GitHub. If they're signed out, they sign in first and return to the same page automatically.
2. **Choose where it goes.** They pick a GitHub account or organization, a name and visibility for their new repository, and a deployment region. If the Prisma GitHub App isn't installed yet, the flow walks them through installing it.
3. **Authorize once.** GitHub asks the user to authorize the repository creation. This authorization is used for that single deployment and is not stored.
4. **Prisma sets everything up.** Your code is copied — at the exact commit shown on the clone page — into a new repository under the user's account. A project with a Prisma Postgres database is provisioned, the database connection is configured automatically, and the first deployment starts.
5. **Push to deploy.** The new repository is connected to the project, so every later push deploys through the normal Git workflow.

The user owns the copy and the project. Prisma adds no credentials or platform files to the repository — database connection settings are managed in the Console, not committed to code.

## Requirements

For you, the author: only the [repository contract](#make-your-repository-deployable) above.

For the user clicking the button:

- A [Prisma Data Platform account](https://pris.ly/pdp) (free to create during the flow).
- A GitHub account with the Prisma GitHub App installed on the account or organization that will own the new repository — the flow offers the installation step when it's missing.
- Permission to create repositories under the selected account or organization.

No environment variables need to be configured up front: `DATABASE_URL` is provisioned and wired automatically. Anything app-specific can be added in the Console after the first deployment — see [environment variables](/compute/environment-variables).

## URL format

A Deploy Button is a link to:

```text
https://console.prisma.io/new/clone?repository-url=<your repository URL>
```

| Parameter | Required | Description |
| --- | --- | --- |
| `repository-url` | Yes | The public GitHub repository to deploy, in the form `https://github.com/owner/repo`. URL-encode it when composing the link. |
| `project-name` | No | Prefills the name of the repository the user creates (they can change it). Defaults to your repository's name. |
| `utm_source` | No | Attribution for where the click came from, for example `github-readme`. |
| `utm_medium` | No | Attribution for the kind of placement, for example `button`. |
| `utm_campaign` | No | Attribution for a specific campaign or launch. |

Example with a project name and attribution:

```text
https://console.prisma.io/new/clone?repository-url=https%3A%2F%2Fgithub.qkg1.top%2Facme%2Fshop-api&project-name=shop-api&utm_source=github-readme
```

## Create your button

Enter your repository URL and copy the generated snippet:

<DeployButtonGenerator />

## Snippets

A complete Markdown example for a README:

```md
[![Deploy with Prisma](https://www.prisma.io/docs/img/deploy-button.svg)](https://console.prisma.io/new/clone?repository-url=https%3A%2F%2Fgithub.qkg1.top%2Facme%2Fshop-api&utm_source=github-readme)
```

The same button in HTML:

```html
<a href="https://console.prisma.io/new/clone?repository-url=https%3A%2F%2Fgithub.qkg1.top%2Facme%2Fshop-api&utm_source=github-readme">
<img src="https://www.prisma.io/docs/img/deploy-button.svg" alt="Deploy with Prisma" width="172" height="36" />
</a>
```

## Troubleshooting

**"This repository cannot be deployed" with a message about `prisma.compute.json`.** The repository is missing the root configuration file. Add a `prisma.compute.json` at the repository root — see [make your repository deployable](#make-your-repository-deployable).

**The clone page says the repository must be public.** The button only supports public source repositories today. The user's *copy* can still be private — that's their choice in the deploy form.

**"A repository with this name already exists."** The user already has a repository with the chosen name. They can pick a different name in the form and deploy again.

**GitHub authorization was canceled or expired.** The authorization step must be completed within a few minutes. Returning to the clone page and clicking Deploy again starts a fresh attempt.

**The GitHub account isn't listed.** The Prisma GitHub App isn't installed on that account or organization. The "Connect GitHub" option in the deploy form starts the installation; after completing it on GitHub, the user continues from the clone page.

**The repository was created, but the deployment didn't start.** The repository and project are preserved — nothing is deleted on a partial failure. Pushing any commit to the new repository's default branch triggers a deployment through the normal Git flow. See [deployments](/compute/deployments).

**The app deployed but shows an error page.** Check the build and runtime logs on the project's deployment page, confirm the database finished provisioning in the Console, and verify the app listens on the port declared in `prisma.compute.json`. The [FAQ](/compute/faq) covers common runtime issues.
1 change: 1 addition & 0 deletions apps/docs/content/docs/compute/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"domains",
"---Integrations---",
"github",
"deploy-button",
"---Reference---",
"configuration",
"cli-reference",
Expand Down
8 changes: 8 additions & 0 deletions apps/docs/public/img/deploy-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
175 changes: 175 additions & 0 deletions apps/docs/src/components/deploy-button-generator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"use client";
import { useMemo, useState } from "react";
import { useCopyButton } from "fumadocs-ui/utils/use-copy-button";
import { buttonVariants } from "@prisma-docs/ui/components/button";
import { cn } from "@prisma-docs/ui/lib/cn";
import { withDocsBasePath } from "@/lib/urls";

const CONSOLE_CLONE_URL = "https://console.prisma.io/new/clone";
const BUTTON_IMAGE_URL = "https://www.prisma.io/docs/img/deploy-button.svg";

const OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
const REPO_PATTERN = /^[A-Za-z0-9._-]{1,100}$/;
const PROJECT_NAME_PATTERN = /^[A-Za-z0-9._-]{1,100}$/;

function parseRepositoryUrl(raw: string): { owner: string; repo: string } | null {
const value = raw.trim();
if (value.length === 0 || value.length > 300) return null;
let url: URL;
try {
url = new URL(value.includes("://") ? value : `https://${value}`);
} catch {
return null;
}
if (url.protocol !== "https:" || url.hostname !== "github.com" || url.port) return null;
if (url.username || url.password || url.search || url.hash) return null;
const segments = url.pathname.split("/").filter(Boolean);
if (segments.length !== 2) return null;
const owner = segments[0];
let repo = segments[1];
if (repo.endsWith(".git")) repo = repo.slice(0, -4);
if (!OWNER_PATTERN.test(owner) || !REPO_PATTERN.test(repo)) return null;
if (repo === "." || repo === "..") return null;
return { owner, repo };
}

function CopyButton({ value }: { value: string }) {
const [checked, onClick] = useCopyButton(() => navigator.clipboard.writeText(value));

return (
<button
type="button"
aria-label="Copy to clipboard"
className={cn(
buttonVariants({ color: "secondary", size: "sm", className: "shrink-0 gap-2" }),
)}
onClick={onClick}
>
{checked ? <i className="fa-regular fa-check" /> : <i className="fa-regular fa-copy" />}
Copy
</button>
);
}

function Snippet({ label, value }: { label: string; value: string }) {
return (
<div className="flex flex-col gap-1.5">
<span className="text-sm font-medium text-fd-muted-foreground">{label}</span>
<div className="flex items-start gap-2">
<pre className="min-w-0 flex-1 overflow-x-auto rounded-lg border bg-fd-secondary/50 p-3 text-xs leading-relaxed">
<code>{value}</code>
</pre>
<CopyButton value={value} />
</div>
</div>
);
}

const fieldClassName =
"w-full rounded-lg border bg-fd-background px-3 py-2 text-sm text-fd-foreground outline-none focus-visible:ring-2 focus-visible:ring-fd-ring";

export function DeployButtonGenerator() {
const [repositoryUrl, setRepositoryUrl] = useState("");
const [projectName, setProjectName] = useState("");
const [utmSource, setUtmSource] = useState("");
const [utmCampaign, setUtmCampaign] = useState("");

const parsed = useMemo(() => parseRepositoryUrl(repositoryUrl), [repositoryUrl]);
const projectNameValid =
projectName.trim() === "" || PROJECT_NAME_PATTERN.test(projectName.trim());

const url = useMemo(() => {
if (!parsed || !projectNameValid) return null;
const search = new URLSearchParams();
search.set("repository-url", `https://github.com/${parsed.owner}/${parsed.repo}`);
if (projectName.trim()) search.set("project-name", projectName.trim());
if (utmSource.trim()) search.set("utm_source", utmSource.trim());
if (utmCampaign.trim()) search.set("utm_campaign", utmCampaign.trim());
return `${CONSOLE_CLONE_URL}?${search.toString()}`;
}, [parsed, projectNameValid, projectName, utmSource, utmCampaign]);

return (
<div className="not-prose flex flex-col gap-5 rounded-xl border p-5">
<div className="grid gap-4 sm:grid-cols-2">
<label className="flex flex-col gap-1.5 sm:col-span-2">
<span className="text-sm font-medium">Repository URL</span>
<input
className={fieldClassName}
placeholder="https://github.com/owner/repo"
value={repositoryUrl}
onChange={(event) => setRepositoryUrl(event.target.value)}
/>
{repositoryUrl.trim() !== "" && !parsed ? (
<span className="text-xs text-fd-muted-foreground">
Enter a public GitHub repository URL like https://github.com/owner/repo.
</span>
) : null}
</label>
<label className="flex flex-col gap-1.5">
<span className="text-sm font-medium">
Project name <span className="font-normal text-fd-muted-foreground">(optional)</span>
</span>
<input
className={fieldClassName}
placeholder="my-app"
value={projectName}
onChange={(event) => setProjectName(event.target.value)}
/>
</label>
<label className="flex flex-col gap-1.5">
<span className="text-sm font-medium">
Source <span className="font-normal text-fd-muted-foreground">(optional)</span>
</span>
<input
className={fieldClassName}
placeholder="github-readme"
value={utmSource}
onChange={(event) => setUtmSource(event.target.value)}
/>
</label>
<label className="flex flex-col gap-1.5">
<span className="text-sm font-medium">
Campaign <span className="font-normal text-fd-muted-foreground">(optional)</span>
</span>
<input
className={fieldClassName}
placeholder="launch-2026"
value={utmCampaign}
onChange={(event) => setUtmCampaign(event.target.value)}
/>
</label>
</div>

{url ? (
<>
<div className="flex items-center gap-3 rounded-lg border border-dashed p-4">
<span className="text-sm text-fd-muted-foreground">Preview:</span>
<a href={url} target="_blank" rel="noreferrer">
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={withDocsBasePath("/img/deploy-button.svg")}
alt="Deploy with Prisma"
width={172}
height={36}
/>
</a>
</div>
<Snippet label="URL" value={url} />
<Snippet
label="Markdown"
value={`[![Deploy with Prisma](${BUTTON_IMAGE_URL})](${url})`}
/>
<Snippet
label="HTML"
value={`<a href="${url}"><img src="${BUTTON_IMAGE_URL}" alt="Deploy with Prisma" width="172" height="36" /></a>`}
/>
</>
) : (
<p className="text-sm text-fd-muted-foreground">
Enter your repository's GitHub URL to generate the button. The repository must be public
and contain a <code>prisma.compute.json</code> file at its root.
</p>
)}
</div>
);
}
2 changes: 2 additions & 0 deletions apps/docs/src/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx";
import { Youtube } from "@prisma-docs/ui/components/youtube";
import { APIPage } from "@/components/api-page";
import { ConceptAnimation } from "@/components/concept-animation";
import { DeployButtonGenerator } from "@/components/deploy-button-generator";
import {
AgentPrompt,
GetStartedTabs,
Expand Down Expand Up @@ -121,6 +122,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
APIPage,
AgentPrompt,
ConceptAnimation,
DeployButtonGenerator,
GetStartedTabs,
HeroGrid,
HeroPitch,
Expand Down
Loading