Dynamic Forms for NotePlan using Templating -- fill out a form and have the data sent to a template for processing
See CHANGELOG for latest updates/changes to this plugin.
The Forms plugin enables you to create dynamic, interactive forms in NotePlan. You define form fields in a template, and when you fill out the form and click "Submit", the data is automatically processed through a template to create notes, tasks, or other content.
β οΈ Beta Warning: This is an early beta release and may not yet be fully functional. Features may change, and you may encounter bugs or incomplete functionality. Please report issues to @dwertheimer on Discord.
| Form Definition > | Form Entry > | Form Processor > | Result |
|---|---|---|---|
receivingTemplateTitle)@Templates directorytemplate-form typeformfields code block defining your form fieldsreceivingTemplateTitle in the frontmatter pointing to your processing template/π Forms: Open Template Form (or /form or /dialog) to launch your formForm templates should have the template-form tyoe. Each form template consists of:
formfields code block containing an array of field definitionsThe frontmatter controls the form's appearance and behavior:
---
title: My Form Template
type: template-form
receivingTemplateTitle: "My Processing Template"
windowTitle: "My Form"
formTitle: "Fill Out This Form"
hideDependentItems: false
allowEmptySubmit: false
width: 750
height: 750
---
| Option | Required | Description | Default |
|---|---|---|---|
title | Yes | The name of your form template | - |
receivingTemplateTitle | Yes | Title of the template that will process the form data | - |
type | Yes | Set to template-form so it comes up in the forms chooser | - |
windowTitle | No | Title shown in the form window | "Form" |
formTitle | No | Title shown inside the form dialog | "Form Entry" |
width | No | Width of the form window in pixels | Auto |
height | No | Height of the form window in pixels | Auto |
hideDependentItems | No | Hide dependent fields until parent is enabled | false |
allowEmptySubmit | No | Allow submitting form with empty required fields | false |
After the frontmatter, include a code block with type formfields containing a JSON array of field definitions.
Important: We highly recommend using JSONLint to validate your JSON code (copy only the content inside the code block, not including the backticks). This will help catch syntax errors like missing commas, incorrect quotes, or malformed structures before you try to use the form.
\`\`\`formfields
[
{
key: 'fieldName',
label: 'Field Label',
type: 'input',
description: 'Help text for this field'
}
]
\`\`\`
The Forms plugin supports the following field types:
inputA standard text input field.
{
key: 'projectName',
label: 'Project Name',
type: 'input',
description: 'Enter the name of your project',
default: 'My Project',
required: true,
compactDisplay: true,
focus: false, // Set to true to focus this field when form opens
validationType: 'email' | 'number' | 'date-interval' // Optional validation
}
Properties:
key (required): Variable name used in processing templatelabel (required): Label displayed to usertype: 'input'description: Help text shown below the fielddefault: Default valuerequired: If true, field must be filledcompactDisplay: If true, label and field are side-by-sidefocus: If true, field receives focus when form opensvalidationType: 'email', 'number', or 'date-interval' for validationinput-readonlyA read-only text input field (display only).
{
key: 'readonlyField',
label: 'Read-only Info',
type: 'input-readonly',
default: 'This cannot be changed',
compactDisplay: true
}
numberA numeric input with increment/decrement buttons.
{
key: 'quantity',
label: 'Quantity',
type: 'number',
default: 0,
step: 1, // Increment/decrement amount
compactDisplay: true
}
Properties:
step: Number to increment/decrement by (default: 1)dropdown-selectA dropdown menu (simple select).
{
key: 'team',
label: 'Team',
type: 'dropdown-select',
options: ['Team Alpha', 'Team Beta', 'Team Charlie'],
default: 'Team Beta',
fixedWidth: 300, // Optional: fixed width in pixels
isEditable: false, // If true, user can type to search/edit
compactDisplay: true
}
Properties:
options: Array of strings or objects {label: 'Display', value: 'value', isDefault: true}fixedWidth: Fixed width in pixelsisEditable: Allow user to edit/type in dropdowncomboAn advanced dropdown with search capabilities (react-select).
{
key: 'priority',
label: 'Priority',
type: 'combo',
options: ['High', 'Medium', 'Low'],
default: 'Medium',
compactDisplay: true,
noWrapOptions: false // If true, truncate options instead of wrapping
}
Properties:
options: Array of strings or objects {label: 'Display', value: 'value', isDefault: true}noWrapOptions: If true, truncate labels instead of wrappingswitchA toggle switch (on/off).
{
key: 'isUrgent',
label: 'Mark as Urgent',
type: 'switch',
default: false,
compactDisplay: true
}
Properties:
default: true or falsecalendarpickerA date picker for selecting dates.
{
key: 'dueDate',
buttonText: 'Select Due Date',
type: 'calendarpicker',
selectedDate: new Date(), // Optional: initially selected date
numberOfMonths: 1 // Optional: number of months to show
}
Properties:
buttonText: Text shown on the date picker buttonselectedDate: Initially selected date (Date object)numberOfMonths: Number of calendar months to displayheadingA section heading.
{
type: 'heading',
label: 'Section Title'
}
Note: No key required for headings.
separatorA horizontal line separator.
{
type: 'separator'
}
Note: No key required for separators.
textDisplay-only text for instructions or descriptions.
{
key: 'instructions', // Optional but recommended
type: 'text',
label: 'Instructions',
textType: 'description' // 'title' | 'description' | 'separator'
}
Properties:
textType: 'title', 'description', or 'separator'jsonA JSON editor for complex data structures.
{
key: 'metadata',
label: 'Metadata',
type: 'json',
default: {}
}
buttonA clickable button that triggers an action.
{
key: 'actionButton',
type: 'button',
label: 'Click Me',
isDefault: false // If true, appears as primary button
}
button-groupA group of buttons for selection.
{
key: 'option',
type: 'button-group',
label: 'Choose Option',
options: ['Option 1', 'Option 2', 'Option 3'],
vertical: false, // If true, stack buttons vertically
isDefault: 0 // Index of default selected option
}
hiddenA hidden field (not displayed, but value is passed to processing template).
{
key: 'hiddenValue',
type: 'hidden',
value: 'some-value'
}
You can make fields conditional using dependsOnKey. A field will only be enabled/visible when the field it depends on is true (for switches) or has a value (for other types).
{
key: 'showAdvanced',
label: 'Show Advanced Options',
type: 'switch',
default: false,
compactDisplay: true
},
{
key: 'advancedSetting',
label: 'Advanced Setting',
type: 'input',
dependsOnKey: 'showAdvanced', // Only enabled when showAdvanced is true
compactDisplay: true
}
Properties:
dependsOnKey: The key of another field this field depends onAll fields support these common properties:
| Property | Type | Description |
|---|---|---|
key | string | Variable name (required for most types) |
label | string | Field label displayed to user |
type | string | Field type (required) |
description | string | Help text shown below field |
default | any | Default value for the field |
compactDisplay | boolean | If true, label and field display side-by-side |
dependsOnKey | string | Make field conditional on another field |
required | boolean | Field must be filled (for input fields) |
The following keys are reserved and should not be used as field keys:
__isJSON__submitlocationwriteUnderHeadingopenNoteTitlewriteNoteTitlegetNoteTitledreplaceNoteContentscreateMissingHeadingreceivingTemplateTitlewindowTitleformTitlewidthheighthideDependentItemsallowEmptySubmittitleThe processing template receives the form data and uses it to generate content. It's a standard NotePlan template with the type "forms-processor"
All form field key values become available as variables in your processing template. Use them with <%- variableName %> syntax.
---
title: Project Form Processing Template
type: forms-processor
newNoteTitle: <%- noteTitle %>
folder: <select Projects>
start: <%- startDateEntry ? date.format("YYYY-MM-DD", startDateEntry) : '' %>
due: <%- dueDateEntry ? date.format("YYYY-MM-DD", dueDateEntry) : '' %>
---
#project @start(<%- start %>) @due(<%- due %>) @review(<%- interval %>)
**Aim:** <%- aim %>
**Context:** <%- context %>
**Team:** <%- team %>
Progress: 0@<%- start %>: project started
Date fields from calendarpicker return ISO date strings. You can format them using the date.format() function:
<%- startDateEntry ? date.format("YYYY-MM-DD", startDateEntry) : '' %>
You can use conditional logic in your processing template:
<% if (isUrgent) { %>
**URGENT:** This task requires immediate attention
<% } %>
---
title: jgclark Project Form
type: form-processor
receivingTemplateTitle: "Project Form Processing Template"
windowTitle: "Project"
formTitle: "Create New Project"
hideDependentItems: false
allowEmptySubmit: false
width: 750
height: 750
---
```formfields
[
{
key: 'noteTitle',
label: 'Project Title',
description: 'This will be the name/title of the project.',
type: 'input',
compactDisplay: true,
required: true,
},
{
key: 'startDateEntry',
buttonText: 'Start date',
type: 'calendarpicker',
visible: false,
},
{
key: 'dueDateEntry',
buttonText: 'Due date',
type: 'calendarpicker',
visible: false,
},
{
key: 'aim',
label: 'Aim',
description: 'The aim/purpose of the project.',
type: 'input',
compactDisplay: true,
},
{
key: 'context',
label: 'Context',
description: 'The context of the project.',
type: 'input',
compactDisplay: true,
},
{
key: 'team',
label: 'Team',
type: 'dropdown-select',
options: ['team alpha', 'team beta', 'team charlie'],
default: 'team beta',
compactDisplay: true,
},
{
key: 'interval',
label: 'Review Interval',
description: 'Enter the review interval in the format: nn[bdwmqy]',
compactDisplay: true,
type: 'input',
validationType: 'date-interval'
},
]
```
---
title: Project Form Processing Template
type: form-processor
newNoteTitle: <%- noteTitle %>
folder: <select Projects>
start: <%- startDateEntry ? date.format("YYYY-MM-DD", startDateEntry) : '' %>
due: <%- dueDateEntry ? date.format("YYYY-MM-DD", dueDateEntry) : '' %>
---
#project @start(<%- start %>) @due(<%- due %>) @review(<%- interval %>)
**Aim:** <%- aim %>
**Context:** <%- context %>
**Team:** <%- team %>
Progress: 0@<%- start %>: project started
/form or /dialog or /π Forms: Open Template FormYou can create links to launch forms directly:
[Launch Project Form](noteplan://x-callback-url/runPlugin?pluginID=dwertheimer.Forms&command=Open%20Template%20Form&arg0=jgclark%20Project%20Form)
Replace jgclark%20Project%20Form with your form template title (URL-encoded).
Tip: Instead of manually creating x-callback-url links, consider using the np.CallbackURLs plugin to create callback links with a user-friendly wizard. This plugin helps you build these URLs correctly without having to URL-encode template names manually.
default values for commonly-used fields to save timerequired: true and validationType for critical fieldsheading and separator to organize complex formstemplate-form folderformfields code block is correctly formatted JSONreceivingTemplateTitle matches your processing template title exactly<%- variableName %>)date.format() if neededformfields code blockkey, type, label where needed) are present