Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Documentation on how to work with Umbraco Forms for both editors and developers.

moment-with-locales.min.js
"Umbraco": {
"Forms": {
"FieldTypes": {
"RecaptchaEnterprise": {
"SiteKey": "",
"ApiKey": "",
"ProjectId": ""
}
}
}
}"Umbraco": {
"Forms": {
"FieldTypes": {
"Recaptcha2": {
"PublicKey": "",
"PrivateKey": ""
}
}
}
}"Umbraco": {
"Forms": {
"FieldTypes": {
"Recaptcha3": {
"SiteKey": "",
"PrivateKey": ""
}
}
}
}



dotnet add package Umbraco.Forms --version <version_number><ItemGroup>
<PackageReference Include="Umbraco.Forms" Version="xx.x.x" />
</ItemGroup>@await Component.InvokeAsync("RenderForm", new { formId = Guid.Parse("<form guid>"), theme = "default", includeScripts = false })





var additionalData = new Dictionary<string, string> { { "foo", "bar" }, { "buzz", "baz" } };
@await Component.InvokeAsync("RenderForm", new { formId = @Model.Form, theme = @Model.Theme, includeScripts = false, additionalData })@addTagHelper *, Umbraco.Forms.Web@if (Model.Form.HasValue)
{
var additionalData = new Dictionary<string, string> { { "foo", "bar" }, { "buzz", "baz" } };
<umb-forms-render form-id="@Model.FormId.Value" theme="@Model.FormTheme" exclude-scripts="true" additional-data="@additionalData" />
}@await Umbraco.RenderMacroAsync("renderUmbracoForm", new { FormGuid = "<form guid>", FormTheme = "default", ExcludeScripts = "1" })@if (Model.FormId is Guid formId)
{
@await Umbraco.RenderMacroAsync("renderUmbracoForm", new { FormGuid = formId, FormTheme = Model.FormTheme, ExcludeScripts = "1" })
}@using Umbraco.Forms.Web.Extensions;
@if (TempData.Get<Guid[]>("UmbracoForms") is Guid[] formIds)
{
foreach (var formId in formIds)
{
@await Component.InvokeAsync("RenderFormScripts", new { formId, theme = "default" })
}
TempData.Remove("UmbracoForms");
}@if (Context.Items.TryGetValue("UmbracoForms", out object? formIdsObject) && formIdsObject is IEnumerable<Guid> formIds)
{
foreach (var formId in formIds)
{
@await Component.InvokeAsync("RenderFormScripts", new { formId, theme = "default" })
}
}@addTagHelper *, Umbraco.Forms.Web<umb-forms-render-scripts theme="default" />@await Umbraco.RenderMacroAsync("renderUmbracoForm", new {FormGuid="6c3f053c-1774-43fa-ad95-710a01d9cd12", FormTheme="bootstrap3-horizontal", ExcludeScripts="1"})





{
"==": [
"{email}",
"{compareEmail}"
]
}{
"or": [
{
"==": [
"{startDate}",
""
]
},
{
"==": [
"{endDate}",
""
]
},
{
">": [
"{endDate}",
"{startDate}"
]
}
]
}{
"or": [
{
"==": [
"{choose}",
"A"
]
},
{
"and": [
{
"==": [
"{choose}",
"B"
]
},
{
"!=": [
"{test}",
""
]
}
]
}
]
} dotnet runRenderUmbracoFormDependencies that internally required service location was removed. It should now be called providing the parameter for an IUrlHelper, with @Html.RenderUmbracoFormDependencies(Url).





















{{contactForm}}{{contactForm | umbFormsFormName}}@using Umbraco.Forms.Web
<head>
@Html.RenderUmbracoFormDependencies(Url)
</head>@using Umbraco.Forms.Web
...
<body>
@Html.RenderUmbracoFormDependencies(Url)
</body>@Html.RenderUmbracoFormDependencies(Url, new { @async = "async" })<head>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.0.0.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js"></script>
</head><body>
<!-- Page content here -->
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.0.0.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js"></script>
</body>{
"Umbraco": {
"Licensing": {
"Directory": "~/custom-licenses-folder/"
}
}
} "Umbraco": {
"Licensing": {
"LicenseEncodeAndDecodeAlgorithm": "DES|TripleDES|AES"
},









using Umbraco.Cms.Core.Composing;
using Umbraco.Forms.Core.Extensions;
internal sealed class TestComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.WebhookEvents().AddForms(formsBuilder => formsBuilder.RemoveDefault());
}example value 1
example value 2
example value 3
example value 4
example value 51|example value 1
2|example value 2
3|example value 3
4|example value 4
5|example value 5


















using Umbraco.Cms.Core.Composing;
using Umbraco.Forms.Core.Providers.Extensions;
using Umbraco.Forms.Core.Providers.FieldTypes;
namespace MyNamespace
{
public class MyFormFieldsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.FormsFields()
.Exclude<Password>()
.Exclude<Recaptcha2>()
.Exclude<RichText>();
}
}
} "Umbraco": {
"CMS": {
...
},
"Forms": {
"FieldTypes": {
"DatePicker": {
"DatePickerYearRange": 12
}
}
}
}/*
Applies recommended primary keys, foreign keys and indexes to Umbraco Forms tables relating to "forms in the database" (i.e.
when configuration key StoreUmbracoFormsInDb = true).
This replicates for SQL Server the migration AddFormKeysAndIndexes.
*/
-- Adds unique constraint to UFForms.
ALTER TABLE dbo.UFForms
ADD CONSTRAINT UK_UFForms_Key UNIQUE NONCLUSTERED
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds unique constraint to UFDataSource.
ALTER TABLE dbo.UFDataSource
ADD CONSTRAINT UK_UFDataSource_Key UNIQUE NONCLUSTERED
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds unique constraint to UFPrevalueSource.
ALTER TABLE dbo.UFPrevalueSource
ADD CONSTRAINT UK_UFPrevalueSource_Key UNIQUE NONCLUSTERED
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds unique constraint to UFWorkflows.
ALTER TABLE dbo.UFWorkflows
ADD CONSTRAINT UK_UFWorkflows_Key UNIQUE NONCLUSTERED
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds index on join field in UFWorkflows.
CREATE NONCLUSTERED INDEX IX_UFWorkflows_FormId ON dbo.UFWorkflows
(
FormId ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO1d084819-84ba-4ac7-b152-2c3d167d22bcusing Umbraco.Forms.Core.Interfaces;
namespace Umbraco.Forms.TestSite.Business.ValidationPatterns
{
public class UkPostCode : IValidationPattern
{
public string Alias => "ukPostCode";
public string Name => "UK Post Code";
public string LabelKey => string.Empty;
public string Pattern => @"^([a-zA-Z]{1,2}[a-zA-Z\d]{1,2})\s(\d[a-zA-Z]{2})$";
public bool ReadOnly => true;
}
}public static IUmbracoBuilder AddCustomProviders(this IUmbracoBuilder builder)
{
builder.FormsValidationPatterns()
.Append<UkPostCode>();
return builder;
}/*
Reverts application of recommended primary keys, foreign keys and indexes to Umbraco Forms tables relating to "forms in the database" (i.e.
when configuration key StoreUmbracoFormsInDb = true).
This reverts for SQL Server the migration AddFormKeysAndIndexes and can be used for rolling that back in testing.
*/
-- Reverts addition of unique constraint to UFForms.
ALTER TABLE dbo.UFForms
DROP CONSTRAINT IF EXISTS UK_UFForms_Key
GO
-- Reverts addition of unique constraint to UFPrevalueSource.
ALTER TABLE dbo.UFDataSource
DROP CONSTRAINT IF EXISTS UK_UFDataSource_Key
GO
-- Reverts addition of unique constraint to UFPrevalueSource.
ALTER TABLE dbo.UFPrevalueSource
DROP CONSTRAINT IF EXISTS UK_UFPrevalueSource_Key
GO
-- Reverts addition of unique constraint to UFWorkflows.
ALTER TABLE dbo.UFWorkflows
DROP CONSTRAINT IF EXISTS UK_UFWorkflows_Key
GO
-- Reverts addition of index on foreign key fields in UFWorkflows.
DROP INDEX IF EXISTS IX_UFWorkflows_FormId ON dbo.UFWorkflows
GO
-- Reverts addition of index on foreign key fields in UFWorkflows.
DROP INDEX IF EXISTS IX_UFWorkflows_FormId ON dbo.UFWorkflows
GO<setting key="StoreUmbracoFormsInDb" value="True" />Documentation on how to apply custom themes to Umbraco Forms

AllowMultipleFileUploads
public class TestFormsContentApp : IContentAppFactory
{
public ContentApp GetContentAppFor(object source, IEnumerable<IReadOnlyUserGroup> userGroups)
{
// Only show app on forms
if (source is FormDesign)
{
return new ContentApp
{
Alias = "testFormsContentApp",
Name = "Test App",
Icon = "icon-calculator",
View = "/App_Plugins/TestFormsContentApp/testformscontentapp.html",
Weight = 0,
};
}
return null;
}
}
using Umbraco.Forms.Core.Interfaces;
public class MyCustomTheme : ITheme
{
private const string FilePathFormat = "{0}/{1}/{2}.cshtml";
public virtual string Name => "my-custom-theme";
public virtual IEnumerable<string> Files =>
[
string.Format(FilePathFormat, Core.Constants.System.ThemesPath, Name, "FieldTypes/FieldType.Textfield"),
];
}public class MyComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Themes()
.Add<MyCustomTheme>();
}
}using Umbraco.Forms.Core.Interfaces;
public class MyCustomEmailTemplate : IEmailTemplate
{
public virtual string FileName => "My-Custom-Email-Template.cshtml";
}public class MyComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.EmailTemplates()
.Add<MyCustomEmailTemplate>();
}
}public class MyComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.EmailTemplates()
.Exclude<DefaultEmailTemplate>();
}
}@await Umbraco.RenderMacroAsync("renderUmbracoForm", new {FormGuid="1ec026cb-d4d3-496c-b8e8-90e0758c78d8", FormTheme="MyFormTheme", ExcludeScripts="0"})Html.SetFormThemeCssFile(Model, "~/App_Plugins/UmbracoForms/Assets/Themes/Default/style.css")Html.AddFormThemeScriptFile("~/App_Plugins/UmbracoForms/Assets/themes/default/umbracoforms.js");// Applies the CSS class 'form-control' to all fields that GetFormFieldClass uses in FieldType views
@Html.SetFormFieldClass("form-control")
// Applies the CSS class 'some-other-class' for the FieldType of the name 'Password'
@Html.SetFormFieldClass("some-other-class", "Password")class="@Html.GetFormFieldClass(Model.FieldTypeName)"// Applies the CSS class 'form-group' around all fields, labels & help texts
@Html.SetFormFieldWrapperClass("form-group")
// Applies the CSS class 'some-other-class' for the FieldType of the name 'Password'
@Html.SetFormFieldWrapperClass("some-other-class", "Password")class="@Html.GetFormFieldWrapperClass(f.FieldTypeName)"@model Umbraco.Forms.Mvc.Models.FieldViewModel
@using Umbraco.Forms.Mvc
<input type="text"
name="@Model.Name"
id="@Model.Id"
class="@Html.GetFormFieldClass(Model.FieldTypeName) text"
value="@Model.ValueAsHtmlString"
maxlength="500"
@{if(string.IsNullOrEmpty(Model.PlaceholderText) == false){<text>placeholder="@Model.PlaceholderText"</text>}}
@{if(Model.Mandatory || Model.Validate){<text>data-val="true"</text>}}
@{if (Model.Mandatory) {<text> data-val-required="@Model.RequiredErrorMessage"</text>}}
@{if (Model.Validate) {<text> data-val-regex="@Model.InvalidErrorMessage" data-val-regex-pattern="@Html.Raw(Model.Regex)"</text>}}
/>using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Forms.Core.Data;
public class PreValueTextFileSystemStorageComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.Services.AddUnique<IPreValueTextFileStorage>(factory => new PreValueTextFileSystemStorage(
factory.GetRequiredService<MediaFileManager>().FileSystem,
factory.GetRequiredService<IScopeProvider>(),
"PreValueTextFiles"));
}{
"Umbraco": {
"Storage": {
"AzureBlob": {
"Forms": {
"ConnectionString": "UseDevelopmentStorage=true",
"ContainerName": "sample-container"
}
}
}
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Forms.Core.Data;
using Umbraco.StorageProviders.AzureBlob.IO;
public class PreValueTextFileSystemStorageComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
=> builder.AddAzureBlobFileSystem("Forms", options => options.VirtualPath = "~/forms")
.Services.AddUnique<IPreValueTextFileStorage>(factory => new PreValueTextFileSystemStorage(
factory.GetRequiredService<IAzureBlobFileSystemProvider>().GetFileSystem("Forms"),
factory.GetRequiredService<IScopeProvider>(),
"PreValueTextFiles"));
}PagedResult<Record> GetApprovedRecordsFromPage(int pageId, int pageNumber, int pageSize)PagedResult<Record> GetApprovedRecordsFromFormOnPage(int pageId, Guid formId, int pageNumber, int pageSize)PagedResult<Record> GetApprovedRecordsFromForm(Guid formId, int pageNumber, int pageSize)PagedResult<Record> GetRecordsFromPage(int pageId, int pageNumber, int pageSize)PagedResult<Record> GetRecordsFromFormOnPage(int pageId, Guid formId, int pageNumber, int pageSize)PagedResult<Record> GetRecordsFromForm(Guid formId, int pageNumber, int pageSize)int Id
FormState State
DateTime Created
DateTime Updated
Guid Form
string IP
int UmbracoPageId
string MemberKey
Guid UniqueId
Dictionary<Guid, RecordField> RecordFields@using Umbraco.Core;
@using Umbraco.Cms.Core.Composing;
@using Umbraco.Forms.Core.Services;
@inject IRecordReaderService _recordReaderService;
<ul id="comments">
@foreach (var record in _recordReaderService.GetApprovedRecordsFromPage(Model.Id, 1, 10).Items)
{
<li>
@record.Created.ToString("dd MMMM yyy")
@if(string.IsNullOrEmpty(record.ValueAsString("email"))){
<strong>@record.ValueAsString("name")</strong>
}
else{
<strong>
<a href="mailto:@record.ValueAsString("email")" target="_blank">@record.ValueAsString("name")</a>
</strong>
}
<span>said</span>
<p>@record.ValueAsString("comment")</p>
</li>
}
</ul>@using Umbraco.Forms.Core.Models
@using Umbraco.Forms.Core.Persistence.Dtos
@using Umbraco.Forms.Core.Data.Storage
@using Umbraco.Forms.Core.Services
@inject IFormService _formService
@inject IRecordStorage _recordStorage
@inherits UmbracoViewPage
@{
Guid formId;
Form? form;
Guid recordId;
Record? record;
string submittedEmail;
if (Guid.TryParse(TempData["UmbracoFormSubmitted"]?.ToString(), out Guid formId) &&
Guid.TryParse(TempData["Forms_Current_Record_id"]?.ToString(), out Guid recordId))
{
form = _formService.Get(formId);
if (form != null && TempData["Forms_Current_Record_id"] != null)
{
Guid.TryParse(TempData["Forms_Current_Record_id"]?.ToString(), out recordId);
record = _recordStorage.GetRecordByUniqueId(recordId, form);
submittedEmail = record.GetRecordFieldByAlias("email")?.ValuesAsString();
}
}
}_httpContextAccessor.HttpContext.Items[Constants.ItemKeys.RedirectAfterFormSubmitUrl] = "https://www.umbraco.com";using Serilog;
using System;
using System.Collections.Generic;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Data.Storage;
using Umbraco.Forms.Core.Enums;
using Umbraco.Forms.Core.Persistence.Dtos;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Composing;
namespace MyFormsExtensions
{
public class TestWorkflow : WorkflowType
{
private readonly ILogger<TestWorkflow> _logger;
public TestWorkflow(ILogger<TestWorkflow> logger)
{
_logger = logger;
this.Id = new Guid("ccbeb0d5-adaa-4729-8b4c-4bb439dc0202");
this.Name = "TestWorkflow";
this.Description = "This workflow is just for testing";
this.Icon = "icon-chat-active";
this.Group = "Services";
}
public override Task<WorkflowExecutionStatus> ExecuteAsync(WorkflowExecutionContext context)
{
// first we log it
_logger.LogDebug("the IP " + context.Record.IP + " has submitted a record");
// we can then iterate through the fields
foreach (RecordField rf in context.Record.RecordFields.Values)
{
// and we can then do something with the collection of values on each field
List<object> vals = rf.Values;
// or get it as a string
rf.ValuesAsString(false);
}
//Change the state
context.Record.State = FormState.Approved;
_logger.LogDebug("The record with unique id {RecordId} that was submitted via the Form {FormName} with id {FormId} has been changed to {RecordState} state",
context.Record.UniqueId, context.Form.Name, context.Form.Id, "approved");
return Task.FromResult(WorkflowExecutionStatus.Completed);
}
public override List<Exception> ValidateSettings()
{
return new List<Exception>();
}
}
}RecordField? recordField = context.Record.GetRecordFieldByAlias("myalias");var fieldValue = recordField.ValuesAsString(false);IEnumerable<string> selectedPrevalues = recordField.GetSelectedPrevalues();using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Forms.Core.Providers;
namespace MyFormsExtensions
{
public class Startup : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
.Add<TestWorkflow>();
}
}
}using System.Globalization;
using Umbraco.Forms.Core.Interfaces;
namespace Umbraco.Forms.Core.Providers.ParsedPlacholderFormatters
{
public class BoundNumber : IParsedPlaceholderFormatter
{
public string FunctionName => "bound";
public string FormatValue(string value, string[] args)
{
if (args.Length != 2)
{
return value;
}
if (!int.TryParse(args[0], out var min) || !int.TryParse(args[1], out var max))
{
return value;
}
if (int.TryParse(value, out int valueAsInteger) ||
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out valueAsInteger))
{
if (valueAsInteger < min)
{
return min.ToString();
}
if (valueAsInteger > max)
{
return max.ToString();
}
return valueAsInteger.ToString();
}
return value;
}
}
}public static IUmbracoBuilder AddCustomProviders(this IUmbracoBuilder builder)
{
builder.FormsParsedPlaceholderFormatters()
.Add<BoundNumber>();
return builder;
}[#field | bound: 1: 10]{
"contentApps": [
{
"name": "Test Forms Content App",
"alias": "TestFormsContentApp",
"weight": 0,
"icon": "icon-calculator",
"view": "~/App_Plugins/TestFormsContentApp/testformscontentapp.html",
"show": [
"+content/*",
"+media/*",
"+member/*",
"+forms/*"
]
}
],
"javascript": [
"~/App_Plugins/TestFormsContentApp/testformscontentapp.controller.js"
]
}<div ng-controller="My.TestFormsContentApp as vm">
<umb-box>
<umb-box-header title="Forms Content App"></umb-box-header>
<umb-box-content>
<p>Current form: <b>{{vm.formName}}</b></p>
</umb-box-content>
</umb-box>
</div>angular.module("umbraco")
.controller("My.TestFormsContentApp", function ($routeParams, formResource) {
var vm = this;
formResource.getWithWorkflowsByGuid($routeParams.id)
.then(function (response) {
vm.formName = response.data.name;
});
}); public class TestSiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.ContentApps().Append<TestFormsContentApp>();
}
}
}






[Umbraco.Forms.Core.Attributes.Setting("Message", View = "TextField")]
public string Message { get; set; }













-- Adds unique constraint to UFForms.
ALTER TABLE dbo.UFForms
ADD CONSTRAINT UK_UFForms_Key UNIQUE NONCLUSTERED
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GOThe CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.UFForms' and the index name 'UK_UFForms_Key'. The duplicate key value is (...).SELECT [Key]
FROM UFForms
GROUP BY [Key]
HAVING COUNT(*) > 1SELECT *
FROM UFForms
WHERE [Key] IN (SELECT [Key]
FROM UFForms
GROUP BY [Key]
HAVING COUNT(*) > 1
) "Umbraco": {
"CMS": {
"Global": {
"Smtp": {
"From": "[email protected]"
}
}
}
} "Umbraco": {
"CMS": {
"Content": {
"Notifications": {
"Email": "[email protected]"
}
}
}
}using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.IO;
namespace RequestSaver.Controllers
{
[ApiController]
[Route("[controller]")]
public class SaveRequestController : ControllerBase
{
private const string _filePath = "c:\\temp\\request-save.txt";
private readonly ILogger<SaveRequestController> _logger;
public SaveRequestController(ILogger<SaveRequestController> logger)
{
_logger = logger;
}
[HttpPost]
public string Save()
{
using (StreamWriter outputFile = new StreamWriter(_filePath))
{
foreach (var key in Request.Form.Keys)
{
outputFile.WriteLine($"{key}: {(Request.Form[key])}");
}
}
return "Done";
}
}
}




ModelError@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Forms.Core.Models.FormsHtmlModel>using System.Linq;
using Umbraco.Cms.Core.Events;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Services.Notifications;
namespace MyFormsExtensions
{
/// <summary>
/// Catch form submissions before being saved and perform custom validation.
/// </summary>
public class FormValidateNotificationHandler : INotificationHandler<FormValidateNotification>
{
public void Handle(FormValidateNotification notification)
{
// If needed, be selective about which form submissions you affect.
if (notification.Form.Name == "Form Name")
{
// Check the ModelState
if (notification.ModelState.IsValid == false)
{
return;
}
// A sample validation
var email = GetFieldValue(notification.Form, "email");
var emailConfirm = GetFieldValue(notification.Form, "verifyEmail");
// If the validation fails, return a ModelError.
if (email.ToLower() != emailConfirm.ToLower())
{
// Standard form POST renders validation messages keyed by field Id,
// while the headless API returns errors keyed by field alias.
Field verifyEmailField = GetField(notification.Form, "verifyEmail")!;
var errorKey = notification.Context.Request.HasFormContentType
? verifyEmailField.Id.ToString()
: verifyEmailField.Alias;
notification.ModelState.AddModelError(errorKey, "Email does not match");
}
}
}
/// <summary>
/// Gets the submitted value for a field by its alias.
/// </summary>
/// <remarks>
/// Field.Values is populated before the notification fires in both submission paths
/// (standard form POST and headless API JSON submissions), making it the reliable way
/// to access submitted values regardless of how the form was submitted.
/// </remarks>
private static string GetFieldValue(Form form, string alias)
{
Field? field = GetField(form, alias);
return field?.Values.FirstOrDefault()?.ToString()?.Trim() ?? string.Empty;
}
private static Field? GetField(Form form, string alias)
=> form.AllFields.SingleOrDefault(f => f.Alias == alias);
}
}public static IUmbracoBuilder AddUmbracoFormsCoreProviders(this IUmbracoBuilder builder)
{
builder.AddNotificationHandler<FormValidateNotification, FormValidateNotificationHandler>();
} public class TestSiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.AddNotificationHandler<FormSavingNotification, FormSavingNotificationHandler>();
}
}
public class FormSavingNotificationHandler : INotificationHandler<FormSavingNotification>
{
public void Handle(FormSavingNotification notification)
{
foreach (Form form in notification.SavedEntities)
{
foreach (Page page in form.Pages)
{
foreach (FieldSet fieldset in page.FieldSets)
{
foreach (FieldsetContainer fieldsetContainer in fieldset.Containers)
{
foreach (Field field in fieldsetContainer.Fields)
{
field.Caption += " (updated)";
}
}
}
}
}
}
} public class TestSiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.AddNotificationHandler<FormSavingNotification, FormSavingNotificationHandler>();
}
}
public class FormSavingNotificationHandler : INotificationHandler<FormSavingNotification>
{
private readonly ILogger<FormSavingNotification> _logger;
public FormSavingNotificationHandler(ILogger<FormSavingNotification> logger) => _logger = logger;
public void Handle(FormSavingNotification notification)
{
foreach (Form savedEntity in notification.SavedEntities)
{
_logger.LogInformation($"Form updated. New parent: {savedEntity.FolderId}. Old parent: {notification.State["MovedFromFolderId"]}");
}
}
} public class TestSiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.AddNotificationHandler<EntrySearchResultFetchingNotification, EntrySearchResultFetchingNotificationHandler>();
}
}
public class EntrySearchResultFetchingNotificationHandler : INotificationHandler<EntrySearchResultFetchingNotification>
{
public void Handle(EntrySearchResultFetchingNotification notification)
{
var transformedFields = new List<object>();
foreach (var field in notification.EntrySearchResult.Fields)
{
if (field?.ToString() == "Test")
{
transformedFields.Add("Test (updated)");
}
else
{
transformedFields.Add(field);
}
}
notification.EntrySearchResult.Fields = transformedFields;
}
}@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Forms.Core.Models.FormsHtmlModel>
@{
//This is an example email template where you can use Razor Views to send HTML emails
//You can use Umbraco.Content & Umbraco.Media etc to use Images & content from your site
//directly in your email templates too
//Strongly Typed
//@Model.GetValue("aliasFormField")
//@foreach (var color in Model.GetValues("checkboxField")){}
//Images need to be absolute - so fetching domain to prefix with images
var siteDomain = Context.Request.Scheme + "://" + Context.Request.Host;
var assetUrl = siteDomain + "/App_Plugins/UmbracoForms/Assets/Email-Example";
}
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* CLIENT-SPECIFIC STYLES */
body, table, td, a {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
-ms-interpolation-mode: bicubic;
}
/* RESET STYLES */
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
}
table {
border-collapse: collapse !important;
}
body {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@@media screen and (max-width:600px) {
h1 {
font-size: 32px !important;
line-height: 32px !important;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] {
margin: 0 !important;
}
</style>
</head>
<body style="background-color: #f4f4f4; margin: 0 !important; padding: 0 !important;">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<!-- HERO -->
<tr>
<td align="center" style="padding: 40px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; color: #000000; font-family: Helvetica, Arial, sans-serif; font-size: 36px; font-weight: 900; line-height: 48px;">
<h1 style="font-size: 36px; font-weight: 900; margin: 0;">Submission for @Model.FormName</h1>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- COPY BLOCK -->
<tr>
<td bgcolor="#F3F3F5" align="center" style="padding: 0px 10px 40px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!-- HEADER COPY -->
@if (Model.HeaderHtml is not null)
{
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #303033; font-family: Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 1.6em;">
@Model.HeaderHtml
</td>
</tr>
}
<!-- BODY COPY -->
@if (Model.BodyHtml is not null)
{
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #303033; font-family: Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 1.6em;">
@Model.BodyHtml
</td>
</tr>
}
<!-- FORM FIELDS HEADING -->
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 40px 30px 0px 30px; color: #000000; font-family: Helvetica, Arial, sans-serif; line-height: 25px;">
<h2 style="font-size: 24px; font-weight: 700; margin: 0;">Form Results</h2>
</td>
</tr>
<!-- FORM FIELDS -->
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #303033; font-family: Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
@{
string[] ignoreFields = new string[]
{
"FieldType.Recaptcha2.cshtml",
"FieldType.Recaptcha3.cshtml",
"FieldType.RecaptchaEnterprise.cshtml",
"FieldType.RichText.cshtml",
"FieldType.Text.cshtml"
};
}
@foreach (var field in Model.Fields.Where(x => ignoreFields.Contains(x.FieldType) == false))
{
<h4 style="font-weight: 700; margin: 0; color: #000000;">@field.Name</h4>
<p style="margin-top: 0;">
@switch (field.FieldType)
{
case "FieldType.FileUpload.cshtml":
var uploadCount = 0;
foreach (var fileUploadValue in field.GetValues())
{
if (fileUploadValue != null && !string.IsNullOrEmpty(fileUploadValue.ToString()))
{
uploadCount++;
}
}
if (uploadCount > 0)
{
<span>@uploadCount file@(uploadCount == 1 ? string.Empty : "s") uploaded</span>
}
break;
case "FieldType.DatePicker.cshtml":
var datePickerValue = field.GetValue();
if (datePickerValue != null && !string.IsNullOrEmpty(datePickerValue.ToString()))
{
DateTime dt;
var dateValid = DateTime.TryParse(datePickerValue != null ? datePickerValue.ToString() : string.Empty, out dt);
var dateStr = dateValid ? dt.ToString("f") : "";
@dateStr
}
break;
default:
var values = field.GetValues();
if (values != null)
{
foreach (var value in values)
{
if (value != null)
{
if (value is string strValue)
{
var processedValue = strValue.ApplyPrevalueCaptions(field.Id, Model.PrevalueMaps).ReplaceLineEndings("<br/>");
@Html.Raw(processedValue)
}
else
{
@value
}
<br />
}
}
}
break;
}
</p>
}
</td>
</tr>
<!-- FOOTER COPY -->
@if (Model.FooterHtml is not null)
{
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #303033; font-family: Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 1.6em;">
@Model.FooterHtml
</td>
</tr>
}
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
</body>
</html>










[#title | truncate: 10]@using Umbraco.Forms.Core.Services;
@inject IPlaceholderParsingService PlaceholderParsingService/*
Reverts application of recommended primary keys, foreign keys and indexes to core Umbraco Forms tables.
This reverts for SQL Server the migration AddRecordKeysAndIndexes and can be used for rolling that back in testing.
*/
-- Reverts addition of relationship between UFRecords and UFRecordFields.
ALTER TABLE dbo.UFRecordFields
DROP CONSTRAINT IF EXISTS FK_UFRecordFields_UFRecords_Record
GO
-- Reverts addition of primary keys to UFRecordData* tables.
ALTER TABLE dbo.UFRecordDataBit
DROP CONSTRAINT IF EXISTS PK_UFRecordDataBit
GO
ALTER TABLE dbo.UFRecordDataDateTime
DROP CONSTRAINT IF EXISTS PK_UFRecordDataDateTime
GO
ALTER TABLE dbo.UFRecordDataInteger
DROP CONSTRAINT IF EXISTS PK_UFRecordDataInteger
GO
ALTER TABLE dbo.UFRecordDataLongString
DROP CONSTRAINT IF EXISTS PK_UFRecordDataLongString
GO
-- Reverts addition of relationship between UFRecordFields and UFREcordData* tables.
ALTER TABLE dbo.UFRecordDataBit
DROP CONSTRAINT IF EXISTS FK_UFRecordDataBit_UFRecordFields_Key
GO
ALTER TABLE dbo.UFRecordDataDateTime
DROP CONSTRAINT IF EXISTS FK_UFRecordDataDateTime_UFRecordFields_Key
GO
ALTER TABLE dbo.UFRecordDataInteger
DROP CONSTRAINT IF EXISTS FK_UFRecordDataInteger_UFRecordFields_Key
GO
ALTER TABLE dbo.UFRecordDataLongString
DROP CONSTRAINT IF EXISTS FK_UFRecordDataLongString_UFRecordFields_Key
GO
-- Reverts addition of index on foreign key fields in UFREcordData* tables.
DROP INDEX IF EXISTS IX_UFRecordDataBit_Key ON dbo.UFRecordDataBit
GO
DROP INDEX IF EXISTS IX_UFRecordDataDateTime_Key ON dbo.UFRecordDataDateTime
GO
DROP INDEX IF EXISTS IX_UFRecordDataInteger_Key ON dbo.UFRecordDataInteger
GO
DROP INDEX IF EXISTS IX_UFRecordDataLongString_Key ON dbo.UFRecordDataLongString
GO
-- Reverts addition of primary key to UFUserSecurity
ALTER TABLE dbo.UFUserSecurity
DROP CONSTRAINT IF EXISTS PK_UFUserSecurity
GO
-- Reverts addition of primary key to UFUserFormSecurity
ALTER TABLE dbo.UFUserFormSecurity
DROP CONSTRAINT IF EXISTS PK_UFUserFormSecurity
GO
-- Reverts addition of unique constraint to UFUserFormSecurity across user/form fields.
ALTER TABLE dbo.UFUserFormSecurity
DROP CONSTRAINT IF EXISTS UK_UFUserFormSecurity_User_Form
GOpublic class LogWorkflow : Umbraco.Forms.Core.WorkflowType
{
private readonly ILogger<LogWorkflow> _logger;
public LogWorkflow(ILogger<LogWorkflow> logger)
{
_logger = logger;
}
public override WorkflowExecutionStatus Execute(WorkflowExecutionContext context)
{
throw new NotImplementedException();
}
public override List<Exception> ValidateSettings() {
throw new NotImplementedException();
}
}public LogWorkflow(ILogger<LogWorkflow> logger) {
_logger = logger;
this.Name = "The logging workflow";
this.Id = new Guid("D6A2C406-CF89-11DE-B075-55B055D89593");
this.Description = "This will save an entry to the log";
}[Umbraco.Forms.Core.Attributes.Setting("Log Header",
Description = "Log item header",
View = "TextField")]
public string LogHeader { get; set; }[Umbraco.Forms.Core.Attributes.Setting("Document ID",
Description = "Node the log entry belongs to",
View = "Pickers.Content")]
public string Document { get; set; }
public override WorkflowExecutionStatus Execute(WorkflowExecutionContext context) {
_logger.LogInformation("Record submitted from: {IP}", context.Record.IP);
return WorkflowExecutionStatus.Completed;
}public override List<Exception> ValidateSettings() {
List<Exception> exceptions = new List<Exception>();
int docId = 0;
if (!int.TryParse(Document, out docId))
exceptions.Add(new Exception("Document is not a valid integer"));
return exceptions;
}public static IUmbracoBuilder AddUmbracoFormsCustomProviders(this IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
.Add<LogWorkflow>();
}public class UmbracoFormsCustomProvidersComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
.Add<LogWorkflow>();
}
} builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
.Add<LogWorkflow>(); builder.AddFormsWorkflow<LogWorkflow>(): builder.FormsWorkflows().Add<LogWorkflow>(); builder.FormsWorkflows().Exclude<Slack>();public class TextareaWithCount : Umbraco.Forms.Core.Providers.FieldTypes.Textarea
{
// Added a new setting when we add our field to the form
[Umbraco.Forms.Core.Attributes.Setting("Max length",
Description = "Max length",
View = "TextField")]
public string MaxNumberOfChars { get; set; }
public TextareaWithCount()
{
// Set a different view for this fieldtype
this.FieldTypeViewName = "FieldType.TextareaWithCount.cshtml";
// We can change the default name of 'Long answer' to something that suits us
this.Name = "Long Answer with Limit";
}
public override IEnumerable<string> ValidateField(Form form, Field field, IEnumerable<object> postedValues, HttpContext context, IPlaceholderParsingService placeholderParsingService, List<string> errors)
{
var baseValidation = base.ValidateField(form, field, postedValues, context, placeholderParsingService, errors);
var value = postedValues.FirstOrDefault();
if (value != null && value.ToString().Length < int.Parse(MaxNumberOfChars))
{
return baseValidation;
}
var custom = new List<string>();
custom.AddRange(baseValidation);
custom.Add("String is way way way too long!");
return custom;
}
}public class UmbracoFormsCustomProvidersComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.FormsFields().Add<TextareaWithCount>();
}
}/*
Applies recommended primary keys, foreign keys and indexes to core Umbraco Forms tables.
This replicates for SQL Server the migration AddRecordKeysAndIndexes.
*/
-- Adds relationship between UFRecords and UFRecordFields.
ALTER TABLE dbo.UFRecordFields
ADD CONSTRAINT
FK_UFRecordFields_UFRecords_Record FOREIGN KEY
(
Record
) REFERENCES dbo.UFRecords
(
Id
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
-- Adds primary keys to UFRecordData* tables.
ALTER TABLE dbo.UFRecordDataBit
ADD CONSTRAINT
PK_UFRecordDataBit PRIMARY KEY CLUSTERED
(
Id
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.UFRecordDataDateTime
ADD CONSTRAINT
PK_UFRecordDataDateTime PRIMARY KEY CLUSTERED
(
Id
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.UFRecordDataInteger
ADD CONSTRAINT
PK_UFRecordDataInteger PRIMARY KEY CLUSTERED
(
Id
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.UFRecordDataLongString
ADD CONSTRAINT
PK_UFRecordDataLongString PRIMARY KEY CLUSTERED
(
Id
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds relationship between UFRecordFields and UFREcordData* tables.
ALTER TABLE dbo.UFRecordDataBit
ADD CONSTRAINT
FK_UFRecordDataBit_UFRecordFields_Key FOREIGN KEY
(
[Key]
) REFERENCES dbo.UFRecordFields
(
[Key]
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.UFRecordDataDateTime
ADD CONSTRAINT
FK_UFRecordDataDateTime_UFRecordFields_Key FOREIGN KEY
(
[Key]
) REFERENCES dbo.UFRecordFields
(
[Key]
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.UFRecordDataInteger
ADD CONSTRAINT
FK_UFRecordDataInteger_UFRecordFields_Key FOREIGN KEY
(
[Key]
) REFERENCES dbo.UFRecordFields
(
[Key]
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.UFRecordDataLongString
ADD CONSTRAINT
FK_UFRecordDataLongString_UFRecordFields_Key FOREIGN KEY
(
[Key]
) REFERENCES dbo.UFRecordFields
(
[Key]
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
-- Adds index on foreign key fields in UFREcordData* tables.
CREATE NONCLUSTERED INDEX IX_UFRecordDataBit_Key ON dbo.UFRecordDataBit
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX IX_UFRecordDataDateTime_Key ON dbo.UFRecordDataDateTime
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX IX_UFRecordDataInteger_Key ON dbo.UFRecordDataInteger
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX IX_UFRecordDataLongString_Key ON dbo.UFRecordDataLongString
(
[Key] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds primary key to UFUserSecurity.
ALTER TABLE dbo.UFUserSecurity
ADD CONSTRAINT
PK_UFUserSecurity PRIMARY KEY CLUSTERED
(
[User]
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds primary key to UFUserFormSecurity.
ALTER TABLE dbo.UFUserFormSecurity
ADD CONSTRAINT
PK_UFUserFormSecurity PRIMARY KEY CLUSTERED
(
Id
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
-- Adds unique constraint to UFUserFormSecurity across user/form fields.
ALTER TABLE dbo.UFUserFormSecurity
ADD CONSTRAINT UK_UFUserFormSecurity_User_Form UNIQUE NONCLUSTERED
(
[User] ASC,
[Form] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GOusing System;
using System.Collections.Generic;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Models;
namespace MyFormsExtensions
{
public class FixedListPrevalueSource : FieldPreValueSourceType
{
public FixedListPrevalueSource()
{
Id = new Guid("42C8158D-2AA8-4621-B653-6A63C7545768");
Name = "Fixed List";
Description = "Example prevalue source providing a fixed list of values.";
}
public override List<PreValue> GetPreValues(Field field, Form form) =>
new List<PreValue>
{
new PreValue
{
Id = 1,
Value = "item-one",
Caption = "Item One"
},
new PreValue
{
Id = 2,
Value = "item-two",
Caption = "Item Two"
}
};
/// <inheritdoc/>
public override List<Exception> ValidateSettings()
{
// this is used to validate any dynamic settings you might apply to the PreValueSource
// if there are no dynamic settings, return an empty list of Exceptions:
var exceptions = new List<Exception>();
return exceptions;
}
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Forms.Core.Providers;
namespace MyFormsExtensions
{
public class Startup : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<FieldPreValueSourceCollectionBuilder>()
.Add<FixedListPrevalueSource>();
}
}
}using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Models;
namespace MyFormsExtensions
public class FormPrevaluesSourceNode : FieldPreValueSourceType
{
private readonly ILogger _logger;
private readonly IUmbracoContextFactory _UmbracoContextFactory;
//DEFINE ANY CONFIGURATION SETTING HERE
[Umbraco.Forms.Core.Attributes.Setting(name: "Source Node",
Alias = "SourceNodeId",
Description = "Node holding the Options desired.",
View = "pickers.content")]
public string SourceNodeId { get; set; }
public FormPrevaluesSourceNode(
ILogger<FormPrevaluesSourceNode> logger
, IUmbracoContextFactory umbracoContextFactory
)
{
_logger = logger;
_UmbracoContextFactory = umbracoContextFactory;
this.Id = new Guid("0E4D4E2B-56E1-4E86-84E4-9A0A6051B57C"); //MAKE THIS UNIQUE!
this.Name = "Content-defined Form Prevalues Source Node";
this.Description = "Select a node of type 'FormPrevaluesSourceNode'";
this.Group = "Custom";
this.Icon = "icon-science";
}
/// <summary>
/// The main method where the PreValues are defined and returned.
/// </summary>
/// <param name="field"></param>
/// <param name="form"></param>
/// <returns>List of 'Umbraco.Forms.Core.Models.PreValue'</returns>
public override List<PreValue> GetPreValues(Field field, Form form)
{
List<PreValue> result = new List<PreValue>();
try
{
// Access the Configuration Setting and check that is is valid
if (!string.IsNullOrEmpty(SourceNodeId))
{
var nodeId = 0;
var isValidId = Int32.TryParse(SourceNodeId, out nodeId);
if (isValidId)
{
IPublishedContent iPub;
using (var umbracoContextReference = _UmbracoContextFactory.EnsureUmbracoContext())
{
iPub = umbracoContextReference.UmbracoContext.Content.GetById(nodeId);
}
if (iPub != null)
{
int sort = 0;
//This is using a ModelsBuilder Model to strongly-type the selected node
var preValSourceNode = new Models.FormPrevaluesSourceNode(iPub, null);
foreach (var prevalue in preValSourceNode.PreValues)
{
PreValue pv = new PreValue();
pv.Id = $"{iPub.Id}-{sort}";
pv.Value = prevalue.StoredValue;
pv.Caption = prevalue.DisplayText; //.Caption only available in Forms Versions 8.13.0+, 9.5.0+, & 10.1.0+
pv.SortOrder = sort;
result.Add(pv);
sort++;
}
}
}
}
}
catch (Exception ex)
{
_logger.LogError($"Unable to get options from FormPrevaluesSourceNode #{SourceNodeId}", ex);
}
return result;
}
/// <summary>
/// This is where any checks for Configuration validity are done.
/// The exceptions will be displayed in the back-office UI to the user.
/// </summary>
/// <returns>List of 'System.Exception'</returns>
public override List<Exception> ValidateSettings()
{
List<Exception> exceptions = new List<Exception>();
if (string.IsNullOrEmpty(SourceNodeId))
{
exceptions.Add(new Exception("'Source Node' setting not filled out"));
}
else
{
var nodeId = 0;
var isValidId = Int32.TryParse(SourceNodeId, out nodeId);
if (isValidId)
{
IPublishedContent iPub;
using (var umbracoContextReference = _UmbracoContextFactory.EnsureUmbracoContext())
{
iPub = umbracoContextReference.UmbracoContext.Content.GetById(nodeId);
}
if (iPub != null && iPub.ContentType.Alias != Models.FormPrevaluesSourceNode.ModelTypeAlias)
{
exceptions.Add(new Exception("'Source Node' needs to be of type 'FormPrevaluesSourceNode'"));
}
}
}
return exceptions;
}
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Forms.Core.Providers;
namespace MyFormsExtensions
{
public class FormsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
//Adding Custom Form PreValueSource
builder.WithCollectionBuilder<FieldPreValueSourceCollectionBuilder>()
.Add<FormPrevaluesSourceNode>();
}
}
}Get an overview of the changes and fixes in each version of Umbraco Forms.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Umbraco.Forms.Core.Enums;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Services;
namespace MyFormsExtensions
{
public class MyCustomField : Umbraco.Forms.Core.FieldType
{
public MyCustomField()
{
Id = new Guid("08b8057f-06c9-4ca5-8a42-fd1fc2a46eff"); // Replace this!
Name = "My Custom Field";
Description = "Render a custom text field.";
Icon = "icon-autofill";
DataType = FieldDataType.String;
SortOrder = 10;
SupportsRegex = true;
FieldTypeViewName = "FieldType.MyCustomField.cshtml";
}
// You can do custom validation in here which will occur when the form is submitted.
// Any strings returned will cause the submission to be considered invalid.
// Returning an empty collection of strings will indicate that it's valid to proceed.
public override IEnumerable<string> ValidateField(Form form, Field field, IEnumerable<object> postedValues, HttpContext context, IPlaceholderParsingService placeholderParsingService, IFieldTypeStorage fieldTypeStorage)
{
var returnStrings = new List<string>();
if (!postedValues.Any(value => value.ToString().ToLower().Contains("custom")))
{
returnStrings.Add("You need to include 'custom' in the field!");
}
// Also validate it against the default method (to handle mandatory fields and regular expressions)
return base.ValidateField(form, field, postedValues, context, placeholderParsingService, fieldTypeStorage, returnStrings);
}
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Forms.Core.Providers;
namespace MyFormsExtensions
{
public class Startup : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<FieldCollectionBuilder>()
.Add<MyCustomField>();
}
}
}@model Umbraco.Forms.Web.Models.FieldViewModel
<input type="text" name="@Model.Name" id="@Model.Id" class="text" value="@Model.ValueAsHtmlString" maxlength="500"
@{if (string.IsNullOrEmpty(Model.PlaceholderText) == false) { <text> placeholder="@Model.PlaceholderText" </text> }}
@{if (Model.Mandatory || Model.Validate) { <text> data-val="true" </text> }}
@{if (Model.Mandatory) { <text> data-val-required="@Model.RequiredErrorMessage" </text> }}
@{if (Model.Validate) { <text> data-val-regex="@Model.InvalidErrorMessage" data-val-regex-pattern="@Html.Raw(Model.Regex)" </text> }} /><input
type="text" tabindex="-1"
class="input-block-level"
style="max-width: 100px"
/>public override string GetDesignView() =>
"~/App_Plugins/UmbracoFormsCustomFields/backoffice/Common/FieldTypes/mycustomfield.html";[Setting("My Setting", Description = "Help text for the setting", View = "TextField", SupportsPlaceholders = "true", DisplayOrder = 10)]
public virtual string MySetting { get; set; }<area alias="formProviderFieldTypes">
<key alias="mySettingName">My Setting</key>
<key alias="mySettingDescription">Help text for the setting</key>
</area>[Setting("My Setting",
Description = "Help text for the setting",
View = "~/App_Plugins/UmbracoFormsCustomFields/backoffice/Common/SettingTypes/mycustomsettingfield.html",
SupportsPlaceholders = true,
DisplayOrder = 10)]
public virtual string MySetting { get; set; }[Setting("Minimum")]
public virtual string Min { get; set; } = "1";[Setting("Minimum", DefaultValue = "1")]
public virtual string Min { get; set; }[Setting("My Setting", Description = "My custom help text for the setting", View = "TextField", SupportsPlaceholders = "true", DisplayOrder = 10)]
public override string MySetting { get; set; }[Setting("My Setting", IsHidden = true)]
public override string MySetting { get; set; }public override string RenderView => "~/App_Plugins/UmbracoFormsCustomFields/backoffice/Common/RenderTypes/mycustomrenderfield.html";using System;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Searchers;
using Umbraco.Forms.Web.Helpers;
namespace MyFormsExtensions
{
public class ExportToHtml : ExportType
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IFormRecordSearcher _formRecordSearcher;
public ExportToHtml(
IHostEnvironment hostEnvironment,
IHttpContextAccessor httpContextAccessor,
IFormRecordSearcher formRecordSearcher)
: base(hostEnvironment)
{
_httpContextAccessor = httpContextAccessor;
_formRecordSearcher = formRecordSearcher;
Name = "Export as HTML";
Description = "Export entries as a single HTML report";
Id = new Guid("4117D352-FB41-4A4C-96F5-F6EF35B384D2");
FileExtension = "html";
Icon = "icon-article";
}
public override string ExportRecords(RecordExportFilter filter)
{
var view = "~/Views/Partials/Forms/Export/html-report.cshtml";
EntrySearchResultCollection model = _formRecordSearcher.QueryDataBase(filter);
return ViewHelper.RenderPartialViewToString(_httpContextAccessor.GetRequiredHttpContext(), view, model);
}
}
}@model Umbraco.Forms.Core.Searchers.EntrySearchResultCollection
@{
var submissions = Model.Results.ToList();
var schemaItems = Model.Schema.ToList();
}
<h1>Form Submissions</h1>
@foreach (var submission in submissions)
{
var values = submission.Fields.ToList();
for (int i = 0; i < schemaItems.Count; i++)
{
<strong>@schemaItems[i].Name</strong> @values[i].Value
<br />
}
<hr />
}using Umbraco.Cms.Core.Composing;
using Umbraco.Forms.Core.Providers.Extensions;
using Umbraco.Forms.TestSite.Business.ExportTypes;
namespace MyFormsExtensions
{
public class TestComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.FormsExporters().Add<ExportToHtml>();
}
}
}using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Searchers;
namespace MyFormsExtensions
{
public class ExportToTextFiles : ExportType
{
private readonly IFormRecordSearcher _formRecordSearcher;
public ExportToTextFiles(
IHostingEnvironment hostingEnvironment,
IFormRecordSearcher formRecordSearcher)
: base(hostingEnvironment)
{
_formRecordSearcher = formRecordSearcher;
this.Name = "Export as text files";
this.Description = "Export entries as text files inside a zip file";
this.Id = new Guid("171CABC9-2207-4575-83D5-2A77E824D5DB");
this.FileExtension = "zip";
this.Icon = "icon-zip";
}
/// <summary>
/// We do not implement this method from the interface
/// As this method is called from ExportToFile that we also override here & is expecting the file contents as a string to be written as a stream to a file
/// Which would be OK if we were creating a CSV or a single based file that can have a simple string written as a string such as one large HTML report or XML file perhaps
/// </summary>
public override string ExportRecords(RecordExportFilter filter) => throw new NotImplementedException();
/// <summary>
/// This gives us greater control of the export process
/// </summary>
/// <param name="filter">
/// This filter contains the date range & other search parameters to limit the entries we are exporting
/// </param>
/// <param name="filepath">
/// The filepath that the export file is expecting to be served from
/// So ensure that the zip of text files is saved at this location
/// </param>
/// <returns>The final file path to serve up as the export - this is unlikely to change through the export logic</returns>
public override string ExportToFile(RecordExportFilter filter, string filepath)
{
// Before Save - Check Path, Directory & Previous File export does not exist
string pathToSaveZipFile = filepath;
// Check our path does not contain \\
// If not, use the filePath
if (filepath.Contains('\\') == false)
{
pathToSaveZipFile = HostingEnvironment.MapPathContentRoot(filepath);
}
// Get the directory (strip out \\ if it exists)
var dir = filepath.Substring(0, filepath.LastIndexOf('\\'));
var tempFileDir = Path.Combine(dir, "text-files");
// If the path does not end with our file extension, ensure it's added
if (pathToSaveZipFile.EndsWith("." + FileExtension) == false)
{
pathToSaveZipFile += "." + FileExtension;
}
// Check that the directory where we will save the ZIP file temporarily exists
// If not just create it
if (Directory.Exists(tempFileDir) == false)
{
Directory.CreateDirectory(tempFileDir);
}
// Check if the zip file exists already - if so delete it, as we have a new update
if (File.Exists(pathToSaveZipFile))
{
File.Delete(pathToSaveZipFile);
}
// Query the DB for submissions to export based on the filter
EntrySearchResultCollection submissions = _formRecordSearcher.QueryDataBase(filter);
// Get the schema objects to a list so we can get items using position index
var schemaItems = submissions.schema.ToList();
// We will use this to store our contents of our file to save as a text file
var fileContents = string.Empty;
// For each submission we have build up a string to save to a text file
foreach (EntrySearchResult submission in submissions.Results)
{
// The submitted data for the form submission
var submissionData = submission.Fields.ToList();
// For loop to match the schema position to the submission data
for (int i = 0; i < schemaItems.Count; i++)
{
// Concat a string of the name of the field & its stored data
fileContents += schemaItems[i].Name + ": " + submissionData[i] + Environment.NewLine;
}
// Now save the contents to a text file
// Base it on the format of the record submission unique id
var textFileName = Path.Combine(tempFileDir, submission.UniqueId + ".txt");
File.WriteAllText(textFileName, fileContents);
// Reset fileContents to be empty again
fileContents = string.Empty;
}
// Now we have a temp folder full of text files
// Generate a zip file containing them & save that
ZipFile.CreateFromDirectory(tempFileDir, pathToSaveZipFile);
// Tidy up after ourselves & delete the temp folder of text files
if (Directory.Exists(tempFileDir))
{
Directory.Delete(tempFileDir, true);
}
// Return the path where we saved the zip file containing the text files
return pathToSaveZipFile;
}
}
}using System;
using System.Collections.Generic;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Forms.Core.Attributes;
using Umbraco.Forms.Core.Enums;
using Umbraco.Forms.Core.Persistence.Dtos;
namespace MyNamespace
{
public class LogMessageWorkflow : WorkflowType
{
public const string LogMessageWorkflowId = "7ca500a7-cb34-4a82-8ae9-2acac777382d";
private readonly ILogger<LogMessageWorkflow> _logger;
public LogMessageWorkflow(ILogger<LogMessageWorkflow> logger)
{
Id = new Guid(LogMessageWorkflowId);
Name = "Test Workflow";
Description = "A test workflow that writes a log line";
Icon = "icon-edit";
_logger = logger;
}
[Setting("Message", Description = "The log message to write", View = "TextField")]
public string Message { get; set; }
public override List<Exception> ValidateSettings()
{
var exs = new List<Exception>();
if (string.IsNullOrEmpty(Message))
{
exs.Add(new Exception("'Message' setting has not been set"));
}
return exs;
}
public override WorkflowExecutionStatus Execute(WorkflowExecutionContext context)
{
_logger.LogInformation($"'{Message}' written at {DateTime.Now}");
return WorkflowExecutionStatus.Completed;
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Enums;
using Umbraco.Forms.Core.Providers;
using Umbraco.Forms.Web.Behaviors;
using Umbraco.Forms.Web.Models.Backoffice;
namespace MyNamespace
{
public class CustomApplyDefaultWorkflowsBehavior : IApplyDefaultWorkflowsBehavior
{
private readonly WorkflowCollection _workflowCollection;
private readonly IHostingEnvironment _hostingEnvironment;
public CustomApplyDefaultWorkflowsBehavior(
WorkflowCollection workflowCollection, IHostingEnvironment hostingEnvironment)
{
_workflowCollection = workflowCollection;
_hostingEnvironment = hostingEnvironment;
}
public void ApplyDefaultWorkflows(FormDesign form)
{
// Retrieve the type of the default workflow to add.
WorkflowType testWorkflowType = _workflowCollection[new Guid(LogMessageWorkflow.LogMessageWorkflowId)];
// Create a workflow object based on the workflow type.
var defaultWorkflow = new FormWorkflowWithTypeSettings
{
Id = Guid.Empty,
Name = "Log a message",
Active = true,
IncludeSensitiveData = IncludeSensitiveData.False,
SortOrder = 1,
WorkflowTypeId = testWorkflowType.Id,
WorkflowTypeName = testWorkflowType.Name,
WorkflowTypeDescription = testWorkflowType.Description,
WorkflowTypeGroup = testWorkflowType.Group,
WorkflowTypeIcon = testWorkflowType.Icon,
// Optionally set the default workflow to be mandatory (which means editors won't be able to remove it
// via the back-office user interface).
IsMandatory = true
};
// Retrieve the settings from the type.
Dictionary<string, Core.Attributes.Setting> workflowTypeSettings = testWorkflowType.Settings();
// Create a collection for the specific settings to be applied to the workflow.
// Populate with the setting details from the type.
var workflowSettings = new List<SettingWithValue>();
foreach (KeyValuePair<string, Core.Attributes.Setting> setting in workflowTypeSettings)
{
Core.Attributes.Setting settingItem = setting.Value;
var settingItemToAdd = new SettingWithValue
{
Name = settingItem.Name,
Alias = settingItem.Alias,
Description = settingItem.Description,
Prevalues = settingItem.GetPreValues(),
View = _hostingEnvironment.ToAbsolute(settingItem.GetSettingView()),
Value = string.Empty
};
workflowSettings.Add(settingItemToAdd);
}
// For each setting, provide a value for the workflow instance (in this example, we only have one).
SettingWithValue messageSetting = workflowSettings.SingleOrDefault(x => x.Alias == "Message");
if (messageSetting != null)
{
messageSetting.Value = "A test log message";
}
// Apply the settings to the workflow.
defaultWorkflow.Settings = workflowSettings;
// Associate the workflow with the appropriate form submission event.
form.FormWorkflows.OnSubmit.Add(defaultWorkflow);
}
}
}using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;
using Umbraco.Forms.Core.Providers;
using Umbraco.Forms.Testsite.Business.Workflows;
using Umbraco.Forms.Web.Behaviors;
namespace MyNamespace
{
public class TestSiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
.Add<LogMessageWorkflow>();
builder.Services.AddUnique<IApplyDefaultWorkflowsBehavior, CustomApplyDefaultWorkflowsBehavior>();
}
}
}using Microsoft.Extensions.Options;
using Umbraco.Forms.Core.Configuration;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Web.Extensions;
using Umbraco.Forms.Web.Models.Backoffice;
namespace Umbraco.Forms.Web.Behaviors
{
internal class CustomApplyDefaultFieldsBehavior : IApplyDefaultFieldsBehavior
{
private readonly FormDesignSettings _formDesignSettings;
public CustomApplyDefaultFieldsBehavior(IOptions<FormDesignSettings> formDesignSettings) =>
_formDesignSettings = formDesignSettings.Value;
public virtual void ApplyDefaultFields(FormDesign form)
{
// Add one page as a starting point.
var page = new Page();
form.Pages.Add(page);
// Add one empty fieldset to the page to start with.
var fieldset = new FieldSet
{
Id = Guid.NewGuid()
};
page.FieldSets.Add(fieldset);
// Add one full-width (12cols) container/row to the fieldset.
var container = new FieldsetContainer
{
Width = 12
};
fieldset.Containers.Add(container);
// As all forms default to having StoreRecordsLocally we need to add the data consent field to the the form
// (unless this feature has been explicitly disabled).
if (_formDesignSettings.DisableAutomaticAdditionOfDataConsentField)
{
return;
}
container.AddDataConsentField(_formDesignSettings, _fieldCollection);
// Add any further fields you require.
}
}
}builder.Services.AddUnique<IApplyDefaultFieldsBehavior, CustomApplyDefaultFieldsBehavior>();builder.Services.Configure<Recaptcha3Settings>(options =>
{
options.SiteKey = "...";
options.PrivateKey = "...";
});
// Ensure the field is added and available
builder.FormsFields().Add<Recaptcha3>();DefaultValueAcceptCopyPlaceholderDefaultValueSelectedFilesListHeadingDefaultValueDefaultValueDisplayLayoutPlaceholderThemeScoreThresholdScoreThresholdHtmlDisplayLayoutDefaultValueCaptionTagWordsUrlFieldsPathEmailUrlWebhookUrlTokenTextFileConnectionDataTypeIdRootNodeConnection






"Umbraco": {
"Forms": {
"Options": {
"EnableFormsApi": true
}
}
}GET /umbraco/forms/api/v1/definitions/{id}?contentId={contentId}&culture={culture}&additionalData[{key}]={value}&additionalData[key2]={value2}{
"disableDefaultStylesheet": false,
"fieldIndicationType": "MarkMandatoryFields",
"hideFieldValidation": false,
"id": "34ef4a19-efa7-40c1-b8b6-2fd7257f2ed3",
"indicator": "*",
"messageOnSubmit": "Thanks for submitting the form",
"name": "Simple Comment Form",
"nextLabel": "Next",
"pages": [
{
"caption": "Your comment",
"fieldsets": [
{
"caption": "",
"columns": [
{
"caption": "",
"width": 12,
"fields": [
{
"alias": "name",
"caption": "Name",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "[#message] from [#pageName]",
"id": "25185934-9a61-491c-9610-83dfe774662c",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for Name",
"placeholder": "",
"preValues": [],
"required": true,
"requiredErrorMessage": "Please provide a value for Name",
"settings": {
"defaultValue": "",
"placeholder": "Please enter your name.",
"showLabel": "",
"maximumLength": "",
"fieldType": "",
"autocompleteAttribute": ""
},
"type": {
"id": "3f92e01b-29e2-4a30-bf33-9df5580ed52c",
"name": "Short answer"
}
},
{
"alias": "email",
"caption": "Email",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "",
"id": "816fdf3b-a796-4677-a317-943a54bf9d55",
"pattern": "^[_a-z0-9-]+(\\.[_a-z0-9-]+)*@[a-z0-9-]+(\\.[a-z0-9-]+)*(\\.[a-z]{2,4})$",
"patternInvalidErrorMessage": "Please provide a valid value for Email",
"placeholder": "",
"preValues": [],
"required": true,
"requiredErrorMessage": "Please provide a value for Email",
"settings": {
"defaultValue": "",
"placeholder": "",
"showLabel": "",
"maximumLength": "",
"fieldType": "email",
"autocompleteAttribute": ""
},
"type": {
"id": "3f92e01b-29e2-4a30-bf33-9df5580ed52c",
"name": "Short answer"
}
},
{
"alias": "comment",
"caption": "Comment",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "",
"id": "9d723100-ec34-412f-aaa5-516634d7c833",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for Comment",
"placeholder": "",
"preValues": [],
"required": false,
"requiredErrorMessage": "Please provide a value for Comment",
"settings": {
"defaultValue": "",
"placeholder": "",
"showLabel": "",
"autocompleteAttribute": "",
"numberOfRows": "2",
"maximumLength": ""
},
"type": {
"id": "023f09ac-1445-4bcb-b8fa-ab49f33bd046",
"name": "Long answer"
}
},
{
"alias": "country",
"caption": "Country",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "",
"id": "30ff8f37-28d4-47df-f281-422b36c62e73",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for Country",
"placeholder": "",
"preValues": [
{
"caption": "France",
"value": "fr"
},
{
"caption": "Italy",
"value": "it"
},
{
"caption": "Span",
"value": "es"
},
{
"caption": "United Kingdom",
"value": "gb"
}
],
"required": false,
"requiredErrorMessage": "Please provide a value for Country",
"settings": {
"defaultValue": "",
"allowMultipleSelections": "",
"showLabel": "",
"autocompleteAttribute": "",
"selectPrompt": "Please select"
},
"type": {
"id": "0dd29d42-a6a5-11de-a2f2-222256d89593",
"name": "Dropdown"
}
},
{
"alias": "favouriteColour",
"caption": "Favourite Colour",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "",
"id": "a6e2e27f-097d-476a-edb9-4aa79449ab5c",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for Favourite Colour",
"placeholder": "",
"preValues": [
{
"caption": "Red",
"value": "red"
},
{
"caption": "Green",
"value": "green"
},
{
"caption": "Yellow",
"value": "yello"
}
],
"required": false,
"requiredErrorMessage": "Please provide a value for Favourite Colour",
"settings": {
"defaultValue": "",
"showLabel": ""
},
"type": {
"id": "fab43f20-a6bf-11de-a28f-9b5755d89593",
"name": "Multiple choice"
}
},
{
"alias": "dataConsent",
"caption": "Data consent",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "Please indicate if it's OK to store your data.",
"id": "9f25acaf-4ac4-4105-9afe-eb0bb0c03b31",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for Data consent",
"placeholder": "",
"preValues": [],
"required": true,
"requiredErrorMessage": "Please confirm your data consent",
"settings": {
"acceptCopy": "Yes, I give permission to store and process my data.",
"showLabel": ""
},
"type": {
"id": "a72c9df9-3847-47cf-afb8-b86773fd12cd",
"name": "Data Consent"
}
},
{
"alias": "tickToAddMoreInfo",
"caption": "Tick to add more info",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": []
},
"helpText": "",
"id": "6ce0cf78-5102-47c1-85c6-9530d9e9c6a6",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for Tick to add more info",
"placeholder": "",
"preValues": [],
"required": false,
"requiredErrorMessage": "Please provide a value for Tick to add more info",
"settings": {
"defaultValue": ""
},
"type": {
"id": "d5c0c390-ae9a-11de-a69e-666455d89593",
"name": "Checkbox"
}
},
{
"alias": "moreInfo",
"caption": "More info",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": [
{
"field": "6ce0cf78-5102-47c1-85c6-9530d9e9c6a6",
"operator": "Is",
"value": "on"
}
]
},
"helpText": "",
"id": "5b4100ed-cc5e-4113-943c-ee5a8f4e448d",
"pattern": "",
"patternInvalidErrorMessage": "Please provide a valid value for More info",
"placeholder": "",
"preValues": [],
"required": false,
"requiredErrorMessage": "Please provide a value for More info",
"settings": {
"defaultValue": "",
"placeholder": "",
"showLabel": "",
"maximumLength": "",
"fieldType": "",
"autocompleteAttribute": ""
},
"type": {
"id": "3f92e01b-29e2-4a30-bf33-9df5580ed52c",
"name": "Short answer"
}
}
],
"width": 0
}
],
"id": "d677b96f-488d-4052-b00d-fb852b35e9c5"
}
]
}
],
"previousLabel": "Previous",
"showValidationSummary": false,
"submitLabel": "Submit"
}{
...
"messageOnSubmit": "Thanks for submitting the form",
...
}{
...
"gotoPageOnSubmit": "eab72f13-b22e-46d5-b270-9c196e49a53b",
"gotoPageOnSubmitRoute": {
"path": "/contact-us/thanks/",
"startItem": {
"id": "ca4249ed-2b23-4337-b522-63cabe5587d1",
"path": "home"
}
},
...
}POST /umbraco/forms/api/v1/entries/{id}{
"values": {
"name": "Fred",
"email": "[email protected]",
"comment": "Test",
"country": "it",
"favouriteColours": ["red", "green"],
"dataConsent": "on"
},
"contentId": "ca4249ed-2b23-4337-b522-63cabe5587d1",
"culture": "en-US",
"additionalData": {
"foo": "bar",
"baz": "buzz",
}
}{
"errors": {
"name": [
"Please provide a value for Name"
]
},
"extensions": {},
"status": 422,
"title": "One or more validation errors occurred."
}{
"gotoPageOnSubmit": "3cce2545-e3ac-44ec-bf55-a52cc5965db3",
"gotoPageOnSubmitRoute": {
"path": "/about-us/",
"startItem": {
"id": "ca4249ed-2b23-4337-b522-63cabe5587d1",
"path": "home"
}
},
"messageOnSubmit": "Thanks for your entry",
"messageOnSubmitIsHtml": false
} ...
"fields": [
{
"alias": "uploadAPicture",
...
"fileUploadOptions": {
"allowAllUploadExtensions": false,
"allowedUploadExtensions": [
"png",
"jpg",
"gif"
],
"allowMultipleFileUploads": false
},
}
]
...{
"values": {
"uploadAPicture": [
{
"fileName": "mypic.jpg",
"fileContents": "data:image/png;base64,iVBORw..."
}
]
},
}@using Microsoft.AspNetCore.Antiforgery
@inject IAntiforgery antiforgery
@{
var tokenSet = antiforgery.GetAndStoreTokens(Context);
} let response = await fetch("/umbraco/forms/api/v1/entries/" + formId, {
method: "POST",
headers: {
"Content-Type": "application/json",
"@tokenSet.HeaderName" : "@tokenSet.RequestToken"
},
body: JSON.stringify(data),
});{
"name": "Contact Us",
"route": {
"path": "/contact-us/",
"startItem": {
"id": "ca4249ed-2b23-4337-b522-63cabe5587d1",
"path": "home"
}
},
"id": "4a1f4198-e143-48ba-a0f5-1a7ef2df23aa",
"contentType": "contactPage",
"properties": {
"title": "Contact Us",
"contactForm": {
"id": "3623b232-9296-4bf0-b16c-57801dc4f296",
"form": null
}
}
}{
"name": "Contact Us",
"route": {
"path": "/contact-us/",
"startItem": {
"id": "ca4249ed-2b23-4337-b522-63cabe5587d1",
"path": "home"
}
},
"id": "4a1f4198-e143-48ba-a0f5-1a7ef2df23aa",
"contentType": "contactPage",
"properties": {
"title": "Contact Us",
"contactForm": {
"id": "3623b232-9296-4bf0-b16c-57801dc4f296",
"form": {
"id": "3623b232-9296-4bf0-b16c-57801dc4f296",
"name": "Contact Form",
...
"pages": [ ... ]
}
}
}
}// Execute a reinitialize on dynamic injections
const reinitializeEvent = new Event('umbracoFormsReinitialize');
document.dispatchEvent(reinitializeEvent);
// Render a specific form via the API
const injectedForm = document.getElementById('injected-umbraco-form');
window.UmbracoForms.reinitialize(injectedForm);
In Umbraco Forms it's possible to customize the functionality with various configuration values.
DefaultValue{
...
"Umbraco": {
"CMS": {
...
},
"Forms": {
...
}
}
} "Forms": {
"FormDesign": {
"DisableAutomaticAdditionOfDataConsentField": false,
"DisableDefaultWorkflow": false,
"MaxNumberOfColumnsInFormGroup": 12,
"DefaultTheme": "default",
"DefaultEmailTemplate": "Forms/Emails/Example-Template.cshtml",
"Defaults": {
"ManualApproval": false,
"DisableStylesheet": false,
"MarkFieldsIndicator": "NoIndicator",
"Indicator": "*",
"RequiredErrorMessage": "Please provide a value for {0}",
"InvalidErrorMessage": "Please provide a valid value for {0}",
"ShowValidationSummary": false,
"HideFieldValidationLabels": false,
"NextPageButtonLabel": "Next",
"PreviousPageButtonLabel": "Previous",
"SubmitButtonLabel": "Submit",
"MessageOnSubmit": "Thank you",
"StoreRecordsLocally": true,
"AutocompleteAttribute": "",
"DaysToRetainSubmittedRecordsFor": 0,
"DaysToRetainApprovedRecordsFor": 0,
"DaysToRetainRejectedRecordsFor": 0,
"ShowPagingOnMultiPageForms": "None",
"PagingDetailsFormat": "Page {0} of {1}",
"PageCaptionFormat": "Page {0}",
"ShowSummaryPageOnMultiPageForms": false,
"SummaryLabel": "Summary of Entry"
},
"RemoveProvidedEmailTemplate": false,
"RemoveProvidedFormTemplates": false,
"FormElementHtmlIdPrefix": "",
"SettingsCustomization": {
"DataSourceTypes": {},
"FieldTypes": {},
"PrevalueSourceTypes": {},
"WorkflowTypes": {},
},
"MandatoryFieldsetLegends": false,
"UseViewEngineFormThemeResolver": false
},
"Options": {
"IgnoreWorkFlowsOnEdit": "True",
"AllowEditableFormSubmissions": false,
"AppendQueryStringOnRedirectAfterFormSubmission": false,
"CultureToUseWhenParsingDatesForBackOffice": "",
"TriggerConditionsCheckOn": "change",
"ScheduledRecordDeletion": {
"Enabled": false,
"FirstRunTime": "",
"Period": "1.00:00:00"
},
"DisableRecordIndexing": false,
"EnableFormsApi": false,
"EnableRecordingOfIpWithFormSubmission": false,
"UseSemanticFieldsetRendering": false,
"DisableClientSideValidationDependencyCheck": false,
"DisableRelationTracking": false,
"TrackRenderedFormsStorageMethod": "TempData",
"EnableMultiPageFormSettings": false,
"EnableAdvancedValidationRules": false
},
"Security": {
"DisallowedFileUploadExtensions": "config,exe,dll,asp,aspx",
"AllowedFileUploadExtensions": "",
"EnableAntiForgeryToken": true,
"SavePlainTextPasswords": false,
"DisableFileUploadAccessProtection": false,
"DefaultUserAccessToNewForms": "Grant",
"ManageSecurityWithUserGroups": false,
"GrantAccessToNewFormsForUserGroups": "admin,editor",
"FormsApiKey": "",
"EnableAntiForgeryTokenForFormsApi": true,
},
"FieldTypes": {
"DatePicker": {
"DatePickerYearRange": 10,
"DatePickerFormat": "LL",
"DatePickerFormatForValidation": ""
},
"Recaptcha2": {
"PublicKey": "",
"PrivateKey": ""
},
"Recaptcha3": {
"SiteKey": "",
"PrivateKey": "",
"Domain": "Google",
"VerificationUrl": "https://www.google.com/recaptcha/api/siteverify",
"ShowFieldValidation": false
},
"RecaptchaEnterprise": {
"SiteKey": "",
"ApiKey": "",
"ProjectId": "",
"Domain": "Google",
"VerificationUrl": "https://recaptchaenterprise.googleapis.com/v1/projects/{PROJECT_ID}/assessments",
"ShowFieldValidation": false
},
"RichText": {
"DataTypeId": "ca90c950-0aff-4e72-b976-a30b1ac57dad"
},
"TitleAndDescription": {
"AllowUnsafeHtmlRendering": true
}
}
}{
"IsHidden": true|false,
"DefaultValue": "",
"IsReadOnly": true|false
} "SettingsCustomization": {
"WorkflowTypes": {
"sendEmailWithRazorTemplate": {
"SenderEmail": {
"IsHidden": true
}
}
},
} "SettingsCustomization": {
"FieldTypes": {
"recaptcha3": {
"ScoreThreshold": {
"DefaultValue": "0.8",
"IsReadOnly": true
}
}
},
}The form's Id.
The Id of the content page on which the form is hosted.
The culture code for the form's localization context.
OK
Bad Request
Not Found
The form's Id.
Accepted
Bad Request
Not Found
Unprocessable Content
No content
GET /umbraco/forms/api/v1/definitions/{id} HTTP/1.1
Accept: */*
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "text",
"indicator": "text",
"cssClass": "text",
"nextLabel": "text",
"previousLabel": "text",
"submitLabel": "text",
"disableDefaultStylesheet": true,
"fieldIndicationType": "NoIndicator",
"hideFieldValidation": true,
"messageOnSubmit": "text",
"messageOnSubmitIsHtml": true,
"showValidationSummary": true,
"gotoPageOnSubmit": "123e4567-e89b-12d3-a456-426614174000",
"gotoPageOnSubmitRoute": {
"path": "text",
"startItem": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"path": "text"
}
},
"pages": [
{
"caption": "text",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": [
{
"field": "text",
"operator": "Is",
"value": "text"
}
]
},
"fieldsets": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"caption": "text",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": [
{
"field": "text",
"operator": "Is",
"value": "text"
}
]
},
"columns": [
{
"caption": "text",
"width": 1,
"fields": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"caption": "text",
"helpText": "text",
"cssClass": "text",
"alias": "text",
"required": true,
"requiredErrorMessage": "text",
"pattern": "text",
"patternInvalidErrorMessage": "text",
"condition": {
"actionType": "Show",
"logicType": "All",
"rules": [
{
"field": "text",
"operator": "Is",
"value": "text"
}
]
},
"fileUploadOptions": {
"allowAllUploadExtensions": true,
"allowedUploadExtensions": [
"text"
],
"allowMultipleFileUploads": true
},
"preValues": [
{
"value": "text",
"caption": "text"
}
],
"settings": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"type": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "text",
"supportsPreValues": true,
"supportsUploadTypes": true,
"renderInputType": "text"
}
}
]
}
]
}
]
}
]
}POST /umbraco/forms/api/v1/entries/{id} HTTP/1.1
Content-Type: application/json
Accept: */*
Content-Length: 135
{
"values": {
"ANY_ADDITIONAL_PROPERTY": [
"text"
]
},
"contentId": "text",
"culture": "text",
"additionalData": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}