PaperStudio/Render Forms
UsuarioSoporteDeveloper

Render Forms

A generic form system that auto-generates user-facing forms from any template's form field definitions. Works with all template types (pintora, echart, typst, html, image_compose, etc.).

Overview

Templates define user-fillable fields via the dedicated form_fields JSONB column (preferred) or the legacy seed_json._form array. The system:

  1. Extracts form field definitions from the template
  2. Renders a dynamic HTML form with validation
  3. Submits user input to the render engine
  4. Displays the rendered output inline

API

Get form metadata

GET /v1/render/form/{idOrKey}?company_id=uuid

Resolves the template by ID, template_key, or @shortref code. Returns:

{
  "template_id": "template_01jq5...",
  "template_key": "my_template",
  "short_ref": "@k7f2n",
  "description": "My Template",
  "source_type": "pintora",
  "form_fields": [...],
  "defaults": {...},
  "output_formats": ["svg", "png", "pdf"]
}

Render with form data

Use the standard render endpoint with the user's form values:

POST /v1/render
{
  "company_id": "uuid",
  "template_id": "template_01jq5...",
  "output_format": "svg",
  "data": { "field1": "user_value", "field2": 42 },
  "inline": true
}

form_fields Column (Recommended)

The form_fields JSONB column on the templates table is the preferred way to define form fields. It's a top-level array of field objects:

[
  {
    "key": "team_name",
    "label": "Team Name",
    "type": "text",
    "required": true,
    "placeholder": "Engineering"
  },
  {
    "key": "style",
    "label": "Style",
    "type": "select",
    "options": ["default", "dark", "forest"],
    "default": "default"
  }
]

Default values for these fields live in seed_json as sibling keys (e.g., "team_name": "Engineering").

Legacy: _form Convention

The _form array inside seed_json is still supported as a fallback. The API prefers form_fields when non-empty, otherwise reads seed_json._form. The _form key is metadata only — stripped before merging into render data.

{
  "_form": [
    {
      "key": "team_name",
      "label": "Team Name",
      "type": "text",
      "required": true,
      "placeholder": "Engineering",
      "description": "Name of the team"
    },
    {
      "key": "style",
      "label": "Style",
      "type": "select",
      "options": ["default", "dark", "forest"],
      "default": "default"
    },
    {
      "key": "max_depth",
      "label": "Max Depth",
      "type": "number",
      "min": 1,
      "max": 10,
      "default": 3
    }
  ],
  "team_name": "Engineering",
  "style": "default",
  "max_depth": 3
}

Field Types

Type HTML Element Output Notes
text <input type="text"> string maxLength, pattern
textarea <textarea> string rows=4 default
number <input type="number"> number min, max, step
select <select> string options array
checkbox <input type="checkbox"> boolean
date <input type="date"> ISO string
color <input type="color"> hex string
email <input type="email"> string validation
url <input type="url"> string validation
hidden (not rendered) any passed silently

Field Properties

Property Type Required Description
key string yes Variable name used in templates
label string yes Display label
type string yes Input type (see above)
required boolean no Validation flag
default any no Pre-fill value
placeholder string no Input placeholder
description string no Help text below field
options string[] no Options for select type
min number no Min for number type
max number no Max for number type

Frontend Route

/render/:key — resolves by template_key, @shortref, or template ID.

The page:

  1. Fetches form metadata from the API
  2. Renders a dynamic form from _form definitions
  3. Pre-fills defaults from seed_json
  4. On submit: renders via POST /v1/render
  5. Displays result inline (SVG/image) or offers download

Integration with $fn:

$fn: data functions in seed_json are resolved before form defaults are applied. This means forms can show live database values as defaults:

{
  "_form": [
    {
      "key": "period",
      "label": "Period",
      "type": "select",
      "options": ["7d", "30d", "90d"]
    }
  ],
  "total_customers": "$fn:count(customers)",
  "period": "30d"
}

The total_customers value is resolved to a real number before Nunjucks rendering, even though it's not a form field.

CLI

# Inspect form fields
docuget render my_template --form-info

# Interactive mode — prompts for each _form field
docuget render my_template --interactive --out=svg

# Override fields directly
docuget render my_template --out=pdf --field company_name="Acme" --field date=2026-03-25

# Generate signed shareable URLs
docuget share my_template --ttl=86400
# Output:
#   View: https://front1.docuget.cloud/render/my_template?company_id=...&sig=...&exp=...
#   Form: https://front1.docuget.cloud/render/my_template/form?company_id=...&sig=...&exp=...

Sharing

Signed URLs allow anyone to access the render view or fillable form without logging in:

  • View URL (/render/:key?sig=...) — renders immediately with seed_json defaults
  • Form URL (/render/:key/form?sig=...) — shows the fillable form, user edits fields, clicks Render

Generate from:

  • CLI: docuget share <template_key> --ttl=86400
  • API: GET /v1/render/form/:key/sign?company_id=...&ttl=86400
  • UI: Click "Share" button in Data Science or Templates editor

Signatures use HMAC-SHA256 with the RENDER_SIGN_SECRET env var. Default TTL is 24 hours.