# Building extensions | Sophia Stack X

Source: http://sophiastack.sophiaxt.com/docs/extensions

# Building extensions

An extension is a folder. It can add content types, page blocks, modules, a whole template, data models with admin screens, web routes and background reactions to events. Server code is JavaScript in a sandbox. There is no Node, no npm and no build tool to install.

## Start one

From a folder that has an `extensions` directory:

```
sophia new app my-app            # also: block, component, template
sophia check extensions/my-app   # add --json for a machine-readable report
```

`check` validates the manifest, parses every template and compiles `server.js`. Then switch the extension on for a site under **Extensions**, or with the `extension_enable` tool.

## The folder

```
extension.json      the manifest
templates/*.html    Jinja templates; names are "<extension>:<path>"
server.js           optional server code
css/, js/, img/     sources bundled by "sophia build" (minified and fingerprinted)
```

After editing, run `sophia check` and `sophia build`, then restart or press **Reload extensions**.

## The manifest

Every key is optional except `name`:

```
{
  "name": "tickets",
  "title": "Tickets",
  "version": "0.1.0",
  "kind": "app",
  "description": "A small support desk.",
  "server": "server.js",
  "permissions": ["data", "mail"],
  "collections": [{ "key": "faq", "label": "FAQs", "fields": [{ "name": "answer", "label": "Answer", "type": "richtext" }] }],
  "models": [{ "key": "ticket", "label": "Tickets", "title_field": "subject",
               "fields": [{ "name": "subject", "label": "Subject", "type": "text", "required": true }] }],
  "blocks": [{ "type": "promo", "label": "Promo", "template": "blocks/promo.html", "fields": [] }],
  "modules": [{ "type": "hours", "label": "Hours", "template": "modules/hours.html", "fields": [] }],
  "admin": [{ "key": "queue", "label": "Support queue", "perm": "content.edit" }],
  "settings": [{ "name": "notify", "label": "Email me at", "type": "email" }]
}
```

Field types: `text`, `textarea`, `richtext`, `number`, `toggle`, `select`, `image`, `file`, `url`, `date`, `color`, `code`, `email`, `secret`, `list` (with its own fields) and `item` (a link to another entry). Saved values are always cleaned against the fields.

`sophia schema` prints the full JSON Schema of the manifest.

## Templates

Templates use Jinja syntax. They see the site, the page, menus, the current entry or list, block and module settings, and the template's own settings as `theme`. Helpers include `position()`, `asset()`, `link()`, `t()` for translated strings, `render_blocks()`, and the filters `excerpt`, `date`, `time12`, `tel` and `money`.

## Server code

```
sx.route("GET", "/open", (req) => ({ json: sx.data.list("ticket", { where: { status: "open" } }) }));

sx.route("POST", "/new", (req) => {
  if (!sx.rateLimit("ip:" + req.ip, 5, 3600)) return { status: 429, text: "Slow down." };
  sx.data.create("ticket", { subject: String(req.form.subject || "").slice(0, 200), status: "open" });
  return { redirect: "/x/tickets/thanks" };
});

sx.admin("queue", (req) => ({ html: sx.render("queue.html", { rows: sx.data.list("ticket", {}).items }) }));

sx.on("record.saved", (event) => sx.log("saved", event));
```

Routes answer at `/x/<extension>/<path>`. A route returns `{ json }`, `{ html }`, `{ text }`, `{ page: { title, html } }` (wrapped in the site's layout) or `{ redirect }`, with optional `status` and `headers`.

The request carries method, path, params, query, body, json, form, headers, ip, the signed-in site account and the site.

What code may call depends on its declared permissions:

CallPermission

`sx.data.list / get / create / update / remove`

`data`

`sx.content.list / get`

`content.read`

`sx.content.draft` (an editor publishes it)

`content.write`

`sx.accounts.get / list / setRoles`

`accounts`

`sx.http.fetch(url, ...)`

`http:<host>` for each host

`sx.mail.send({ to, subject, text })`

`mail`

Always available: `sx.settings()`, `sx.site()`, `sx.render()`, `sx.escape()`, `sx.log()`, `sx.rateLimit()` and `sx.requireRole(req, "staff")`. Each call runs fresh with a memory and time limit (64 MB, 3 seconds by default).

## Live page regions

Parts of a page can update from the server without a reload, with no framework and no build step. Include the core script and mark the region:

```
<div id="queue" data-sx-live="/x/tickets/queue" data-sx-every="15"></div>
<form data-sx-target="#queue" method="post" action="/x/tickets/close">...</form>
<a data-sx-target="#detail" href="/x/tickets/item/4">Open</a>
<script src="{{ asset('core:live.js') }}" defer></script>
```

`data-sx-live` loads the region from a route that returns `{ html }`, and `data-sx-every` refreshes it every so many seconds while the tab is visible. A form or link with `data-sx-target` sends through fetch and swaps the answer into the target. `data-sx-swap="outer"` replaces the element itself. The route sees `req.live` as true, so one route can answer a whole page or just the part.

## Rules worth keeping

*   Escape anything a visitor typed with `sx.escape` before putting it in HTML.
*   Declare only the permissions you use. Secrets go in settings fields of type `secret`.
*   Content written from code is saved as a draft. People publish.

## Packing and signing

```
sophia keygen my.key
sophia pack extensions/my-app --key my.key
sophia trust <public key>        # on the install that will accept it
sophia install packages/my-app-0.1.0.sxp
```

A package installs only when a key that install trusts signed it.

The help desk extension that ships with Sophia Stack X is a complete example of an app with models, admin screens, routes and email.
