원클릭으로
create-activity
// Generate complete, deployable UiPath activity packages. Detects SDK presence and produces all necessary files (activity, viewmodel, metadata, packaging, tests). Use when asked to create or scaffold UiPath activities.
// Generate complete, deployable UiPath activity packages. Detects SDK presence and produces all necessary files (activity, viewmodel, metadata, packaging, tests). Use when asked to create or scaffold UiPath activities.
Generate structured markdown documentation for UiPath XAML activities from source code. Produces per-activity docs with full properties (inputs, outputs, configuration), types, defaults, project settings, valid property combinations, and copy-paste XAML examples. Use when asked to document activities, generate activity docs, create activity reference files, build activity mini-skills, or prepare package documentation for any activity domain in this repository. Also use when the user mentions "activity reference", "activity cheat sheet", "activity docs", or wants to understand what an activity does as a black box. If the user also wants coded workflow API docs, use the `coded-api-doc-generator` skill for that separately.
Generate structured markdown documentation for UiPath coded workflow APIs from source code. Produces one doc per package covering service interfaces, methods, return types/outputs, handle types, options classes, and copy-paste C# examples. Use when asked to generate coded workflow API docs, document coded workflow services, or create API references for coded C# workflows. Also use when the user mentions "coded API docs", "service interface docs", or wants to understand what methods are available for coded workflows. The XAML activity docs are handled by the separate `activity-doc-generator` skill.
| name | create-activity |
| description | Generate complete, deployable UiPath activity packages. Detects SDK presence and produces all necessary files (activity, viewmodel, metadata, packaging, tests). Use when asked to create or scaffold UiPath activities. |
| allowed-tools | Bash, Glob, Grep, Read, Write, Edit, AskUserQuestion |
This skill file enables Claude Code to generate complete, deployable UiPath activity packages. For detailed reference on widgets, data sources, rules, bindings, advanced patterns, and testing, see the companion guide:
activity-development-guide/index.md.
Use this skill when asked to:
Before generating any file, determine {PackageName} and {Namespace}:
Step 1 — Infer from the prompt.
If the user's request contains a dotted name (e.g. "AlexPetre.ConvertXml"), extract
the full dotted name as {PackageName} and use it as {Namespace} too.
| User writes | {PackageName} | {Namespace} |
|---|---|---|
| "AlexPetre.ConvertXml activity" | AlexPetre.ConvertXml | AlexPetre.ConvertXml |
| "create Acme.Utils.Csv activity" | Acme.Utils.Csv | Acme.Utils.Csv |
| "ConvertXml activity" (no dots) | ask — see Step 2 | ask — see Step 2 |
Step 2 — Ask if no dotted name is found. If the prompt contains no dotted name, stop and ask:
What should the package be called? (e.g. YourName.ConvertXml)
Use the user's full answer as {PackageName} and {Namespace}.
Step 3 — Normalize to PascalCase. If the name is not already PascalCase, convert it: capitalize the first letter of each word and remove spaces/hyphens.
Rule: {PackageName} MUST NOT start with UiPath. Never generate a package whose name begins with UiPath.
Before generating any files, check if the UiPath Activities SDK is present in the repository:
Glob for **/UiPath.Sdk.Activities/*.cs or **/UiPath.Sdk.Activities.projitems
When creating a new activity package from scratch, generate ALL of these files in order:
| # | File | Purpose |
|---|---|---|
| 1 | nuget.config | NuGet feed configuration |
| 2 | {PackageName}/{PackageName}.csproj | Main library project |
| 3 | {PackageName}/Activities/{ActivityName}.cs | Runtime activity class |
| 4 | {PackageName}/ViewModels/{ActivityName}ViewModel.cs | Design-time ViewModel |
| 5 | {PackageName}/Helpers/ActivityContextExtensions.cs | Runtime logging helper |
| 6 | {PackageName}/Resources/ActivitiesMetadata.json | Activity discovery metadata |
| 6b | {PackageName}/Resources/Icons/activityicon.svg | Default activity icon (SVG 24x24) |
| 7 | {PackageName}/Resources/Resources.resx | Localized display strings |
| 7b | {PackageName}/Resources/Resources.Designer.cs | Strongly-typed resource accessor |
| 8 | {PackageName}.Packaging/{PackageName}.Packaging.csproj | NuGet packaging project |
| 9 | {PackageName}.Tests/{PackageName}.Tests.csproj | Test project |
| 10 | {PackageName}.Tests/Unit/{ActivityName}UnitTests.cs | Unit tests |
| 11 | {PackageName}.Tests/Workflow/{ActivityName}WorkflowTests.cs | Workflow integration tests |
Then run these commands to create the solution and build:
dotnet new sln -n {PackageName}
dotnet sln add {PackageName}/{PackageName}.csproj
dotnet sln add {PackageName}.Packaging/{PackageName}.Packaging.csproj
dotnet sln add {PackageName}.Tests/{PackageName}.Tests.csproj
dotnet build -c Release
dotnet test
When adding an activity to an existing package, generate only files 3, 4, 10, 11 and update files 6, 7, and 7b.
When the SDK is detected, the checklist changes. Differences from classic mode are marked with [SDK].
| # | File | Purpose |
|---|---|---|
| 1 | nuget.config | NuGet feed configuration (same as classic) |
| 2 | {PackageName}/{PackageName}.csproj | Main library project [SDK] — extra properties & packages |
| 3 | {PackageName}/Activities/{ActivityName}.cs | Runtime activity class [SDK] — partial, inherits SdkActivity<T> |
| 3b | {PackageName}/ViewModels/{ActivityName}.Design.cs | [SDK NEW] — partial class with [ViewModelClass] attribute |
| 4 | {PackageName}/ViewModels/{ActivityName}ViewModel.cs | Design-time ViewModel [SDK] — inherits BaseViewModel |
| -- | ActivityContextExtensions.cs | [SDK] — NOT needed (services via RuntimeServices) |
| 5 | {PackageName}/Directory.build.targets | [SDK NEW] — imports SDK build targets |
| 6 | {PackageName}/Resources/ActivitiesMetadata.json | Activity discovery metadata (same as classic) |
| 6b | {PackageName}/Resources/Icons/activityicon.svg | Default activity icon (same as classic) |
| 7 | {PackageName}/Resources/Resources.resx | Localized display strings (same as classic) |
| 7b | {PackageName}/Resources/Resources.Designer.cs | Strongly-typed resource accessor (same as classic) |
| 8 | {PackageName}.Packaging/{PackageName}.Packaging.csproj | NuGet packaging project (same as classic) |
| 9 | {PackageName}.Tests/{PackageName}.Tests.csproj | Test project [SDK] — extra properties |
| 10 | {PackageName}.Tests/Unit/{ActivityName}UnitTests.cs | Unit tests [SDK] — DI-based testing |
| 11 | {PackageName}.Tests/Workflow/{ActivityName}WorkflowTests.cs | Workflow integration tests (same as classic) |
When adding an activity to an existing SDK package, generate files 3, 3b, 4, 10, 11 and update files 6, 7, and 7b.
Resources.Designer.cs — generate and commit for CLI builds: PublicResXFileCodeGenerator only runs inside Visual Studio. For dotnet build and CI builds, Resources.Designer.cs must be generated and committed to source (see Template 7b). Visual Studio will regenerate it automatically when the project is opened; that is expected and harmless.
For more details, see core/project-structure.md.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="UiPath Official" value="https://uipath.pkgs.visualstudio.com/Public.Feeds/_packaging/UiPath-Official/nuget/v3/index.json" />
</packageSources>
</configuration>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Library gets distinct PackageId to avoid "Ambiguous project name" with Packaging project -->
<PackageId>{PackageName}.Library</PackageId>
<IsPackable>false</IsPackable>
<!-- Explicit RootNamespace ensures embedded resource manifest name is {PackageName}.Resources.Resources -->
<RootNamespace>{PackageName}</RootNamespace>
<NoWarn>$(NoWarn);NU5104</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\ActivitiesMetadata.json" />
<EmbeddedResource Include="Resources\ActivitiesMetadata.json" />
<EmbeddedResource Include="Resources\Icons\*.svg" />
</ItemGroup>
<ItemGroup>
<!-- Pin to stable release (1.YYYYMMDD.patch). Never use 1.0.0-* — resolves to alpha missing key APIs. -->
<PackageReference Include="System.Activities.ViewModels" Version="1.20251103.2" PrivateAssets="All" />
<PackageReference Include="UiPath.Activities.Api" Version="24.10.1" PrivateAssets="All" />
<PackageReference Include="UiPath.Workflow" Version="6.0.0-*" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<CustomToolNamespace>{Namespace}</CustomToolNamespace>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
<CustomToolNamespace>{Namespace}</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
</Project>
// Activities/{ActivityName}.cs
using System.Activities;
using System.ComponentModel;
using System.Diagnostics;
using {Namespace}.Helpers;
using UiPath.Robot.Activities.Api;
namespace {Namespace};
public class {ActivityName} : CodeActivity{<ResultType> if applicable}
{
[RequiredArgument]
public InArgument<T>? {InputProp} { get; set; }
public OutArgument<T>? {OutputProp} { get; set; }
// Enum selector: plain TEnum, NOT InArgument<TEnum> — see activity-code.md
public TEnum {EnumProp} { get; set; } = TEnum.DefaultValue;
protected override {ReturnType} Execute(CodeActivityContext context)
{
context.GetExecutorRuntime().LogMessage(new LogMessage
{
EventType = TraceEventType.Information,
Message = "Executing {ActivityName} activity"
});
var input = {InputProp}.Get(context);
var result = ExecuteInternal(input);
{OutputProp}?.Set(context, result);
return result; // if CodeActivity<T>
}
// ExecuteInternal reads enum property directly — not via parameter or private field.
// See activity-development-guide/runtime/activity-code.md for the ExecuteInternal pattern.
public {ReturnType} ExecuteInternal({params})
{
return {EnumProp} switch
{
// Business logic here
};
}
}
Classic mode base class selection (for SDK mode, see SDK Mode Overrides section):
| Base Class | When to Use |
|---|---|
CodeActivity<T> | Simple synchronous activities returning a value |
CodeActivity | Simple synchronous activities with no return value |
AsyncCodeActivity<T> | Async activities (I/O, network calls) |
Property type mapping:
| C# Type | Purpose |
|---|---|
InArgument<T>? | Input — accepts expressions/variables |
OutArgument<T>? | Output — writes to variables |
InOutArgument<T>? | Bidirectional — read and modify |
TEnum (plain type) | Enum selector — maps to DesignProperty<TEnum> in ViewModel |
T (direct type) | Other constants (bool, int, etc.) |
For details on enum selectors, the ExecuteInternal pattern, and GetExecutorRuntime, see runtime/activity-code.md.
Property names MUST exactly match the Activity class property names.
// ViewModels/{ActivityName}ViewModel.cs
using System.Activities.DesignViewModels;
namespace {Namespace}.ViewModels;
public class {ActivityName}ViewModel : DesignPropertiesViewModel
{
// Nullable without initializer — framework populates before InitializeModel
public DesignInArgument<T>? {InputProp} { get; set; }
public DesignOutArgument<T>? {OutputProp} { get; set; }
public DesignProperty<TEnum>? {EnumProp} { get; set; }
public {ActivityName}ViewModel(IDesignServices services) : base(services) { }
protected override void InitializeModel()
{
base.InitializeModel();
PersistValuesChangedDuringInit();
var order = 0;
// Use ! on every nullable property access — framework guarantees non-null here
{InputProp}!.DisplayName = Resources.{ActivityName}_{InputProp}_DisplayName;
{InputProp}!.Tooltip = Resources.{ActivityName}_{InputProp}_Tooltip;
{InputProp}!.IsRequired = true;
{InputProp}!.IsPrincipal = true;
{InputProp}!.OrderIndex = order++;
// Enum properties — runtime auto-renders enums as dropdowns
{EnumProp}!.DisplayName = Resources.{ActivityName}_{EnumProp}_DisplayName;
{EnumProp}!.IsPrincipal = true;
{EnumProp}!.OrderIndex = order++;
// Output properties — not principal, at the end
{OutputProp}!.DisplayName = Resources.{ActivityName}_{OutputProp}_DisplayName;
{OutputProp}!.Tooltip = Resources.{ActivityName}_{OutputProp}_Tooltip;
{OutputProp}!.OrderIndex = order++;
}
}
ViewModel property type mapping:
| ViewModel Type | Maps to Activity Type | .Widget supported |
|---|---|---|
DesignInArgument<T> | InArgument<T> | No |
DesignOutArgument<T> | OutArgument<T> | No |
DesignInOutArgument<T> | InOutArgument<T> | No |
DesignProperty<T> | Direct T property | Yes |
Common widget assignments (set in InitializeModel()):
// Toggle for booleans (only on DesignProperty, not DesignInArgument)
BoolProp!.Widget = new DefaultWidget { Type = ViewModelWidgetType.Toggle };
// Multi-line text
TextProp!.Widget = new DefaultWidget
{
Type = ViewModelWidgetType.TextComposer,
Metadata = new() { { TextComposerMetadata.IsSingleLineFormat, true.ToString() } }
};
// Autocomplete dropdown (searchable, allows expressions)
SearchProp!.Widget = new DefaultWidget { Type = ViewModelWidgetType.AutoCompleteForExpression };
// Plain number with constraints
NumProp!.Widget = new DefaultWidget
{
Type = ViewModelWidgetType.PlainNumber,
Metadata = new() { [PlainNumber.Min] = "0", [PlainNumber.Max] = "100", [PlainNumber.Step] = "1" }
};
For nullable properties, the ! operator, rules, dependencies, data sources, menu actions, validation, and advanced patterns, see:
// Helpers/ActivityContextExtensions.cs
using System.Activities;
using UiPath.Robot.Activities.Api;
namespace {Namespace}.Helpers;
public static class ActivityContextExtensions
{
public static IExecutorRuntime GetExecutorRuntime(this ActivityContext context)
=> context.GetExtension<IExecutorRuntime>();
}
{
"resourceManagerName": "{PackageName}.Resources.Resources",
"activities": [
{
"fullName": "{Namespace}.{ActivityName}",
"shortName": "{ActivityName}",
"displayNameKey": "{ActivityName}_DisplayName",
"descriptionKey": "{ActivityName}_Description",
"categoryKey": "{Category}",
"iconKey": "activityicon.svg",
"viewModelType": "{Namespace}.ViewModels.{ActivityName}ViewModel"
}
]
}
When adding multiple activities, add entries to the activities array.
Default activity icon. Place at {PackageName}/Resources/Icons/activityicon.svg.
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.00671 19V14H7.00671V19H18V15.5H16L19 12.5L22 15.5H20V19C20 20.1 19.1 21 18 21H10.5H8.00671H7.00671C5.90214 21 5.00671 20.1046 5.00671 19Z" fill="#556068"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.11702 2.87616L12 5.46482V9.67703L5.86054 12.1328L2 9.3446V5.32297L8.11702 2.87616ZM4 6.67703V8.32198L6.13946 9.86718L10 8.32297V6.53518L7.88298 5.12384L4 6.67703Z" fill="#556068"/>
<path d="M16 7.5C16 9.15685 17.3431 10.5 19 10.5C20.6569 10.5 22 9.15685 22 7.5C22 5.84315 20.6569 4.5 19 4.5C17.3431 4.5 16 5.84315 16 7.5Z" fill="#556068"/>
</svg>
All activities in the package share this icon. For per-activity icons, add SVG files to Resources/Icons/ and update iconKey in ActivitiesMetadata.json — the glob <EmbeddedResource Include="Resources\Icons\*.svg" /> picks them up automatically.
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="0" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!-- Activity display name and description -->
<data name="{ActivityName}_DisplayName" xml:space="preserve">
<value>{Activity Display Name}</value>
</data>
<data name="{ActivityName}_Description" xml:space="preserve">
<value>{Activity description text}</value>
</data>
<!-- Property display names and tooltips: {ActivityName}_{PropertyName}_DisplayName / _Tooltip -->
<data name="{ActivityName}_{PropertyName}_DisplayName" xml:space="preserve">
<value>{Property Display Name}</value>
</data>
<data name="{ActivityName}_{PropertyName}_Tooltip" xml:space="preserve">
<value>{Property tooltip/help text}</value>
</data>
</root>
Naming convention:
{ActivityName}_DisplayName — Activity display name{ActivityName}_Description — Activity description{ActivityName}_{PropertyName}_DisplayName — Property display name{ActivityName}_{PropertyName}_Tooltip — Property tooltipGenerate this file manually and commit it. Add one public static string property per key defined in Resources.resx.
// Resources/Resources.Designer.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace {Namespace} {
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
internal Resources() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("{PackageName}.Resources.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string {ActivityName}_DisplayName {
get {
return ResourceManager.GetString("{ActivityName}_DisplayName", resourceCulture);
}
}
public static string {ActivityName}_Description {
get {
return ResourceManager.GetString("{ActivityName}_Description", resourceCulture);
}
}
public static string {ActivityName}_{PropertyName}_DisplayName {
get {
return ResourceManager.GetString("{ActivityName}_{PropertyName}_DisplayName", resourceCulture);
}
}
public static string {ActivityName}_{PropertyName}_Tooltip {
get {
return ResourceManager.GetString("{ActivityName}_{PropertyName}_Tooltip", resourceCulture);
}
}
}
}
The ResourceManager string "{PackageName}.Resources.Resources" must match the embedded resource manifest name. This is ensured by setting <RootNamespace>{PackageName}</RootNamespace> in the .csproj.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<VersionBuild>$([System.DateTime]::UtcNow.DayOfYear.ToString("F0"))</VersionBuild>
<VersionRevision>$([System.DateTime]::UtcNow.TimeOfDay.TotalMinutes.ToString("F0"))</VersionRevision>
<VersionPrefix Condition="'$(Configuration)' == 'Release'">1.0.0</VersionPrefix>
<VersionPrefix Condition="'$(Configuration)' == 'Debug'">1.0.$(VersionBuild)-dev.$(VersionRevision)</VersionPrefix>
<PackageId>{PackageName}</PackageId>
<Authors>{AuthorName}</Authors>
<Description>{Package description}</Description>
<PackageTags>UiPathActivities</PackageTags>
<PackageOutputPath>..\Output\Packages\</PackageOutputPath>
<TargetsForTfmSpecificBuildOutput>AddDlls</TargetsForTfmSpecificBuildOutput>
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
</PropertyGroup>
<Target Name="AddDlls">
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<BuildOutputInPackage Include="$(OutputPath){PackageName}.pdb" />
</ItemGroup>
<ItemGroup>
<BuildOutputInPackage Include="$(OutputPath){PackageName}.dll" />
</ItemGroup>
</Target>
<Target Name="RemoveMetaDll" AfterTargets="BuiltProjectOutputGroup">
<ItemGroup>
<BuiltProjectOutputGroupOutput Remove="@(BuiltProjectOutputGroupOutput)" />
</ItemGroup>
</Target>
<Target Name="CleanPackageFiles" BeforeTargets="Build">
<ItemGroup>
<PackageFilesToDelete Include="$(PackageOutputPath)\$(PackageId)*.nupkg" />
</ItemGroup>
<Delete Files="@(PackageFilesToDelete)" ContinueOnError="WarnAndContinue" />
</Target>
<ItemGroup>
<ProjectReference Include="..\{PackageName}\{PackageName}.csproj">
<PrivateAssets>All</PrivateAssets>
</ProjectReference>
</ItemGroup>
</Project>
Critical: PackageTags must contain UiPathActivities for Studio to discover the package.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="UiPath.Activities.Api" Version="24.10.1" DevelopmentDependency="true" />
<PackageReference Include="UiPath.Workflow" Version="6.0.0-*" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\{PackageName}\{PackageName}.csproj" />
</ItemGroup>
</Project>
// Tests/Unit/{ActivityName}UnitTests.cs
using Xunit;
namespace {Namespace}.Tests.Unit;
public class {ActivityName}UnitTests
{
[Fact]
public void ExecuteInternal_ValidInput_ReturnsExpected()
{
var activity = new {ActivityName}();
var result = activity.ExecuteInternal(/* args */);
Assert.Equal(expected, result);
}
[Fact]
public void ExecuteInternal_InvalidInput_Throws()
{
var activity = new {ActivityName}();
Assert.Throws<ExpectedException>(() => activity.ExecuteInternal(/* bad args */));
}
}
// Tests/Workflow/{ActivityName}WorkflowTests.cs
using System.Activities;
using Moq;
using UiPath.Robot.Activities.Api;
using Xunit;
namespace {Namespace}.Tests.Workflow;
public class {ActivityName}WorkflowTests
{
private readonly Mock<IExecutorRuntime> _runtimeMock = new();
[Fact]
public void Execute_ValidInputs_ReturnsExpected()
{
// Use bare OutArgument — do NOT use Variable<T> (requires enclosing scope).
// See activity-development-guide/testing/activity-testing.md
var activity = new {ActivityName}
{
{InputProp} = new InArgument<T>(/* value */),
{OutputProp} = new OutArgument<T>()
};
var runner = new WorkflowInvoker(activity);
runner.Extensions.Add(() => _runtimeMock.Object);
var result = runner.Invoke(TimeSpan.FromSeconds(5));
Assert.True(result.ContainsKey("{OutputProp}"));
Assert.NotNull(result["{OutputProp}"]);
}
}
When in SDK mode, use these templates instead of the corresponding classic templates above. For files not listed here (nuget.config, ActivitiesMetadata.json, Resources.resx, Resources.Designer.cs, Packaging .csproj, activityicon.svg), use the classic templates unchanged.
The SDK compiles into your project via shared projects (.shproj/.projitems). Package versions are centrally managed by the SDK's Sdk.dependencies.build.targets — do NOT specify versions for SDK-managed packages.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>{PackageName}.Library</PackageId>
<IsPackable>false</IsPackable>
<RootNamespace>{PackageName}</RootNamespace>
<UiPathActivityProject>True</UiPathActivityProject>
<UiPathActivityDesignProject>True</UiPathActivityDesignProject>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\ActivitiesMetadata.json" />
<EmbeddedResource Include="Resources\ActivitiesMetadata.json" />
<EmbeddedResource Include="Resources\Icons\*.svg" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="UiPath.Activities.Api" />
<PackageReference Include="UiPath.Activities.Contracts" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="UiPath.Telemetry.Client" />
<PackageReference Condition=" '$(IsNetCoreUiPath)' == 'true' " Include="System.Activities.ViewModels" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<CustomToolNamespace>{Namespace}</CustomToolNamespace>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
<CustomToolNamespace>{Namespace}</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
</Project>
UiPathActivityProject=True -> imports UiPath.Sdk.Activities.shproj (runtime SDK code)UiPathActivityDesignProject=True -> imports UiPath.Sdk.Activities.Design.shproj (design-time SDK code)<UseIntegrationService>True</UseIntegrationService> for connection/Integration Service support<UseGovernanceService>True</UseGovernanceService> for governance supportThe activity is a partial class. The other partial contains the [ViewModelClass] attribute (see Template 3b).
// Activities/{ActivityName}.cs
using System;
using System.Activities;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using UiPath.Robot.Activities.Api;
using UiPath.Sdk.Activities;
namespace {Namespace};
public partial class {ActivityName} : SdkActivity<{ResultType}>
{
[RequiredArgument]
public InArgument<T>? {InputProp} { get; set; }
public {ActivityName}() : base() { }
/// <summary>
/// Constructor for unit testing — accepts a pre-configured service provider.
/// </summary>
internal {ActivityName}(IServiceProvider provider) : base(provider) { }
protected override async Task<{ResultType}> ExecuteAsync(
AsyncCodeActivityContext context,
IServiceProvider serviceProvider,
CancellationToken cancellationToken)
{
// Read ALL inputs before any await — context is disposed after first await
var input = {InputProp}.Get(context);
RuntimeServices.ExecutorRuntime?.LogMessage(new LogMessage
{
EventType = TraceEventType.Information,
Message = $"Executing {ActivityName} with input: {input}"
});
var result = await DoWorkAsync(input, cancellationToken);
return result;
}
protected override void OnCompleted(AsyncCodeActivityContext context, IServiceProvider serviceProvider)
{
base.OnCompleted(context, serviceProvider);
// Runs after ExecuteAsync completes, with a fresh valid context.
}
}
SDK base class selection:
| Base Class | When to Use |
|---|---|
SdkActivity<T> | Async activities returning a value (most common) |
SdkNativeActivity<T> | Activities needing retry, bookmarks, or child activity scheduling |
CodeActivity<T> | Simple synchronous activities (SDK not needed) |
CodeActivity | Simple synchronous with no return value (SDK not needed) |
Custom service registration — override DefaultRuntimeServicePolicy to register your own services:
public class MyServicePolicy : DefaultRuntimeServicePolicy
{
public override IServicePolicy Register(Action<IServiceCollection> collection = null)
{
_services.TryAddSingleton<IMyService, MyService>();
return base.Register(collection);
}
}
// Use the custom policy as the second type parameter
public partial class {ActivityName} : SdkActivity<{ResultType}, MyServicePolicy>
This file links the activity to its ViewModel via the [ViewModelClass] attribute. It is a partial of the activity class.
// ViewModels/{ActivityName}.Design.cs
using System.Activities.DesignViewModels;
using {Namespace}.ViewModels;
namespace {Namespace};
[ViewModelClass(typeof({ActivityName}ViewModel))]
public partial class {ActivityName}
{
}
Inherits from the SDK's BaseViewModel instead of DesignPropertiesViewModel. Uses the built-in PropertyOrderIndex counter.
// ViewModels/{ActivityName}ViewModel.cs
using System;
using System.Activities.DesignViewModels;
using System.Activities.ViewModels;
using UiPath.Sdk.Activities.Design.ViewModels;
namespace {Namespace}.ViewModels;
internal class {ActivityName}ViewModel : BaseViewModel
{
public DesignInArgument<T>? {InputProp} { get; set; }
public DesignOutArgument<{ResultType}>? Result { get; set; }
public DesignProperty<TEnum>? {EnumProp} { get; set; }
public {ActivityName}ViewModel(IDesignServices services) : base(services) { }
/// <summary>
/// Constructor for unit testing — accepts a pre-configured service provider.
/// </summary>
internal {ActivityName}ViewModel(IDesignServices services, IServiceProvider activityServices)
: base(services, activityServices) { }
protected override void InitializeModel()
{
base.InitializeModel();
{InputProp}!.IsPrincipal = true;
{InputProp}!.IsRequired = true;
{InputProp}!.DisplayName = Resources.{ActivityName}_{InputProp}_DisplayName;
{InputProp}!.Tooltip = Resources.{ActivityName}_{InputProp}_Tooltip;
{InputProp}!.OrderIndex = PropertyOrderIndex++;
Result!.DisplayName = Resources.{ActivityName}_Result_DisplayName;
Result!.OrderIndex = PropertyOrderIndex++;
}
}
Key differences from classic ViewModel:
| Classic | SDK |
|---|---|
DesignPropertiesViewModel base | BaseViewModel base (or BaseViewModel<TPolicy>) |
var order = 0; prop.OrderIndex = order++; | prop.OrderIndex = PropertyOrderIndex++; (built-in) |
public class | internal class (linked via [ViewModelClass] attribute) |
Constructor: (IDesignServices) only | Constructor: (IDesignServices) + testable (IDesignServices, IServiceProvider) |
| No DI in ViewModel | ActivityServices.GetService<T>() available for design-time DI |
Place this file in the activity pack directory (parent of the .csproj). Adjust the relative path to point to the SDK location.
<Project>
<Import Project="..\Activities.SDK\Sdk.dependencies.build.targets"/>
<Import Project="..\Activities.SDK\Sdk.imports.build.targets"/>
</Project>
Add these properties to the test .csproj to import SDK test infrastructure:
<PropertyGroup>
<UiPathActivityTests>true</UiPathActivityTests>
<UiPathActivityDesignTests>true</UiPathActivityDesignTests>
</PropertyGroup>
SDK activities accept an IServiceProvider in their constructor, enabling clean DI-based testing:
// Tests/Unit/{ActivityName}UnitTests.cs
using System;
using System.Activities;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using UiPath.Robot.Activities.Api;
using Xunit;
namespace {Namespace}.Tests.Unit;
public class {ActivityName}UnitTests
{
private readonly Mock<IExecutorRuntime> _runtimeMock = new();
private IServiceProvider BuildServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton(_runtimeMock.Object);
return services.BuildServiceProvider();
}
[Fact]
public async Task ExecuteAsync_ValidInput_ReturnsExpected()
{
var provider = BuildServiceProvider();
var activity = new {ActivityName}(provider)
{
{InputProp} = new InArgument<T>(/* value */)
};
var runner = new WorkflowInvoker(activity);
var result = runner.Invoke(TimeSpan.FromSeconds(5));
Assert.NotNull(result["Result"]);
}
}
IsPrincipal = true for the 2-4 most important properties (shown in non-collapsible main panel).IsRequired = true for mandatory properties.OrderIndex with an incrementing counter to control display order.Resources.resx — never hardcode display names.PackageTags must contain UiPathActivities for Studio discovery.resourceManagerName in metadata JSON must be {PackageName}.Resources.Resources — set <RootNamespace>{PackageName}</RootNamespace> in the .csproj to guarantee this.Resources.Designer.cs — PublicResXFileCodeGenerator only runs in Visual Studio; CLI builds need the committed file.await — the ActivityContext is disposed after the first await. See core/activity-context.md..xaml workflows serialize by name; renaming/removing breaks deserialization. Deprecate with [Obsolete] + [Browsable(false)] instead.TEnum — not InArgument<TEnum>. Maps to DesignProperty<TEnum> in the ViewModel.System.Activities.DesignViewModels — NOT System.Activities.ViewModels.DesignInArgument<T>?) — use ! on every access in InitializeModel().base.InitializeModel() then PersistValuesChangedDuringInit() at the start of InitializeModel().ExecuteInternal() for testability. See runtime/activity-code.md.partial — one file for runtime logic, one for [ViewModelClass] attribute.internal — linked to activity via [ViewModelClass(typeof(...))], not via metadata JSON viewModelType.RuntimeServices.ExecutorRuntime for logging — no helper extension needed.PropertyOrderIndex++ — built into BaseViewModel, no manual counter variable needed.IServiceProvider in an internal constructor.For these features, consult the corresponding guide file in activity-development-guide/:
| Feature | Guide File |
|---|---|
| UiPath platform (Studio, Robot, Orchestrator) | core/architecture.md |
| ActivityContext lifetime, async patterns | core/activity-context.md |
Project structure, .csproj setup, troubleshooting | core/project-structure.md |
| Best practices | core/best-practices.md |
| Activity code patterns, enum selectors, ExecuteInternal | runtime/activity-code.md |
| Activities API (runtime/design-time services) | runtime/platform-api.md |
| Orchestrator integration and version checks | runtime/orchestrator.md |
| ViewModel patterns, nullable props, InitializeModel | design/viewmodel.md |
| Widget types and configuration | design/widgets/index.md |
| DataSource patterns (static, dynamic, enum, multi-select) | design/datasources.md |
| Rules and reactive dependencies | design/rules-and-dependencies.md |
| Menu actions (buttons, mode switching) | design/menu-actions.md |
| Validation (property-level, model-level, preview) | design/validation.md |
| Metadata schema (full field reference) | design/metadata.md |
| Orchestrator bindings (ActivitiesBindings.json) | design/bindings.md |
| Project settings (ArgumentSettingAttribute) | design/project-settings.md |
| Solutions vs Project scope (SolutionResourcesWidget) | design/solutions.md |
| Activity testing (unit + workflow) | testing/activity-testing.md |
| ViewModel testing approaches | testing/viewmodel-testing.md |
| Advanced patterns (bidirectional mapping, NativeActivity, bookmarks) | advanced/patterns.md |
| Activities SDK (DI, telemetry, retry, service policies) | advanced/sdk-framework.md |