---
summary: Jinja2 themes packaged into S3, hot-swappable per site, declarative block
  types.
title: Template packs
path: concepts/template-packs
status: published
---

# Template packs

A **template pack** is a directory of Jinja2 templates, CSS, JS, and static
assets bundled together and uploaded to ScaiCMS. Each site has one active
pack; switching themes is one admin click.

## Pack structure

```
my-pack/
  manifest.json
  templates/
    base.html
    page.html
    blog-post.html
    partials/
      header.html
      footer.html
  assets/
    css/
    js/
    images/
```

## manifest.json

The manifest names the templates and declares custom block types:

```json
{
  "name": "Scailabs",
  "slug": "scailabs",
  "version": "1.0.0",
  "templates": [
    {"slug": "page", "file": "templates/page.html", "type": "page",
     "content_types": ["page"]}
  ],
  "block_types": {
    "text_image": {
      "label": "Text + Image",
      "icon": "image",
      "fields": [
        {"slug": "title", "type": "text"},
        {"slug": "content", "type": "richtext"},
        {"slug": "image", "type": "asset"}
      ]
    }
  }
}
```

The admin's `BlocksEditor` reads `block_types` at runtime — no frontend
code change needed when a pack adds a new section.

## Upload flow

```bash
python -m scaicms.cli.template_packs verify ./my-pack --check-syntax
python -m scaicms.cli.template_packs package ./my-pack -o /tmp/my-pack.zip
curl -X POST "$API/api/v1/template-packs/upload?set_as_default=true" \
     -H "Authorization: Bearer $TOKEN" \
     -H "X-Site-ID: $SITE" \
     -F "file=@/tmp/my-pack.zip"
redis-cli PUBLISH scaicms:cache:invalidate "site:$SITE:*"
```

The backend uploads the pack to S3 and registers each template. Delivery
fetches templates from S3 on demand and caches them.

See [Tutorials → First template pack](/docs/scaicms/tutorials/first-template-pack)
for an end-to-end walkthrough.
