# Custom templates

> Write your own Falak templates — the template.yaml format, inputs, generators, placeholders and validation — and import from YAML, a URL or a Compose site.

Source: https://falak.sh/docs/templates/custom-templates/

Organizations can add their own templates next to the catalog. A custom template uses the same format as the catalog: a `template.yaml` describing the app and its inputs, plus a Docker Compose file.

## Create one

  
    
    1. Open **Settings → Templates** → **Import**.
    2. Paste or upload a template bundle (a `template.yaml` with a `compose:` key), or enter an `https://` URL to fetch it from.
    3. Falak validates it and shows problems line by line. Save.
    
    URL imports: https only, public addresses only, at most 3 redirects, 10-second timeout, 20 fetches per minute.
  
  
    
    1. Open a Compose site's panel → **⋯ → Save as template**.
    2. Falak drafts `template.yaml` from the site: its public services, and inputs for its variables.
    3. Review the name, slug, inputs and generators, then save.
    
  

Custom templates appear in the gallery next to catalog templates. Edit or delete them under **Settings → Templates** (`templates.manage`: owners, admins, developers). Each organization can have up to 200.

## The format

```yaml title="template.yaml"
name: Wiki.js
slug: wikijs
version: 1.0.0
description: Modern wiki on Node.js with PostgreSQL.
category: cms
icon: wikidotjs            # a simple-icons key, or ./icon.svg
docs: https://docs.requarks.io
stateful: true             # warn when deploying to more than one server
min_memory_mb: 512
popular: false
tags: [wiki, docs]
public:
  - service: wiki
    port: 3000
inputs:
  - key: DB_PASSWORD
    type: secret
    generate: password(32)
    label: Database password
  - key: SITE_TITLE
    type: string
    label: Site title
    default: Our wiki
    required: false
compose:
  services:
    wiki:
      image: ghcr.io/requarks/wiki:2.5.300
      expose: ["3000"]
      environment:
        DB_TYPE: postgres
        DB_HOST: db
        DB_USER: wiki
        DB_PASS: ${DB_PASSWORD}
        DB_NAME: wiki
      depends_on: [db]
    db:
      image: postgres:17.11-alpine
      environment:
        POSTGRES_USER: wiki
        POSTGRES_PASSWORD: ${DB_PASSWORD}
        POSTGRES_DB: wiki
      volumes: [wiki-db:/var/lib/postgresql/data]
  volumes:
    wiki-db:
```

In the catalog, the Compose file lives next to `template.yaml` as `compose.yaml` (plus an optional `icon.svg`); an import bundle carries it under `compose:` (as a mapping or a string). Use one or the other, not both.

### Top-level keys

| Key | Required | Rule |
|---|:-:|---|
| `name` | ✓ | Up to 60 characters |
| `slug` | ✓ | `^[a-z0-9][a-z0-9-]{0,49}$` |
| `version` | ✓ | Semantic version, e.g. `1.0.0` |
| `description` | ✓ | Up to 300 characters |
| `category` | ✓ | `automation`, `analytics`, `cms`, `databases`, `dev-tools`, `monitoring`, `storage`, `communication`, `ai` |
| `icon` | | A simple-icons key (`[a-z0-9]`, up to 64) or `./icon.svg` |
| `docs` | | An `https://` URL |
| `stateful` | | Boolean; warns on multi-server deploys |
| `min_memory_mb` | | Integer 64–262144 |
| `popular` | | Boolean |
| `tags` | | Up to 10 lowercase words (up to 24 characters each) |
| `public` | ✓ | At least one `{service, port}` |
| `inputs` | | List of inputs (below) |
| `compose` | ✓* | The Compose file (*or a separate `compose.yaml`) |

Unknown keys are errors, so typos never silently drop settings.

### Inputs

| Key | Meaning |
|---|---|
| `key` | Variable name, `^[A-Z_][A-Z0-9_]{0,127}$` |
| `type` | `string`, `secret`, `email`, `number`, `boolean`, `select`, `domain` |
| `label`, `description`, `placeholder` | Form text |
| `default` | Default value. May contain references like `${{ postgres.DATABASE_URL }}` or `${{ falak.url(web) }}` |
| `generate` | `secret(n)`, `password(n)`, `hex(n)` (n = 8–128) or `uuid` |
| `options` | Choices for `select` |
| `required` | Default `true` |

Inputs become **site variables** (secrets encrypted). Generated values are created once when the site is created and never change on redeploy.

| Generator | Produces |
|---|---|
| `secret(n)` | n characters from A–Z, a–z, 0–9 |
| `password(n)` | n characters from an unambiguous alphabet with upper case, lower case and digits (URL and dotenv safe) |
| `hex(n)` | n lowercase hex characters |
| `uuid` | A random UUID v4 |

### Falak placeholders

Rendered **once**, when a site is created from the template:

| Placeholder | Becomes |
|---|---|
| `${{ falak.url(<service>) }}` | `https://<domain of that public service>` |
| `${{ falak.domain(<service>) }}` | The domain of that public service |
| `${{ falak.site }}` | The site slug |

Compose interpolation (`${VAR}`) stays Compose-native and reads the site's variables at deploy time. Besides inputs, `FALAK_SITE_ID`, `FALAK_SERVER_ID`, `FALAK_DEPLOYMENT_ID` and `FALAK_RELEASE_ID` are available.

## Validation rules

A template is rejected when:

- `compose.yaml` does not parse, or `include`/`extends` are used;
- a service uses `build:` (templates must reference published images);
- an image is missing, interpolated (`${…}`), untagged or tagged `latest` (pin a version or a `@sha256:` digest);
- a service sets `container_name` (Falak names containers per site so a template can be deployed twice);
- a `${VAR}` is neither an input nor a Falak variable (use `${VAR:-default}` for optional ones);
- a `${{ … }}` in `compose.yaml` is not a Falak placeholder (variable references belong in input defaults);
- a public service does not exist or does not expose its port (add `expose: ["3000"]`);
- the Compose [security policy](/docs/deploy/docker-compose/#security-policy) is violated.

Each of `template.yaml` and the Compose file may be up to 256 KiB.

## Preview locally

On the control plane host, render a catalog template with sample inputs to see what a site would get:

```bash
falak-ctl artisan templates:render n8n --domain=falak.test --env-file=/tmp/n8n.env
```

## Next steps
