Enums
Learn how to define C# enums that are automatically generated as TypeScript enums for your Angular frontend.
Overview
Spiderly lets you define enums once in C# and automatically generates matching TypeScript enums for your Angular frontend. This keeps backend and frontend enum definitions in sync — no manual duplication or copy-paste errors.
How It Works
NgEnumsGenerator is a Roslyn source generator that runs at build time (when you build the .Business project). It scans for two kinds of types carrying the [SpiderlyEnum] marker:
enumdeclarations → generated as numeric TypeScript enums (explicit values preserved)static classdeclarations → generated as string TypeScript enums (property names become string values)
Types without the marker stay backend-only. This is the correct default — most enums exist to label database rows and never need to reach the frontend.
All output is consolidated into a single file:
Frontend\src\app\business\enums\enums.generated.tsEnums are sorted alphabetically by name in the generated file.
Do not edit enums.generated.ts — your changes will be overwritten on the next build. To add
custom TypeScript enums, create a separate file alongside the generated one.
See Architecture for the full list of generators, and spiderly.json Configuration for disabling the generator.
Defining an Enum
- Create a file in
Backend\{YourAppName}.Business\Enums\ - Use a block namespace (file-scoped syntax is not supported)
- Define a standard C# enum and decorate it with
[SpiderlyEnum]
C# input:
// File: Backend\YourAppName.Business\Enums\OrderStatusCodes.cs
using Spiderly.Shared.Attributes;
namespace YourAppName.Business.Enums
{
[SpiderlyEnum]
public enum OrderStatusCodes
{
PendingPayment = 1,
Pending = 2,
Processing = 3,
Shipped = 4,
Delivered = 5,
Cancelled = 6
}
}Generated TypeScript output:
// File: Frontend\src\app\business\enums\enums.generated.ts (auto-generated)
export enum OrderStatusCodes {
PendingPayment = 1,
Pending = 2,
Processing = 3,
Shipped = 4,
Delivered = 5,
Cancelled = 6,
}You must use block namespace syntax (namespace X.Y {'{'} ... {'}'}), not file-scoped
(namespace X.Y;). The source generator relies on the syntax tree structure of block
namespaces to read the enum.
Naming Convention
Spiderly's examples use the ...Codes suffix (OrderStatusCodes, PaymentMethodCodes, CartStatusCodes) to make it clear the type represents a fixed set of coded values. This is a style recommendation, not a technical requirement — the only thing that marks a type as a generator-aware enum is the [SpiderlyEnum] attribute.
Class-Based Enums
Static classes carrying [SpiderlyEnum] are emitted as string enums. This is used internally for PermissionCodes — a partial class where Spiderly auto-generates CRUD permission codes for every entity.
C# input:
// File: Backend\YourAppName.Business\Enums\PermissionCodes.cs
using Spiderly.Shared.Attributes;
namespace YourAppName.Business.Enums
{
[SpiderlyEnum]
public static partial class PermissionCodes
{
}
}The class itself is empty — Spiderly auto-generates Read/Update/Insert/Delete permission codes for every entity in your project. You can add custom permission codes by adding public static string properties to the partial class.
Generated TypeScript output:
// File: Frontend\src\app\business\enums\enums.generated.ts (auto-generated)
export enum PermissionCodes {
ReadBanner = "ReadBanner",
UpdateBanner = "UpdateBanner",
InsertBanner = "InsertBanner",
DeleteBanner = "DeleteBanner",
// ... Read/Update/Insert/Delete for every entity
}See Backend Customization for how to extend PermissionCodes with custom permissions.
Enum Properties on Entities
A [SpiderlyEnum]-decorated type can also be used directly as an entity property type — Spiderly recognizes it as a scalar value and generates a dropdown UI control instead of treating it as a foreign-key relationship.
C# input:
// File: Backend\YourAppName.Business\Enums\OrderStatusCodes.cs
using Spiderly.Shared.Attributes;
namespace YourAppName.Business.Enums
{
[SpiderlyEnum]
public enum OrderStatusCodes
{
Pending,
Paid,
Shipped,
Cancelled,
}
}// File: Backend\YourAppName.Business\Entities\Order.cs
[SpiderlyEntity]
public class Order : BusinessObject<long>
{
public OrderStatusCodes Status { get; set; }
}Generated behavior:
- The Angular form renders
Statusas a dropdown whose options are populated client-side from the generated TypeScript enum — no lookup table, no M2O navigation, and no API round-trip. Alongside each enum, the generator emits aget{Enum}NamebookList(translocoService)builder inenums.generated.ts, and the form binds the dropdown to it. IsManyToOneTypeshort-circuits for properties whose type is in the[SpiderlyEnum]registry, so the generators that would otherwise emit FK plumbing skip it for these properties.[SpiderlyEnum]is the single source of truth — the type does not need to follow the...Codesnaming convention to be recognized.- This applies to real
enumtypes only. A class-based enum (astatic classof string constants) can't be used as a property type, so it can't drive a dropdown — use anenumfor dropdown fields.
Use this pattern when the set of values is fixed at compile time and you don't need per-row metadata. For values that need labels, ordering, or admin-managed lifetimes, keep the lookup-table pattern with ReadonlyObject<T>.
Translating enum options
Option labels are localized through Transloco, with the translation key equal to the enum member name. The generated builder wraps each member in translate(marker('Member')) so transloco-keys-manager discovers the keys — run npm run i18n:extract, then fill each locale file:
// Frontend/src/assets/i18n/sr-Latn-RS.json
{ "Pending": "Na čekanju", "Paid": "Plaćeno", "Shipped": "Poslato", "Cancelled": "Otkazano" }A missing value renders the raw key, so don't skip the locale entries. If two enums share a member name (e.g. both have Pending) but need different translations, rename one member (e.g. PendingReview) — the key follows the member name, so no extra configuration is needed.
The same get{Enum}NamebookList(translocoService) builder also feeds a list-table column's multiselect filter — wrap it in the spiderly helper getPrimengNamebookOptions, which maps Namebook[] to the table's { label, code }[] shape:
{ name: t('Status'), filterType: 'multiselect', field: 'status',
dropdownOrMultiselectValues:
getPrimengNamebookOptions(getOrderStatusCodesNamebookList(this.translocoService)) }