# Create a site

## When to use this

You have an active server and want a new site on it. Scope: `sites:write` (and `sites:read` to check). For servers that already run sites, see "Import" at the end.

## Site types

`type` is one of:

| Type | For | Extra fields |
| --- | --- | --- |
| `laravel` | Laravel apps | `php_version`, `create_database` |
| `php` | Other PHP apps | `php_version`, `create_database` |
| `static` | HTML files, or a front end built with a tool: **React** (Vite, Create React App), Vue, Svelte, Angular. pfcloud runs `npm install` and `npm run build`, then serves `dist` (or `build` / `out` if the build writes that) | none |
| `nextjs`, `node` | **Next.js** (runs as a server with `npm run start`) and other Node apps | `app_port`, `start_command` (see the API reference for the exact rules) |
| `python` | Python apps | `app_port`, `start_command` |
| `custom` | Any language, started by your own command | `app_port`, `start_command` (required) |
| `docker` | A Dockerfile at the repository root | `container_port`; the server needs Docker installed |
| `wordpress` | WordPress installed for you (MySQL or MariaDB servers only) | `wordpress_title`, `wordpress_admin`, `wordpress_email` |

Common fields: `server` (public id), `domain`, `aliases`, `branch`, `create_database` (boolean). The complete list is in [Sites](/developers/api-sites).

## Three ways to give the site its code

### A. A Git URL

```bash
curl -s -X POST $MYFORGE/sites \
  -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: create-shop-1" \
  -H "Content-Type: application/json" \
  -d '{"server":"SERVER_ID","domain":"shop.example.com","type":"laravel",
       "repository":"git@github.com:your-org/shop.git","branch":"main",
       "php_version":"8.4","create_database":true}'
```

For a private repository over SSH, pfcloud's deploy key must be allowed on the repository (the server page and site page show it).

### B. A repository from a connected GitHub account

Connect your GitHub account to the team first (team settings, GitHub). Then list accounts and repositories:

```bash
curl -s -H "Authorization: Bearer $TOKEN" $MYFORGE/github/accounts
curl -s -H "Authorization: Bearer $TOKEN" $MYFORGE/github/accounts/ACCOUNT/repositories
```

Accounts show their `login`. Repositories show `full_name` and `default_branch`. Both need `sites:read`. Then create the site:

```bash
curl -s -X POST $MYFORGE/sites \
  -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: create-shop-2" \
  -H "Content-Type: application/json" \
  -d '{"server":"SERVER_ID","domain":"shop.example.com","type":"laravel",
       "github_account":"your-org","github_repository":"your-org/shop"}'
```

`branch` is optional here and defaults to the repository's default branch. The site follows the repository through pfcloud's GitHub App, and a push to the branch deploys it. No deploy key or webhook is needed.

### C. No code yet

Leave out `repository` and the GitHub fields. pfcloud creates the site's folder on the server, the Nginx configuration and the database, but the site has no code. It shows as waiting for a repository and cannot be deployed. A deploy request answers `409`. Connect a repository later:

```bash
curl -s -X PUT $MYFORGE/sites/SITE_ID/github \
  -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: connect-shop-1" \
  -H "Content-Type: application/json" \
  -d '{"account":"your-org","repository":"your-org/shop","branch":"main"}'
```

For a plain Git address instead of GitHub:

```bash
curl -s -X PUT $MYFORGE/sites/SITE_ID/repository \
  -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: repo-shop-1" \
  -H "Content-Type: application/json" \
  -d '{"repository":"git@github.com:your-org/shop.git","branch":"main"}'
```

This works only while the site has no repository. You can also use the **Repository** card on the site page. To stop following GitHub use `DELETE /sites/SITE_ID/github`.

WordPress sites are the exception: they are installed by pfcloud and need no repository.

## Wait for it

The answer is `201`. Poll `GET /sites/{site}` until `status` is `active` or `failed` (see [Requests and errors](/developers/requests-and-errors)). If it failed, read the reason in the dashboard run log, then `POST /sites/{site}/retry`.

A WordPress answer contains `wordpress_admin` once (the generated admin password). It is never stored and never replayed, so save it at once.

## DNS and HTTPS

1. Point the domain to the server. If your team connected a DNS provider, `POST /sites/{site}/dns` adds the missing A records. See [DNS and domains](/developers/dns-and-domains).
2. HTTPS is automatic for sites pfcloud created: once the domain points at the server, pfcloud requests a Let's Encrypt certificate (`auto_https` is on by default). To ask now, `POST /sites/{site}/https`. See [HTTPS certificates](/developers/https-certificates).

## Settings after creation

`PATCH /sites/{site}` accepts `auto_deploy`, `backup_before_deploy`, `uptime_enabled`, `uptime_path`, `health_check_path`, `build_script`, `after_deploy_script`, `start_command`, `app_port`, `canonical_host`, `custom_nginx`, `php_version`. Fields are applied in that order. If one is refused (`422`), the ones before it are already saved, so send one field per call when that matters.

## Import a site that already runs on a server

For servers pfcloud adopted: `GET /site-imports?server=SERVER_ID` lists importable sites; `POST /site-imports` imports one (`server`, `domain`, `type`, `mode` = `in-place` or `watch`, `run_as`, `branch`); `POST /site-imports/all` imports all. pfcloud backs up the database first and refuses to import if it cannot.

## What can go wrong

| Problem | Cause and fix |
| --- | --- |
| `422` plan limit | Your plan's site limit is reached. Remove a site or upgrade. |
| `422` domain used | The domain is already in the team, or was deleted and is waiting to be purged. |
| `404` on the server | The server is outside the token's limits, or not in the team. |
| Site `failed` | Read the run log, fix the cause (for example a PHP version missing on the server), then `POST /sites/{site}/retry`. |
| Deploy `409` | No repository connected yet, or the site is not `active`. |
