Molnify App Development Guide
This is the primary reference for building Molnify applications.
Overview
Molnify apps are spreadsheet-driven web applications. You design your app in Excel or Google Sheets, where:
- Green cells = Inputs (user-provided parameters)
- Red cells = Outputs (calculated results)
- Blue cells = Charts and Tables (aggregated data visualizations)
- Yellow cells = Actions (automated operations like email, HTTP calls)
- Purple cells = Metadata (app settings and configuration)
The spreadsheet formulas perform all calculations. Molnify renders the UI and handles user interaction.
Molnify reads the Excel file left to right, top to bottom. The order cells appear determines the order in the app UI.
What Molnify Covers
The spreadsheet is how an app is authored, not a ceiling on what it can be. Actions reach outside the workbook (email, HTTP, AI prompts), database tables give apps real storage, and a headless frontend puts the whole DOM under your control. A Molnify app can do anything a web page can do.
Cell Structure
Inputs, Outputs, Charts require three consecutive cells:
- Title cell - The label shown to users
- Value cell (colored) - The actual value/formula
- UI cell - Configuration options (can be empty)
Metadata requires two consecutive cells, both colored purple:
- Key cell (purple) - The metadata property name (e.g., "Name", "ID")
- Value cell (purple) - The metadata value
Actions require pairs of cells per property, value cell colored yellow:
- Key cell (no color) - The property name (e.g., "type", "to", "subject")
- Value cell (yellow) - The property value
Each action is a contiguous block of these pairs, separated from other actions by a blank row.
Quick Start
Most common patterns at a glance:
| Task | How |
|---|---|
| Create a new app from scratch | Use AppBuilder - see creating-from-scratch.md |
| Convert an existing Excel file | Follow converting-excel.md step-by-step |
| Add an input | Green cell (#00B050), title left, UI options right |
| Add an output | Red cell (#FF0000), formula referencing model or inputs |
| Add a chart | Blue data cells (#0070C0), headers/labels uncolored |
| Add an action | Yellow cell pairs (#FFFF00), key left, value right |
| Add metadata | Purple cell pairs (#7030A0), first sheet only |
| Style the app | CSS metadata + styling.md recipes |
Workflow: Define → Build (AppBuilder or openpyxl) → Validate → Deploy
Best Practices
Always set the
IDmetadata - This gives your app a stable URL and ensures you keep access to database tables when updating the app. Without an ID, Molnify auto-generates one, and updating creates a copy instead of replacing.Only include metadata that differs from defaults - Do not add metadata cells for values that match the default. This keeps your spreadsheet clean and easier to maintain. For example, don't include
AutoCalcEnabled: TRUEsince TRUE is already the default.Metadata must be on the first sheet - All purple metadata cells must be placed on the first sheet of your workbook. Metadata on other sheets will be ignored.
Set
ParseAllSheets: TRUEfor multi-sheet apps - By default, Molnify only parses the first sheet. If your app has inputs, outputs, actions, charts or metadata on multiple sheets, you must set the metadataParseAllSheetstoTRUE. AppBuilder always adds this automatically.Use
molnifyIgnoreto exclude sheets from color interpretation - Place the textmolnifyIgnorein cell A1 of any sheet that should not be scanned for Molnify components. If A1 already contains data, insert a new row at the top first. This is essential for sheets containing raw data models, lookup tables, or any content with conditional formatting or incidental cell colors that could be misinterpreted as inputs/outputs.Ensure text is readable on its background - When customizing colors (buttons, panels, headers), always verify sufficient contrast between text and background. Light text on light backgrounds or dark text on dark backgrounds makes the app unusable.
Display numbers at a sensible scale and precision - Raw formula results are rarely display-ready: they carry excessive decimals (
33.333333333) and often an awkward magnitude. Two separate decisions:- Precision: Round to what the reader can act on. ~3-4 significant figures is plenty for most displays. Use Excel's
ROUND()in the formula, or thedecimals=NUI option (andaxisDecimals=Nfor chart axes - long axis tick labels are a common readability killer). - Magnitude: If values run in the thousands or millions, scale the unit instead of printing every digit.
124 500 SEKreads better as124.5 tSEK, and8 300 000 SEKas8.3 MSEK. Divide by 1000/1e6 in the formula (or use themultiplierUI option) and state the scaled unit in the title - e.g. title "Revenue (tSEK)" rather than a 7-digit axis. Keep the chosen unit consistent across related outputs and a chart's whole axis. - Rule of thumb: before finalizing, ask "at this scale, is this number easy to read at a glance?" An axis labelled
0, 25000, 50000, 75000should usually be0, 25, 50, 75with a "tSEK" axis title. This applies to scalar outputs, table cells, and chart axes alike.
- Precision: Round to what the reader can act on. ~3-4 significant figures is plenty for most displays. Use Excel's
Verify formulas after adding or removing cells - When inserting or deleting rows/columns, Excel formulas may shift or break. Always verify that all formulas still reference the correct cells after structural changes. This is especially critical when working programmatically (e.g., with openpyxl) where automatic reference adjustment doesn't occur.
Keep strings in formulas under 256 characters - Excel automatically inserts
_LONGTEXTreferences when strings exceed 256 characters, but Molnify does not support_LONGTEXT. Split long strings using&orCONCATENATE():❌ ="This is a very long string that exceeds 256 characters..." ✓ ="This is a very long string that"&" has been split into chunks"&" each under 256 characters"Keep calculations in the spreadsheet, not in JavaScript - Molnify's strength is that business logic lives in Excel formulas. Avoid offloading calculations, conditional logic, or data transformations to JavaScript. JS should be reserved for UI manipulation (DOM changes, styling, event handling), not for computing values that formulas can handle. This keeps the app auditable, testable, and maintainable.
COUNTA counts empty strings from autofill - When autofill populates a named range, cells that receive no data are cleared but hold the value
""(empty string).COUNTAcounts these as non-empty, and so doesCOUNTIF(range,"<>")- the<>criterion matches an empty string and only excludes a genuinely blank cell. To count cells with actual data useROWS(range)-COUNTBLANK(range)orCOUNTA(range)-COUNTIF(range,""); both treat""as empty.SUMPRODUCT((range<>"")*1)does not work here - see Range arithmetic is not evaluated as an array below.Design with intent, not with effects - Every app should look like it was designed for its purpose, not generated from a template. Pick a color palette that fits the domain. Choose fonts with character. Avoid generic AI-generated aesthetics: purple gradients on white, uniform rounded corners and card shadows on every element, pulsing animations, gradient text, emoji placeholders, and staggered fade-up animations. Left-aligned content is easier to scan than centered. Vary visual weight instead of making everything look the same. If it looks like every other AI-generated dashboard, it needs a point of view.
Validate the assembled string when building formulas in code - Concatenating formula text in Python (especially adjacent f-strings) easily produces a stray
""that Excel reads as an escaped quote, swallowing every following&operator and string into one literal. A corrupted formula doesn't just fail its own cell: on load Excel runs "file level validation and repair" and strips the entire formula table for that sheet rather than dropping the one bad formula - so the app can render with every formula gone. Adjacent pieces likef'="<div>…"'+f'"&"<h2>…"'concatenate to="<div>…""&"<h2>…", where the""is misread. After building a formula programmatically,print/assertthe final string and confirm the double-quotes are balanced and every"&"is a real operator, not buried inside a literal.molnify_validate.pyflags formulas with an odd number of double-quotes, which catches the common form of this bug.Range arithmetic is not evaluated as an array - Molnify's engine does not do implicit array evaluation. In an ordinary formula cell, an argument that computes over a whole range -
(range>2)*1,--(range<>""),rangeA&rangeB,IF(range="","",range)- collapses to a single value by implicit intersection with the formula's own row or column. When there is no overlap the formula errors; when there is one it returns a plausible-looking wrong answer instead. The familiar Excel idioms built on this therefore do not work:❌ =SUMPRODUCT((A2:A50>2)*B2:B50) ✓ =SUMIF(A2:A50,">2",B2:B50) ❌ =SUMPRODUCT(--(A2:A50<>"")) ✓ =COUNTA(A2:A50)-COUNTIF(A2:A50,"") ❌ =SUM(IF(A2:A50="Bob",B2:B50)) ✓ =SUMIFS(B2:B50,A2:A50,"Bob")Reach for
SUMIF/SUMIFS,COUNTIF/COUNTIFS,AVERAGEIFS, or a helper column that computes the per-row value so a plainSUMcan total it.SUMPRODUCTis supported, but only with plain range references as arguments:SUMPRODUCT(A2:A50,B2:B50)is fine. Array formulas that a spreadsheet application genuinely entered as such (Ctrl+Shift+Enter) are evaluated correctly, butmolnify_builder.pydoes not write them, so they are not an option when building an app in code.
CSS & DOM Quick Reference
The rendered DOM does NOT match the spreadsheet - inputs are wrapped in third-party widgets and the selectors differ. styling.md has the full DOM tree, selector reference, default color scheme, chart-series colors, the Select2 / Font Awesome / custom-header gotchas, CSS Grid layouts, and dashboard recipes.
Companion Guides
Alongside this reference, companion guides cover specific needs: creating-from-scratch.md, converting-excel.md, styling.md, styling-examples.md, design.md, patterns.md, database.md, report-templates.md, custom-frontend.md, advanced-topics.md, python.md, and examples/. See local-instructions.md for an index of what each covers and when to reach for it, plus local setup and deployment.
Choosing Your UI Approach
- Headless (
Headless: TRUE) - Molnify serves an empty page plus the SDK; you write the DOM. Seecustom-frontend.md. - Standard UI - Molnify builds the page from colored cells; you restyle with CSS metadata and
JavaScriptAfterLoad. Seedesign.md,styling.md,patterns.md.
Default to headless. It does anything a web page can, usually renders faster, and is generally less work to build than bending the default DOM into shape - there are no inherited quirks to fight, only the ones you introduce. It can look exactly the way that you want, whereas the standard UI is bound by Molnify's default components.
Choose the standard UI when the user needs to own the interface. Molnify generates it from the spreadsheet, so the user can add an input, drop an output or re-order a section by editing cells and re-uploading. A headless frontend lives in JS and CSS we assume the user does not read: they can still change any calculation, never the interface.
| Situation | Choose |
|---|---|
| The user will want to add or remove UI elements without you | Standard UI |
| Anything else | Headless |
If you can't tell which it is, ask - it's one question and it decides the whole build.
All calculation stays in the spreadsheet either way. A number derived in JavaScript is a number the user cannot audit. Send input values through MolnifySDK.calculate() and render what comes back.
Headless gives up: scenarios (persist state in database tables instead), every metadata setting that configures the default chrome (JavaScriptAfterLoad, JavaScriptAfterCalc, AutoCalcEnabled, EnabledFor*, panel/banner/color settings, Template), SDK access to dropdown options, and automatic responsevalue handling.
Inputs Reference
IMPORTANT: Input formulas are only calculated once when the app opens. They are NOT recalculated during app use. This means:
- Inputs with formulas act as static default values
- Any cell that depends on user input (intermediate calculations, derived values) must be an output (red cell), not an input
- Only outputs are recalculated when users change values
Basic Properties
| Property | Description |
|---|---|
cell |
Excel cell reference (e.g., Sheet1!B2) |
name |
Display label for the input |
value |
Current/default value |
validation |
Validation rules |
UI |
UI-specific options (semicolon-separated) |
details |
Tooltip text (from cell comment) |
Input Types (UI Options)
Text & Numeric Inputs
| UI Type | Description | Example |
|---|---|---|
| (default) | Standard text/number field | UI: "" |
textarea |
Multi-line text input | UI: textarea |
nobuttons |
Numeric without +/- buttons | UI: nobuttons |
Sliders & Ranges
| UI Type | Description | Example |
|---|---|---|
slider |
Single-handle slider | UI: slider;min=0;max=100 |
range |
Dual-handle range slider | UI: range;min=0;max=100 |
delta=N |
Increment step between slider ticks | UI: slider;min=0;max=100;delta=10 |
gridNum=N |
Number of ticks on slider grid | UI: slider;min=0;max=100;gridNum=5 |
Dropdowns & Selection
| UI Type | Description | Example |
|---|---|---|
dropdown |
Dropdown from data validation list | UI: dropdown |
select |
Same as dropdown | UI: select |
multiple |
Multi-select dropdown | UI: multiple |
recorddropdown |
Dropdown populated from the app's record table | UI: recorddropdown;display=customerName |
rowdropdown |
Dropdown populated from any database table | UI: rowdropdown;tableName=data_my-app_0;display=name;map=name->custName |
To create a dropdown: create a named range with the list values, add Data Validation to the input cell referencing that range, and set UI: dropdown.
Database dropdowns (recorddropdown and rowdropdown) load options from a database table via AJAX. See database.md for full configuration.
Date & Time
| UI Type | Description | Example |
|---|---|---|
date |
Date picker | UI: date |
time |
Time picker | UI: time |
Special Inputs
| UI Type | Description | Example |
|---|---|---|
fileupload |
File upload | UI: fileupload;maxFiles=3;acceptedFileTypes=.pdf,.jpg |
signature |
Signature capture | UI: signature |
barcode |
Barcode/QR scanner | UI: barcode |
infotext |
Read-only info display (can contain HTML) | UI: infotext |
longinfotext |
Long read-only info (can contain HTML) | UI: longinfotext |
Boolean toggle buttons: Inputs with boolean values (True or False) automatically render as clickable toggle buttons instead of text fields.
Numeric Display Options
| Option | Description | Example |
|---|---|---|
min=N |
Minimum allowed value | UI: min=0 |
max=N |
Maximum allowed value | UI: max=100 |
delta=N |
Increment step for +/- buttons and slider ticks | UI: delta=0.5 |
multiplier=N |
Display multiplier | UI: multiplier=100 (shows 0.5 as 50) |
prefix=X |
Display prefix (inputs only) | UI: prefix=$ |
postfix=X |
Display suffix (inputs only) | UI: postfix=% |
decimals=N |
Number of decimal places | UI: decimals=2 |
placeholder=X |
Placeholder text | UI: placeholder=Enter value... |
regEx=X |
Regex pattern for validation | UI: regEx=^[A-Z]{2}[0-9]{4}$ |
Layout & Display Options
| Option | Description | Example |
|---|---|---|
hidden |
Hide the input | UI: hidden |
noTitle |
Hide the label | UI: noTitle |
class=X |
Custom CSS class - applied to .panel-body, not the outer .panel. For inputs, Molnify appends -form-group-class (e.g., class=half-left → .half-left-form-group-class) |
UI: class=my-custom-class |
dividerName=X |
Add section divider above | UI: dividerName=Personal Details |
tab=X |
Start a new tab - all following inputs are placed in this tab until the next tab=. Only set on the first input of each tab, not on every input. |
UI: tab=Advanced |
Conditional Display
Show/hide inputs and outputs based on variable values:
| Option | Description | Example |
|---|---|---|
showIfVariable=X |
Variable name to check | UI: showIfVariable=myVar |
showIfValue=X |
Show if variable equals value | UI: showIfVariable=myVar;showIfValue=Yes |
showIfValueNot=X |
Show if variable NOT equals value | UI: showIfVariable=myVar;showIfValueNot=No |
resetWhenHidden |
Reset value to default while hidden (prevents submission of stale data). Previously entered values reappear when the input is shown again. | UI: showIfVariable=myVar;showIfValue=Yes;resetWhenHidden |
To use conditional display, first expose the controlling input or output as a variable with variable=X, then reference it in dependent inputs.
User Context Variables
Automatically populate inputs with data from the user's session. These are set in the input's UI cell and are populated once when the app loads (before the first calculation). Not related to database autofill (autofill.* metadata).
| Variable | Value | Notes |
|---|---|---|
user.email |
Logged-in user's email | Preferred over user |
user |
User's email (or identification number if available) | Legacy alias - use user.email for clarity |
user.ip |
User's IP address | |
superuser |
"true" or "false" |
String, not boolean - compare with ="true" in formulas |
manager |
"true" or "false" |
String, not boolean - compare with ="true" in formulas |
currentRealm |
Current realm name | Only meaningful when Realms metadata is set |
tag1 through tag10 |
Custom per-user data fields | Requires DBUsers: TRUE - these fields are managed via the sidebar user interface |
Example: UI: user.email auto-fills with the user's email.
Example: UI: user.email;hidden auto-fills and hides the input (common for tracking who submitted).
JavaScript Integration
| Option | Description | Example |
|---|---|---|
variable=X |
Expose value to JavaScript (ASCII only - no ö, ä, å) | UI: variable=myInput |
jsOnChange=X |
Call JS function on change | UI: jsOnChange=handleInputChange |
Outputs Reference
Basic Properties
Same as inputs: cell, name, value, UI, details
Output Display Options
| Option | Description | Example |
|---|---|---|
icon=X |
Font Awesome 4.7.0 icon (FA5/6 names won't render) | UI: icon=fa-line-chart |
background=X |
Background color | UI: background=#f0f0f0 |
color=X |
Text color | UI: color=#333333 |
amongInputs |
Show in input panel (html outputs only). Tab placement depends on physical row order - see note below. | UI: amongInputs |
hidden |
Hide the output | UI: hidden |
hideCopy |
Hide copy button (html outputs only) | UI: hideCopy |
leftColumn |
Force into left column (works on html, charts, tables, peopleMatrix) | UI: leftColumn |
rightColumn |
Force into right column (works on html, charts, tables, peopleMatrix). Without this, panels alternate between columns. Required when using CSS Grid on #boxRow - the default alternation logic doesn't apply in Grid layouts. |
UI: rightColumn |
panelHidden |
Panel collapsed by default | UI: panelHidden |
dividerName=X |
Section divider above output (requires amongInputs) | UI: dividerName=Revenue and profit |
peopleMatrix |
People/team matrix visualization | UI: peopleMatrix;columns=5;rows=3 |
amongInputs and tabs: Molnify assigns items to tabs based on physical row order in the spreadsheet. An amongInputs output with tab=X will only join an existing tab created by inputs if it appears between those input rows in the spreadsheet. If the output is placed after all inputs (e.g., because AppBuilder writes inputs first, then outputs), tab=X creates a new, separate tab instead of joining the existing one. To place an amongInputs output inside an input tab, it must be physically interleaved among the inputs in the spreadsheet. Use app.add_output(..., among_inputs=True) in AppBuilder to achieve this.
Charts and Tables Structure
Charts and tables use blue cells for data values only.
CRITICAL: Only the numeric/data value cells are colored blue. Everything else has NO color:
- Chart title cell - NO color
- Column headers (series names) - NO color
- Row labels (category names) - NO color
- UI configuration cell - NO color
Example - multi-series (single-series is the same with one data column; pie/donut use one column too):
| | A | B | C | D | E |
|-----|----------------|------------|------------|------------|------------|
| 1 | Sales Trend | Product A | Product B | Product C | lineChart | <- NO color (all headers)
| 2 | Q1 | [BLUE]100 | [BLUE]150 | [BLUE]80 | | <- A2 no color, B2:D2 BLUE
| 3 | Q2 | [BLUE]120 | [BLUE]140 | [BLUE]95 | | <- A3 no color, B3:D3 BLUE
| 4 | Q3 | [BLUE]110 | [BLUE]160 | [BLUE]100 | | <- A4 no color, B4:D4 BLUE
Key rules:
- Data cell color: RGB 0,112,192 / hex #0070C0
- Rows where ALL values are empty (
"") are hidden automatically - Series (columns) where ALL values are empty are hidden automatically
- For pie/donut charts, series with all zeros are also hidden
- Column titles (row 1) become legend/series names
- Row labels (column A) become x-axis categories
Chart Types
| Chart Type | Description | Example |
|---|---|---|
barChart |
Vertical bar chart | UI: barChart |
lineChart |
Line chart | UI: lineChart |
pieChart |
Pie chart (single column) | UI: pieChart |
donutChart |
Donut chart (single column) | UI: donutChart |
waterfallChart |
Waterfall chart | UI: waterfallChart |
scatterChart |
Scatter plot | UI: scatterChart |
geoChart |
Geographic map | UI: geoChart;map=world_mill |
lineBarChart |
Combined line/bar (first series = bars, rest = lines) | UI: lineBarChart |
table |
Data table | UI: table |
Chart Options
| Option | Description | Example |
|---|---|---|
stacked |
Stacked bars (default off) | UI: barChart;stacked |
horizontal |
Horizontal bars | UI: barChart;horizontal |
showValues |
Show values on top of bars | UI: barChart;showValues |
xAxis=X |
X-axis title | UI: lineChart;xAxis=Time |
yAxis=X |
Y-axis title | UI: lineChart;yAxis=Value |
yAxisTicks=N |
Number of Y-axis ticks | UI: lineChart;yAxisTicks=5 |
decimals=N |
Decimals for values displayed on chart | UI: barChart;decimals=2 |
axisDecimals=N |
Decimals on axis labels | UI: axisDecimals=1 |
noGridLines |
Hide grid lines | UI: lineChart;noGridLines |
steps |
Step interpolation for lines | UI: lineChart;steps |
atLeast=N |
Y-axis must include this value | UI: barChart;atLeast=0 |
atLeastSync |
Sync Y-axis range for dual Y-axes (lineBarChart) | UI: lineBarChart;atLeastSync |
centerZero |
Center zero on Y-axis (negative values mirror positive) | UI: barChart;centerZero |
hideMaxMin |
Hide max/min labels on Y-axis | UI: lineChart;hideMaxMin |
staggerLabels |
Stagger X-axis labels (auto-enabled if >3 values) | UI: barChart;staggerLabels |
noWordWrap |
Disable label word wrap (horizontal bars only) | UI: barChart;horizontal;noWordWrap |
noControls |
Hide legend and stacked/grouped toggle | UI: barChart;noControls |
noGroupedStackedControls |
Hide only stacked/grouped toggle | UI: barChart;noGroupedStackedControls |
map=X |
Map name for geoChart | UI: geoChart;map=world_mill |
class=X |
Custom CSS class - applied to .panel-body, not the outer .panel. Use $('.my-class').closest('.panel') in JS to target the outer panel. |
UI: barChart;class=revenue-chart |
Scatter chart specific:
- Column headers must use these reserved names:
x,y, and optionallyseries,size,shape atLeasttakes four comma-separated values:xMin,xMax,yMin,yMax
Waterfall chart specific: Use value e in a data cell to mark it as a subtotal row (sums all values above)
Chart Series Styling
Style individual series:
UI: lineChart;series1.color=#FF0000;series2.color=#00FF00;series1.strokewidth=3;series2.dashedline;series1.showarea
| Property | Description |
|---|---|
seriesN.color=X |
Series color (hex or name) |
seriesN.strokewidth=N |
Line thickness |
seriesN.dashedline |
Dashed line style |
seriesN.showarea |
Fill area under line |
HTML Panels
Display custom HTML:
UI: html
The cell value should contain HTML content.
Actions Reference
Actions are automated operations triggered by button clicks. Each action is defined in yellow cells in the spreadsheet. Action property values are regular cell values - they can be formulas that reference inputs/outputs, and are evaluated during calculation before the action runs.
Common Action Properties
| Property | Description |
|---|---|
type |
Action type (required) |
title |
Button label |
name |
Internal name (used by performActionWithName()) |
hidden |
TRUE to hide button |
cssClass |
Custom CSS class |
showIfCell |
Conditional display cell |
showIfValue |
Show if cell equals value |
showIfValueNot |
Show if cell NOT equals value |
skip |
TRUE to skip execution (returns success without running) |
skipMessage |
Message to return when action is skipped |
Success Handling Properties
These properties control what happens in the UI after an action completes successfully:
| Property | Description |
|---|---|
successText |
Message displayed in the action modal |
successUrl |
Redirects the entire page (including parent if in iframe) to this URL |
successFrameUrl |
Redirects only the current frame to this URL (stays within iframe) |
successNewTabURL |
Shows an "Open in new tab" button in the modal that opens this URL |
successCalculate |
TRUE to trigger app recalculation after the action |
successRefreshScenarioList |
TRUE to reload the scenario dropdown list |
successHTMLElementId |
DOM element ID to update (used with successHTMLElementContent) |
successHTMLElementContent |
HTML injected into the element; modal is hidden automatically. Can include <script> tags for JS execution |
successSilent |
TRUE to hide modal immediately with no message |
successJavaScript |
JavaScript code to execute after the action succeeds (runs via eval) |
If none of these are set, a default success message is shown and the modal stays open until the user closes it.
Using successHTMLElementId: This property injects HTML into an existing DOM element after the action succeeds. The target element must already exist in the page. To create a target element, add an infotext input with the desired id as an HTML attribute:
# Create target element using an infotext input
app.add_input("", '<div id="actionResult"></div>', ui="infotext;noTitle;hidden")
# Reference it in the action
app.add_action({
"type": "addrecord",
"successHTMLElementId": "actionResult",
"successHTMLElementContent": '<div class="alert alert-success">Saved!</div>',
})
Do NOT use existing page element IDs like content, app, boxRow, or inputpanel - injecting HTML into these will destroy the page layout. Always create a dedicated target element.
Action Types
Email Action (type: email)
Send emails via Mailjet.
| Property | Description | Default |
|---|---|---|
to |
Recipient email(s), comma or semicolon separated | (required) |
from |
Sender display name | "Molnify sender" |
email |
Sender email address | "no-reply@molnify.com" |
subject |
Email subject | "Email from an app on Molnify!" |
content |
Plain text body | - |
contentHTML |
HTML body (overrides content if both set) |
- |
Either content or contentHTML should be provided. If neither is set, a default placeholder message is sent. Default success message: "Email sent!"
No template substitution: The content and contentHTML values are sent as-is. There is no {{variable}} substitution for email actions. To include dynamic values, make the action property a formula that concatenates HTML with cell references:
contentHTML: ="<h1>Your BMI: "&App!B4&"</h1><p>Category: "&App!B5&"</p>"
Limitations: No CC/BCC, no attachments, no reply-to address. Recipients are split on , and ; without trimming whitespace - use "a@x.com,b@x.com" not "a@x.com, b@x.com".
HTTP Action (type: http)
Make HTTP requests to external APIs.
| Property | Description | Default |
|---|---|---|
url |
Endpoint URL | (required) |
method |
HTTP method (GET, POST, PUT, etc.) | "POST" |
payload |
Request body (overridden if autoPayload produces data) |
"OK" |
autoPayload |
Auto-generate JSON payload: inputs, outputs, or all |
- |
token |
Authorization token (added to Authorization header) | - |
authorizationType |
Auth scheme prefix for token | "Token" |
contentType |
Content-Type header | "application/json" |
responseVariable |
Variable to store the response body in | "httpresponse" |
Response handling: Only HTTP 200 and 201 are treated as success - other 2xx codes (202, 204, etc.) are treated as failures. The response body is set on the input matching responseVariable (default httpresponse), allowing you to parse it with FILTERJSON(). Newlines are stripped from the response body.
autoPayload details: Generates a JSON object using cell references as keys (e.g. {"Sheet1!B2": "value"}), not variable names. inputs includes all input cells, outputs includes all output cells, all includes both. If the generated JSON is empty, the original payload value is used instead.
Request details:
Acceptheader is alwaysapplication/json(not configurable)User-Agentis set toMolnify-HTTP-Connector/1.1- Connect timeout: 5 seconds, read timeout: 12 seconds
- No automatic redirect following (3xx responses are treated as failures)
Default success message: "All received. Thank you!"
Scenario Save Action (type: scenariosave)
Save current input values as a named scenario.
| Property | Description | Default |
|---|---|---|
newName |
Scenario name | (required) |
oldName |
Previous name to delete (for rename operations) | - |
private |
TRUE for private scenario, FALSE for public | "true" |
allowOverwrite |
TRUE to allow others to overwrite this scenario | "false" |
overwriteCurrentScenario |
TRUE to update the currently loaded scenario | "false" |
Use with successRefreshScenarioList: TRUE to update the scenario dropdown after saving.
Generate Report Action (type: generatereport)
Generate PDF reports from templates. Runs asynchronously. The template syntax depends on the engine - HTML/markdown use Mustache, DOCX uses poi-tl, XLSX uses named ranges. See report-templates.md for details.
| Property | Description | Default |
|---|---|---|
template |
Template name (created via Template Manager) | "template" |
engine |
Conversion engine: html, markdown, docx, xlsx |
"html" |
fileName |
Output PDF filename (.pdf added automatically) | "{appId}-molnify.pdf" |
suppressDownload |
TRUE to not auto-download the file | "false" |
to |
Email recipient (sends email with report link) | - |
content |
Plain text email body (use {{urlToReport}} placeholder) |
- |
contentHTML |
HTML email body (use {{urlToReport}} placeholder) |
- |
saveIp |
FALSE to not save user IP in metadata | "true" |
PDF rendering properties:
| Property | Description | Default |
|---|---|---|
landscape |
TRUE for landscape orientation | "false" |
paperSize |
Paper size: LETTER, LEGAL, TABLOID, LEDGER, A0-A6 | "LETTER" |
cssPageSize |
TRUE to use CSS @page size instead of paperSize |
"false" |
margin |
Default margin for all sides (inches) | "0.5" |
marginTop, marginBottom, marginLeft, marginRight |
Override individual margins | uses margin |
waitDelay |
Milliseconds to wait before PDF conversion (for JS rendering) | "0" |
waitForExpression |
JavaScript expression that must return truthy before conversion | - |
pageRanges |
Page ranges to include (e.g., "1-3,5") | all pages |
includeBackground |
TRUE to print background graphics/colors | "false" |
mediaType |
CSS media type: screen or print |
"print" |
performanceMode |
TRUE to skip rendering delay | "false" |
Shows "Preparing your file" in the modal while generating. Once ready, the file downloads automatically. Use suppressDownload: TRUE to skip the download while still emailing the report link via the to/content/contentHTML options. If no content/contentHTML is set but to is provided, the email body is just the report URL.
Generate File Action (type: generatefile)
Generate Excel or PDF files from XLSX templates. Runs asynchronously. Prefer generatereport for most use cases - generatefile is only needed when you want the output as an .xlsx file (not PDF) or need workbook password protection.
| Property | Description | Default |
|---|---|---|
template |
XLSX template filename | (required) |
format |
Output format: pdf or xlsx |
"pdf" |
fileName |
Output filename | "{appId}-molnify.{format}" |
landscape |
TRUE for landscape orientation (PDF only) | "false" |
overwriteFormatting |
TRUE to apply template formatting to output | "true" |
protectWorkbook |
TRUE to password-protect the Excel output | "false" |
workbookPassword |
Password for workbook protection | - |
Shows "Preparing your file" in the modal while generating. Once ready, the file downloads automatically.
Database Actions
Insert/Update Record (type: addrecord)
Insert or update a record in the application's configured record table.
| Property | Description | Default |
|---|---|---|
saveIp |
TRUE to save user IP address | "true" |
recordTableName |
Override the app's default record table for this action | app's RecordTableName metadata |
idVariable |
Variable that holds/receives the record ID | app's RecordTableIdVariable metadata, or "recordId" |
Requires RecordTableName metadata (or recordTableName action option) to be set to the full table name including the data_ prefix (e.g., data_my-app_0). Maps input/output variables to database columns. If the ID variable has a value, updates that record; otherwise creates a new record. The new/updated record ID is automatically set on the input matching idVariable.
Important: Only modified ("dirty") inputs are sent. Columns for unmodified inputs need DEFAULT values or must be nullable. See database.md for details.
Insert SQL Row (type: insertrow)
Insert or update a row in a specific database table with explicit column mapping.
| Property | Description | Default |
|---|---|---|
tablename |
Target table name (include data_ prefix for consistency) |
(required) |
.{columnname} |
Column value (prefix column name with .) |
- |
k.{columnname} |
Primary key column (prefix with k. for upsert) |
- |
idVariable |
Variable that receives the inserted/updated row ID | - |
saveIp |
TRUE to save user IP address | "true" |
Uses INSERT ... ON DUPLICATE KEY UPDATE for upsert behavior. Automatically adds _molnify_user_email, _molnify_application_id, _molnify_user_ip columns.
Important: Only modified ("dirty") inputs are sent. Columns for unmodified inputs need DEFAULT values or must be nullable. See database.md for details.
Download Query Action (type: downloadquery)
Download SQL query results as a file.
| Property | Description | Default |
|---|---|---|
query |
SQL SELECT query to execute | (required) |
format |
Output format: csv or json |
"csv" |
Shows "Preparing your file" in the modal, then downloads automatically. CSV files use semicolon delimiter and include UTF-8 BOM. Filename format: YYYYMMDD-HHmm-{appId}.{format}.
E-Sign Action (type: createesign)
Create e-signature request via Zigned. Runs asynchronously.
| Property | Description | Default |
|---|---|---|
template |
Template name (created via Template Manager) | (required) |
provider |
E-sign provider (only "zigned" supported) | (required) |
fileName |
Output PDF filename | "{appId}-molnify.pdf" |
attachments |
Comma-separated URLs of files to attach | - |
saveIp |
TRUE to save user IP in metadata | "true" |
Reset Inputs Action (type: resetinputs)
Reset input variables to specified values or their spreadsheet defaults.
| Property | Description | Default |
|---|---|---|
.{variableName} |
Value to reset to (prefix variable name with .). Empty = use spreadsheet default. |
- |
If no .{variableName} properties are specified, all inputs that have a variable are reset to their spreadsheet defaults. Otherwise, only the listed variables are reset.
Example:
type: resetinputs
title: Reset Form
.name: Default Name
.age:
Resets name to "Default Name" and age to its spreadsheet default value. Inputs without a variable UI option are not affected.
Multiple Action (type: multiple)
Execute several actions in sequence with a single button click.
| Property | Description |
|---|---|
1, 2, 3, ... |
Name of the sub-action at each step (references another action's name property) |
Number sub-actions starting from 1. Each references another action by its name property. Sub-actions run in numeric order and execution stops immediately if any sub-action fails - later sub-actions do not run.
Success handling properties (successText, successUrl, etc.) set on the multiple action take precedence over those from sub-actions.
Do not wrap aiprompt or http actions inside multiple if you need their response. The multiple wrapper consumes the responsevalue field - the AI or HTTP response becomes inaccessible. Use skip formulas on the action directly for conditional execution, and chain follow-up work via successJavaScript instead.
Example - define a scenariosave action named saveStep and an email action named emailStep, both with hidden: TRUE. Then create the visible button:
type: multiple
title: Save & Email
1: saveStep
2: emailStep
successText: Saved and emailed!
AI Prompt Action (`type:
…(truncated)