This guide explains what Custom Actions are, how to build them from scratch, how their decorators work, and how to use the Actions with Events feature to enable design-time behavior. It is generic and applies to any business logic you want to implement.
Knowledge base references recommended:
- https://docs.procesio.com/custom-actions
- https://docs.procesio.com/custom-actions/your-first-custom-action
- https://docs.procesio.com/custom-actions/custom-action-decorators
- https://docs.procesio.com/custom-actions/frontend-decorator
- https://docs.procesio.com/custom-actions/frontend-select-decorator-guide
- https://docs.procesio.com/custom-actions/class-decorator
- https://docs.procesio.com/custom-actions/backend-decorator
- https://docs.procesio.com/custom-actions/lists-and-display-rules-inside-custom-actions
- https://docs.procesio.com/custom-actions/full-guide-on-custom-actions
Custom Actions are .NET classes compiled into a library and uploaded to PROCESIO to extend the platform with your own logic. An action exposes typed inputs and outputs (properties), a runtime entry point (Execute()), and metadata via decorators that drive how the action is displayed and behaves in the canvas.
- Runtime: The
Execute()method runs during a process execution (or when testing an action). Put your business logic here. - Design-time: The decorators configure the properties (their visuals and behaviors). Optional methods annotated with
ControlEventHandlercan run in the canvas to pre-populate options and preview values when users interact with inputs.
Target framework: .NET 8.
Decorators are attributes placed on classes, properties, and methods to configure how an action appears and behaves.
ClassDecorator: Configures the action’s name, icon shape, description, classification, and whether it is testable.Permissions: Allows delete/duplicate/add from toolbar.FEDecorator(Front-End): Configures a property’s UI (label, type, tab, row order, options binding, tooltips).BEDecorator(Back-End): Marks a property asDirection.Input,Direction.Output, orDirection.InputOutputfor runtime mapping.Validator: Adds simple validation metadata such asIsRequiredto mark property as mandatory before execution.DependencyDecorator: Declares front-end display rules based on other properties (visibility/enablement dependent relationships).ControlEventHandler: Attaches a design-time event handler (method) for event types such asOnLoad,OnChangeorOnClicktriggers.
Use these building blocks to model your action’s UI and data flow.
ClassDecorator(Name, Shape, Description, Classification, IsTestable): Controls the action tile and metadata.Permissions(CanDelete, CanDuplicate, CanAddFromToolbar): Controls how users can manage the action in the canvas.FEDecoratorcan be used at class level to defineFeComponentType.Side_panneland Tab relationships viaParentattribute (Yes, we're aware of the side panel typo, but it's a pain to fix).
Each property exposed to the front end typically has:
FEDecorator(Label, Type, RowId, Tab, Options?, Tooltip?)Typerepresents the UI control type (text, number, select, checkbox, file, button, credentials, etc.).RowIddetermines ordering in the tab. SetRowIdto keep a clean vertical order, otherwise properties get displayed in random order in the UI.Tabgroups properties into tabs. Use consistent tab name for your properties since you may have more than one tab for an action.Parentdefines the parent side panel for the property declared at class level.Tooltipprovides additional information about the property when hovered over.DefaultValuecan set an initial value for text/number/checkbox types (such as a default value of 60 seconds for a number property representing a timeout).MinandMaxcan constrain number inputs within a limited range.TextFormatis an optional attribute used alongsideFeComponentType.Code_editorfor the UI to assist with syntax highlighting (PLAINTEXT, JSON, SQL, HTML, JAVASCRIPT).CustomCredentialsTypeGuidis an optional attribute paired withFeComponentType.Credentials_Customto bind a specific credentials type's id. This must be a valid GUID.Optionsbinds a collection property providing dropdown entries forFeComponentType.Select. The list property contains items ofOptionModelinstances filled either statically (hardcoded) or dynamically (events).
BEDecorator(IOProperty = Direction.Input|Output|InputOutput)to map to runtime inputs/outputs.Validator(IsRequired = true|false)for basic validation prior to execution.DependencyDecorator(Tab, Control, Operator, Value): Shows a property only when another property meets a rule (e.g.,NotEquals null). This controls properties visibility in the UI, but does not execute logic. Examples of dependency patterns:- A second dropdown depends on the first dropdown's selection as a chain reaction.
- A modified value depends on a checkbox being checked.
- With credentials, most inputs could depend on
Credentialsbeing provided first in order to make external calls needing authentication (REST API, SMTP, SFTP, etc.). - A file input may be necessary before making certain calls (such as certificates, or excel/csv with filtered row data, etc.).
At runtime (process execution or test action), only Execute() method runs.
- Ensure your
Execute()validates inputs before using them and sets outputs after your code logic. - Implement your own business validation in code. It's the user's responsibility to ensure all inputs have the expected value. If a property is not set as
IsRequired = truethen the property may have null value at runtime. BEDecoratordetermines what property values are sent to/returned from runtime. This is done via reflection, hence whyExecute()does not have a method return type.
Events allow you to populate options and preview values while users configure the action in the canvas. This is configured using the attribute ControlEventHandler applied on your methods.
-ControlEventHandler has several parameters to control when and how it runs:
TriggerControl: The property name that triggers the event. If noTriggerControlthen it is consider an action-level event and will run automatically right after the action is dropped in the canvas (e.g. for configuring an actionOnLoadto pre-populate the action with defaults dynamically).EventType: The type of event (OnLoad,OnChange,OnClick, etc.). This informs the UI what kind of behavior is expected for the event (action loaded, dropdown loaded, value changed, dropdown value changed, button clicked, etc.)InputControls: A comma-separated list of property names that must receive from the front end before the method runs. Just like theExecute()method, this is done via reflection so any property used in code must be declared here to ensure it is populated. It's the user's responsibility to ensure all inputs are declared in the decorator and validated before use. Attribute can be empty if no inputs are needed.OutputControls: A comma-separated list of property names that are sending values back to the front end after the method runs. This is done via reflection, hence why method return types are not needed and will be ignored. It's the user's responsibility to ensure all outputs are declared here to have their values returned from runtime in the UI. Attribute can be empty if no outputs are modified/needed.OutputTarget: EitherOptions(to update dropdown options) orValue(to update data values). Do not mix both in the same attribute line. Use multiple attributes on the same method if both are needed. Set output property names accordingly, since the bound collection for options will be retrieved by the BackEnd automatically fromFEDecorator.Optionswhile the value property will be retrieved from the property itself.Order: An optional attribute of integer type to control execution order when multiple methods share the sameTriggerControl. Lower numbers run first. This is useful if instead of one single method that has complex logic you want to split logic across methods for different affected properties or if you want a chained execution logic in which case method1 mutates properties on the action and method 2 sees those changes and uses the updated values. Default is 0 when attribute is not declared. On multiple methods without an order, they will execute in random order, so consider if methods could override property values in order to determine method execution order.
Use credentials whenever you require external connections such as REST API, SMTP, SFTP, etc.
APICredentialsManageris the typical property type used for REST credentials paired withFeComponentType.Credentials_Rest. If Procesio's Call Api Action does not satisfy your need and you want custom implementations, this is useful for when an API requires tokens/passwords/apiKeys/OAuth2 so that the data comes from Procesio's Credentials safely without users exposing sensitive information. Alternatively, when you want to move base URLs and API versions out of code for ease of access and easy modifications so that you don't reupload the action because the code has it hardcoded (e.g.,https://api.something.com/v2can change to/v3and could be edited easy from Procesio's Credentials Manager). Call only relative paths in code (e.g.,endpoint = "/orders"forvar apiResponse = await Credentials.Client.GetAsync(endpoint, queries, headers)whereCredentialsis a property of typeAPICredentialsManager).DbCredentialsManageris the typical property type used for Database credentials paired withFeComponentType.Credentials_Db. Use this when you want to connect to databases if the Procesio Database Actions do not satisfy your needs, but make sure to use parameters for safety against SQL injection and to set a timeout for your command. However, the existing Procesio Database actions should satisfy basic requirements and the platform will be updated to include different types of databases in the future.FTPCredentialsManageris the typical property type used for SFTP credentials paired withFeComponentType.Credentials_Ftp. Use this when you want to connect to FTP/SFTP servers if the Procesio FTP Actions do not satisfy your needs. However, Procesio has comprehensive FTP actions so this is a less common need.SMTPCredentialsManageris the typical property type used for SMTP credentials paired withFeComponentType.Credentials_SmtporFeComponentType.Credentials_Smtp_Inbound. However, Procesio's Send Email and Read Mailbox should already cover most needs.CustomCredentialsManageris the typical property type used for custom credentials paired withFeComponentType.Credentials_CustomandFEDecorator's CustomCredentialsTypeGuidwhich is a GUID of a custom credentials type created in your workspace. Use this when you want to create your own credentials type with specific fields (e.g., API key + secret, or username + password + domain, etc.) and use it in your action. This is useful for custom authentication schemes or when you want to move configuration data out of code for easy access and modifications without re-uploading the action.
- Configure your development environment as per the official docs: https://docs.procesio.com/custom-actions/your-first-custom-action. Set your .csproj and Action.Core NuGet package accordingly. Your new class should inherit Action.Core.IAction. You should have only one Custom Action per .csproj.
- Setup the mandatory
ClassDecoratoron your class and consider whichPermissionsyou want to enable on your action. - Consider your actions input and output properties and declare them accordingly with the mandatory
BEDecoratordecorator. - Consider whether the properties are mandatory or optional and add the mandatory
Validatordecorator as needed. The UI will apply the validation before you and prevent saving your process or action testing if value is missing onIsRequiredproperties. - Configure properties with the mandatory decorator
FEDecoratorto define how they will appear in the UI (label, type, tab, tooltip, etc.). - Don't forget to set
RowIdorder so that the properties don't get displayed in random order each time. This is only a visual improvement, it does not affect execution, but it helps the user experience when reusing the action. - Add
DependencyDecoratorto guide the UI as to which inputs appear after which in case you want to configure chain reactions. This is a visual improvement, it does not affect execution, but it helps in case you have multiple properties to follow the steps more easily as you setup your action in the canvas. - Implement
Execute()for runtime. Keep it resilient to missing inputs if some are optional. Add business validation as needed. Set mandatory outputs at the end. - Implement events using
ControlEventHandleron your methods. The name of the methods are the user's choice and has no impact on executing them. - If you are implementing events, consider if these events will have a
TriggerControlor they will be action-level (no trigger, runs on first time configuring the action). Set theControlEventTypeaccordingly to determine the UI behavior. Consider having reset/refresh posibility forOnLoadinitializing events so that the user isn't forced to drag and drop the action in the canvas again to trigger a refresh of first load time. - If you are implementing events, ensure
InputControlsincludes everything used by the method and validate them at the start of the method. EnsureOutputControlsincludes everything to return to the UI. Both are optional if you don't need inputs or affect outputs for your method (e.g. maybe it just does an API call). However, if you forget declaring them you will get null values at runtime since reflection won't populate them, or you won't get the returned output values at the end of the execution. - If you are implementing events and you have
OutputControls, ensure you declare theOutputTargetwhich is the type of result the output will have. Use separate attributes for options vs values on a single method, or split in multiple methods triggered by the sameTriggerControlproperty and ensure usingOrderif their logic impact each other. If you have multiple methods with the sameTriggerControland they don't have anOrder, they will execute in random order, so consider if they could override each other's values. If you have multiple outputs in theOutputControlsarray and they have the sameOutputTargettype, then a single attribute is enough for all of them. - Use
nameof(Property)for each property to avoid typos.Options = nameof(MyOptionsListProperty)orOptions = "MyOptionsListProperty"mean the same, just that the second option with hardcoded strings is more prone to errors in case you rename your properties and forget to update all impacted decorators using that property.
- One property example and its decorators:
[FEDecorator(Label = "Currency", Type = FeComponentType.Select, RowId = 5, Tab = "Geo", Options = nameof(CurrencyList), Tooltip = "Currencies of the selected country.")]
[BEDecorator(IOProperty = Direction.InputOutput)]
[DependencyDecorator(Tab = "Geo", Control = nameof(Country), Operator = Operator.NotEquals, Value = null)]
public string? Currency { get; set; }
private IList<OptionModel> CurrencyList { get; set; } = new List<OptionModel>();- When
Countryproperty's value is NotEquals null in the UI, the dependency relationship with the propertyCurrencyis triggered and it will display theFeComponentType.Selectdropdown in the UI and trigger all the methods withTriggerControl = nameof(Country). - Event method example:
[ControlEventHandler(EventType = ControlEventType.OnChange, TriggerControl = nameof(Country), InputControls = [nameof(Country)], OutputControls = [nameof(CountryLocalTime)], OutputTarget = OutputTarget.Value)]
[ControlEventHandler(EventType = ControlEventType.OnChange, TriggerControl = nameof(Country), InputControls = [nameof(Country)], OutputControls = [nameof(Currency)], OutputTarget = OutputTarget.Options)]
public async Task OnCountryChange() {}- In our example there is only one method with this
TriggerControl:OnCountryChange(). The UI will send all inputs values forInputControls = [nameof(Country)]to be used at runtime. OutputControls = [nameof(CountryLocalTime)]will have their direct value populated whileOutputControls = [nameof(Currency)]has its options targeted, which means behind the scenes at runtime theCurrencyListwill be populated and its value returned for the dropdown to display the options visually.- Runtime execution example:
public async Task Execute()
{
Validations.ValidateCountry(Country);
Validations.ValidateCurrency(Currency);
CurrencyInfo = await Commons.BuildCurrencyInfo(Country, Currency);
}CurrencyInfowill be populated at runtime based on what the user selects from dropdowns forCountryandCurrencyin our example.- When are events useful? If you want
CountryandCurrencyto have their dropdowns populated dynamically via a public API or external source like a google drive excel, otherwise consider the no-events variation: OmitControlEventHandlermethods, predefine hardcoded option lists in code and just put all computation inExecute()only. Example:
private IList<OptionModel> CurrencyList { get; set; } = new List<OptionModel>
{
new OptionModel { name = "Euro", value = "EUR" },
new OptionModel { name = "United States dollar", value = "USD" },
new OptionModel { name = "Japanese yen", value = "JPY" },
new OptionModel { name = "Pound sterling", value = "GBP" }
};- The official documentation links above cover all decorators and platform specifics in depth. Checkout our Discord to discuss Custom Actions with the community: https://discord.com/invite/CEBuKgJefv
- The
net8.0/Events/GetCountryDatahas examples of events-focused actions and different ways to configure a Custom Action. The sample actions in this repo demonstrate different styles: all events, split-by-output, runtime-tail, no-events. - The
net8.0/Events/GetCountryData/Common/ActionsWithEvents.mdfile in this repo explains events-focused patterns and trade-offs based onGetCountryDataactions examples.