---
slug: 14-modules
title: Modules (use / export)
description: Split Synsema across files with use and export — a module is a map of its exports, imported under an alias; export routes lets a serve mount whole route groups.
example_ids: [modules]
---

# Modules (`use` / `export`)

Split code across files. `export` makes a `task`/`type`/`let`/`enum`/`routes` public; anything else stays private. A module is a **map of its exports**, imported under an alias.

The module — `mathlib.syn`:

```synsema
export task square(n)
    give n * n

export let VERSION be "1.0"

task private_helper()        -- no `export` → not visible to importers
    give 42
```

The entry that imports it:

```synsema
-- Doc example: modules (use / export). Imports ./mathlib.syn under the alias `math`.
intent: "doc example: modules"
use "./mathlib.syn" as math

print("math.square(5) = " + text(math.square(5)) + ",  VERSION " + math.VERSION)

test "import an exported task and let (private_helper is NOT visible)"
    assert_eq(math.square(5), 25)
    assert_eq(math.VERSION, "1.0")
```

## Rules

- `use "./path.syn" as alias` — paths are **relative to the importing file**; `.syn` only, no URLs/FFI, and traversal (`../`) is blocked.
- Cross-file calls need the **alias prefix**: `math.square(5)`, `math.VERSION`.
- Imports are **cached** (loaded once), **transitive**, and **cycle-checked**.
- A module must **not** have a top-level `require` or `serve` — those belong in the entry file. A per-task `require` inside a module task is fine.
- `synsema check entry.syn` resolves and parses the **whole import graph** (broken paths, cycles, forbidden `serve`/`require` in modules all fail the check).

## `export routes` — route groups a serve can `mount`

A big site doesn't have to be one big serve block. A module exports a routes group; the entry mounts it (bodies can call the module's **private** helpers by simple name):

```synsema
-- shop.syn
task fmt(n)                        -- private
    give "$" + text(n)

export routes shop
    route "GET /shop"
        give html("<h1>" + fmt(99) + "</h1>")
    route "POST /shop/buy"
        expect body {item: text}
        give created(json of request)
```

```synsema
-- app.syn
use "./shop.syn" as shop
serve on 8080
    mount shop.shop                -- or: mount shop.shop at "/store"
```

Groups accept `route` entries with their own `rate_limit` and `timeout` (engine v0.6.19+: a mounted route gets its own rate zone, and a mount prefix is another zone — before v0.6.19 both were refused inside a group); `stream` and `socket` routes still belong in the serve block, and `synsema check` says so with the same message `serve` would give at start. A mounted `requires auth` still demands `auth with` on the serve block, validated when the serve is built. See [HTTP server](40-serve) and [Build a website](41a-build-a-website).
