# Layers & rendering

How artifacts become output documents

Canonical URL: https://docs.paradoc.dev/concepts/rendering/

Artifacts define structure. **Layers** define how that structure becomes output.

A single artifact can have multiple layers—Markdown for AI agents, PDF for printing, HTML for web display—all representing the same underlying data.

***

## What Layers Are

A layer is a **view** of an artifact. It combines a template with the artifact's data to produce output.

Layers are not transformations—they don't change the artifact. They're representations: different ways of looking at the same source of truth.

```ts
const form = p.form()
  .name("invoice")
  .fields({
    customer: { type: "text" },
    total: { type: "money" },
  })
  .defaultLayer("markdown")
  .layers({
    markdown: p.layer().inline().mimeType("text/markdown").text(`
# Invoice
Customer: {{fields.customer}}
Total: {{fields.total}}
    `),
    pdf: p.layer().file().mimeType("application/pdf").path("templates/invoice.pdf"),
  })
  .build()
```

***

## Inline vs File Layers

**Inline layers** define the template directly in the artifact—good for simple templates or keeping everything in one place.

**File layers** reference an external template file—good for complex templates or when designers maintain templates separately.

A React layer (`text/tsx` or `text/jsx`) must be a file layer. Its `path` names the composition module, so an inline React layer fails validation.

***

## Templates and Bindings

Text-based layers (Markdown, HTML, plain text) and DOCX layers use Paradoc
templates. Everything inside a marker is an [artifact expression](/concepts/logic):
`{{fields.fieldName}}` inserts a formatted value, `{{#if fields.total > 1000}}`
tests a boolean condition, and `{{#each parties.tenant}}` loops with `item` as
the current row. See [Text layers](/guides/text-layers#template-syntax-reference).

PDF layers work differently. A PDF template has AcroForm fields rather than
placeholders, so a PDF layer declares **bindings**: each key is an AcroForm
field name in the template, and each value is the Paradoc path that fills it.
Only PDF layers take `bindings` and `bindingsFrom`; validation rejects them on
any other layer.

```ts
pdf: p.layer()
  .file()
  .mimeType("application/pdf")
  .path("templates/lease.pdf")
  .bindings({
    "topmostSubform[0].Page1[0].TenantName[0]": "fields.tenantName",
  })
```

***

## Resolution

When layers reference external files, **resolvers** tell Paradoc how to load them. This makes Paradoc environment-agnostic—the same artifact works in Node, browsers, or custom environments.

A resolver is bound once, when the artifact is built or loaded. Every instance
you derive from it afterward—every `fill`, every render, every mutator—carries
that same resolver, so render calls don't repeat it.

```ts
import { createFsResolver } from "@paradoc/resolvers/fs";

const form = p.form()
  .name("invoice")
  // ...fields, layers...
  .build({ resolver: createFsResolver({ root: "./templates" }) })

const output = await form.render({ layer: "pdf" })
```

See [Resolvers](/sdk/resolvers) for available resolvers and how to write custom ones.

***

## Formatting

When rendering, field values are converted to strings. A `money` value becomes `"$1,500.00"`, a `date` becomes `"Mar 15, 2024"`.

One immutable **formatter** controls value presentation for an artifact. Select a precise locale and compose any value-specific policy explicitly.

```ts
const output = await form.render({
  renderer: createLayerRenderer({ formatter: createFormatter({ locale: 'de-DE' }) })
})
```

See [Formatting](/sdk/formatting) for details on formatting and localization.

***

## Renderers

Paradoc selects the built-in implementation from the layer's MIME type. The
same API renders Markdown, HTML, plain text, PDF, or DOCX, so applications do
not need their own format switch.

```ts
const markdown = await filled.render({ layer: 'markdown' })
const pdf = await filled.render({ layer: 'pdf' })
```

Pass a custom renderer or configured `createLayerRenderer()` through the optional
`renderer` property. Format entry points remain available for low-level
operations. See [Renderers](/sdk/renderers) for the full API.

***

## React Layers

A React layer points at a React component in a `.tsx` or `.jsx` file. Core does
not render React itself, so pass a renderer for the React MIME types in
`renderers`. `reactLayerRenderers()` from `@paradoc/react-pdf` builds the
entries for both `text/tsx` and `text/jsx`.

```tsx
import { reactLayerRenderers } from "@paradoc/react-pdf";
import Invoice from "./invoice.tsx";

const form = p.form()
  .name("invoice")
  .fields({ customer: { type: "text" } })
  .layers({
    composition: p.layer().file().mimeType("text/tsx").path("./invoice.tsx"),
  })
  .build()

const pdf = await form.fill({ fields: { customer: "Acme" } }).render({
  layer: "composition",
  renderers: reactLayerRenderers({ components: { "./invoice.tsx": Invoice } }),
})
```

`components` binds the layer's `path` to a component that is already imported,
so a bundled application never loads the module at render time. Without a
registered renderer, rendering a React layer throws
`UnregisteredLayerRendererError`.
