diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 61cf14b67c..d3f150be7a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -785,6 +785,7 @@ "Enum:UiFramework:5": "Blazor Server", "Enum:UiFramework:6": "Blazor WebApp", "Enum:UiFramework:7": "Blazor MAUI", + "Enum:UiFramework:8": "React", "Enum:DatabaseProvider:0": "Unknown", "Enum:DatabaseProvider:1": "None", "Enum:DatabaseProvider:2": "EfCore", diff --git a/common.props b/common.props index 1b4476b499..8e25ef7520 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 10.5.0-rc.3 - 5.5.0-rc.3 + 10.6.0-preview + 5.6.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/Post.md b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/Post.md new file mode 100644 index 0000000000..8dcde9d11a --- /dev/null +++ b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/Post.md @@ -0,0 +1,581 @@ +Multi-tenancy sounds simple at first: one application, many customers. In practice, it changes how you design data access, authentication, configuration, monitoring, migrations, and even support workflows. + +If you get it right, you can serve many organizations efficiently from one platform. If you get it wrong, you risk the worst kind of bug in SaaS: one tenant seeing another tenant's data. + +This article explains how to implement multi-tenancy in a practical way, starting from the core patterns and ending with an ABP-based implementation approach. The goal is not just to define multi-tenancy, but to help you choose the right model and avoid the mistakes that usually show up after launch. + +## What multi-tenancy actually means + +A tenant is usually a customer organization, company, school, or business unit using your application. Multi-tenancy means multiple tenants use the same application platform while remaining isolated from each other. + +That isolation can include: + +- Data isolation +- Authentication and authorization boundaries +- Feature differences per tenant +- Performance protection +- Audit and compliance boundaries + +It is useful to separate two ideas that are often mixed up: + +- **Multi-tenant application**: one application instance serves many tenants +- **Multi-instance deployment**: each customer gets a separate deployment + +Multi-instance is simpler from an isolation perspective, but more expensive to operate. Multi-tenancy is usually more efficient, but it requires stronger architectural discipline. + +## Choose your data isolation model first + +Most multi-tenancy decisions become easier once you choose the data isolation model. There are three common patterns. + +### 1. Shared database, shared schema + +This is the most common starting point. All tenants use the same tables, and each tenant-owned row includes a `TenantId`. + +Example: + +```sql +Orders +- Id +- TenantId +- CustomerId +- TotalAmount +- CreationTime +``` + +Every query must be tenant-aware. + +**Pros** + +- Lowest infrastructure cost +- Simplest provisioning +- One migration path +- Fastest way to launch + +**Cons** + +- Highest risk of accidental data leakage if tenant filtering is missed +- Noisy neighbor problems are more likely +- Harder to isolate performance-heavy tenants +- Compliance requirements may rule it out + +**Best for** + +- MVPs +- Early-stage SaaS products +- Products with many small tenants +- Teams that want operational simplicity first + +### 2. Shared database, separate schema per tenant + +In this model, tenants share the same database server, but each tenant has its own schema. + +For example: + +- `tenant_a.Orders` +- `tenant_b.Orders` + +**Pros** + +- Better separation than shared schema +- Easier tenant-specific backup and restore +- Lower leakage risk than row-level isolation alone + +**Cons** + +- Migrations become harder +- Schema drift becomes a real risk +- Managing hundreds of schemas gets messy +- Connection and routing logic becomes more complex + +**Best for** + +- Moderate compliance needs +- Tens to a few hundreds of tenants +- Teams that need more isolation without going full database-per-tenant + +### 3. Database per tenant + +Each tenant gets its own database, and sometimes even its own server. + +**Pros** + +- Strongest isolation +- Easier compliance story +- Easier tenant-specific backup, restore, and residency rules +- Better performance isolation +- Easier to support premium tenants with custom SLAs + +**Cons** + +- Higher cost +- More provisioning automation required +- More complex migrations and monitoring +- Operational overhead grows quickly + +**Best for** + +- Enterprise SaaS +- Regulated industries +- High-value tenants +- Cases where data residency or strict isolation matters + +## How to choose the right pattern + +There is no universally correct model. The right choice depends on trade-offs. + +Use these questions to decide: + +- How many tenants do you expect in 6 to 24 months? +- Do you have compliance requirements like HIPAA, GDPR residency constraints, or strict audit demands? +- How much tenant customization do you need? +- Can your team operate many databases reliably? +- What happens if one tenant runs expensive reports all day? +- Do you need tenant-specific backup and restore? + +A practical rule: + +- Start with **shared schema** if speed and cost matter most +- Use **database per tenant** if isolation and compliance matter most +- Use a **hybrid model** if your tenant base is mixed + +A hybrid model is common in real SaaS systems: + +- Small tenants live in shared infrastructure +- Large enterprise tenants get dedicated databases + +That gives you a better cost curve without forcing every customer into the most expensive setup. + +## The core building blocks of a multi-tenant system + +Regardless of the storage model, most implementations need the same building blocks. + +### Tenant resolution + +Before your application can isolate anything, it must know which tenant the request belongs to. + +Common tenant resolution strategies: + +- Subdomain: `acme.yourapp.com` +- Custom domain: `portal.acme.com` +- Header: `X-Tenant-Id` +- JWT claim or token metadata +- URL segment: `/t/acme/orders` + +Subdomain-based resolution is usually the cleanest for SaaS products. Header-based resolution is common for internal APIs but easier to misuse if not protected. + +The important part is consistency. Resolve the tenant early in the request pipeline and make it available everywhere else. + +### Tenant context + +Once resolved, store the active tenant in a tenant context that application services, repositories, caches, and logs can access. + +Typical tenant context data includes: + +- Tenant ID +- Tenant name or slug +- Connection string or database mapping +- Enabled features +- Region or residency info + +### Data filtering + +If you use shared tables, tenant filtering must be automatic. Do not rely on developers remembering to add `where TenantId == currentTenantId` in every query. + +This is where frameworks matter. ABP helps a lot here because it has built-in multi-tenancy support and data filters for tenant-aware entities. + +### Tenant-aware configuration + +Sooner or later, tenants will ask for differences: + +- Feature A enabled only for premium plans +- Different email templates +- Different password policies +- Different integrations +- Different branding + +Do not solve this with `if (tenant == ...)` scattered across the codebase. + +Use: + +- Feature flags +- Tenant settings +- Edition or plan-based configuration +- Modular integration points + +### Tenant-aware logging and auditing + +Every log entry and audit event should include tenant context where possible. + +That makes it much easier to answer questions like: + +- Which tenant triggered this error? +- Which tenant is causing high database load? +- Who accessed this record? +- Which tenant had failed background jobs? + +## Implementing multi-tenancy in ASP.NET Core + +If you build multi-tenancy manually in ASP.NET Core, the implementation usually follows this flow: + +1. Resolve tenant from the request +2. Load tenant metadata from a tenant store +3. Set current tenant context +4. Route database access based on tenant +5. Apply tenant filters automatically +6. Include tenant info in logs, cache keys, and background jobs + +A minimal tenant resolver middleware might look like this: + +```csharp +public class TenantResolutionMiddleware +{ + private readonly RequestDelegate _next; + + public TenantResolutionMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context, ICurrentTenantAccessor tenantAccessor) + { + var host = context.Request.Host.Host; + var subdomain = host.Split('.').FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(subdomain) && subdomain != "www") + { + tenantAccessor.CurrentTenantId = await ResolveTenantIdAsync(subdomain); + } + + await _next(context); + } + + private Task ResolveTenantIdAsync(string subdomain) + { + return Task.FromResult(null); + } +} +``` + +That is only the beginning. The hard part is making the rest of the application consistently tenant-aware. + +For example, your repository layer must avoid this kind of bug: + +```csharp +var orders = await _dbContext.Orders + .Where(x => x.Status == OrderStatus.Pending) + .ToListAsync(); +``` + +In a shared-schema model, that query is dangerous because it ignores tenant isolation. + +Safer approaches include: + +- Global query filters +- Tenant-aware repositories +- Row-level security at the database level +- Framework-level multi-tenancy abstractions + +## Implementing multi-tenancy with ABP + +ABP is a strong fit for multi-tenant business applications because multi-tenancy is built into the framework rather than bolted on later. + +ABP gives you several useful pieces out of the box: + +- Tenant resolution pipeline +- `ICurrentTenant` abstraction +- Multi-tenant entity support via `IMultiTenant` +- Data filters for tenant isolation +- Tenant management module +- Per-tenant connection string support +- Feature and setting systems + +### Tenant-aware entities + +In ABP, tenant-owned entities typically implement `IMultiTenant`. + +```csharp +using System; +using Volo.Abp.MultiTenancy; + +public class Product : IMultiTenant +{ + public Guid Id { get; set; } + public Guid? TenantId { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } +} +``` + +This matters because ABP can automatically apply tenant filters for entities that belong to a tenant. + +### Accessing the current tenant + +ABP exposes the current tenant through `ICurrentTenant`. + +```csharp +public class ProductAppService : ApplicationService +{ + private readonly IRepository _productRepository; + private readonly ICurrentTenant _currentTenant; + + public ProductAppService( + IRepository productRepository, + ICurrentTenant currentTenant) + { + _productRepository = productRepository; + _currentTenant = currentTenant; + } + + public async Task> GetListAsync() + { + var products = await _productRepository.GetListAsync(); + return ObjectMapper.Map, List>(products); + } +} +``` + +In a tenant context, ABP automatically scopes the repository query to the current tenant for multi-tenant entities. + +That is exactly the kind of default you want in a multi-tenant system: safe behavior unless you intentionally opt out. + +### Switching tenant context + +Background jobs, admin tools, and migration workflows sometimes need to operate in a specific tenant context. + +ABP supports this with `ICurrentTenant.Change(...)`. + +```csharp +using (_currentTenant.Change(tenantId)) +{ + var count = await _productRepository.GetCountAsync(); +} +``` + +This is useful, but it should be used carefully. Tenant context switching is powerful and easy to abuse if you do not keep boundaries clear. + +### Per-tenant connection strings + +If you choose database-per-tenant, ABP supports tenant-specific connection strings. That lets you keep the same application code while routing different tenants to different databases. + +This is one of the biggest practical advantages of using ABP for multi-tenancy: you can start simple and evolve toward stronger isolation without rewriting your entire application model. + +## Shared schema vs database per tenant in ABP + +ABP supports both host and tenant concepts, which makes it flexible enough for different SaaS stages. + +### Shared schema with ABP + +This is usually the easiest starting point. + +Use it when: + +- You want fast delivery +- Your tenants are relatively small +- Compliance requirements are moderate +- Your team wants simpler operations + +What to watch: + +- Ensure all tenant-owned entities implement `IMultiTenant` +- Be careful with raw SQL and custom queries +- Include `TenantId` in indexes where needed +- Test cross-tenant isolation aggressively + +### Database per tenant with ABP + +Use it when: + +- You need stronger isolation +- You have enterprise customers +- You need tenant-specific restore or residency +- You expect some tenants to be much larger than others + +What to watch: + +- Automate provisioning +- Automate migrations across tenant databases +- Monitor connection usage and migration failures +- Keep tenant metadata accurate and centralized + +## Migrations and schema management + +This is where many multi-tenant systems become painful. + +With shared schema, migrations are straightforward: apply once. + +With schema-per-tenant or database-per-tenant, you need orchestration. + +A practical migration workflow includes: + +- Track tenant inventory centrally +- Apply migrations in batches +- Record migration status per tenant +- Retry safely on failure +- Alert on drift +- Avoid manual one-off fixes unless documented and automated later + +If you have 5 tenants, manual migration might feel acceptable. If you have 500, it becomes an operational risk. + +For ABP-based systems, treat tenant database migration as a first-class operational workflow, not an afterthought. + +## Security pitfalls to avoid + +Multi-tenancy failures are usually not caused by the concept itself. They are caused by inconsistent enforcement. + +The most common mistakes are: + +### 1. Missing tenant filters + +This is the classic bug. One query forgets tenant scoping, and data leaks. + +Reduce the risk with: + +- Framework-level filters +- Repository abstractions +- Database row-level security where appropriate +- Integration tests that verify isolation + +### 2. Unsafe admin features + +Support tools, exports, reporting endpoints, and background jobs often bypass normal application flows. That makes them common leakage points. + +Treat admin code as high-risk code. + +### 3. Tenant context lost in async or background processing + +If a background job processes tenant data, it must explicitly carry tenant context. + +Do not assume the request context still exists. + +### 4. Shared cache keys + +If your cache key is just `product-list`, you already have a bug. + +Use tenant-aware cache keys such as: + +```text +tenant:{tenantId}:product-list +``` + +### 5. Weak audit trails + +When something goes wrong, you need to know: + +- Which tenant was affected +- Which user triggered the action +- Which service handled it +- Which data store was involved + +Without tenant-aware auditing, incident response becomes much harder. + +## Performance and noisy neighbor control + +Shared infrastructure saves money, but it also creates contention. + +A single tenant can hurt others through: + +- Expensive reports +- Large imports +- Chatty integrations +- Poorly indexed queries +- Background jobs running at the wrong time + +Mitigations include: + +- Indexing by `TenantId` +- Query timeouts +- Rate limiting per tenant +- Queue isolation for heavy jobs +- Read replicas for reporting +- Partitioning large tables +- Moving heavy tenants to dedicated databases + +This is why hybrid multi-tenancy is so common. It gives you an escape hatch when one tenant outgrows the shared model. + +## A practical implementation plan + +If you are building a new SaaS product, this is a sensible rollout path. + +### Phase 1: Start with shared schema + +- Resolve tenant from subdomain or domain +- Store tenant metadata centrally +- Use framework-level tenant filters +- Make all tenant-owned entities explicit +- Add tenant-aware logging, caching, and auditing + +For ABP, this is usually the fastest path because the framework already supports the core abstractions. + +### Phase 2: Add tenant configuration and feature management + +- Per-tenant settings +- Feature flags +- Plan-based capabilities +- Branding and integration settings + +This keeps customization manageable without branching the codebase. + +### Phase 3: Prepare for tenant mobility + +Even if you start with shared schema, design for future migration. + +That means: + +- Stable tenant IDs +- Export/import or replication strategy +- Clear ownership boundaries in data +- No hidden cross-tenant joins + +### Phase 4: Move selected tenants to dedicated databases + +Use an expand-backfill-contract approach: + +- **Expand**: create the target database +- **Backfill**: copy tenant data +- **Dual-write**: temporarily write to both stores if needed +- **Contract**: switch traffic and retire the old location + +This is much safer than a big-bang migration. + +## When to use multi-tenancy and when not to + +### When to use multi-tenancy + +- You are building a SaaS platform for many organizations +- Tenants share most application behavior +- Operational efficiency matters +- You want centralized upgrades and deployment +- You need a scalable commercial model + +### When NOT to use multi-tenancy + +- Every customer needs deep infrastructure-level customization +- Compliance requires strict physical isolation from day one +- Your team cannot support the operational complexity safely +- You only have a few large customers and each behaves like a separate product + +In those cases, multi-instance deployment may be the better choice. + +## Final recommendations + +If you are unsure where to start, start simpler than you think, but not sloppier than you can afford. + +That usually means: + +- Shared schema first +- Strong tenant resolution +- Automatic tenant filtering +- Tenant-aware logs, cache, and jobs +- A migration path to dedicated databases later + +ABP is especially useful here because it gives you the right primitives early: current tenant context, tenant-aware entities, filters, settings, and connection string support. That reduces the amount of custom plumbing you need to build and maintain. + +The biggest mistake is not choosing the wrong pattern. It is choosing a pattern without planning how tenant isolation will be enforced everywhere. + +## TL;DR + +- Multi-tenancy is mostly about safe isolation of data, behavior, and operations across customers. +- Shared schema is the easiest starting point; database-per-tenant gives the strongest isolation but adds operational cost. +- In ASP.NET Core, tenant resolution, tenant context, automatic filtering, and tenant-aware caching/logging are essential. +- ABP makes multi-tenancy easier with `ICurrentTenant`, `IMultiTenant`, data filters, tenant management, and per-tenant connection strings. +- Design for evolution early so large tenants can move to dedicated databases without a painful rewrite. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/cover.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/cover.png new file mode 100644 index 0000000000..2fde29d221 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-1.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-1.png new file mode 100644 index 0000000000..47af7d55fd Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-1.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-2.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-2.png new file mode 100644 index 0000000000..a3aebbdbf7 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-2.png differ diff --git a/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-3.png b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-3.png new file mode 100644 index 0000000000..0f96c0835a Binary files /dev/null and b/docs/en/Community-Articles/2026-06-03-how-to-implement-multitenancy-in-asp.net-core-and-abp/inline-3.png differ diff --git a/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/Post.md b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/Post.md new file mode 100644 index 0000000000..909aed4a3a --- /dev/null +++ b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/Post.md @@ -0,0 +1,1713 @@ +# Implementing Multi-Tenancy in ABP Framework: A Complete Practical Guide + +Multi-tenancy is one of those architectural decisions that looks simple in a slide deck and becomes very real the moment your SaaS application gets its second serious customer. + +At that point, questions start piling up: + +- How do you isolate tenant data safely? +- How do you resolve the current tenant from a request? +- Should all tenants share one database, or should large customers get dedicated databases? +- How do authentication, background jobs, caching, and seeding behave in a tenant-aware system? + +ABP Framework gives you a strong foundation for all of this. Instead of building tenant resolution, data filters, tenant-aware repositories, and host/tenant boundaries from scratch, you get a consistent multi-tenancy model integrated into the framework. + +This guide explains both the concepts and the implementation details. We will start with the architecture, then move into configuration, entity design, tenant resolution, seeding, authentication, database-per-tenant setups, and advanced production concerns. Along the way, we will build a practical SaaS CRM example to show how the pieces fit together. + +## What Multi-Tenancy Means in Practice + +Multi-tenancy means a single application serves multiple customers, where each customer is a tenant. Those tenants share the application platform, but their data, configuration, users, and operational boundaries must remain isolated. + +In ABP terms: + +- A **tenant** is typically a customer organization using your SaaS product. +- The **host** is the application owner or platform operator. +- In host context, `CurrentTenant` is `null`. +- In tenant context, `CurrentTenant.Id` identifies the active tenant. + +### Single-tenant vs multi-tenant + +A **single-tenant** system usually means: + +- one deployment per customer, +- one database per customer, +- isolated infrastructure by default, +- higher operational cost. + +A **multi-tenant** system usually means: + +- one application serves many customers, +- data isolation is enforced logically or physically, +- lower operational overhead, +- more architectural responsibility. + +### Why SaaS teams care about multi-tenancy + +Multi-tenancy matters because it directly affects: + +- cost efficiency, +- onboarding speed, +- operational scalability, +- security boundaries, +- customization strategy, +- enterprise sales readiness. + +### Advantages + +- Lower infrastructure cost per customer +- Centralized deployment and upgrades +- Easier feature rollout +- Better operational consistency +- Faster tenant provisioning + +### Challenges + +- Strong data isolation is mandatory +- Noisy-neighbor performance issues can appear +- Authentication becomes tenant-aware +- Background processing must preserve tenant context +- Reporting across tenants requires deliberate design +- Database-per-tenant operations add migration complexity + +### Why ABP simplifies multi-tenant development + +ABP helps because multi-tenancy is not treated as an afterthought. It is built into the framework through: + +- `ICurrentTenant` +- `IMultiTenant` +- automatic data filters +- tenant resolution middleware +- tenant management abstractions +- tenant-aware repositories and unit of work integration +- support for shared database, database-per-tenant, and hybrid models + +That combination removes a lot of repetitive plumbing and reduces the chance of subtle isolation bugs. + +## Understanding ABP Multi-Tenancy Architecture + +ABP’s multi-tenancy architecture is centered around tenant context propagation. The framework resolves the tenant from the incoming request, stores it in the current execution context, and applies that context to repositories, filters, services, and modules. + +### Core concepts + +#### Tenant concept + +A tenant is a customer boundary. In a CRM SaaS product, each company using the system is a tenant. + +Examples: + +- `Acme Logistics` +- `Northwind Retail` +- `Contoso Health` + +Each tenant may have: + +- its own users, +- its own roles, +- its own settings, +- its own features, +- its own data, +- optionally its own database. + +#### Host side + +The host side is the platform owner context. + +Typical host responsibilities: + +- create and manage tenants, +- assign subscription plans, +- configure editions and features, +- monitor usage, +- run cross-tenant administration, +- manage billing and provisioning. + +In ABP, host-side entities often have `TenantId = null`. + +#### Tenant side + +The tenant side is the customer-facing application context. + +Typical tenant responsibilities: + +- manage tenant users, +- manage tenant roles, +- create business data, +- configure tenant settings, +- consume tenant-specific features. + +#### `ICurrentTenant` + +`ICurrentTenant` is the main runtime service for reading the active tenant context. + +It exposes: + +- `Id` +- `Name` +- `IsAvailable` + +This service is used everywhere from application services to background jobs. + +#### Tenant resolution pipeline + +ABP resolves the tenant before your application logic runs. It uses configured contributors such as: + +- current user claims, +- query string, +- route values, +- headers, +- domain or subdomain, +- custom resolvers. + +Then `app.UseMultiTenancy()` sets the current tenant context. + +#### `IMultiTenant` + +Entities implementing `IMultiTenant` gain a `Guid? TenantId` property. ABP uses this to apply automatic filtering. + +If `TenantId` is: + +- a tenant id: the entity belongs to that tenant, +- `null`: the entity belongs to the host side. + +#### Data filters + +ABP automatically filters `IMultiTenant` entities so tenant queries only see their own data by default. + +This is one of the most important safety features in the framework. + +#### Tenant Management Module + +ABP provides tenant management infrastructure through its tenant management module and `ITenantStore` abstraction. + +This is used to: + +- store tenant definitions, +- resolve tenant configuration, +- retrieve tenant-specific connection strings, +- support tenant provisioning workflows. + +### Architecture diagram + +```mermaid +flowchart LR + A[Incoming HTTP Request] --> B[Tenant Resolution Contributors] + B --> C[Multi-Tenancy Middleware] + C --> D[ICurrentTenant] + D --> E[Application Service] + E --> F[Repository] + F --> G[Data Filter IMultiTenant] + G --> H[(Database)] +``` + +### Host and tenant boundaries + +```mermaid +flowchart TD + H[Host Context CurrentTenant = null] + T1[Tenant A Context] + T2[Tenant B Context] + + H --> HM[Tenant Management] + H --> FM[Feature Management] + H --> SM[Subscription Management] + + T1 --> D1[Tenant A Data] + T1 --> U1[Tenant A Users] + + T2 --> D2[Tenant B Data] + T2 --> U2[Tenant B Users] +``` + +### Query isolation flow + +```mermaid +sequenceDiagram + participant Req as Request + participant MW as Multi-Tenancy Middleware + participant CT as ICurrentTenant + participant Repo as Repository + participant DB as Database + + Req->>MW: Resolve tenant + MW->>CT: Set TenantId + CT->>Repo: Current tenant context available + Repo->>DB: SELECT ... WHERE TenantId = @CurrentTenantId + DB-->>Repo: Tenant-scoped rows +``` + +## Multi-Tenancy Models Supported by ABP + +ABP supports the three models most SaaS teams actually use: shared database, database per tenant, and hybrid. + +### 1) Single Database / Shared Database + +All tenants share the same physical database. Tenant-specific rows are separated by `TenantId`. + +#### How it works + +- One database stores host and tenant data. +- Tenant-aware entities implement `IMultiTenant`. +- ABP filters rows automatically. + +#### Advantages + +- Lowest infrastructure cost +- Simplest migrations +- Easier backup strategy +- Easier reporting across tenants +- Faster onboarding for small SaaS products + +#### Disadvantages + +- Weaker isolation than dedicated databases +- Noisy-neighbor risk +- Large tenants can affect shared performance +- Cross-tenant leak risk if filters are bypassed incorrectly + +### 2) Database Per Tenant + +Each tenant gets its own physical database. The host may also have a separate database. + +#### How it works + +- Tenant metadata is stored centrally or in a host database. +- `ITenantStore` resolves tenant configuration. +- Connection strings are selected dynamically per tenant. + +#### Advantages + +- Strong isolation +- Easier compliance conversations +- Better performance isolation +- Easier tenant-specific backup and restore +- Large tenants can scale independently + +#### Disadvantages + +- More operational overhead +- More complex migrations +- Harder cross-tenant analytics +- More secrets and connection strings to manage +- More provisioning automation required + +### 3) Hybrid Model + +Some tenants share a database, while larger or regulated tenants get dedicated databases. + +#### Enterprise SaaS scenarios + +This is often the most realistic model when: + +- small tenants are cost-sensitive, +- enterprise tenants require stronger isolation, +- some customers need regional or compliance-specific storage, +- you want a migration path from shared to dedicated databases. + +#### Advantages + +- Flexible cost model +- Better fit for mixed customer sizes +- Easier enterprise upsell path + +#### Disadvantages + +- Highest architectural complexity +- More operational branching +- More migration and observability work + +### Comparison table + +| Model | Isolation | Cost | Operational Complexity | Best For | +|---|---|---:|---:|---| +| Shared Database | Logical | Low | Low | Early-stage SaaS, many small tenants | +| Database Per Tenant | Physical | High | High | Enterprise SaaS, regulated workloads | +| Hybrid | Mixed | Medium to High | High | Growing SaaS with mixed tenant profiles | + +| Concern | Shared Database | Database Per Tenant | Hybrid | +|---|---|---|---| +| Migrations | Simple | Complex | Complex | +| Cross-tenant reporting | Easy | Harder | Mixed | +| Performance isolation | Limited | Strong | Selective | +| Tenant onboarding | Fast | Slower | Depends on tier | +| Compliance flexibility | Moderate | Strong | Strong | + +### When to use / When NOT to use + +#### Use shared database when + +- you are building an MVP or early SaaS, +- tenants are relatively small, +- cost efficiency matters more than hard isolation, +- your team wants simpler operations. + +#### Do not use shared database when + +- tenants require strict physical isolation, +- you expect very uneven tenant load, +- compliance or contractual requirements demand dedicated storage. + +#### Use database per tenant when + +- enterprise customers require isolation, +- you need tenant-specific backup/restore, +- large tenants justify dedicated infrastructure. + +#### Do not use database per tenant when + +- your team is not ready for migration automation, +- you have many tiny tenants and limited ops capacity, +- cross-tenant analytics is a core requirement and you have no aggregation strategy. + +#### Use hybrid when + +- you need both efficiency and enterprise flexibility, +- you want to move premium tenants to dedicated databases over time. + +#### Do not use hybrid when + +- your operational tooling is immature, +- your team is still validating the product and needs simplicity first. + +## Enabling Multi-Tenancy in ABP + +In most ABP solutions, multi-tenancy is enabled through a shared constant and framework options. + +### `MultiTenancyConsts` + +A typical template includes a constant like this: + +```csharp +namespace Acme.Crm; + +public static class MultiTenancyConsts +{ + public const bool IsEnabled = true; +} +``` + +This constant is often referenced across layers so the application has one central switch. + +### Configure `AbpMultiTenancyOptions` + +In your module: + +```csharp +using Volo.Abp.MultiTenancy; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + options.UserSharingStrategy = TenantUserSharingStrategy.Isolated; + }); +} +``` + +`UserSharingStrategy` matters when you decide whether users are isolated per tenant or shared across tenants. + +### Configure middleware + +In your HTTP pipeline: + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + app.UseAuthentication(); + app.UseMultiTenancy(); + app.UseAuthorization(); + + app.UseConfiguredEndpoints(); +} +``` + +The exact middleware order can vary by solution template, but `UseMultiTenancy()` must be present so tenant resolution runs. + +### Example appsettings.json + +For a shared database setup: + +```json +{ + "ConnectionStrings": { + "Default": "Server=localhost;Database=CrmShared;Trusted_Connection=True;TrustServerCertificate=True" + } +} +``` + +For a host database plus tenant metadata: + +```json +{ + "ConnectionStrings": { + "Default": "Server=localhost;Database=CrmHost;Trusted_Connection=True;TrustServerCertificate=True" + } +} +``` + +### Full module example + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm; + +[DependsOn( + typeof(AbpMultiTenancyModule) +)] +public class CrmHttpApiHostModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + options.UserSharingStrategy = TenantUserSharingStrategy.Isolated; + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + app.UseRouting(); + app.UseAuthentication(); + app.UseMultiTenancy(); + app.UseAuthorization(); + app.UseConfiguredEndpoints(); + } +} +``` + +## Tenant Resolution Strategies + +Tenant resolution is where multi-tenancy becomes real. The framework must determine which tenant the request belongs to before your application logic executes. + +ABP supports multiple strategies, and you can combine them. + +### Default resolution contributors + +ABP can resolve tenants from: + +- current user claims, +- query string `__tenant`, +- route values, +- custom contributors. + +### Configure tenant resolution + +```csharp +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.AddDomainTenantResolver("{0}.mycrm.com"); + options.TenantResolvers.Add(new HeaderTenantResolveContributor()); + }); +} +``` + +### Subdomain resolution + +This is common in SaaS products: + +- `acme.mycrm.com` +- `northwind.mycrm.com` + +Configuration: + +```csharp +Configure(options => +{ + options.AddDomainTenantResolver("{0}.mycrm.com"); +}); +``` + +ABP extracts `acme` or `northwind` and uses it as the tenant identifier. + +### Domain resolution + +You can also map full domains to tenants, especially for enterprise customers using custom domains. + +Examples: + +- `crm.acme-enterprise.com` +- `portal.contosohealth.io` + +This usually requires custom tenant lookup logic or a domain mapping table in your tenant store. + +### Header-based resolution + +Useful for APIs, gateways, or internal service-to-service calls. + +Example custom contributor: + +```csharp +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +public class HeaderTenantResolveContributor : TenantResolveContributorBase +{ + public const string HeaderName = "X-Tenant"; + + public override string Name => "Header"; + + public override Task ResolveAsync(ITenantResolveContext context) + { + var httpContext = context.GetHttpContext(); + if (httpContext == null) + { + return Task.CompletedTask; + } + + var tenant = httpContext.Request.Headers[HeaderName].ToString(); + if (!tenant.IsNullOrWhiteSpace()) + { + context.TenantIdOrName = tenant; + } + + return Task.CompletedTask; + } +} +``` + +### Query string resolution + +Useful for testing and some integration scenarios. + +Example: + +- `/api/products?__tenant=acme` + +This is convenient, but usually not the best primary strategy for production browser apps. + +### Route-based resolution + +Example: + +- `/t/acme/products` + +This can work well for APIs or apps where tenant identity is part of the route structure. + +### Custom tenant resolver + +If your tenant identification depends on something domain-specific, implement your own contributor. + +Example: resolve tenant from an API key prefix. + +```csharp +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +public class ApiKeyTenantResolveContributor : TenantResolveContributorBase +{ + public override string Name => "ApiKey"; + + public override Task ResolveAsync(ITenantResolveContext context) + { + var httpContext = context.GetHttpContext(); + if (httpContext == null) + { + return Task.CompletedTask; + } + + var apiKey = httpContext.Request.Headers["X-Api-Key"].ToString(); + if (string.IsNullOrWhiteSpace(apiKey)) + { + return Task.CompletedTask; + } + + if (apiKey.StartsWith("acme_")) + { + context.TenantIdOrName = "acme"; + } + + return Task.CompletedTask; + } +} +``` + +### HTTP request flow + +```mermaid +sequenceDiagram + participant C as Client + participant R as Tenant Resolver Contributors + participant M as UseMultiTenancy Middleware + participant T as ITenantStore + participant CT as ICurrentTenant + participant A as Application Service + + C->>R: HTTP request with host/header/query/route + R->>M: TenantIdOrName resolved + M->>T: Load tenant configuration + T-->>M: Tenant info + connection strings + M->>CT: Set current tenant context + CT->>A: Tenant-aware execution begins +``` + +### Practical guidance + +- Use **subdomain resolution** for browser-based SaaS apps. +- Use **header-based resolution** for APIs behind gateways. +- Keep **query string resolution** mainly for testing or controlled integrations. +- Add **custom resolvers** only when the business rule is stable and well documented. + +## Creating Tenant-Aware Entities + +The most important rule in ABP multi-tenancy is simple: entities that belong to a tenant should implement `IMultiTenant`. + +### Basic entity design + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Products; + +public class Product : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; protected set; } + public string Name { get; private set; } + public decimal Price { get; private set; } + + protected Product() + { + } + + public Product(Guid id, Guid? tenantId, string name, decimal price) + : base(id) + { + TenantId = tenantId; + Name = name; + Price = price; + } +} +``` + +### Customer example + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Customers; + +public class Customer : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; protected set; } + public string CompanyName { get; private set; } + public string Email { get; private set; } + + protected Customer() + { + } + + public Customer(Guid id, Guid? tenantId, string companyName, string email) + : base(id) + { + TenantId = tenantId; + CompanyName = companyName; + Email = email; + } +} +``` + +### Order example + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Orders; + +public class Order : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; protected set; } + public Guid CustomerId { get; private set; } + public DateTime OrderDate { get; private set; } + public decimal TotalAmount { get; private set; } + + protected Order() + { + } + + public Order(Guid id, Guid? tenantId, Guid customerId, DateTime orderDate, decimal totalAmount) + : base(id) + { + TenantId = tenantId; + CustomerId = customerId; + OrderDate = orderDate; + TotalAmount = totalAmount; + } +} +``` + +### Aggregate root considerations + +A few practical rules help avoid trouble: + +- Put `TenantId` on aggregate roots that are tenant-owned. +- Keep child entities inside the same tenant boundary as the aggregate root. +- Avoid aggregates that reference entities from different tenants. +- Be explicit about whether an entity is host-owned or tenant-owned. + +### How ABP filters tenant data automatically + +Once an entity implements `IMultiTenant`, ABP applies the multi-tenant data filter automatically. + +That means this repository call: + +```csharp +var products = await _productRepository.GetListAsync(); +``` + +will only return rows for the current tenant when tenant context is active. + +### Generated SQL shape + +The exact SQL depends on your provider and query, but conceptually it looks like this: + +```sql +SELECT Id, TenantId, Name, Price +FROM CrmProducts +WHERE TenantId = @CurrentTenantId +``` + +In host context, behavior depends on the entity and query shape. Host-owned rows typically have `TenantId IS NULL`. + +### Important design note about `TenantId` + +`IMultiTenant` defines `Guid? TenantId`, which is nullable because host-owned entities may exist. + +If your entity must always belong to a tenant, you can enforce that in domain logic and EF configuration, but be careful. The framework’s default contract is nullable, and forcing non-null semantics requires deliberate mapping and validation. + +## Working with CurrentTenant + +`ICurrentTenant` is the runtime API you will use most often in multi-tenant services. + +### Reading current tenant information + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Products; + +public class ProductAppService : ApplicationService +{ + private readonly ICurrentTenant _currentTenant; + + public ProductAppService(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public Task GetTenantInfoAsync() + { + var tenantId = _currentTenant.Id?.ToString() ?? "Host"; + var tenantName = _currentTenant.Name ?? "Host"; + + return Task.FromResult($"TenantId: {tenantId}, TenantName: {tenantName}"); + } +} +``` + +### Creating tenant-owned data safely + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Products; + +public class ProductAppService : ApplicationService +{ + private readonly IRepository _productRepository; + private readonly ICurrentTenant _currentTenant; + + public ProductAppService( + IRepository productRepository, + ICurrentTenant currentTenant) + { + _productRepository = productRepository; + _currentTenant = currentTenant; + } + + public async Task CreateAsync(string name, decimal price) + { + if (!_currentTenant.IsAvailable) + { + throw new BusinessException("Products can only be created in tenant context."); + } + + var product = new Product(GuidGenerator.Create(), _currentTenant.Id, name, price); + await _productRepository.InsertAsync(product, autoSave: true); + return product.Id; + } +} +``` + +### Changing tenant context + +ABP allows temporary tenant switching with `CurrentTenant.Change(...)`. + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Reporting; + +public class TenantProductCounter : ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + private readonly IRepository _productRepository; + + public TenantProductCounter( + ICurrentTenant currentTenant, + IRepository productRepository) + { + _currentTenant = currentTenant; + _productRepository = productRepository; + } + + public async Task CountForTenantAsync(Guid tenantId) + { + using (_currentTenant.Change(tenantId)) + { + return await _productRepository.GetCountAsync(); + } + } +} +``` + +### Nested tenant scopes + +```csharp +public async Task CompareTwoTenantsAsync(Guid tenantA, Guid tenantB) +{ + using (_currentTenant.Change(tenantA)) + { + var countA = await _productRepository.GetCountAsync(); + + using (_currentTenant.Change(tenantB)) + { + var countB = await _productRepository.GetCountAsync(); + } + } +} +``` + +Nested scopes are useful, but they should remain easy to reason about. If tenant switching becomes deeply nested, move that logic into dedicated services. + +### Practical rules for `ICurrentTenant` + +- Read it at the application or domain service boundary. +- Avoid passing raw tenant ids everywhere when the current context already exists. +- Use `Change(...)` sparingly and intentionally. +- Never assume tenant context exists in host-side operations. + +## Data Isolation Mechanisms + +ABP’s biggest multi-tenancy advantage is that data isolation is integrated into repositories and unit of work. + +### Automatic data filtering + +When an entity implements `IMultiTenant`, ABP applies the multi-tenant filter automatically. + +That means: + +- tenant A cannot see tenant B rows through normal repository queries, +- host operations can work in host context, +- the same repository code behaves differently depending on `ICurrentTenant`. + +### Tenant-specific repositories + +You usually do not need separate repositories per tenant. The same repository becomes tenant-aware because the current tenant context changes the filter behavior. + +Example: + +```csharp +var customers = await _customerRepository.GetListAsync(); +``` + +In tenant `Acme`, this returns only Acme customers. + +### Unit of Work integration + +ABP’s unit of work carries tenant context through the execution flow. This matters because: + +- repository queries stay tenant-scoped, +- transactional operations remain consistent, +- tenant switching inside a scope affects subsequent repository calls. + +### Disabling the filter carefully + +Sometimes host-side reporting or maintenance tasks need cross-tenant access. + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +public class CrossTenantProductReader : ITransientDependency +{ + private readonly IDataFilter _dataFilter; + private readonly IRepository _productRepository; + + public CrossTenantProductReader( + IDataFilter dataFilter, + IRepository productRepository) + { + _dataFilter = dataFilter; + _productRepository = productRepository; + } + + public async Task> GetAllAsync() + { + using (_dataFilter.Disable()) + { + return await _productRepository.GetListAsync(); + } + } +} +``` + +This is powerful and dangerous. + +### Security implications + +Disabling `IMultiTenant` filtering means you are bypassing one of your main isolation protections. + +Use it only when: + +- the operation is explicitly host-level, +- authorization is strong, +- the result is not accidentally exposed to tenant users, +- the code path is easy to audit. + +### SQL examples + +Normal tenant-scoped query: + +```sql +SELECT * +FROM CrmCustomers +WHERE TenantId = @CurrentTenantId +ORDER BY CompanyName +``` + +Cross-tenant query with filter disabled: + +```sql +SELECT * +FROM CrmCustomers +ORDER BY TenantId, CompanyName +``` + +### Isolation checklist + +- Tenant-owned entities implement `IMultiTenant` +- Tenant context is resolved before app logic +- Filters remain enabled by default +- Host-only operations are explicitly separated +- Cross-tenant reads are rare and audited + +## Seeding Tenant Data + +Seeding is where many multi-tenant applications become inconsistent. The host needs one set of seed data, while each tenant may need its own initialization. + +ABP’s `IDataSeedContributor` makes this manageable. + +### Host and tenant seeding model + +- Host seeding runs when `DataSeedContext.TenantId == null` +- Tenant seeding runs when `DataSeedContext.TenantId` has a value +- You can switch tenant context with `CurrentTenant.Change(...)` + +### Host seed contributor example + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Data; + +public class HostDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + + public HostDataSeedContributor(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (context.TenantId != null) + { + return; + } + + using (_currentTenant.Change(null)) + { + await Task.CompletedTask; + // Seed host-side editions, plans, global settings, etc. + } + } +} +``` + +### Tenant seed contributor example + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Data; + +public class TenantDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + private readonly IRepository _productRepository; + + public TenantDataSeedContributor( + ICurrentTenant currentTenant, + IRepository productRepository) + { + _currentTenant = currentTenant; + _productRepository = productRepository; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (context.TenantId == null) + { + return; + } + + using (_currentTenant.Change(context.TenantId)) + { + if (await _productRepository.GetCountAsync() > 0) + { + return; + } + + await _productRepository.InsertAsync( + new Products.Product(Guid.NewGuid(), context.TenantId, "Starter Plan", 49), + autoSave: true + ); + + await _productRepository.InsertAsync( + new Products.Product(Guid.NewGuid(), context.TenantId, "Growth Plan", 99), + autoSave: true + ); + } + } +} +``` + +### Per-tenant initialization during provisioning + +A common SaaS flow looks like this: + +1. Host admin creates tenant +2. Tenant metadata is stored +3. Tenant database is created or assigned +4. Migrations run +5. Seed contributors initialize tenant data +6. Admin user is created +7. Features/settings are applied + +### Practical seeding advice + +- Keep seeders idempotent +- Separate host and tenant concerns clearly +- Avoid large business workflows inside seed contributors +- Use provisioning services for complex onboarding + +## Multi-Tenant Authentication and Identity + +Authentication is where many multi-tenant systems fail in subtle ways. If tenant resolution happens too late, users may authenticate against the wrong tenant context. + +ABP’s identity integration helps because users are tenant-aware. + +### Tenant-specific users and roles + +In ABP: + +- users can belong to a tenant, +- roles can be tenant-specific, +- host users can exist separately, +- identity behavior depends on the user sharing strategy. + +### Identity module integration + +ABP Identity integrates with multi-tenancy so that user and role management respects tenant boundaries. + +Typical outcomes: + +- tenant admins manage only their tenant users, +- host admins manage platform-level concerns, +- tenant users do not see host-level identity data. + +### Login flow + +The tenant should be resolved before authentication whenever possible. + +Typical browser flow: + +1. User visits `acme.mycrm.com` +2. Tenant resolver identifies `acme` +3. Multi-tenancy middleware sets current tenant +4. Authentication runs in tenant context +5. User is validated against tenant-aware identity data +6. Authorized application logic executes + +### Login request flow diagram + +```mermaid +sequenceDiagram + participant U as User Browser + participant D as Domain/Subdomain Resolver + participant MT as Multi-Tenancy Middleware + participant ID as Identity/Auth + participant APP as Application + + U->>D: Request acme.mycrm.com/login + D->>MT: Tenant = acme + MT->>ID: Authenticate in tenant context + ID-->>APP: Authenticated tenant user + APP-->>U: Tenant-scoped session +``` + +### Tenant switching + +Tenant switching can mean different things: + +- switching browser context from one tenant domain to another, +- host admin impersonating or managing a tenant, +- shared-user scenarios where one identity can access multiple tenants. + +In most SaaS applications, the cleanest approach is domain-based switching rather than trying to mutate tenant context inside a long-lived UI session. + +### Shared user accounts vs isolated users + +ABP supports user sharing strategies. + +#### Isolated users + +- usernames/emails are unique within tenant scope, +- simpler mental model, +- best default for most SaaS apps. + +#### Shared users + +- one user identity may exist across tenants, +- global uniqueness rules become important, +- database-per-tenant setups become more complex, +- host-side identity metadata may need replication or synchronization. + +Unless you have a strong product reason, isolated users are usually the safer choice. + +## Database Per Tenant Configuration + +Database-per-tenant is where ABP’s abstractions become especially valuable. + +### Connection string management + +At runtime, the application needs to know which database to use for the current tenant. + +That information typically comes from `ITenantStore`. + +### `ITenantStore` + +`ITenantStore` is the abstraction used to retrieve tenant configuration, including: + +- tenant id, +- tenant name, +- activation state, +- connection strings. + +ABP provides implementations through tenant management infrastructure. In simpler setups, configuration-based tenant stores can also be used. + +### Tenant-specific database setup + +For module-specific database usage, you can configure database options like this: + +```csharp +using Volo.Abp.Data; +using Volo.Abp.Modularity; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.Databases.Configure("Saas", database => + { + database.IsUsedByTenants = true; + }); + }); +} +``` + +### Example tenant configuration shape + +In a custom store or configuration source, you may keep tenant metadata like this: + +```json +{ + "Tenants": [ + { + "Id": "11111111-1111-1111-1111-111111111111", + "Name": "acme", + "ConnectionStrings": { + "Default": "Server=sql1;Database=Crm_Acme;User Id=app;Password=secret;TrustServerCertificate=True" + } + }, + { + "Id": "22222222-2222-2222-2222-222222222222", + "Name": "northwind", + "ConnectionStrings": { + "Default": "Server=sql2;Database=Crm_Northwind;User Id=app;Password=secret;TrustServerCertificate=True" + } + } + ] +} +``` + +### Production-ready custom tenant store example + +```csharp +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +public class AppTenantStore : ITenantStore, ITransientDependency +{ + private readonly ConcurrentDictionary _tenantsByName; + private readonly ConcurrentDictionary _tenantsById; + + public AppTenantStore(IOptions options) + { + _tenantsByName = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _tenantsById = new ConcurrentDictionary(); + + foreach (var tenant in options.Value.Tenants) + { + _tenantsByName[tenant.Name] = tenant; + if (tenant.Id.HasValue) + { + _tenantsById[tenant.Id.Value] = tenant; + } + } + } + + public Task FindAsync(string normalizedName) + { + _tenantsByName.TryGetValue(normalizedName, out var tenant); + return Task.FromResult(tenant); + } + + public Task FindAsync(Guid id) + { + _tenantsById.TryGetValue(id, out var tenant); + return Task.FromResult(tenant); + } +} + +public class AppTenantStoreOptions +{ + public TenantConfiguration[] Tenants { get; set; } = Array.Empty(); +} +``` + +### Provisioning flow for dedicated databases + +A production-grade tenant provisioning process usually includes: + +- create tenant record, +- generate or assign connection string, +- create database, +- run migrations, +- seed tenant data, +- create tenant admin, +- verify health checks. + +### Open source vs PRO note + +ABP supports database-per-tenant architecture in general, but UI-based connection string management for tenants is part of the commercial SaaS tooling. In open-source solutions, you typically implement your own management UI or provisioning workflow. + +## Advanced Scenarios in Real Applications + +Multi-tenancy affects more than repositories. In production systems, the tricky parts are usually background jobs, events, caching, features, settings, and observability. + +### Background jobs in tenant context + +If a background job processes tenant data, it must restore the correct tenant context. + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +public class RebuildCustomerStatsArgs +{ + public Guid TenantId { get; set; } +} + +public class RebuildCustomerStatsJob : AsyncBackgroundJob, ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + + public RebuildCustomerStatsJob(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public override async Task ExecuteAsync(RebuildCustomerStatsArgs args) + { + using (_currentTenant.Change(args.TenantId)) + { + await Task.CompletedTask; + // Recalculate tenant-specific metrics here. + } + } +} +``` + +### Distributed events + +When publishing distributed events, include tenant context explicitly if consumers need it. + +Good practice: + +- include `TenantId` in event payloads, +- restore tenant context in handlers, +- avoid assuming ambient tenant context exists in asynchronous boundaries. + +### Caching per tenant + +Cache keys should include tenant identity. + +Bad: + +- `customer-list` + +Better: + +- `tenant:{tenantId}:customer-list` + +Without tenant-aware cache keys, cross-tenant data leaks become very possible. + +### Feature management + +Features are a natural fit for SaaS plans. + +Examples: + +- maximum users, +- advanced reporting, +- API access, +- custom branding. + +ABP feature management supports tenant-level configuration and edition-based grouping. + +### Setting management + +Settings can be scoped globally, per tenant, or per user. + +Examples: + +- default currency, +- email sender, +- invoice numbering format, +- CRM pipeline defaults. + +### Audit logging + +Audit logs should capture tenant context so you can answer questions like: + +- which tenant triggered this action, +- which admin changed a tenant setting, +- which background job modified tenant data. + +### Localization + +Tenants often need different localization defaults: + +- language, +- timezone, +- regional formatting, +- legal text. + +Tenant-level settings are usually the right place for these preferences. + +### Common pitfalls + +- forgetting tenant context in background jobs, +- using cache keys without tenant id, +- disabling data filters too broadly, +- mixing host and tenant responsibilities in one service, +- assuming tenant resolution works the same in browser and API flows, +- not testing wildcard domain auth configuration carefully. + +## Building a Sample SaaS CRM Application + +Let’s connect the concepts with a practical example. + +Imagine a SaaS CRM built with ABP. + +### Functional areas + +#### Host administration + +The host side manages: + +- tenant creation, +- subscription plans, +- feature packages, +- billing integration, +- tenant health and usage. + +#### Tenant administration + +Each tenant manages: + +- users, +- roles, +- settings, +- branding, +- sales workflows. + +#### Customer management + +Tenant users create and manage: + +- customers, +- contacts, +- opportunities, +- orders, +- notes. + +#### Subscription management + +The host assigns plans such as: + +- Starter +- Growth +- Enterprise + +These plans map naturally to ABP features and settings. + +### Suggested architecture + +- **Host app** for platform administration +- **Tenant-facing app** resolved by subdomain +- **Shared modules** for identity, audit logging, feature management, setting management +- **Tenant-aware domain entities** for CRM data +- **Hybrid database strategy** if enterprise tenants need dedicated databases + +### Example domain boundaries + +Host-owned entities: + +- Tenant +n- SubscriptionPlan +- Edition +- BillingAccount + +Tenant-owned entities: + +- Customer +- Product +- Order +- SalesPipeline +- ActivityLog + +### Tenant-aware application service example + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Customers; + +public class CustomerAppService : ApplicationService +{ + private readonly IRepository _customerRepository; + private readonly ICurrentTenant _currentTenant; + + public CustomerAppService( + IRepository customerRepository, + ICurrentTenant currentTenant) + { + _customerRepository = customerRepository; + _currentTenant = currentTenant; + } + + public async Task CreateAsync(string companyName, string email) + { + if (!_currentTenant.IsAvailable) + { + throw new BusinessException("Customer creation requires tenant context."); + } + + var customer = new Customer( + GuidGenerator.Create(), + _currentTenant.Id, + companyName, + email + ); + + await _customerRepository.InsertAsync(customer, autoSave: true); + return customer.Id; + } +} +``` + +### End-to-end request example + +A request to `https://acme.mycrm.com/api/customers` flows like this: + +1. Domain resolver extracts `acme` +2. `ITenantStore` loads tenant metadata +3. `ICurrentTenant` is set +4. Authentication runs in tenant context +5. Repository queries apply `IMultiTenant` filter +6. Only Acme customer data is returned + +That is the core ABP multi-tenancy story in one request. + +## Best Practices for ABP Multi-Tenant Applications + +Here are practical best practices that hold up well in real systems. + +1. **Implement `IMultiTenant` on every tenant-owned aggregate root.** Do not rely on conventions or memory. +2. **Use subdomain-based tenant resolution for browser SaaS apps.** It is usually the cleanest UX and operational model. +3. **Keep host and tenant application services separate.** Mixing them creates authorization and maintenance problems. +4. **Treat `IDataFilter.Disable()` as a privileged operation.** Review those code paths carefully. +5. **Index `TenantId` on large tables.** Shared-database performance depends on it. +6. **Include `TenantId` in cache keys, distributed events, and background job payloads.** Ambient context does not cross every boundary. +7. **Resolve tenant before authentication.** Especially for domain/subdomain-based login flows. +8. **Prefer isolated users unless shared identities are a real product requirement.** Shared users add complexity fast. +9. **Automate tenant provisioning.** Manual database creation and migration does not scale. +10. **Make seed contributors idempotent.** Tenant provisioning may be retried. +11. **Design for hybrid early if you expect enterprise customers.** Moving from shared-only to hybrid later is possible, but easier if planned. +12. **Store tenant connection strings securely.** Use secret management and rotation policies. +13. **Log tenant context in audit and operational logs.** Troubleshooting without tenant identity is painful. +14. **Test host context explicitly.** `CurrentTenant` being `null` is a valid and important scenario. +15. **Avoid cross-tenant joins in domain logic.** If you need cross-tenant analytics, build reporting pipelines intentionally. +16. **Keep tenant switching localized.** Use `CurrentTenant.Change(...)` in small, obvious scopes. +17. **Validate tenant ownership in domain rules where it matters.** Filters help, but domain invariants still matter. +18. **Monitor noisy-neighbor patterns.** Shared databases need tenant-level performance visibility. +19. **Run tenant-aware integration tests.** Unit tests alone will not catch resolution and filter issues. +20. **Document your tenant resolution strategy clearly.** Operations, frontend, and identity flows all depend on it. + +## Common Mistakes to Avoid + +Even experienced teams make the same multi-tenancy mistakes. + +### 1) Assuming repository filtering solves everything + +Repository filtering is excellent, but it does not replace: + +- authorization, +- cache isolation, +- event payload design, +- background job context restoration. + +### 2) Treating host context as an edge case + +In ABP, host context is a first-class concept. Design for it explicitly. + +### 3) Choosing database-per-tenant too early or too late + +Too early: + +- unnecessary operational burden. + +Too late: + +- painful migration for large tenants. + +### 4) Forgetting tenant-aware testing + +You should test: + +- host requests, +- tenant requests, +- invalid tenant requests, +- cross-tenant access attempts, +- background jobs with tenant context, +- cache behavior across tenants. + +## Final Thoughts + +ABP’s multi-tenancy support is one of the framework’s strongest features because it combines architectural clarity with practical runtime behavior. + +You get: + +- a clear host/tenant model, +- tenant resolution middleware, +- `ICurrentTenant` for runtime context, +- `IMultiTenant` for entity ownership, +- automatic data filtering, +- tenant-aware identity and module integration, +- support for shared, dedicated, and hybrid database strategies. + +That does not remove the need for good architecture. You still need to make deliberate choices about isolation, authentication, provisioning, caching, and operations. But ABP gives you a solid default path and a consistent set of abstractions, which is exactly what you want in a serious SaaS platform. + +If you are building an enterprise-grade SaaS application on .NET, ABP lets you spend more time on product logic and less time reinventing multi-tenancy infrastructure. + +## TL;DR + +- ABP multi-tenancy is built around `ICurrentTenant`, `IMultiTenant`, tenant resolution middleware, and automatic data filters. +- ABP supports shared database, database-per-tenant, and hybrid models, so you can match architecture to customer and compliance needs. +- Tenant resolution should happen before authentication, and subdomain resolution is usually the best default for SaaS apps. +- Tenant-aware design must extend beyond repositories to seeding, background jobs, caching, events, settings, and audit logging. +- The safest production approach is clear host/tenant separation, minimal filter bypassing, strong provisioning automation, and tenant-aware testing. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/cover.png b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/cover.png new file mode 100644 index 0000000000..b13a985e7e Binary files /dev/null and b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png new file mode 100644 index 0000000000..c8c2186287 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png differ diff --git a/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png new file mode 100644 index 0000000000..b49288411b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png differ diff --git a/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png new file mode 100644 index 0000000000..78d74772ad Binary files /dev/null and b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png differ diff --git a/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-4.png b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-4.png new file mode 100644 index 0000000000..c1568b4320 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-04-implementing-multitenancy-in-abp-framework-a-complete/inline-4.png differ diff --git a/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/Post.md b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/Post.md new file mode 100644 index 0000000000..e56e2b215b --- /dev/null +++ b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/Post.md @@ -0,0 +1,1452 @@ +# Implementing Multi-Tenancy in ABP Framework: A Complete Practical Guide + +Multi-tenancy is one of those architectural decisions that looks simple in a slide deck and becomes very real the moment you build a SaaS product. The hard part is not adding a `TenantId` column. The hard part is making tenant resolution, data isolation, authentication, seeding, background processing, caching, and database management work together without leaking data or creating operational pain. + +ABP Framework gives you a strong foundation for this. It has built-in multi-tenancy support, tenant-aware entities, automatic data filters, tenant resolution contributors, tenant management, and connection string infrastructure that supports shared database, database-per-tenant, and hybrid models. + +This guide explains both the concepts and the implementation details. It is written for intermediate to advanced .NET developers building real SaaS applications with ABP. + +## What Multi-Tenancy Means in SaaS + +A multi-tenant application serves multiple customers from the same application codebase and runtime while keeping each customer's data, users, configuration, and behavior isolated. + +In ABP terminology: + +- **Host side**: the application owner or platform operator +- **Tenant side**: each customer using the application +- **Current tenant**: the tenant context of the current request or operation + +### Single-tenant vs multi-tenant + +A **single-tenant** system usually means: + +- one deployment per customer +- isolated infrastructure by default +- simpler compliance story +- higher operational cost + +A **multi-tenant** system usually means: + +- one application serving many customers +- shared operational model +- lower cost per customer +- more architectural responsibility around isolation + +### Why multi-tenancy matters + +For SaaS products, multi-tenancy is often the difference between a manageable platform and an expensive collection of customer-specific deployments. + +Benefits: + +- lower infrastructure cost +- centralized updates and fixes +- faster onboarding of new customers +- easier feature rollout +- consistent operational model + +Challenges: + +- strict data isolation +- tenant-aware authentication and authorization +- scaling noisy tenants +- tenant-specific configuration +- migrations across shared or separate databases +- reporting across tenants + +### Why ABP simplifies this + +ABP removes a lot of repetitive plumbing: + +- `ICurrentTenant` exposes tenant context everywhere +- `IMultiTenant` enables automatic tenant filtering +- tenant resolution is built into the ASP.NET Core pipeline +- tenant management is available out of the box +- per-tenant connection strings support database-per-tenant and hybrid models +- identity, permissions, settings, features, and audit logging are tenant-aware + +That does not eliminate architectural decisions, but it gives you a consistent framework for implementing them correctly. + +## Understanding ABP Multi-Tenancy Architecture + +ABP's multi-tenancy model is practical: tenant context is resolved early, stored in the current execution scope, and then used by repositories, DbContexts, identity, settings, and other infrastructure. + +### Core concepts + +#### Tenant concept + +A tenant represents a customer organization in your SaaS system. A tenant typically has: + +- an `Id` +- a `Name` +- optional connection strings +- optional settings and features +- tenant-specific users and roles + +#### Host side + +The host side is where platform-wide operations happen: + +- creating and managing tenants +- assigning plans or subscriptions +- viewing cross-tenant analytics +- configuring defaults +- running migrations and maintenance + +Host-side operations usually run with `CurrentTenant.Id == null`. + +#### Tenant side + +The tenant side is where customer-specific operations happen: + +- managing users inside a tenant +- creating business data such as products, customers, and orders +- configuring tenant-level settings +- consuming tenant-specific features + +#### `ICurrentTenant` + +`ICurrentTenant` is the central service for reading the active tenant context. + +It exposes: + +- `Id` +- `Name` +- `IsAvailable` + +You will use it in application services, domain services, background jobs, seed contributors, and event handlers. + +#### Tenant resolution pipeline + +ABP resolves the tenant from the incoming request using contributors. Common sources include: + +- current user claims +- query string +- route values +- headers +- cookies +- domain or subdomain +- custom resolvers + +#### `IMultiTenant` + +Entities implementing `IMultiTenant` become tenant-aware. ABP applies automatic data filtering so tenant users only see rows belonging to their tenant. + +#### Data filters + +ABP uses data filters, typically backed by EF Core global query filters, to enforce tenant isolation for `IMultiTenant` entities. + +#### Tenant Management module + +ABP's Tenant Management module provides the infrastructure to create and manage tenants. In startup templates, this is often already integrated. In open-source ABP, core tenant management exists, while some advanced SaaS UI capabilities are part of ABP Commercial. + +### Request-to-data flow + +```mermaid +flowchart LR + A[Incoming HTTP Request] --> B[Tenant Resolution Contributors] + B --> C[Resolved Tenant Id or Name] + C --> D[ICurrentTenant Scope] + D --> E[Application Service] + E --> F[Repository / DbContext] + F --> G[IMultiTenant Data Filter] + G --> H[Tenant-Isolated Data] +``` + +### Host and tenant architecture view + +```mermaid +flowchart TB + Host[Host Side\nPlatform Owner] --> TM[Tenant Management] + Host --> Billing[Subscription / Plan Management] + Host --> Reporting[Cross-Tenant Reporting] + + TenantA[Tenant A] --> AppA[Application Modules] + TenantB[Tenant B] --> AppB[Application Modules] + TenantC[Tenant C] --> AppC[Application Modules] + + AppA --> Data[(Shared DB or Tenant DB)] + AppB --> Data + AppC --> Data +``` + +### How ABP applies tenant context + +Once the tenant is resolved: + +- repositories automatically filter `IMultiTenant` entities +- identity operations become tenant-specific +- settings and features can be resolved per tenant +- connection strings can switch dynamically for tenant databases +- audit logs can include tenant information + +This is why ABP multi-tenancy feels cohesive instead of bolted on. + + + +![Generated illustration](inline-1.png) + +## Multi-Tenancy Models Supported by ABP + +ABP supports three practical models. + +### 1) Single Database + +All tenants share one database. Tenant-specific rows are separated by `TenantId`. + +```mermaid +flowchart LR + T1[Tenant A] --> DB[(Shared Database)] + T2[Tenant B] --> DB + T3[Tenant C] --> DB +``` + +#### Advantages + +- lowest infrastructure cost +- simplest deployment model +- easiest migrations +- fast tenant provisioning +- easier aggregate reporting across tenants + +#### Disadvantages + +- weaker isolation than separate databases +- higher risk if filtering is misconfigured +- noisy tenants can affect others +- compliance requirements may be harder to satisfy + +### 2) Database Per Tenant + +Each tenant gets its own database. ABP switches connection strings based on tenant configuration. + +```mermaid +flowchart LR + T1[Tenant A] --> DB1[(Tenant A DB)] + T2[Tenant B] --> DB2[(Tenant B DB)] + T3[Tenant C] --> DB3[(Tenant C DB)] +``` + +#### Advantages + +- strongest data isolation +- easier tenant-specific backup and restore +- better compliance story +- easier per-tenant scaling and maintenance + +#### Disadvantages + +- more operational overhead +- migrations must run for every tenant database +- monitoring and backup complexity increases +- provisioning is slower than shared DB + +### 3) Hybrid Model + +Some tenants use the shared database, while premium or regulated tenants get dedicated databases. + +```mermaid +flowchart LR + T1[Tenant A] --> Shared[(Shared Database)] + T2[Tenant B] --> Shared + T3[Enterprise Tenant C] --> Dedicated[(Dedicated Database)] +``` + +#### Advantages + +- flexible cost/isolation tradeoff +- supports enterprise customers with stricter requirements +- lets you upgrade tenants without redesigning the app + +#### Disadvantages + +- most complex operational model +- more testing scenarios +- migration and support workflows become more involved + +### Comparison table + +| Model | Isolation | Cost | Operational Complexity | Reporting Across Tenants | Best Fit | +|---|---|---:|---:|---|---| +| Single Database | Medium | Low | Low | Easy | Early-stage SaaS, many small tenants | +| Database Per Tenant | High | High | High | Harder | Regulated or enterprise SaaS | +| Hybrid | Medium to High | Medium | High | Mixed | SaaS with tiered customer needs | + +### When to use / When NOT to use + +#### Use single database when + +- you need fast onboarding +- tenants are relatively small +- compliance requirements are moderate +- cross-tenant reporting matters + +#### Avoid single database when + +- customers require strict physical isolation +- one tenant can generate extreme load +- backup/restore must be tenant-specific + +#### Use database per tenant when + +- enterprise customers demand isolation +- you need tenant-specific maintenance windows +- compliance or residency rules are strict + +#### Avoid database per tenant when + +- you have thousands of tiny tenants +- your team is not ready for operational complexity +- your deployment and migration automation is immature + +#### Use hybrid when + +- you serve both SMB and enterprise customers +- you want a premium isolation tier +- you need a migration path from shared to dedicated databases + +#### Avoid hybrid when + +- your team wants the simplest possible operations +- you cannot invest in strong automation and observability + + + +![Generated illustration](inline-2.png) + +## Enabling Multi-Tenancy in ABP + +In ABP solutions, multi-tenancy is commonly controlled by a shared constant and configured through `AbpMultiTenancyOptions`. + +### `MultiTenancyConsts` + +Create or update `MultiTenancyConsts` in your `.Domain.Shared` project: + +```csharp +namespace Acme.Crm; + +public static class MultiTenancyConsts +{ + public const bool IsEnabled = true; +} +``` + +This keeps the setting centralized and easy to reference across modules. + +### Configure `AbpMultiTenancyOptions` + +In your web or HTTP API host module: + +```csharp +using Volo.Abp.MultiTenancy; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + }); +} +``` + +### Typical module configuration example + +```csharp +using Acme.Crm.MultiTenancy; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm; + +[DependsOn( + typeof(AbpAspNetCoreMultiTenancyModule) +)] +public class CrmHttpApiHostModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + }); + + Configure(options => + { + options.TenantKey = "__tenant"; + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + app.UseRouting(); + + if (MultiTenancyConsts.IsEnabled) + { + app.UseMultiTenancy(); + } + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseConfiguredEndpoints(); + } +} +``` + +### Relevant configuration files + +`appsettings.json` usually contains the default connection string and may contain tenant-related settings depending on your setup: + +```json +{ + "ConnectionStrings": { + "Default": "Server=localhost;Database=CrmShared;Trusted_Connection=True;TrustServerCertificate=True" + }, + "App": { + "SelfUrl": "https://localhost:44388" + } +} +``` + +If you use `DefaultTenantStore` for simple scenarios, tenant definitions can also be stored in configuration. In production, most real systems use the Tenant Management module and a database-backed tenant store. + +## Tenant Resolution Strategies + +Tenant resolution is where multi-tenancy becomes real. If the wrong tenant is resolved, everything after that is wrong too. + +ABP supports multiple strategies and lets you combine them. + +### Default resolution contributors + +Common contributors include: + +- current user claims +- query string: `?__tenant=acme` +- route value: `/api/acme/products` +- header: `__tenant: acme` +- cookie: `__tenant=acme` + +### Resolution flow through an HTTP request + +```mermaid +sequenceDiagram + participant Client + participant Middleware as MultiTenancy Middleware + participant Resolver as Tenant Resolvers + participant Store as ITenantStore + participant App as Application Service + participant Db as Repository/DbContext + + Client->>Middleware: HTTP Request + Middleware->>Resolver: Resolve tenant from claims/header/query/domain + Resolver->>Store: Find tenant by id or name + Store-->>Resolver: Tenant configuration + Resolver-->>Middleware: Active tenant + Middleware->>App: Execute under tenant scope + App->>Db: Query IMultiTenant entities + Db-->>App: Tenant-filtered data + App-->>Client: Response +``` + +### Subdomain and domain resolution + +Subdomain resolution is usually the cleanest production approach for SaaS. + +Example: `acme.mycrm.com` resolves tenant `acme`. + +```csharp +using Volo.Abp.MultiTenancy; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.AddDomainTenantResolver("{0}.mycrm.com"); + }); +} +``` + +You can also use full domain mapping patterns depending on your routing strategy. + +### Header-based resolution + +Useful for APIs, internal gateways, or integration testing. + +```csharp +Configure(options => +{ + options.TenantKey = "__tenant"; +}); +``` + +Request example: + +```http +GET /api/app/products HTTP/1.1 +Host: api.mycrm.com +__tenant: acme +Authorization: Bearer eyJ... +``` + +### Query string resolution + +Useful for demos and debugging, but not ideal as the primary production strategy. + +Example: + +```http +GET /api/app/products?__tenant=acme +``` + +### Route-based resolution + +If your API design includes tenant in the route, ABP can resolve from route values. + +Example route: + +```http +GET /api/acme/products +``` + +### Custom tenant resolver + +Sometimes tenant identification comes from a reverse proxy header, a custom JWT claim, or a partner integration contract. + +Create a custom contributor: + +```csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.MultiTenancy; + +public class XTenantResolveContributor : TenantResolveContributorBase +{ + public const string ContributorName = "X-Tenant-Resolver"; + + public override string Name => ContributorName; + + public override Task ResolveAsync(ITenantResolveContext context) + { + var httpContext = context.ServiceProvider + .GetRequiredService() + .HttpContext; + + if (httpContext == null) + { + return Task.CompletedTask; + } + + var tenant = httpContext.Request.Headers["X-Tenant"].ToString(); + + if (!tenant.IsNullOrWhiteSpace()) + { + context.TenantIdOrName = tenant; + } + + return Task.CompletedTask; + } +} +``` + +Register it: + +```csharp +Configure(options => +{ + options.TenantResolvers.Insert(0, new XTenantResolveContributor()); +}); +``` + +### Practical guidance on resolver choice + +Prefer this order in production: + +1. subdomain/domain +2. authenticated user claim +3. trusted gateway header +4. route value +5. query string only for development or support scenarios + +Do not rely on query string alone for sensitive production flows. + + + +![Generated illustration](inline-3.png) + +## Creating Tenant-Aware Entities + +The most important rule is simple: if an entity belongs to a tenant, implement `IMultiTenant`. + +### Product entity example + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Products; + +public class Product : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; private set; } + public string Name { get; private set; } + public decimal Price { get; private set; } + + protected Product() + { + } + + public Product(Guid id, Guid? tenantId, string name, decimal price) + : base(id) + { + TenantId = tenantId; + Name = name; + Price = price; + } + + public void ChangePrice(decimal price) + { + Price = price; + } +} +``` + +### Customer entity example + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Customers; + +public class Customer : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; private set; } + public string CompanyName { get; private set; } + public string Email { get; private set; } + + protected Customer() + { + } + + public Customer(Guid id, Guid? tenantId, string companyName, string email) + : base(id) + { + TenantId = tenantId; + CompanyName = companyName; + Email = email; + } +} +``` + +### Order aggregate example + +```csharp +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Orders; + +public class Order : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; private set; } + public Guid CustomerId { get; private set; } + public DateTime OrderDate { get; private set; } + public List Lines { get; private set; } + + protected Order() + { + Lines = new List(); + } + + public Order(Guid id, Guid? tenantId, Guid customerId, DateTime orderDate) + : base(id) + { + TenantId = tenantId; + CustomerId = customerId; + OrderDate = orderDate; + Lines = new List(); + } +} + +public class OrderLine +{ + public Guid ProductId { get; private set; } + public int Quantity { get; private set; } + public decimal UnitPrice { get; private set; } + + protected OrderLine() + { + } + + public OrderLine(Guid productId, int quantity, decimal unitPrice) + { + ProductId = productId; + Quantity = quantity; + UnitPrice = unitPrice; + } +} +``` + +### Design considerations + +A few rules matter a lot: + +- keep `TenantId` immutable after creation whenever possible +- avoid moving entities between tenants +- ensure child entities belong to the same tenant as the aggregate root +- do not mix host-owned and tenant-owned data in the same aggregate casually +- index `TenantId` in large tables + +### How ABP filters tenant data automatically + +When an entity implements `IMultiTenant`, ABP applies a tenant filter automatically. If tenant `A` is active, queries only return rows where `TenantId == A`. + +That means this repository call: + +```csharp +var products = await _productRepository.GetListAsync(); +``` + +returns only the current tenant's products in a shared database model. + +### Host-owned entities + +Not every entity should implement `IMultiTenant`. + +Examples of host-owned entities: + +- subscription plans +- global feature definitions +- platform announcements +- tenant catalog records + +Those entities are intentionally shared or host-scoped. + +## Working with `ICurrentTenant` + +`ICurrentTenant` is the API you will use most often in multi-tenant business logic. + +### Reading current tenant information + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Products; + +public class ProductAppService : ApplicationService +{ + private readonly ICurrentTenant _currentTenant; + + public ProductAppService(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public Task GetTenantInfoAsync() + { + var tenantId = _currentTenant.Id?.ToString() ?? "Host"; + var tenantName = _currentTenant.Name ?? "Host"; + + return Task.FromResult($"TenantId: {tenantId}, TenantName: {tenantName}"); + } +} +``` + +### Creating tenant-aware data + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Products; + +public class ProductAppService : ApplicationService +{ + private readonly IRepository _productRepository; + private readonly IGuidGenerator _guidGenerator; + private readonly ICurrentTenant _currentTenant; + + public ProductAppService( + IRepository productRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + { + _productRepository = productRepository; + _guidGenerator = guidGenerator; + _currentTenant = currentTenant; + } + + public async Task CreateAsync(string name, decimal price) + { + var product = new Product( + _guidGenerator.Create(), + _currentTenant.Id, + name, + price + ); + + await _productRepository.InsertAsync(product, autoSave: true); + } +} +``` + +### Changing tenant context temporarily + +Host-side services often need to execute logic for a specific tenant. + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Reporting; + +public class TenantReportService : ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + private readonly IRepository _productRepository; + + public TenantReportService( + ICurrentTenant currentTenant, + IRepository productRepository) + { + _currentTenant = currentTenant; + _productRepository = productRepository; + } + + public async Task GetProductCountAsync(Guid tenantId) + { + using (_currentTenant.Change(tenantId)) + { + return await _productRepository.GetCountAsync(); + } + } +} +``` + +### Nested tenant scopes + +Nested scopes are supported and useful when host-side orchestration calls tenant-specific logic. + +```csharp +using (_currentTenant.Change(null)) +{ + // host context + + using (_currentTenant.Change(tenantAId)) + { + // tenant A context + } + + using (_currentTenant.Change(tenantBId)) + { + // tenant B context + } +} +``` + +### Common mistake + +Do not cache `CurrentTenant.Id` globally or in singleton state. Tenant context is request or scope specific. + +## Data Isolation Mechanisms in ABP + +ABP's biggest value in multi-tenancy is not just storing tenant information. It is enforcing isolation consistently. + +### Automatic data filtering + +For `IMultiTenant` entities, ABP applies a tenant filter automatically. + +In a shared database model, a query conceptually becomes: + +```sql +SELECT Id, TenantId, Name, Price +FROM Products +WHERE TenantId = @CurrentTenantId +``` + +If soft delete is also enabled, it may look more like: + +```sql +SELECT Id, TenantId, Name, Price +FROM Products +WHERE TenantId = @CurrentTenantId + AND IsDeleted = 0 +``` + +The exact SQL depends on your provider and EF Core version, but the important point is that tenant filtering is automatic. + +### Tenant-specific repositories + +Standard repositories already respect tenant filters. In most cases, you do not need a special repository implementation just for tenant isolation. + +What you do need is discipline: + +- implement `IMultiTenant` +- avoid raw SQL that bypasses filters unless you know exactly what you are doing +- include tenant context in custom queries and projections + +### Disabling the tenant filter intentionally + +Host-side reporting or maintenance may require access to all tenants in a shared database. + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; + +namespace Acme.Crm.Reporting; + +public class HostReportingService : ITransientDependency +{ + private readonly IDataFilter _dataFilter; + private readonly IRepository _productRepository; + + public HostReportingService(IDataFilter dataFilter, IRepository productRepository) + { + _dataFilter = dataFilter; + _productRepository = productRepository; + } + + public async Task> GetAllProductsAcrossTenantsAsync() + { + using (_dataFilter.Disable()) + { + return await _productRepository.GetListAsync(); + } + } +} +``` + +Important limitation: this only works for shared database scenarios. In database-per-tenant mode, there is no single query that can magically span all tenant databases. + +### Unit of Work integration + +Tenant context and data filters participate naturally in ABP's Unit of Work pipeline. That means: + +- repository operations inside the same UoW use the same tenant context +- transaction boundaries remain consistent +- switching tenant context inside a UoW should be done carefully and intentionally + +### Security implications + +Automatic filtering is a safety net, not a substitute for security design. + +You still need: + +- authorization checks +- tenant-aware cache keys +- careful raw SQL usage +- secure resolver configuration +- tests that verify cross-tenant isolation + +## Seeding Host and Tenant Data + +Seeding is where many multi-tenant applications become inconsistent. The fix is to make seeding explicit, idempotent, and tenant-aware. + +### `IDataSeedContributor` basics + +ABP uses `IDataSeedContributor` for modular data seeding. + +### Host and tenant seed contributor example + +```csharp +using System; +using System.Threading.Tasks; +using Acme.Crm.Products; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Data; + +public class CrmDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IRepository _productRepository; + private readonly IGuidGenerator _guidGenerator; + private readonly ICurrentTenant _currentTenant; + + public CrmDataSeedContributor( + IRepository productRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + { + _productRepository = productRepository; + _guidGenerator = guidGenerator; + _currentTenant = currentTenant; + } + + public async Task SeedAsync(DataSeedContext context) + { + using (_currentTenant.Change(context.TenantId)) + { + if (await _productRepository.GetCountAsync() > 0) + { + return; + } + + await _productRepository.InsertAsync( + new Product(_guidGenerator.Create(), context.TenantId, "Starter Plan", 49), + autoSave: true + ); + + await _productRepository.InsertAsync( + new Product(_guidGenerator.Create(), context.TenantId, "Professional Plan", 99), + autoSave: true + ); + } + } +} +``` + +### Seeding host data + +When `context.TenantId` is `null`, the contributor runs in host context. Use that for: + +- subscription plans +- global settings +- default editions or features +- host admin data + +### Seeding tenant data + +When `context.TenantId` has a value, seed tenant-specific defaults such as: + +- default CRM pipeline stages +- sample products +- tenant admin roles +- onboarding templates + +### Triggering seeding + +In migrator or startup code: + +```csharp +await dataSeeder.SeedAsync(new DataSeedContext()); +``` + +For a specific tenant: + +```csharp +await dataSeeder.SeedAsync(new DataSeedContext(tenantId)); +``` + +### Best seeding rules + +- make seeders idempotent +- never assume execution order unless you control it +- seed host and tenant data separately when needed +- in database-per-tenant mode, run migrations and seeding for each tenant database + +## Multi-Tenant Authentication and Identity + +Authentication in a multi-tenant app is not just about validating a user. It is about validating a user in the correct tenant context. + +ABP Identity is already tenant-aware: + +- `IdentityUser` includes `TenantId` +- `IdentityRole` includes `TenantId` +- permissions can target host, tenant, or both sides + +### Tenant-specific users and roles + +This means: + +- host admins can exist with `TenantId = null` +- tenant users belong to a specific tenant +- tenant roles are isolated from other tenants + +### Login flow + +A typical login flow looks like this: + +```mermaid +sequenceDiagram + participant User + participant Browser + participant App + participant Resolver as Tenant Resolver + participant Identity as Identity Module + + User->>Browser: Open tenant URL or login page + Browser->>App: Request with tenant context + App->>Resolver: Resolve tenant + Resolver-->>App: Tenant identified + User->>App: Submit username/password + App->>Identity: Authenticate within tenant scope + Identity-->>App: User validated for tenant + App-->>Browser: Auth cookie/token with tenant context +``` + +### Tenant switching + +Tenant switching usually happens through one of these patterns: + +- user visits a tenant-specific subdomain +- login page asks for tenant name first +- gateway injects tenant header +- token contains tenant claim after authentication + +For SaaS UX, subdomain-based switching is usually the cleanest. + +### Permissions by multi-tenancy side + +When defining permissions, ABP lets you specify whether a permission applies to: + +- host +- tenant +- both + +That matters for admin screens. For example: + +- tenant creation should be host-only +- customer management should be tenant-only +- profile management may be both + +## Database Per Tenant Configuration + +Database-per-tenant is where ABP's tenant infrastructure becomes especially valuable. + +### Connection string management + +Each tenant can have its own connection string. If a tenant-specific connection string exists, ABP uses it. Otherwise, it falls back to the default connection string. + +That gives you hybrid support naturally. + +### Tenant configuration storage with `ITenantStore` + +`ITenantStore` is responsible for retrieving tenant configuration. + +It can provide: + +- tenant id +- tenant name +- connection strings +- activation state and related metadata depending on implementation + +In simple setups, `DefaultTenantStore` can read from configuration. In production, tenant data is usually stored in the database through the Tenant Management module. + +### Example tenant configuration in code-backed store scenarios + +Conceptually, a tenant record may look like this: + +```json +{ + "id": "2f7f8f8d-8f8d-4f8d-9f8d-2f7f8f8d8f8d", + "name": "acme", + "connectionStrings": { + "Default": "Server=sql01;Database=Crm_Acme;User Id=app;Password=***;TrustServerCertificate=True" + } +} +``` + +### Production-ready considerations + +For database-per-tenant setups: + +- encrypt or securely store connection strings +- automate tenant database creation +- automate migrations per tenant +- monitor schema drift +- support backup and restore per tenant +- define a fallback strategy for unavailable tenant databases + +### Dynamic connection string resolution in practice + +In ABP, once the tenant is resolved and the tenant store returns a tenant-specific connection string, EF Core DbContexts use that connection automatically. You usually do not write custom DbContext switching logic yourself. + +That is one of the biggest practical benefits of using ABP instead of hand-rolling multi-tenancy. + +## Building a Sample SaaS CRM Application + +Let's connect the pieces with a realistic example. + +Imagine a SaaS CRM product with these modules: + +- host administration +- tenant administration +- customer management +- product catalog +- order management +- subscription management + +### Host administration + +Host users can: + +- create tenants +- assign subscription plans +- decide shared DB vs dedicated DB +- seed tenant defaults +- monitor tenant health + +### Tenant administration + +Tenant admins can: + +- manage users and roles +- configure CRM settings +- manage customers and products +- view tenant-specific reports + +### Domain model split + +Host-owned entities: + +- `SubscriptionPlan` +- `TenantSubscription` +- `TenantProvisioningLog` + +Tenant-owned entities implementing `IMultiTenant`: + +- `Customer` +- `Product` +- `Order` +- `Invoice` +- `SalesPipeline` + +### Provisioning flow + +A practical tenant onboarding flow: + +```mermaid +flowchart TD + A[Host Admin Creates Tenant] --> B[Store Tenant Record] + B --> C{Dedicated DB?} + C -- Yes --> D[Create Tenant Database] + C -- No --> E[Use Shared Database] + D --> F[Run Migrations] + E --> F[Run Shared/Tenant Seed Logic] + F --> G[Create Tenant Admin User] + G --> H[Tenant Ready] +``` + +### Example application service for tenant provisioning + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Data; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Tenants; + +public class TenantProvisioningAppService : ApplicationService +{ + private readonly ITenantAppService _tenantAppService; + private readonly IDataSeeder _dataSeeder; + + public TenantProvisioningAppService( + ITenantAppService tenantAppService, + IDataSeeder dataSeeder) + { + _tenantAppService = tenantAppService; + _dataSeeder = dataSeeder; + } + + public async Task ProvisionAsync(string tenantName, string adminEmail) + { + var tenant = await _tenantAppService.CreateAsync(new TenantCreateDto + { + Name = tenantName, + AdminEmailAddress = adminEmail, + Password = "ChangeMe123*" + }); + + await _dataSeeder.SeedAsync(new DataSeedContext(tenant.Id)); + } +} +``` + +The exact DTOs and APIs may vary by ABP version and modules used, but the pattern is the same: create tenant, configure infrastructure, seed tenant data, then hand over to tenant admins. + + + +![Generated illustration](inline-4.png) + +## Advanced Multi-Tenant Scenarios + +Real SaaS systems need more than request-time filtering. + +### Background jobs in tenant context + +Background jobs must run under the correct tenant. + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Acme.Crm.Jobs; + +public class RecalculateMetricsArgs +{ + public Guid TenantId { get; set; } +} + +public class RecalculateMetricsJob : AsyncBackgroundJob, ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + + public RecalculateMetricsJob(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public override async Task ExecuteAsync(RecalculateMetricsArgs args) + { + using (_currentTenant.Change(args.TenantId)) + { + await Task.CompletedTask; + // tenant-aware work here + } + } +} +``` + +### Distributed events + +Include tenant information in event payloads or ensure handlers execute under the correct tenant scope. Otherwise, event consumers may process data in host context accidentally. + +### Caching per tenant + +Always include tenant id in cache keys. + +Bad: + +- `customer-list` + +Good: + +- `tenant:{tenantId}:customer-list` + +Without tenant-aware keys, cache leakage is almost guaranteed. + +### Feature management + +ABP feature management is useful for SaaS plans: + +- host defines available features +- tenants receive plan-based feature values +- premium tenants can unlock advanced modules + +Examples: + +- max users +- advanced reporting +- API access +- dedicated database eligibility + +### Setting management + +Settings can be layered: + +- application default +- host override +- tenant override +- user override where appropriate + +This is ideal for SMTP settings, branding, localization preferences, and business rules. + +### Audit logging + +Audit logs should capture tenant context so you can answer: + +- who changed what +- in which tenant +- from which client +- under which user identity + +### Localization + +Multi-tenant apps often need tenant-specific culture defaults, branding, or custom terminology. Keep localization extensible, but avoid turning every string into tenant-specific data unless there is a real business need. + +### Common pitfalls + +- forgetting `IMultiTenant` on a tenant-owned entity +- disabling tenant filters too broadly +- using raw SQL without tenant predicates +- not including tenant id in cache keys +- running jobs without tenant context +- migrating only the host database in DB-per-tenant mode +- allowing `TenantId` changes after entity creation +- trusting query string tenant resolution in production + +## Best Practices for Multi-Tenant ABP Applications + +Here are practical rules that hold up in production. + +1. **Implement `IMultiTenant` on every tenant-owned aggregate root.** Missing it is a classic data leak. +2. **Treat `TenantId` as immutable.** Moving data between tenants is rarely safe. +3. **Prefer subdomain or domain-based tenant resolution in production.** It is cleaner and harder to spoof than query strings. +4. **Use query string resolution mainly for development, support, or controlled integrations.** +5. **Index `TenantId` on large tables.** Shared database performance depends on it. +6. **Include tenant id in every cache key.** Never share cache entries across tenants accidentally. +7. **Test host context explicitly.** `CurrentTenant.Id == null` is a real execution mode. +8. **Test tenant context explicitly.** Verify that tenant A cannot see tenant B data. +9. **Be careful when disabling `IMultiTenant` filters.** Keep the scope as small as possible. +10. **Avoid raw SQL unless necessary.** If you use it, add tenant predicates yourself. +11. **Automate migrations for every tenant database.** Manual migration workflows do not scale. +12. **Make seed contributors idempotent.** Provisioning and recovery flows depend on repeatable seeding. +13. **Run background jobs under the correct tenant scope.** Pass tenant id in job args. +14. **Include tenant information in distributed event contracts or handler context.** +15. **Separate host-owned and tenant-owned entities clearly.** Ambiguous ownership creates bugs. +16. **Use feature management for plan-based SaaS behavior.** Do not hardcode plan logic everywhere. +17. **Use setting management for tenant-specific configuration.** Avoid custom config tables unless necessary. +18. **Secure tenant connection strings properly.** Treat them as secrets. +19. **Monitor tenant-level performance and failures.** One noisy tenant should be visible operationally. +20. **Design for tenant lifecycle operations.** Provisioning, suspension, upgrade, backup, restore, and deletion all matter. +21. **Plan reporting architecture early.** Cross-tenant reporting is easy in shared DB and harder in DB-per-tenant. +22. **Keep authorization tenant-aware.** Filtering data is not enough if permissions are wrong. +23. **Log tenant context in diagnostics and audit trails.** It speeds up support and incident response. +24. **Use hybrid architecture only when the business case is real.** It is powerful, but operationally expensive. +25. **Document your tenant model for the team.** Most multi-tenant bugs come from inconsistent assumptions. + +## Final Thoughts + +ABP does not make multi-tenancy trivial, but it makes it systematic. That matters a lot. Instead of scattering tenant checks across controllers, repositories, and middleware, you get a coherent model built around tenant resolution, current tenant context, data filters, tenant-aware identity, and connection string management. + +For most SaaS teams, the right path is: + +- start with shared database if your compliance and scale profile allow it +- model tenant ownership carefully with `IMultiTenant` +- use `ICurrentTenant` consistently +- automate seeding and provisioning +- move selected tenants to dedicated databases when the business case appears + +That is exactly the kind of evolution ABP supports well. + +## TL;DR + +- ABP provides built-in multi-tenancy with tenant resolution, `ICurrentTenant`, `IMultiTenant`, data filters, tenant management, and per-tenant connection strings. +- Shared database is simpler and cheaper; database-per-tenant gives stronger isolation; hybrid supports both at the cost of more complexity. +- Correct tenant resolution and entity modeling are the foundation of safe multi-tenancy in ABP. +- Use tenant-aware seeding, caching, background jobs, identity, and monitoring to avoid subtle production bugs. +- For real SaaS systems, ABP gives you the infrastructure you would otherwise spend months rebuilding. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/cover.png b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/cover.png new file mode 100644 index 0000000000..b7962924a9 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png new file mode 100644 index 0000000000..67da7fdaca Binary files /dev/null and b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png differ diff --git a/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png new file mode 100644 index 0000000000..9dfccabb2c Binary files /dev/null and b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png differ diff --git a/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png new file mode 100644 index 0000000000..a320e1a22a Binary files /dev/null and b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png differ diff --git a/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-4.png b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-4.png new file mode 100644 index 0000000000..1478c47a5e Binary files /dev/null and b/docs/en/Community-Articles/2026-06-05-implementing-multitenancy-in-abp-framework-a-complete/inline-4.png differ diff --git a/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-ai-agent.png b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-ai-agent.png new file mode 100644 index 0000000000..3c5bce22c0 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-ai-agent.png differ diff --git a/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-download-linux.png b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-download-linux.png new file mode 100644 index 0000000000..3f30213a31 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-download-linux.png differ diff --git a/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-solution-properties.png b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-solution-properties.png new file mode 100644 index 0000000000..913a193926 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-solution-properties.png differ diff --git a/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-solution-system.png b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-solution-system.png new file mode 100644 index 0000000000..be30260c71 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/abp-studio-solution-system.png differ diff --git a/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/cover.png b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/cover.png new file mode 100644 index 0000000000..d0cdbfa372 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/post.md b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/post.md new file mode 100644 index 0000000000..1a50443a8c --- /dev/null +++ b/docs/en/Community-Articles/2026-06-08-abp-studio-is-now-available-on-linux/post.md @@ -0,0 +1,116 @@ +# ABP Studio Is Now Available on Linux + +We are excited to announce that [ABP Studio](https://abp.io/studio), our cross-platform desktop application for ABP developers, is now available on Linux. + +With this release, Linux users can download and run ABP Studio as an **x64 AppImage**. This is an important step in making ABP Studio available wherever .NET and ABP developers prefer to work. + +## What can you do with ABP Studio? + +[ABP Studio](https://abp.io/studio) is a desktop application designed to make ABP development faster, easier, and more comfortable. It offers: + +* Easy creation of new solutions, from simple applications to distributed systems +* Visual architecture management for modular monolith and microservice solutions +* Solution exploration tools for entities, services, packages, and HTTP APIs +* Simplified running, debugging, and monitoring of multi-application solutions +* Kubernetes integration capabilities +* Built-in access to ABP-specific tooling and workflows + +The screenshots below were captured from ABP Studio on Linux through the AppImage. + +![ABP Studio solution system selection](abp-studio-solution-system.png) + +![ABP Studio solution properties step](abp-studio-solution-properties.png) + +## Linux Support Has Arrived + +ABP Studio has already been supporting multiple desktop environments, and now Linux joins that list. + +You can currently use ABP Studio on: + +* Windows x64 +* Windows ARM +* macOS Apple Silicon +* macOS Intel +* Linux x64 **(New!)** + +On Linux, the current distribution format is **AppImage**, which provides a practical way to distribute a desktop application across different Linux distributions without requiring a distribution-specific installer package. + +## What This Means for Developers + +Many ABP developers use Linux as their daily development environment. Until now, they needed to switch to another operating system to use ABP Studio. With Linux support, developers can now stay on their preferred platform and still benefit from ABP Studio's solution creation, architecture design, solution runner, monitoring, and integrated development experience. + +This is especially valuable for teams that already build and run their backend services on Linux-based environments and want to keep their development workflow aligned with that ecosystem. + +## AI Agent Is Available on Linux Too + +Another common question from the community has been whether ABP Studio AI Agent can be used on Linux machines. With this release, the answer is yes. + +Linux users can now use ABP Studio AI Agent in their own development environment. You can ask questions about your solution, plan implementation steps before changing code, and let the agent help with coding tasks while ABP Studio understands your ABP solution structure, build flow, and runtime context. + +![abp studio ai agent on linux](abp-studio-ai-agent.png) +For a deeper look at the AI Agent experience, see the original announcement: [Introducing ABP Studio AI Agent](https://abp.io/community/announcements/introducing-abp-studio-ai-agent-o1ni0toc). + +## Getting Started + +Downloading and running ABP Studio on Linux is simple: + +![abp studio download on linux](abp-studio-download-linux.png) + +1. Go to [abp.io/studio](https://abp.io/studio) +2. Download the **Linux x64 AppImage** +3. Open a terminal in the folder where the file was downloaded +4. Make the AppImage executable and run it + +```bash +chmod +x ./AbpStudio-stable.AppImage +./AbpStudio-stable.AppImage +``` + +Once launched, you can start using ABP Studio just like on the other supported platforms. + +## If the AppImage Does Not Run Directly + +Some Linux distributions may require additional runtime support for direct AppImage execution. + +For example, on some Ubuntu and Debian-based systems, you may need `libfuse2`: + +```bash +sudo apt update +sudo apt install libfuse2 +``` + +If FUSE is not available on your machine, you can still extract and run the AppImage manually: + +```bash +./AbpStudio-stable.AppImage --appimage-extract +./squashfs-root/AppRun +``` + +This fallback can be useful for testing or for environments where AppImage mounting is restricted. + +## Current Scope and Limitations + +This first Linux release is intentionally focused so we can deliver a reliable experience quickly. + +Here is the current scope: + +* Linux distribution is currently provided as an **x64 AppImage** +* **Linux ARM builds are not published yet** +* Depending on your Linux distribution, some native desktop or browser-related libraries may need to be installed +* When ABP Studio can detect a known native dependency problem, it tries to show guidance in the UI instead of leaving you with an unclear failure + +This means Linux support is ready to use today, and we will continue to improve the Linux experience in future releases. + +## A Better Cross-Platform Experience + +ABP Studio has always aimed to be the default way to start and develop ABP solutions. Linux support brings us closer to that goal by making the Studio experience more accessible across major desktop platforms. + +Whether you are creating a new solution, exploring packages and services, running multiple applications together, or monitoring runtime behavior, you can now do that on Linux too. + +## Conclusion + +We are happy to finally make ABP Studio available on Linux. + +This first release focuses on a practical and reliable target: **Linux x64 through AppImage**. It already opens the door for many developers who prefer Linux as their primary development environment, and it gives us a strong foundation to improve the Linux experience further. + +Please download it, try it in your daily workflow, and share your feedback with us. If you encounter a problem or want to request additional Linux targets like ARM, feel free to open an issue and let us know. diff --git a/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/Post.md b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/Post.md new file mode 100644 index 0000000000..a63ce8c77c --- /dev/null +++ b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/Post.md @@ -0,0 +1,496 @@ +ABP has supported multiple UI approaches for a long time, but many teams building line-of-business apps have been waiting for a first-class React option that feels native to the framework instead of bolted on. That is exactly what the ABP React template brings. + +If you are already using ABP for application services, modules, authentication, multi-tenancy, and code generation, the React template gives you a modern frontend stack without forcing you to hand-wire the same infrastructure in every project. You get React + TypeScript, a sensible project structure, generated API clients, authentication, localization, permission-aware UI, and a prebuilt admin experience that matches how ABP applications are typically built. + +This article explains what the ABP React template is, how it is structured, what you get out of the box, where it fits well, and what to watch for before adopting it. + +## What is the ABP React template? + +The ABP React template is the React UI option in ABP Framework's modern template system. It is available through ABP Studio v3.0+ and through the CLI when you create a modern solution. + +Typical commands look like this: + +```bash +abp new MyCompany.MyApp --modern --ui-framework react +``` + +Depending on your architecture, ABP supports React in these template types: + +- Layered: `app --modern` +- Single-layer: `app-nolayers --modern` +- Microservice: `microservice --modern` + +A very important detail: React UI is part of the modern template system. If you create a classic ABP solution, React UI is not included. + +At the time of its introduction, the React UI arrived as a beta/preview in 10.4.0-rc.1, with general availability planned for ABP 10.4 stable. So if you are evaluating it, make sure your ABP version and tooling align with the current docs. + +## The tech stack behind the template + +ABP did not build the React template around random choices. The stack is opinionated, but in a practical way that fits business applications. + +### Core frontend stack + +The template uses: + +- React +- TypeScript +- Vite +- TanStack Router +- TanStack Query +- Tailwind CSS +- shadcn/ui +- Radix UI +- Zod +- React Hook Form +- Axios +- Vitest + +That combination matters because it covers the common pain points in enterprise React apps: + +- Routing is structured and type-friendly. +- Server state is handled properly instead of scattered across components. +- Forms and validation are consistent. +- UI components are source-owned, not black-box widgets. +- Build and local development are fast. + +### Why this stack makes sense for ABP apps + +ABP applications usually have: + +- many CRUD screens +- authenticated users +- role and permission checks +- localization +- backend-driven DTOs +- modular features +- admin pages +- multi-tenant concerns + +The React template maps well to that reality. It is not trying to be a blank React starter. It is trying to be a production-ready frontend foundation for ABP-based applications. + + + +![Generated illustration](inline-1.png) + +## Project structure and template layout + +The structure depends on which ABP solution type you choose. + +### Layered and single-layer solutions + +In layered and single-layer templates, the React application lives under the solution root: + +```text +react/ +``` + +The Admin Console is embedded under the backend host and served from: + +```text +/admin-console/* +``` + +This setup is convenient when you want a unified application host and do not need to split the frontend into separate deployable apps. + +### Microservice solutions + +In the microservice template, the React application is placed under: + +```text +apps/react/ +``` + +The Admin Console is a separate app: + +```text +apps/react-admin-console/ +``` + +It is typically served through the Web Gateway using YARP. This is a better fit when your architecture already separates concerns across gateways and independently deployed services. + +### Common folders you will work with + +The frontend typically includes folders like these: + +- `components/` for layouts, reusable UI, and feature components +- `lib/` for auth, API setup, theme, and routing helpers +- `pages/` for page-level components +- `routes/` for route definitions +- `locales/` for translations + +This is a familiar structure for React teams, but it also reflects ABP conventions well enough that backend and frontend concerns stay aligned. + +## What you get out of the box + +The main value of the ABP React template is not that it uses React. Plenty of templates do that. The real value is that it wires React into ABP's application model. + + + +![Generated illustration](inline-2.png) + +## Authentication and authorization + +Authentication is one of the first places where many custom React frontends get messy. The ABP React template handles this using OpenID Connect with Authorization Code flow and PKCE against the ABP Auth Server. + +That means you get a setup suitable for modern browser-based applications without having to build the auth plumbing from scratch. + +### What is included + +Out of the box, you get: + +- OIDC integration +- route protection +- auth-related helpers and hooks +- support for ABP's authorization model +- permission-aware navigation and UI + +Permission-aware UI is especially useful in real applications. Instead of hardcoding role checks everywhere, you can use ABP's permission system in the frontend so menus, buttons, and screens reflect what the current user is actually allowed to do. + +For example, a user without the right permission should not see management actions just because the page rendered successfully. + +### Why this matters in practice + +In many projects, teams secure the backend correctly but forget to make the frontend behave consistently. The result is confusing UX: + +- users see actions they cannot execute +- menus show modules they cannot access +- pages fail after navigation instead of being guarded earlier + +The ABP React template reduces that mismatch. + +## API integration without hand-written client boilerplate + +One of the most useful parts of the template is the generated API client approach. + +ABP can generate frontend API clients from OpenAPI definitions, so your React app consumes backend endpoints using generated contracts instead of duplicated DTO definitions or hand-written fetch code. + +### Why this is a big deal + +Without generated clients, frontend/backend integration often drifts over time: + +- DTOs change but frontend types do not +- query strings are built inconsistently +- error handling varies by developer +- service layers become repetitive + +With the ABP React template, Axios is already set up and typically used together with TanStack Query. That gives you a cleaner pattern for data fetching, caching, invalidation, and loading states. + +A simplified example looks like this: + +```tsx +import { useQuery } from '@tanstack/react-query'; +import { identityUserControllerGetList } from '@/client'; + +export function UsersPage() { + const query = useQuery({ + queryKey: ['users'], + queryFn: () => identityUserControllerGetList({ maxResultCount: 10, skipCount: 0 }), + }); + + if (query.isLoading) return
Loading...
; + if (query.isError) return
Failed to load users.
; + + return ( +
    + {query.data.items.map((user) => ( +
  • {user.userName}
  • + ))} +
+ ); +} +``` + +The exact generated function names may vary based on your solution, but the pattern is the point: use generated contracts, wrap them with TanStack Query, and keep components focused on UI. + +## UI system and customization model + +The ABP React template uses shadcn/ui with Radix UI primitives and Tailwind CSS. That is a smart choice for teams that want control over their UI instead of being locked into a vendor component library. + +### Source-owned components + +This is one of the most important characteristics of the template: the frontend is source-owned. + +Your components, pages, and UI primitives live in your solution. They are not hidden behind proprietary packages that you cannot meaningfully adjust. + +That gives you freedom to: + +- restyle components +- change layouts +- modify route structure +- reorganize menus +- build your own design language on top +- customize page composition without fighting framework internals + +In practice, ABP places UI primitives under locations like `src/components/ui/`, so you can modify them directly when needed. + +### Theme support + +The template supports light, dark, and system themes. Styling is based on Tailwind CSS and CSS variables, which is a good fit for maintainable theming. + +If your team needs brand-level customization, this approach is usually easier to work with than overriding a large third-party theme package. + +### A practical trade-off + +Source-owned UI gives you control, but it also means you own consistency. If every developer changes shared components casually, the design system can drift quickly. + +The template gives you freedom. Your team still needs discipline. + + + +![Generated illustration](inline-3.png) + +## Forms, validation, and developer ergonomics + +Business apps live and die by form quality. The ABP React template uses React Hook Form and Zod, which is a solid setup for forms that need validation, predictable behavior, and clean code. + +This stack helps with: + +- schema-driven validation +- reusable form components +- clearer error handling +- less form boilerplate +- better TypeScript integration + +For ABP-style applications with lots of create/update dialogs and data-entry screens, that is a practical choice. + +A minimal example might look like this: + +```tsx +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; + +const schema = z.object({ + name: z.string().min(1, 'Name is required'), +}); + +type FormValues = z.infer; + +export function ProductForm() { + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: { name: '' }, + }); + + const onSubmit = (values: FormValues) => { + console.log(values); + }; + + return ( +
+ +

{form.formState.errors.name?.message}

+ +
+ ); +} +``` + +The exact ABP project will likely wrap inputs with shared UI components, but the workflow remains familiar. + +## Localization and multi-tenancy + +If you are building a SaaS product or an internal enterprise application for multiple regions, this part matters a lot. + +The ABP React template includes support for: + +- localization with i18next +- ABP localization resources and keys +- culture information from the backend +- end-to-end multi-tenant scenarios + +This is where ABP has always been strong, and the React template carries that strength into the frontend. + +### Why this matters + +Many React starters look great until you need to answer these questions: + +- Where does the current tenant come from? +- How does the frontend know which culture is active? +- How are permission and tenant context propagated to API requests? +- How do I keep localization aligned with backend resources? + +With the ABP React template, these are not afterthoughts. + + + +![Generated illustration](inline-4.png) + +## The built-in Admin Console + +Another notable piece is the React-based Admin Console provided through `Volo.Abp.AdminConsole`. + +This gives you a prebuilt admin UI for common ABP modules such as: + +- Identity +- Settings +- Audit Logs +- and other standard management features + +### Why it is useful + +For many projects, admin screens are necessary but not differentiating. You need them, but you do not want to spend weeks rebuilding infrastructure screens that ABP already understands well. + +The Admin Console helps you start with a solid baseline. + +### One thing to be clear about + +The Admin Console is managed by ABP and is often intended to be used mostly as-is. If your project requires heavy customization there, you should evaluate that path carefully before assuming it behaves like the rest of your source-owned frontend. + +That is not necessarily a problem, but it is worth knowing early. + +## Runtime configuration with dynamic-env.json + +A practical detail that deserves more attention is `dynamic-env.json` under the public folder. + +This file allows runtime configuration for values such as: + +- OAuth issuer +- client ID +- API base URLs +- related environment settings + +That means you can adapt environment-specific settings without rebuilding the frontend for every deployment target. + +For teams deploying across dev, staging, and production, this is much more convenient than baking every environment value into the build. + +### Real-world example + +Suppose your staging environment uses different hostnames and an alternate auth server URL. With runtime configuration, you can update those values at deployment time instead of producing another frontend artifact just for staging. + +That said, when you change URLs or hostnames, remember that runtime config is only part of the story. You also need to update OpenIddict client settings such as redirect URIs and related auth configuration. + +## Development workflow + +The local development loop is straightforward. + +### Running the application + +You typically: + +1. start the backend host with `dotnet run` +2. go to the React app folder +3. install dependencies with `npm install` +4. start the frontend with `npm run dev` + +Vite keeps frontend startup and rebuild times fast, which helps a lot when you are iterating on pages and forms. + +### Production build + +For production, you build the frontend with: + +```bash +npm run build +``` + +This outputs the app to `dist/`. How it is served depends on the template: + +- via the backend host in simpler solution structures +- via a gateway or separate frontend host in microservice setups + +### Testing + +The template uses Vitest and React Testing Library, with scripts such as: + +- `npm run test` +- `npm run test:coverage` + +This is a good baseline. It encourages teams to test UI behavior without dragging in slow or outdated frontend test setups. + +## When to use the ABP React template + +The ABP React template is a strong fit when your project already wants ABP's backend capabilities and your team prefers React on the frontend. + +Use it when: + +- you are starting a new ABP project with a modern template +- you want React + TypeScript with ABP conventions already wired in +- you need authentication, permissions, localization, and multi-tenancy from day one +- you want generated API clients instead of duplicated DTOs +- you prefer source-owned UI components +- your app is admin-heavy, form-heavy, or module-heavy + +### Good fit examples + +- SaaS admin portals +- internal enterprise dashboards +- operations and management systems +- modular business applications with many CRUD workflows +- ABP-based platforms needing a modern React frontend + +## When not to use it + +It is not the right default for every case. + +Avoid or reconsider it when: + +- you are on a classic ABP template and do not want to migrate to modern templates +- your team wants a very custom frontend architecture unrelated to ABP conventions +- your application is mostly marketing pages or content-driven public pages +- you do not need ABP's auth, permission, module, or tenant model +- you expect deep customization of every built-in admin experience without validating the boundaries first + +### A practical warning + +Do not choose the template just because it uses React. Choose it because you want React inside the ABP ecosystem. + +That distinction matters. + +## Common pitfalls and things to check early + +The template is productive, but there are a few details worth validating at the beginning of a project. + +### 1. Make sure you are using a modern template + +This is the most common source of confusion. React UI is for modern templates. If you create a classic solution, the React UI option is not there. + +### 2. Align auth settings across environments + +If you change frontend URLs, callback paths, ports, or hostnames, update: + +- runtime frontend configuration +- OpenIddict client redirect URIs +- post-logout redirect URIs if applicable +- any gateway or proxy configuration involved + +A lot of login issues come from partial updates here. + +### 3. Treat source-owned UI like a product asset + +Because the UI code is in your solution, it is easy to modify. That is good. It also means teams can slowly lose consistency unless they define clear frontend standards. + +### 4. Understand the Admin Console boundary + +The Admin Console is valuable, but it is not the same customization surface as your app pages. If extensive admin customization is a requirement, validate that path before committing architecture decisions around it. + +## Why the ABP React template is different from a plain React starter + +A plain React starter gives you flexibility, but also leaves many critical concerns unresolved. The ABP React template starts from a different assumption: most business applications need the same infrastructure pieces, and those pieces should work together. + +Compared to a generic starter, ABP gives you tighter integration for: + +- auth and authorization +- generated API clients +- localization +- tenant-aware applications +- modular backend alignment +- admin capabilities +- solution-level conventions + +That makes it less minimal than a blank React scaffold, but much more useful for real ABP projects. + +## Final thoughts + +The ABP React template is not interesting because it says React on the label. It is interesting because it brings React into ABP's application model in a way that feels intentional. + +You get a modern frontend stack, source-owned customization, generated client integration, and the ABP features many teams actually need in production: permissions, localization, multi-tenancy, and admin tooling. + +If your team already values ABP on the backend and wants React on the frontend, this template is one of the fastest ways to get to a serious foundation without spending the first sprint rebuilding plumbing. + +## TL;DR + +- The ABP React template is available in ABP's modern template system, not classic templates. +- It uses a practical stack: React, TypeScript, Vite, TanStack Router/Query, shadcn/ui, Tailwind, Zod, and Axios. +- Key strengths are generated API clients, OIDC auth, permission-aware UI, localization, and multi-tenancy. +- The frontend is source-owned, which gives you flexibility but also requires discipline. +- It is a strong choice for ABP-based business apps, especially admin-heavy and SaaS-style applications. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/cover.png b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/cover.png new file mode 100644 index 0000000000..030338577f Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-1.png b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-1.png new file mode 100644 index 0000000000..32b829bbf4 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-1.png differ diff --git a/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-2.png b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-2.png new file mode 100644 index 0000000000..11d686df90 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-2.png differ diff --git a/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-3.png b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-3.png new file mode 100644 index 0000000000..91d7568b10 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-3.png differ diff --git a/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-4.png b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-4.png new file mode 100644 index 0000000000..73bc2d57fc Binary files /dev/null and b/docs/en/Community-Articles/2026-06-08-getting-started-with-the-abp-react-template/inline-4.png differ diff --git a/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/Post.md b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/Post.md new file mode 100644 index 0000000000..5997617f4c --- /dev/null +++ b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/Post.md @@ -0,0 +1,1363 @@ +# Implementing Multi-Tenancy in ABP Framework: A Complete Practical Guide + +Multi-tenancy is one of those features that looks straightforward on a whiteboard and gets complicated the moment real customers, databases, authentication, billing, and background jobs enter the picture. + +If you are building a SaaS application on ASP.NET Core, you need more than a `TenantId` column scattered across a few tables. You need a consistent way to resolve the tenant for each request, isolate data safely, seed tenant-specific records, handle host-level administration, and support different deployment models as your product grows. + +This is where ABP Framework helps a lot. Multi-tenancy is built into the framework's core architecture: request pipeline, entity model, data filters, tenant store, identity integration, settings, features, and modules all understand the concept of host and tenant. + +In this guide, we will go from architecture to implementation. The focus is practical: how ABP multi-tenancy works, how to enable it correctly, how to model tenant-aware entities, how request resolution flows through the app, and how to build a real SaaS CRM application on top of it. + +## Introduction to Multi-Tenancy + +Multi-tenancy means a single software application serves multiple customers, where each customer is treated as a separate tenant. A tenant usually has its own users, roles, settings, data, and operational boundaries. + +In SaaS terms: + +- The **host** is the software provider, the platform owner. +- A **tenant** is a customer organization using the system. + +### Single-tenant vs multi-tenant architecture + +A **single-tenant** system typically gives each customer a separate deployed application instance, often with a separate database and infrastructure boundary. + +A **multi-tenant** system shares the application runtime and, depending on the model, may share databases too. + +**Single-tenant** usually gives: + +- simpler isolation reasoning +- easier customer-specific customization +- higher infrastructure cost +- more operational duplication + +**Multi-tenant** usually gives: + +- better resource efficiency +- easier centralized updates +- lower cost per customer +- more complexity around isolation and scaling + +### Why multi-tenancy matters for SaaS + +Most SaaS products eventually need: + +- centralized onboarding +- tenant-specific authentication and authorization +- subscription plans and features +- controlled data isolation +- operational efficiency at scale + +Without a solid multi-tenancy model, these become ad hoc implementations. That tends to create subtle security bugs and hard-to-maintain code. + +### Advantages and challenges + +**Advantages** + +- Better infrastructure utilization +- Centralized deployment and upgrades +- Lower operating cost per customer +- Easier platform-wide monitoring and governance +- Feature and setting management per tenant + +**Challenges** + +- Preventing cross-tenant data leakage +- Tenant-aware caching and background jobs +- Managing request resolution correctly +- Handling database-per-tenant operations +- Supporting host-side administration cleanly + +### Why ABP Framework simplifies it + +ABP does not treat multi-tenancy as a naming convention. It treats it as a first-class capability. + +You get: + +- built-in tenant resolution pipeline +- `ICurrentTenant` for runtime tenant context +- `IMultiTenant` support for entities +- automatic data filters +- host and tenant side concepts across modules +- tenant management module and tenant store abstraction +- identity, features, settings, and permissions that understand tenants + +That combination is what makes ABP especially useful for serious SaaS applications. + +## Understanding ABP Multi-Tenancy Architecture + +At the center of ABP multi-tenancy are a few concepts that appear everywhere in the application lifecycle. + +### Tenant concept + +A tenant is usually a customer organization. In a CRM product, one tenant might be `acme`, another might be `globex`. Each one has: + +- its own users +- its own roles +- its own application data +- optionally its own connection string +- optionally its own features and settings + +The host is not just another tenant. It is the platform owner context. + +### Host side vs tenant side + +ABP explicitly distinguishes between host-side and tenant-side operations. + +**Host side** typically includes: + +- creating and managing tenants +- viewing subscription status +- assigning features and plans +- managing platform-level settings +- running cross-tenant operations + +**Tenant side** typically includes: + +- managing tenant users and roles +- working with tenant business data +- updating tenant settings +- tenant-specific administration + +In shared database setups, host-side records usually have `TenantId == null`. Tenant-side records have a concrete `TenantId`. + +### `ICurrentTenant` + +`ICurrentTenant` is the runtime source of truth for tenant context. + +It exposes: + +- `Id` +- `Name` +- `IsAvailable` + +In application services, domain services, controllers, and many ABP base classes, it is already available or easy to inject. + +Example: + +```csharp +public class DashboardAppService : ApplicationService +{ + public string GetContextInfo() + { + if (!CurrentTenant.IsAvailable) + { + return "Host context"; + } + + return $"Tenant: {CurrentTenant.Name} ({CurrentTenant.Id})"; + } +} +``` + +If `CurrentTenant.Id` is `null`, you are in host context. + +### Tenant resolution pipeline + +Before your application logic runs, ABP tries to determine which tenant the request belongs to. + +ABP uses a set of tenant resolvers, executed in order. Common sources include: + +- current user claims +- query string parameter `__tenant` +- route value `__tenant` +- header `__tenant` +- cookie `__tenant` +- domain or subdomain pattern + +The middleware `UseMultiTenancy()` plugs this into the ASP.NET Core pipeline. + +Because diagram DSLs are not suitable here, the request flow is best explained in steps: + +1. An HTTP request reaches the ASP.NET Core pipeline. +2. ABP multi-tenancy middleware runs. +3. Configured tenant resolvers inspect the request. +4. If a tenant identifier is found, ABP loads tenant information from the tenant store. +5. `ICurrentTenant` is populated for the rest of the request scope. +6. Repository queries and data filters automatically use the current tenant context. +7. Authentication, authorization, settings, features, and caches can all behave tenant-aware. + +### `IMultiTenant` + +`IMultiTenant` marks an entity as tenant-aware. + +It defines: + +```csharp +public interface IMultiTenant +{ + Guid? TenantId { get; } +} +``` + +In practice, entities implementing this interface participate in ABP's multi-tenant data filtering behavior. + +### Data filters + +ABP automatically applies data filters to entities implementing `IMultiTenant`. In a shared database model, queries are filtered so the current tenant sees only its own records. + +That means code like this: + +```csharp +var products = await _productRepository.GetListAsync(); +``` + +will only return the current tenant's products when `Product` implements `IMultiTenant`. + +If you are on host side and intentionally need cross-tenant access, you can disable the filter temporarily: + +```csharp +using (_dataFilter.Disable()) +{ + var allProducts = await _productRepository.GetListAsync(); +} +``` + +This is powerful and dangerous. Use it carefully. + +### Tenant Management module + +ABP's Tenant Management module gives you a production-ready foundation for tenant administration. + +It provides: + +- tenant CRUD operations +- tenant connection strings +- tenant store integration +- management UI in ABP-based solutions +- admin user provisioning support + +In real projects, this removes a lot of plumbing work. + + + +![Generated illustration](inline-1.png) + +## Multi-Tenancy Models Supported by ABP + +ABP supports all common SaaS multi-tenancy models. + +### 1. Single database + +All tenants share one database and typically the same schema. Data is separated logically using `TenantId` and data filters. + +**How it works** + +- one database for host and tenant data +- tenant-aware tables contain a `TenantId` +- ABP filters queries automatically based on current tenant + +**Advantages** + +- simplest to start with +- cheapest operationally +- easiest schema migration story +- simpler reporting when all tenant data is in one place + +**Disadvantages** + +- strongest need for careful isolation +- noisy-neighbor effects at database level +- scaling limits can appear earlier +- large shared tables can become operational pain points + +### 2. Database per tenant + +Each tenant gets a dedicated database. The host may still use a separate shared database for platform-level data. + +**How it works** + +- tenant-specific connection strings are stored per tenant +- ABP resolves the current tenant, then resolves the tenant's database connection +- repositories work in the tenant's database context + +**Advantages** + +- stronger isolation +- easier compliance story for some customers +- easier tenant-specific restore and backup +- tenant-level scaling options + +**Disadvantages** + +- more complex provisioning +- harder cross-tenant reporting +- more migration orchestration +- greater operational overhead + +### 3. Hybrid model + +Some tenants share a database, others get dedicated databases. + +This is common in enterprise SaaS. + +**Typical scenario** + +- small and mid-market tenants use shared infrastructure +- premium or regulated customers get dedicated databases +- host still manages everything through the same application codebase + +**Advantages** + +- flexible cost model +- premium isolation for large customers +- efficient default for smaller tenants + +**Disadvantages** + +- highest operational complexity +- more provisioning paths to maintain +- more testing combinations + +### Comparison + +| Model | Database layout | Strengths | Weaknesses | Best fit | +|---|---|---|---|---| +| Single database | All tenants share one DB | Simple, cheap, easy migrations | Lower isolation, shared load | Early-stage SaaS, internal SaaS | +| Database per tenant | One DB per tenant | Strong isolation, restore flexibility | Higher ops cost, harder reporting | Enterprise SaaS, regulated environments | +| Hybrid | Shared for some, dedicated for others | Flexible pricing and isolation | Most complex to operate | Growing SaaS platforms | + +### When to use / When NOT to use + +**Use single database when:** + +- you are launching an MVP or early SaaS +- tenant counts are moderate +- compliance requirements are manageable +- operational simplicity matters most + +**Do not use single database when:** + +- customers require physical data isolation +- large tenants can dominate database resources +- tenant-specific backup and restore is mandatory + +**Use database per tenant when:** + +- compliance and isolation matter a lot +- customers pay enough to justify dedicated infrastructure +- you need tenant-level backup, restore, and scaling + +**Do not use database per tenant when:** + +- you are optimizing for low operational overhead +- your team is not ready to automate migrations and provisioning +- most tenants are very small and margins are tight + +**Use hybrid when:** + +- you want shared infrastructure by default +- you have a premium enterprise tier +- you need a migration path from shared to dedicated tenants + +**Do not use hybrid when:** + +- your platform operations are still immature +- you want to minimize architectural branching + + + +![Generated illustration](inline-2.png) + +## Enabling Multi-Tenancy in an ABP Application + +Multi-tenancy is off by default in the framework, although ABP startup templates commonly enable it for you. + +A clean approach is to centralize the flag in `MultiTenancyConsts`. + +### `MultiTenancyConsts` + +```csharp +namespace Acme.Crm; + +public static class MultiTenancyConsts +{ + public const bool IsEnabled = true; +} +``` + +### Configure `AbpMultiTenancyOptions` + +In your HTTP API Host module: + +```csharp +using Volo.Abp.MultiTenancy; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + }); +} +``` + +### Add middleware + +In `OnApplicationInitialization`: + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + app.UseAuthentication(); + + if (MultiTenancyConsts.IsEnabled) + { + app.UseMultiTenancy(); + } + + app.UseAuthorization(); + app.UseConfiguredEndpoints(); +} +``` + +A practical rule: place `UseMultiTenancy()` after authentication setup and before application endpoints. + +### Configuration example + +If you use ABP's default tenant store from configuration in a simple setup, you can define tenants in `appsettings.json`. + +```json +{ + "TenantManagement": { + "Tenants": [ + { + "Id": "11111111-1111-1111-1111-111111111111", + "Name": "acme" + }, + { + "Id": "22222222-2222-2222-2222-222222222222", + "Name": "globex" + } + ] + } +} +``` + +In real applications, the Tenant Management module is usually a better choice than static config. + +## Tenant Resolution Strategies + +Tenant resolution is where many real-world mistakes happen. The framework can only isolate data correctly if the tenant is resolved correctly. + +### Default resolvers + +ABP can resolve tenant information from: + +- authenticated user claims +- query string: `__tenant` +- route value: `__tenant` +- header: `__tenant` +- cookie: `__tenant` + +### Configure resolver options + +```csharp +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.AddDefaultResolvers(); + }); +} +``` + +### Subdomain resolution + +Subdomain-based tenant resolution is common in SaaS. + +Examples: + +- `acme.mycrm.com` +- `globex.mycrm.com` + +Configuration: + +```csharp +Configure(options => +{ + options.AddDomainTenantResolver("{0}.mycrm.com"); +}); +``` + +Now a request to `acme.mycrm.com` resolves tenant name `acme`. + +### Domain resolution + +You can also map full domains, especially for custom domains. + +For example, a tenant might use: + +- `crm.acme.com` +- `sales.globex.io` + +You typically handle these with custom tenant resolution logic backed by your own domain mapping table. + +### Header-based resolution + +Useful for internal APIs, gateways, or backend-to-backend communication. + +Example request: + +```http +GET /api/products +__tenant: acme +``` + +This is convenient, but do not trust arbitrary tenant headers from public traffic unless a trusted gateway injects them. + +### Query string resolution + +Useful for demos, diagnostics, or very simple integrations. + +Example: + +```text +https://api.mycrm.com/api/products?__tenant=acme +``` + +It works, but it is not usually the best production UX. + +### Route-based resolution + +You can support routes like: + +```text +/api/{__tenant}/products +``` + +This is more common in APIs than browser-based SaaS frontends. + +### Custom tenant resolver + +For custom logic, implement a tenant resolve contributor. + +```csharp +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +public class ApiKeyTenantResolveContributor : TenantResolveContributorBase +{ + public override string Name => "ApiKey"; + + public override Task ResolveAsync(ITenantResolveContext context) + { + var httpContext = context.GetHttpContext(); + var apiKey = httpContext?.Request.Headers["X-Api-Key"].ToString(); + + if (string.IsNullOrWhiteSpace(apiKey)) + { + return Task.CompletedTask; + } + + if (apiKey == "acme-key") + { + context.Handled = true; + context.TenantIdOrName = "acme"; + } + + return Task.CompletedTask; + } +} +``` + +Register it: + +```csharp +Configure(options => +{ + options.TenantResolvers.Insert(0, new ApiKeyTenantResolveContributor()); +}); +``` + +Putting your resolver at the beginning gives it priority. + +### How tenant identification flows through an HTTP request + +A practical request flow looks like this: + +1. Browser requests `https://acme.mycrm.com/api/app/customers`. +2. ASP.NET Core receives the request. +3. `UseMultiTenancy()` runs. +4. Domain resolver extracts `acme` from the host. +5. ABP loads tenant metadata from `ITenantStore`. +6. `ICurrentTenant.Id` is set for the request scope. +7. Authentication and authorization proceed in tenant context. +8. Repositories apply the tenant data filter. +9. Only `acme` data is returned. + +That is the core path you should have in mind when debugging tenant issues. + + + +![Generated illustration](inline-3.png) + +## Creating Tenant-Aware Entities + +The heart of tenant data isolation is entity design. + +### Using `IMultiTenant` + +Let's model a SaaS CRM with `Product`, `Customer`, and `Order`. + +```csharp +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +public class Product : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } + public string Name { get; private set; } + public decimal Price { get; private set; } + + protected Product() + { + } + + public Product(Guid id, string name, decimal price, Guid? tenantId) + : base(id) + { + TenantId = tenantId; + Name = name; + Price = price; + } +} +``` + +```csharp +public class Customer : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } + public string Name { get; private set; } + public string Email { get; private set; } + + protected Customer() + { + } + + public Customer(Guid id, string name, string email, Guid? tenantId) + : base(id) + { + TenantId = tenantId; + Name = name; + Email = email; + } +} +``` + +```csharp +public class Order : FullAuditedAggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } + public Guid CustomerId { get; private set; } + public decimal TotalAmount { get; private set; } + + protected Order() + { + } + + public Order(Guid id, Guid customerId, decimal totalAmount, Guid? tenantId) + : base(id) + { + TenantId = tenantId; + CustomerId = customerId; + TotalAmount = totalAmount; + } +} +``` + +### Aggregate root considerations + +In a multi-tenant model: + +- aggregate roots should clearly belong to host or tenant side +- child entities usually follow the aggregate root's tenant boundary +- references between aggregates from different tenants should be avoided +- host-side entities should not accidentally depend on tenant-scoped data + +A simple rule is helpful: if an entity exists only inside a tenant's business workflow, make it tenant-aware. + +### Automatically assigning `TenantId` + +In application services, use `CurrentTenant.Id` when creating tenant-owned entities. + +```csharp +public class ProductAppService : ApplicationService +{ + private readonly IRepository _productRepository; + + public ProductAppService(IRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task CreateAsync(string name, decimal price) + { + var product = new Product( + GuidGenerator.Create(), + name, + price, + CurrentTenant.Id + ); + + await _productRepository.InsertAsync(product, autoSave: true); + return product.Id; + } +} +``` + +### How ABP filters tenant data automatically + +Suppose `acme` is the current tenant. + +This code: + +```csharp +var customers = await _customerRepository.GetListAsync(); +``` + +will generate SQL conceptually similar to: + +```sql +SELECT * +FROM CrmCustomers +WHERE TenantId = @CurrentTenantId +``` + +If the filter is disabled from host context, the generated query may no longer include the tenant predicate. + +The exact SQL depends on EF Core and your provider, but the practical point is the same: ABP injects the tenant boundary for you. + +## Working with `ICurrentTenant` + +`ICurrentTenant` is not just for reading tenant info. It is also how you intentionally switch tenant context during controlled operations. + +### Reading current tenant information + +```csharp +public class TenantInfoAppService : ApplicationService +{ + public object GetCurrent() + { + return new + { + CurrentTenant.Id, + CurrentTenant.Name, + CurrentTenant.IsAvailable + }; + } +} +``` + +### Changing tenant context + +A host-side admin service may need to run work inside a specific tenant. + +```csharp +public class TenantReportingAppService : ApplicationService +{ + private readonly IRepository _customerRepository; + + public TenantReportingAppService(IRepository customerRepository) + { + _customerRepository = customerRepository; + } + + public async Task GetCustomerCountAsync(Guid tenantId) + { + using (CurrentTenant.Change(tenantId)) + { + return await _customerRepository.GetCountAsync(); + } + } +} +``` + +### Switching to host context + +```csharp +using (CurrentTenant.Change(null)) +{ + // host-side logic here +} +``` + +### Nested tenant scopes + +ABP restores the previous tenant automatically after the `using` block ends. + +```csharp +using (CurrentTenant.Change(tenantA)) +{ + // tenant A + + using (CurrentTenant.Change(tenantB)) + { + // tenant B + } + + // back to tenant A +} +``` + +This matters in background jobs, cross-tenant maintenance tasks, and provisioning routines. + +## Data Isolation Mechanisms in ABP + +ABP's multi-tenancy model is more than a `TenantId` field. + +### Automatic data filtering + +If an entity implements `IMultiTenant`, ABP applies a filter automatically. This reduces the amount of repetitive tenant checks you need to write manually. + +That said, you should still think in layers: + +- **Resolution** determines who the tenant is. +- **Filtering** limits data access. +- **Authorization** controls what the current user can do. +- **Database strategy** defines physical or logical isolation. + +These layers complement each other. + +### Tenant-specific repositories + +You usually do not need separate repository implementations just to filter by tenant. The standard repository already respects the active tenant context. + +But custom repository methods still need discipline. If you write raw SQL, disable filters, or query across host boundaries, you must preserve isolation intentionally. + +### Unit of Work integration + +ABP's Unit of Work operates inside the current tenant context. That means reads and writes in the same UoW use the same tenant scope unless you explicitly change it. + +This is one reason `CurrentTenant.Change(...)` is safer than trying to pass tenant identifiers manually through every service method. + +### Security implications + +Multi-tenancy bugs are often security bugs. + +Be especially careful with: + +- host-side screens that disable tenant filters +- header and query-string based tenant resolution in public endpoints +- cross-tenant exports and reports +- tenant-aware caches +- background jobs and event handlers that run without explicit tenant context + +A common mistake is assuming data filtering replaces authorization. It does not. + +## Seeding Tenant Data + +A good SaaS system usually needs both host seed data and tenant seed data. + +Examples: + +- host roles and platform settings +- tenant admin user +- default CRM stages +- default product catalog or sample records + +ABP uses `IDataSeedContributor` for this. + +### Basic seed contributor + +```csharp +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +public class CrmDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + private readonly IRepository _productRepository; + + public CrmDataSeedContributor( + ICurrentTenant currentTenant, + IRepository productRepository) + { + _currentTenant = currentTenant; + _productRepository = productRepository; + } + + public async Task SeedAsync(DataSeedContext context) + { + using (_currentTenant.Change(context?.TenantId)) + { + if (await _productRepository.GetCountAsync() > 0) + { + return; + } + + await _productRepository.InsertAsync( + new Product(Guid.NewGuid(), "Starter Plan", 49, _currentTenant.Id), + autoSave: true + ); + + await _productRepository.InsertAsync( + new Product(Guid.NewGuid(), "Growth Plan", 99, _currentTenant.Id), + autoSave: true + ); + } + } +} +``` + +### Host seed vs tenant seed + +Inside `SeedAsync`, `context.TenantId` tells you which scope you are seeding. + +- `null` means host context +- a concrete value means tenant context + +That makes it easy to branch logic: + +```csharp +public async Task SeedAsync(DataSeedContext context) +{ + using (_currentTenant.Change(context?.TenantId)) + { + if (_currentTenant.Id == null) + { + await SeedHostAsync(); + } + else + { + await SeedTenantAsync(_currentTenant.Id.Value); + } + } +} +``` + +### Per-tenant initialization + +For onboarding, a common pattern is: + +1. Create tenant. +2. Create tenant admin user. +3. Configure tenant connection string if needed. +4. Run migration for tenant database if dedicated. +5. Seed tenant defaults. +6. Enable tenant features. + +That workflow becomes especially important in database-per-tenant setups. + + + +## Multi-Tenant Authentication and Identity + +Identity is where host and tenant boundaries become visible to users. + +### Tenant-specific users and roles + +ABP Identity supports tenant-specific users and roles. + +- Host users have `TenantId == null` +- Tenant users have a tenant `TenantId` + +This allows the same email or username patterns to exist in separate tenant scopes, depending on your identity rules and configuration. + +### Permission definitions by side + +ABP permissions can be restricted by multi-tenancy side. + +Example: + +```csharp +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.MultiTenancy; + +public class CrmPermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var crmGroup = context.AddGroup("Crm"); + + crmGroup.AddPermission( + "Crm.Tenants.Manage", + multiTenancySide: MultiTenancySides.Host + ); + + crmGroup.AddPermission( + "Crm.Customers.Manage", + multiTenancySide: MultiTenancySides.Tenant + ); + } +} +``` + +This is a clean way to avoid accidentally exposing host admin actions to tenant users. + +### Login flow in a multi-tenant app + +A typical login flow works like this: + +1. User opens tenant URL such as `acme.mycrm.com`. +2. ABP resolves tenant `acme`. +3. Login request is processed within tenant context. +4. Identity validates the user against that tenant. +5. Claims are issued with tenant information. +6. Subsequent requests continue in the same tenant context. + +For host login: + +1. User opens host administration URL. +2. No tenant is resolved. +3. Login happens in host context. +4. Host-only permissions become available. + +### Tenant switching + +Tenant switching can mean different things: + +- a host admin acting on behalf of a tenant in backend services +- a user selecting a tenant context in a multi-organization portal +- changing browser origin or subdomain for tenant-specific UI access + +For backend logic, `CurrentTenant.Change(...)` is the mechanism. + +For frontend flows, tenant resolution strategy usually drives the user experience. + +## Database-Per-Tenant Configuration + +Database-per-tenant is where ABP's tenant abstractions become especially valuable. + +### Tenant-specific connection strings + +The Tenant Management module supports storing per-tenant connection strings. Once configured, ABP can use the tenant's own database automatically. + +Typical flow: + +- host creates tenant +- tenant record stores connection string override +- request resolves tenant +- connection string resolver uses tenant-specific value +- DbContext points to the tenant database + +### `ITenantStore` + +`ITenantStore` is the abstraction responsible for retrieving tenant configuration. + +That includes: + +- tenant id +- tenant name +- active status +- connection strings + +The default implementation may read from configuration or from the Tenant Management module database, depending on setup. + +### Example: tenant configuration in appsettings + +```json +{ + "Tenants": [ + { + "Id": "11111111-1111-1111-1111-111111111111", + "Name": "acme", + "ConnectionStrings": { + "Default": "Server=.;Database=Crm_Acme;Trusted_Connection=True;TrustServerCertificate=True" + } + }, + { + "Id": "22222222-2222-2222-2222-222222222222", + "Name": "globex", + "ConnectionStrings": { + "Default": "Server=.;Database=Crm_Globex;Trusted_Connection=True;TrustServerCertificate=True" + } + } + ] +} +``` + +### Production-ready considerations + +For production, prefer: + +- tenant metadata in a central host database +- automated tenant provisioning pipeline +- a migration runner for new and existing tenant databases +- secure connection string storage +- monitoring and health checks per tenant database + +### What changes operationally + +With one shared database, disabling the tenant filter can make host-wide queries possible. + +With separate databases, cross-tenant queries are fundamentally different. The host cannot query all tenant rows with one SQL statement because the data lives in different databases. + +That changes how you design: + +- reporting +- analytics +- support tooling +- exports +- migration scripts + +## Advanced Multi-Tenancy Scenarios + +Once the core application works, the interesting problems begin. + +### Background jobs in tenant context + +Background jobs often execute outside the original HTTP request. That means tenant context is not automatically available unless you pass and restore it. + +```csharp +public class RebuildCustomerStatsJob : AsyncBackgroundJob +{ + private readonly ICurrentTenant _currentTenant; + private readonly IRepository _customerRepository; + + public RebuildCustomerStatsJob( + ICurrentTenant currentTenant, + IRepository customerRepository) + { + _currentTenant = currentTenant; + _customerRepository = customerRepository; + } + + public override async Task ExecuteAsync(Guid tenantId) + { + using (_currentTenant.Change(tenantId)) + { + var count = await _customerRepository.GetCountAsync(); + // Rebuild stats for this tenant + } + } +} +``` + +If you queue jobs without tenant identity, you will eventually process data in the wrong context. + +### Distributed events + +Distributed event handlers should also be tenant-aware. + +A practical pattern is to include tenant id in the event payload and restore it in the handler before performing repository operations. + +### Caching per tenant + +Cache keys must include tenant identity when the cached value is tenant-specific. + +Bad: + +- `dashboard-summary` + +Good: + +- `tenant:{tenantId}:dashboard-summary` + +This sounds obvious, but cross-tenant cache leakage is a very real failure mode. + +### Feature management + +ABP feature management is a natural fit for SaaS plans. + +Use features for things like: + +- max number of users +- advanced reporting availability +- API access +- storage limits + +A tenant on a basic plan and a tenant on an enterprise plan can run the same codebase with different capabilities. + +### Setting management + +Settings are another strong tenant-aware capability. + +Examples: + +- default currency +- email sender name +- CRM pipeline defaults +- localization preferences + +Settings should be tenant-scoped when they represent tenant configuration, not platform-wide behavior. + +### Audit logging + +Audit logs should always preserve tenant identity. That makes support and incident investigation much easier. + +When reviewing logs, you should be able to answer: + +- which tenant performed the operation +- whether it happened in host or tenant context +- which user executed it + +### Localization + +Localization becomes multi-tenant when tenants can override culture, language preferences, or content. Keep shared localization resources separate from tenant-specific content whenever possible. + +### Common pitfalls + +A few pitfalls show up repeatedly in real projects: + +- tenant resolution order produces unexpected `CurrentTenant.Id == null` +- public APIs trust `__tenant` header without gateway validation +- seed logic is not idempotent and creates duplicates +- background jobs run without restored tenant context +- raw SQL bypasses tenant filtering +- cache keys omit tenant id +- host admin screens disable filters too broadly +- database-per-tenant migrations are not automated + +## Building a Sample SaaS CRM Application + +Let's put the concepts together into a practical example. + +### Business scenario + +We are building a SaaS CRM platform with: + +- host administration +- tenant administration +- customer management +- product catalog +- order management +- subscription management + +### Host administration responsibilities + +The host side manages the platform itself. + +Typical host features: + +- create tenant +- suspend or reactivate tenant +- assign subscription plan +- configure dedicated database +- view platform metrics +- trigger tenant provisioning + +### Tenant administration responsibilities + +Each tenant manages its own organization. + +Typical tenant features: + +- invite users +- manage roles +- configure CRM settings +- manage customers and sales pipeline +- view tenant-specific reports + +### Suggested module boundaries + +A practical application split might look like this: + +- **SaaS/Host module**: tenant lifecycle, subscriptions, plans +- **Identity module**: users, roles, permissions +- **CRM module**: customers, contacts, products, orders +- **Billing module**: plan, invoice metadata, subscription status +- **Reporting module**: tenant-level analytics + +### Example tenant onboarding flow + +Suppose a new tenant signs up: `acme`. + +1. Host creates a `Tenant` record. +2. Subscription plan is assigned. +3. If enterprise tier, a dedicated database is created. +4. Migrations run for the tenant database if needed. +5. Tenant admin user is provisioned. +6. CRM defaults are seeded. +7. Features are assigned based on plan. +8. Tenant accesses `acme.mycrm.com`. + +### Example application service for tenant onboarding + +```csharp +public class TenantProvisioningAppService : ApplicationService +{ + private readonly ITenantRepository _tenantRepository; + private readonly IDataSeeder _dataSeeder; + + public TenantProvisioningAppService( + ITenantRepository tenantRepository, + IDataSeeder dataSeeder) + { + _tenantRepository = tenantRepository; + _dataSeeder = dataSeeder; + } + + public async Task ProvisionAsync(Guid tenantId) + { + var tenant = await _tenantRepository.GetAsync(tenantId); + + await _dataSeeder.SeedAsync(new DataSeedContext(tenant.Id)); + } +} +``` + +In a real solution, provisioning usually includes database creation, migration, admin user setup, and feature initialization as well. + +### How the pieces fit together + +In the CRM app: + +- tenant is resolved from subdomain +- identity authenticates users within the tenant +- entities implement `IMultiTenant` +- repositories automatically filter by tenant +- host users manage tenant lifecycle +- features and settings control plan differences +- background jobs and event handlers restore tenant context explicitly + +That is the practical ABP multi-tenancy story end to end. + +## Best Practices for ABP Multi-Tenant Applications + +Here are 15+ best practices that hold up well in production. + +1. **Decide host vs tenant ownership early.** Not every entity should implement `IMultiTenant`. +2. **Choose the database model intentionally.** Start simple, but plan the migration path. +3. **Prefer subdomain resolution for browser-based SaaS.** It is usually the cleanest user experience. +4. **Do not trust public tenant headers blindly.** Accept them only behind trusted infrastructure. +5. **Keep tenant resolution order explicit.** Unexpected resolver precedence causes hard-to-debug bugs. +6. **Use `CurrentTenant.Change(...)` for cross-tenant work.** Do not fake tenant context with ad hoc parameters. +7. **Make seeding idempotent.** Seed contributors should safely run more than once. +8. **Include tenant id in cache keys.** Always. +9. **Be cautious when disabling `IMultiTenant` filters.** Limit scope and review security impact. +10. **Define permissions with the right `MultiTenancySides`.** Host and tenant actions should be separated clearly. +11. **Automate tenant database migrations.** Manual DB-per-tenant operations do not scale. +12. **Monitor by tenant.** Errors, latency, and usage metrics should be attributable to tenant context. +13. **Design indexes around tenant access patterns.** Shared-database tables often need `(TenantId, ...)` composite indexes. +14. **Avoid cross-tenant joins in business logic.** They are usually a sign of a leaking domain boundary. +15. **Test host context explicitly.** Many bugs appear only when `CurrentTenant.Id` is null. +16. **Test tenant switching and nested scopes.** Especially in background and integration flows. +17. **Preserve tenant id in audit logs and events.** It helps debugging, support, and compliance. +18. **Plan backup and restore by tenancy model.** Shared and dedicated databases require different operational playbooks. +19. **Keep raw SQL rare and reviewed.** Repository filters do not protect careless SQL. +20. **Treat multi-tenancy as a security concern, not just an architecture pattern.** Because in practice, it is both. + +## Conclusion + +ABP Framework gives you a strong, practical multi-tenancy foundation. The important part is that its support is not isolated in one package or middleware. It is woven through the framework: request resolution, current tenant context, repositories, data filters, identity, tenant management, features, settings, and background processing. + +That matters because real SaaS applications do not fail on the happy path. They fail in the edge cases: host-side screens, cache leakage, background jobs, custom resolvers, and rushed provisioning logic. + +If you understand the host vs tenant model clearly and use ABP's abstractions as intended, you can build a multi-tenant application that stays maintainable as your product grows from a few customers to many. + +Start with a simple shared-database model if it fits. Move to dedicated databases where needed. Keep tenant context explicit. Respect the boundaries. ABP does the heavy lifting, but architecture discipline is still your job. + +## TL;DR + +- ABP multi-tenancy gives you built-in tenant resolution, `ICurrentTenant`, `IMultiTenant`, data filters, and tenant management support. +- Shared DB is the easiest starting point; DB-per-tenant and hybrid models fit stronger isolation and enterprise needs. +- Correct tenant resolution is the foundation of data isolation, authentication, and authorization. +- Use `CurrentTenant.Change(...)` for controlled cross-tenant work, especially in jobs, seeders, and host-side services. +- Production success depends on secure resolvers, tenant-aware caching, automated migrations, and strict host/tenant boundaries. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/cover.png b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/cover.png new file mode 100644 index 0000000000..def7467e73 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png new file mode 100644 index 0000000000..3940d506be Binary files /dev/null and b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-1.png differ diff --git a/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png new file mode 100644 index 0000000000..9c0d224922 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-2.png differ diff --git a/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png new file mode 100644 index 0000000000..b0a8cb5a96 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-09-implementing-multitenancy-in-abp-framework-a-complete/inline-3.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/abp-agent-mode-picker.png b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/abp-agent-mode-picker.png new file mode 100644 index 0000000000..e4ac0af1fe Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/abp-agent-mode-picker.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/abp-agent-plan-actions.png b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/abp-agent-plan-actions.png new file mode 100644 index 0000000000..ef99ac7ee6 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/abp-agent-plan-actions.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/cover.png b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/cover.png new file mode 100644 index 0000000000..ef6b3dd2a6 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/post.md b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/post.md new file mode 100644 index 0000000000..82fed5324d --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-1-agent-plan-ask-abp-studio-ai/post.md @@ -0,0 +1,105 @@ +# Deep Dive on ABP AI Agent #1: Agent, Plan and Ask Modes + +There is a small question I like to answer before I type anything into **ABP Agent**: + +**Do I want an answer, a plan, or action?** + +That question looks simple, but it changes the whole experience. Sometimes I am only trying to understand why a module is structured a certain way. Sometimes I already know the direction, but I want the implementation path checked before touching files. And sometimes the task is clear enough that I want ABP Agent to do the work, run the checks, and iterate with me. + +That is where the three modes in ABP Studio AI become more than labels. They help me choose the right level of trust, risk, and action for the moment. + +![ABP Agent mode picker showing Agent, Plan, and Ask](abp-agent-mode-picker.png) + +## Ask Mode: When I Want To Understand + +**Ask** is the mode I reach for when I want to stay in learning mode. + +It is useful when I am reading a solution and want to ask questions like: + +* What is this module responsible for? +* Why is this permission checked here? +* How does this application service relate to the domain layer? +* What would happen if I changed this setting, dependency, or flow? +* Which ABP concept should I use for this requirement? + +The important part is that Ask mode is read-only. I can explore the codebase, ABP concepts, architecture, or possible approaches without worrying that files will be changed as a side effect of the conversation. + +That makes it a comfortable starting point. I do not need to prepare a perfect prompt. I can ask a rough question, follow up with more context, and slowly turn uncertainty into something clearer. + +For me, Ask mode is especially helpful when I join a solution after some time away. Instead of jumping between files and trying to rebuild the story manually, I can ask ABP Agent to explain the shape of the solution in the language of ABP: modules, layers, permissions, application services, entities, settings, events, and runtime pieces. + +## Plan Mode: When I Want To Think Before Changing Code + +**Plan** is the mode I use when the next step is probably implementation, but I do not want to start editing yet. + +This is the middle ground between a conversation and a code change. ABP Agent can inspect the solution in a read-only way, ask clarifying questions when the requirement is not clear enough, and produce a structured plan before any file is modified. + +That changes the feeling of working with AI. Instead of saying "go build this" and reviewing only the result, I can review the approach first: + +* Which files will likely be affected? +* Which ABP layers are involved? +* Does the implementation path match the existing solution style? +* Are there missing decisions before the work starts? +* Is this a small change, or is it actually a larger workflow? + +This is useful for changes that cross boundaries: adding a new entity, adjusting an application service, introducing a permission, changing a UI flow, or touching more than one module. Those are exactly the moments where I want a second pass before code starts moving. + +When a plan is active, ABP Studio gives me clear actions around it. I can view the plan, detach it if it is no longer the right direction, or apply it with Agent mode when I am ready to move from planning to implementation. + +![ABP Agent plan actions for viewing, detaching, or applying a plan](abp-agent-plan-actions.png) + +The small detail I like here is that the plan does not disappear into the chat history. It becomes something I can review and intentionally carry into the next step. + +## Agent Mode: When I Am Ready For Action + +**Agent** is the mode I choose when I am ready to let ABP Agent work on the solution. + +This is the action mode. ABP Agent can edit files, run commands, build projects, use ABP Studio tasks, and iterate when something fails. It is the right choice when the task is clear enough and I am comfortable letting the agent make changes that I will review afterward. + +For small trusted tasks, I may go directly to Agent mode: + +* Add a missing localization entry +* Fix a straightforward build error +* Update a simple DTO mapping +* Add a validation rule that matches an existing pattern +* Apply a plan that I have already reviewed + +For larger work, I prefer not to start here. Agent mode is powerful, and power is better when it is intentional. If I am not sure about the shape of the change, I usually start with Ask or Plan first. + +## A Practical Workflow + +The modes are most useful when I treat them as a workflow, not as three disconnected buttons. + +For larger changes, my usual flow is: + +1. **Ask** to understand the area and the existing conventions. +2. **Plan** to turn the requirement into a reviewable implementation path. +3. **Agent** to apply the plan, build, and iterate. + +For learning, I often stay entirely in Ask mode. If I am trying to understand ABP multi-tenancy behavior, module dependencies, permission definitions, or why a solution is organized a certain way, there is no need to involve file changes. + +For small tasks, I may go directly to Agent mode. The key is that I already know what I want, the risk is low, and the expected result is easy to review. + +Here is the simple rule I keep in mind: + +| Situation | Mode I Choose | Why | +| --- | --- | --- | +| I need an explanation | Ask | It keeps the conversation read-only. | +| I need a direction before implementation | Plan | It gives me a reviewable path before changes. | +| I am ready for ABP Agent to work | Agent | It can edit, build, run tasks, and iterate. | + +## Why This Matters + +AI-assisted development can feel too fast when the tool moves from idea to code before I have decided what kind of help I actually need. + +The three modes slow that moment down in a good way. They let me say: + +* "Just explain this." +* "Think through the change first." +* "Now implement it." + +That separation makes the work feel more intentional. It also makes the output easier to review, because the mode already tells me what kind of result I should expect. + +Ask gives me understanding. Plan gives me a path. Agent gives me action. + +Used together, they make ABP Studio AI feel less like a single big button and more like a development partner that can adapt to the level of confidence I have at each step. diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-catalog.png b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-catalog.png new file mode 100644 index 0000000000..8cdcc12bf0 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-catalog.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-selector.png b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-selector.png new file mode 100644 index 0000000000..84c8d4161f Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-selector.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-settings-agents.png b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-settings-agents.png new file mode 100644 index 0000000000..5acd071643 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-settings-agents.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-settings-git-review.png b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-settings-git-review.png new file mode 100644 index 0000000000..0ede751e7f Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/abp-agent-model-settings-git-review.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/cover.png b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/cover.png new file mode 100644 index 0000000000..49e2b7d868 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/cover.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/post.md b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/post.md new file mode 100644 index 0000000000..ef54e2c01c --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-2-supported-ai-models-abp-studio-ai/post.md @@ -0,0 +1,138 @@ +# Deep Dive on ABP AI Agent #2: Supported AI Models in ABP Studio + Usage Recommendations + +There is one question I ask almost as often as "Which mode should I use?": + +**Which model should do this work?** + +At first, it is tempting to answer that question by always choosing the strongest model in the list. That feels safe. If a model is more capable, why not use it for everything? + +In real work, I do not think about it that way. + +When I use **ABP Studio AI**, the model choice is part of the workflow. Some tasks need careful reasoning. Some need speed. Some need a large context window. Some need image support because the browser or a screenshot is involved. Some are small text-processing tasks where using the most capable model would only make the work slower and more expensive. + +So I treat model selection as a practical decision, not a trophy selection. + +## What ABP Studio Supports Today + +ABP Studio gives me a curated model setup by default. It keeps the first experience simple, while still letting me choose from a broader model catalog when I want to tune the setup for a specific kind of work. + +The important word here is **focused**. + +ABP Studio does not treat every model as an equally good choice for agent work. A coding agent needs things like a useful context window, tool support, text output, and reliable behavior in repeated agent loops. Studio keeps the model experience closer to that reality. + +The built-in model set currently includes: + +| Model | How I think about it | +| --- | --- | +| Claude Sonnet 4.6 | The default main model for day-to-day Ask, Plan, and Agent work. | +| Claude Haiku 4.5 | A fast supporting model for research, browser work, and lightweight text processing. | +| Claude Opus 4.7 | A stronger option when the task needs deeper reasoning or more careful review. | +| GPT-5.5 | Another strong option for main conversations or review-style work. | +| GLM-5.1 | A text/code option for tasks that do not need image input. | + +I do not read this list as a ranking. I read it as the default toolbox. + +And it is not a closed box. If I need a different model for a specific task, I can open the Models settings, search the catalog, filter by category, and add more models to my selection. + +![ABP Studio Models settings showing the selectable model catalog](abp-agent-model-catalog.png) + +That is an important distinction. The built-in models are there so I can start with sensible defaults. They are not there to force every team, every solution, or every workflow into the same model choices. + +## The Main Model + +The main model is the one I feel most directly in the conversation. + +It is used when I ask questions, create plans, or let ABP Agent work through an implementation. This is the model behind the normal flow of the chat. + +![ABP Agent model selector showing the current conversation model](abp-agent-model-selector.png) + +For most work, I keep a balanced model as the main model. A Sonnet-style model is a good default because it is capable enough for real development tasks without making every small question feel heavy. + +This is the model I use for: + +* Understanding a module or package +* Planning a feature before editing code +* Applying a reviewed plan +* Fixing ordinary build or test failures +* Making changes where the expected result is easy to review + +When the task gets broader, I become more intentional. + +If I am asking ABP Agent to reason across several modules, plan a risky refactor, review architecture, or inspect a subtle regression, I am more willing to switch to a stronger model. The extra capability is useful when the cost of a shallow answer is high. + +For a quick localization change or a small DTO update, that same choice can be wasteful. The strongest model is not always the best model for the moment. + +## Role-Based Models + +One detail I like in ABP Studio AI is that model selection is not only one global dropdown. + +Studio separates the main conversation model from supporting model roles. That means I can keep the main model strong enough for the conversation while using lighter models for background work. + +![ABP Agent model settings for main, research, browser, and text processor models](abp-agent-model-settings-agents.png) + +The roles are easier to understand if I describe them by how they feel in daily use. + +**Main Model** is the model I am actively talking to. It carries the normal Ask, Plan, and Agent experience. + +**Research Model** is for research and ABP documentation searcher work. I usually keep this lightweight because research often involves gathering, narrowing, and summarizing context before the main model decides what to do with it. + +**Browser Model** is used by the browser subagent in Agent mode. This role should stay fast and practical. When browser screenshots are involved, I choose a model that supports image input. A text-only model may be fine for code, but it is not the right fit when the work depends on seeing the UI. + +**Text Processor Model** is for smaller language tasks such as summarizing errors, generating commit messages, or consolidating learned lessons. This is exactly where I do not want to spend the most capable model every time. + +The Git Review model is separate too. + +![ABP Agent model settings for Git Review model selection](abp-agent-model-settings-git-review.png) + +For AI Review, I like the "Ask me every time" behavior. Some reviews are routine. Some reviews deserve a stronger model because the change is large, security-sensitive, or touches architecture. Asking each time keeps that decision close to the actual change. + +If a team wants consistent review behavior, a fixed review model also makes sense. The key is that Git Review does not have to silently follow the same model I use for ordinary chat. + +## How I Choose In Practice + +For quick questions, I use the default main model. + +If I am asking "Where is this permission defined?" or "Why does this module reference that package?", I do not need to overthink the model. I want a clear answer and maybe a few source references. + +For planning larger work, I use a stronger main model when the decision matters. + +Plan mode is where the model can save me from an expensive wrong turn. If the change crosses layers, modules, permissions, UI, or database behavior, I prefer a model that can hold more context and reason carefully. I still narrow the scope where possible, because a focused prompt usually beats a huge unfocused one. + +For implementation, I care about reliability more than raw size. + +Agent mode is not only about generating code. It is about reading the solution, editing files, running checks, seeing failures, and trying again. A good main model should follow instructions consistently and use tools well. For supporting roles, I usually keep the lighter defaults. + +For UI and browser tasks, I check image support. + +If the task involves screenshots, browser interaction, visual verification, or UI state, the browser model needs to be able to understand images. This is one reason I do not treat every text/code model as interchangeable. + +For reviews, I choose based on risk. + +A small formatting or localization change does not need the same review setup as a large change in authorization, multi-tenancy, persistence, or distributed behavior. For deeper reviews, I am willing to use a stronger model because the goal is not speed. The goal is to catch what I missed. + +For cost and latency, I avoid using the strongest model everywhere. + +This is not only about credits. It is also about pace. If every background task uses a heavy model, the development loop feels slower. Keeping lightweight models for lightweight jobs makes ABP Studio AI feel more responsive. + +## A Simple Rule + +The model name matters less than the job. + +When I choose a model in ABP Studio AI, I usually ask: + +| Situation | Model choice I prefer | +| --- | --- | +| Normal Ask, Plan, or Agent work | Balanced main model | +| Broad planning or risky implementation | Stronger main model | +| Research and documentation lookup | Lightweight supporting model | +| Browser tasks with screenshots | Vision-capable browser model | +| Error summaries and commit messages | Lightweight text processor model | +| Important AI Review | Stronger model or ask each time | + +That keeps the experience practical. + +Modes decide how much action I want: Ask, Plan, or Agent. + +Models decide which brain should handle the work. + +When those two choices are made intentionally, ABP Studio AI feels less like one generic AI button and more like a set of tools I can tune for the task in front of me. diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/POST.md b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/POST.md new file mode 100644 index 0000000000..1440f5061d --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/POST.md @@ -0,0 +1,143 @@ +# Deep Dive on ABP AI Agent #3: Rules, Skills and Lessons + +Every new chat starts from zero. + +The model can write the API, the permissions, and the UI. What disappears between sessions is everything specific to *this* solution: your naming rules, your feature checklist, the demo-data fix from yesterday. You end up re-teaching before you start building. + +That is not an intelligence problem. The model is already capable enough. What does not carry over is the setup around it. + +## Agent, Model, and Harness + +Three terms get mixed up constantly. Keeping them separate makes everything else in this article click. + +**The model** is the LLM: the weights, the training data, the reasoning engine. You do not get to change it. You pick which one to use, and that is about it. + +**The harness** is everything wrapped around the model so it can actually finish work in *your* environment: the system prompt, the tools it can call, the files it may read, the checks that run on its output, the limits on what it may touch, and the instructions injected into every session. A raw model is not an agent. An agent is the model plus the harness. If you are not the model, you are building the harness. + +**The agent** is the whole system: model plus harness, running in a loop until the task is done. The behaviour you experience is dominated by the harness, not just the model. [Addy Osmani](https://addyosmani.com/blog/agent-harness-engineering/) puts the same idea plainly: *a decent model with a great harness beats a great model with a bad harness.* The gap between what today's models can do and what you actually see them do in your codebase is mostly a harness gap. + +The habit that makes a harness good is simple: treat every mistake as a reason to update the setup, not just to fix the output in chat. Each time the agent gets something wrong, change what it always knows, can look up, or carries forward so it cannot repeat the same error. Do that consistently and the agent stops repeating mistakes and starts working the way your team works. + +In **ABP Studio AI Coding Agent**, the harness includes three pieces that hold what the agent knows about your solution: **Rules**, **Skills**, and **Lessons**. They live under **Settings > Rules & Skills**. + +![The Rules & Skills settings page in ABP Studio](rules-and-skills-settings.png) + +## Rules: The House Rules On The Wall + +A **Rule** is a standing instruction that the harness injects into the agent's context on every turn of every session. In harness-engineering terms, it is always-on context injection: the same role as an `AGENTS.md` or `CLAUDE.md` at the root of a repo, except scoped to your solution or your profile inside ABP Studio. + +Think of the house rules pinned to a kitchen wall: how we do things here, every shift, no exceptions. Unlike a glance at a poster, though, a Rule is not ambient background. It is read on every turn, so it costs context budget every time the agent acts. + +ABP Agent already follows the framework's own conventions by default. It goes through the data layer instead of hitting the database directly, uses the built-in permission system instead of hand-rolled checks, and reads user-facing text from the translation files instead of hardcoding it. You do not write those down. + +Rules are where *your* conventions go. The ones the framework cannot guess because they belong to your solution and your team. The good part is that these read like plain conventions any developer would recognize, not framework trivia: + +* New code goes in the same place, with the same naming, as the code already around it. +* Every endpoint that changes data checks permissions before it does anything. +* Money is stored in minor units (cents), never as a floating-point number. +* API errors use our standard response shape, never ad-hoc JSON. + +With those on the wall, I stop repeating them. Instead of writing this: + +```text +Add a Category screen. Use our data layer, do not query the database +directly. Check permissions on every write. Keep money in cents. Put the +files where the other features live. +``` + +I can write this: + +```text +Add a Category screen. +``` + +The conventions are not renegotiated, one prompt at a time, in every session. The agent applies them the way a teammate who has read the contributor guide would. + +This matters more than it looks. A model with no standing rules does not stay neutral. It falls back on the defaults from its training data. Ask a generic model for "an endpoint that returns categories" and you often get one with the database query written straight into the handler, because that is the most common shape on the public internet. Rules steer the agent's output back toward *your* codebase. + +One caution: treat the rule list like a pilot's checklist, not a style guide. A Rule is injected on every turn, so keep the list short. Every line should earn its place, ideally traceable to a real failure or a hard constraint you cannot ignore. If a line does not change what the agent produces, it is noise, and it dilutes the rules that actually matter. + +You also choose where a rule is saved. Global rules sit under your user profile and apply to every solution on your machine. Solution rules apply only to the current solution, and only while it is open. + +## Skills: Recipes You Pull Off The Shelf + +A **Skill** is the same kind of note as a Rule, but loaded only when the task calls for it. The harness keeps a short description of each skill in context at all times; the agent reads the full text only when that description matches what you asked it to do. Harness engineers call this **progressive disclosure**: reveal instructions and detail only when they are needed, instead of stuffing everything into the opening prompt. + +A recipe is not pinned to the wall. It sits in a drawer, and the cook pulls it out only when making that dish. That is a Skill. + +In ABP Studio, a rule and a skill are the same kind of note with one switch between them. + +![Creating a rule or a skill: the Always Apply toggle decides which one it is](create-rule-skill-dialog.png) + +You write a name (which becomes the file name) and some content, then set **Always Apply**. Turn it on and the note is a Rule, always in context. Turn it off and the note is a Skill, fetched on demand. So if a skill turns out to be something the agent should never skip, you flip one switch and it becomes a rule. + +My favorite example of a skill is a "build a feature end to end" checklist: the ordered steps your team follows to ship one complete feature. The data model comes first, then the data access layer, then the application service, then the API, then the translations, then the UI, and finally the demo data. Write it once, and the agent follows it whenever it builds a feature, instead of inventing its own order each time. + +Writing a skill is like writing an onboarding note for a new teammate. You are not making them smarter. You are saving them the week it would take to work out how your team does this one thing. + +A skill can be long without slowing anything down. The agent sees only the short description of each skill at all times, and reads the full text only when the description matches the task. The detail stays in the drawer until it is needed. + +Skills are also portable. A skill is plain Markdown, so you can import one written elsewhere instead of retyping it. + +![Importing skills from .md files into a solution or your global profile](import-rules.png) + +You point the importer at a file or a folder of `.md` files and choose whether they go into the current solution or your global profile. A procedure one person worked out can travel to the rest of the team, or to your next solution. + +A simple test for which one to reach for: + +* If it should hold no matter what you are doing, it is a **Rule**. ("Always return errors in our standard shape.") +* If it is a procedure you follow only for a certain kind of task, it is a **Skill**. ("Here is our checklist for adding a feature end to end.") + +## Lessons: What The Agent Learns On Its Own + +Rules and Skills are written by a person. **Lessons** are written by the agent. + +Think of a shift handoff log in the kitchen: after something goes wrong, someone writes down what happened and what to do differently next time. The next cook reads it before repeating the same work. In ABP Studio, you correct the mistake; the agent records the verified fix so future sessions do not trip over it again. That entry is a Lesson. + +When the agent gets something wrong and is corrected, by you, by a failing build, or by the official ABP documentation, it can record the correction as a short, verified note. The harness carries that note into later turns and later sessions as high-priority context. That is the `ai-learned-lessons` entry you saw in the settings list, growing as the agent works in your solution. + +The idea behind it is simple: when the agent gets something wrong, fix it once so it never gets it wrong the same way again. You already do a version of this when you edit a notes file by hand. Lessons do the recording for you, at the moment of the correction, when the reason is still fresh and you would otherwise forget to write it down. + +Here is the shape of a real one. I ask the agent to add a `Category` screen. It builds the data model, the API, and the UI, but forgets to grant the new permission in the demo data. The screen compiles, the API responds, everything looks finished, and the only symptom is that real users get "access denied." I correct it, the agent fixes the demo data, and it records a note: in this solution, a new permission is not done until it is granted in the demo data. Next time, it remembers. + +A Lesson is not a general fact about ABP. The documentation already covers that, and the agent reads it. A Lesson is a solution-specific, verified correction: the kind of knowledge a team usually keeps in people's heads and loses when they move on. Here it is written down the moment it is learned, instead of weeks later, if ever. + +## When To Use Which? + +Once the three are clear, they sort themselves out: + +| Mechanism | Who writes it | When it loads | Answers | +| --- | --- | --- | --- | +| **Rules** | You | Always, every turn | "What must always be true here?" | +| **Skills** | You | When relevant to the task | "How do we do this kind of task?" | +| **Lessons** | The agent | In later sessions, as high-priority context | "What did we get wrong before in this solution?" | + +They feed into each other. A Lesson that keeps coming back is a sign it should be promoted. If the agent records the same correction again and again, it is no longer a one-off note. It belongs in a Rule, if it is an always-true convention, or in a Skill, if it is a procedure. Knowledge tends to move from Lessons toward Rules and Skills: the agent finds the pattern by tripping over it, and you write it down properly once it has earned the spot. + +Keep an eye on the budget, because all three share the same limited space, the context window. Rules cost the most, since they are always present, so an overstuffed rule list weighs down every turn and crowds out the rules that matter. Skills are cheaper, paid for only when used, which is a good reason to move anything situational out of Rules and into a Skill. Lessons add up too, so clear out the stale ones now and then. The point is not to write down everything you know. It is to write down the few things that change what the agent produces, and put each one where it loads at the right time. + +## Why This Is Different From Generic Coding Agents? + +To be fair, the ideas themselves are not unique to ABP. Tools like Cursor, Claude Code, Codex, and Windsurf are strong general-purpose coding tools, and they already have always-on instruction files and on-demand skill files. If all you compare is whether a tool can hold rules and skills, there is no real difference, and I would not pretend otherwise. + +The difference is in the two places where a generic tool has to guess. + +The first is memory of its own mistakes. In most tools, remembering a past mistake means you stop and edit a memory file by hand. Lessons remove that step. The agent records the correction itself, the moment it happens, so the knowledge survives without you having to maintain it. + +The second, and the one that matters most, is what all of this sits on. A generic instruction file can say "use the data layer," but the tool still has to read your files and guess what the data layer is and where it lives. ABP Agent does not guess. It starts every session with an ABP-aware map of your solution, and when it is unsure how something should be done, it checks the official ABP documentation instead of the average of the public internet. So the rules and skills you write are not hints dropped into a tool that barely understands the project. They are backed by one that already knows the framework underneath them. + +There is also a smaller convenience worth a line: a rule and a skill are the same note with one switch between them, scoped to the solution or your profile from the same place, instead of two separate file formats to keep track of. + +## Conclusion + +It is best to see the three as one system, not three switches. + +* **Rules** hold the conventions you refuse to repeat. +* **Skills** hold the procedures you want to reuse. +* **Lessons** hold the corrections the agent learns as it works. + +Together they answer the blank-memory problem. They do not give the model new weights. They inject, at the start of each session, the smallest useful set of instructions so the agent can work as if it has been here before. + +A generic agent with no harness tuning stays roughly as capable on its thousandth task in your codebase as on its first. With Rules, Skills, and Lessons, ABP Agent accumulates solution-specific knowledge over time, especially through Lessons and the promotions you make from them. + +That is the real value: **not just an AI that writes code, but one that learns how your solution is built and keeps that knowledge from one session to the next.** diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/cover-image.png b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/cover-image.png new file mode 100644 index 0000000000..f88dd33b79 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/cover-image.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/create-rule-skill-dialog.png b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/create-rule-skill-dialog.png new file mode 100644 index 0000000000..0d874ae897 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/create-rule-skill-dialog.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/import-rules.png b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/import-rules.png new file mode 100644 index 0000000000..6e5f10dab0 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/import-rules.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/rules-and-skills-settings.png b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/rules-and-skills-settings.png new file mode 100644 index 0000000000..d66fdda94b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-3-rules-skills-lessons/rules-and-skills-settings.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/POST.md b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/POST.md new file mode 100644 index 0000000000..1f736795d7 --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/POST.md @@ -0,0 +1,275 @@ +# Deep Dive on ABP AI Agent #4: Integrated ABP Studio Tools + +When I use an AI coding agent, there is a point where plain code awareness is not enough. + +The agent may understand the project structure. It may read the failing method. It may even guess the most likely cause of an error. But in a real development session, I usually need more than a guess. I need the latest exception, the request that caused it, the logs around it, the application that is running, the containers it depends on, the tasks I can execute, and the build result after a fix. + +That is where **ABP AI Coding Agent** becomes different from a generic coding assistant. It is not only connected to files. It is connected to **ABP Studio**. + +ABP Studio already knows the solution, run profiles, runnable applications, containers, tasks, monitoring data, and build actions. ABP AI Coding Agent can use that context through integrated tools when those tools are enabled. So instead of copying exception details, terminal output, container names, or build logs into the chat manually, I can let the agent work with the same ABP Studio environment I am using. + +![ABP AI Coding Agent with tools](tools-with-agent.png) + +> **Note:** ABP AI Coding Agent is available directly to ABP license holders. License holders have predefined credits so they can try it without setting up a separate AI workflow first. When those credits run out, they can buy more and continue using the same integrated experience. + +## Why Integrated Tools Matter + +Most coding agents start from the same place: **source code**. + +That is useful, but ABP development is not only source code. A running ABP solution has applications, modules, services, containers, database connections, migrations, logs, exceptions, requests, tasks, and build steps. When these pieces are outside the agent's reach, the developer becomes the bridge: + +* Copy this exception. +* Paste that log. +* Run this build. +* Check that container. +* Explain which application is currently running. +* Tell the agent what failed after the last change. + +Integrated ABP Studio tools reduce that manual work. They let the agent ask ABP Studio for runtime and solution information directly, within the permission boundary I choose. + +The important detail is that this access is still explicit. Tools can be enabled or disabled. If I do not want the agent to use a tool, I can keep it disabled. If I want the agent to troubleshoot with runtime information, I can enable the relevant tools and ask for a more complete investigation. + +That gives me a practical balance: the productivity of automation, with a visible boundary around what the agent can use. + +## Tool Access: What The Agent Is Allowed To Use + +The tools view is the control point. + +This is where ABP Studio shows the integrated tools that can be used by ABP AI Coding Agent. Some tools are for reading runtime information. Some are for interacting with applications. Some are for containers, tasks, or build actions. + +![ABP AI Coding Agent tools overview](abp-agent-tools-overview.png) + +> The names are intentionally direct. A monitoring tool that gets exceptions is about exceptions. A build tool is about build validation. A task tool is about ABP Studio tasks. That makes the tool list easy to understand even before using it in a real prompt. + +For me, the key idea is not the individual button names. It is the permission model: + +* If a tool is disabled, the agent should not act as if it has that information. +* If a tool is enabled, the agent can use it as part of the current session. +* If a task needs runtime evidence, I can enable only the tools needed for that task. + +This makes ABP AI Coding Agent feel more intentional than a black box. I can decide when it should stay in code reasoning and when it should use ABP Studio's runtime view of the solution. + +## Monitoring Tools + +Monitoring tools are the first group I reach for when something fails at runtime. + +![ABP AI Coding Agent monitoring tools](abp-agent-monitoring-tools.png) + +These tools help the agent inspect what happened while the application was running. In practice, this means information like **exceptions**, **logs**, **events**, and **request details**. + +This is a big difference from a generic coding agent. Without monitoring tools, the agent can read the code and make a reasonable guess. With monitoring tools, it can work from the actual failure. + +For example, if a page throws an exception, I can ask: + +```text +Get the latest exception from ABP Studio Monitoring and explain what failed. +``` + +If the exception tool is enabled, the agent can use that runtime signal. It can look at the exception message, stack trace, request context, and related code. Then it can connect the runtime failure to the implementation. + +That changes the debugging loop: + +1. Trigger the problem. +2. Ask the agent to inspect the runtime evidence. +3. Let it find the related code. +4. Apply the fix. +5. Validate again. + +The developer no longer needs to manually copy the exception from one place and paste it into another. ABP Studio becomes part of the agent's working context and give it ***harness***! + +## Application Tools + +Application tools connect the agent to the applications defined in the active ABP Studio run profile. + +![ABP AI Coding Agent application tools](abp-agent-application-tools.png) + +This matters because ABP solutions often contain more than one runnable application. A layered solution may have a web application, an API host, a DbMigrator, and other executable projects. A microservice solution may have several services with different roles. + +ABP Studio already understands these applications through the solution and run profile. When application tools are available, the agent does not need to rediscover everything from file names or ask me which project is running. It can use ABP Studio's view of the solution. + +That is useful for prompts like: + +```text +Check which application is running and use the relevant runtime information to investigate the problem. +``` + +The benefit is not only convenience. It also reduces mistakes. The agent can reason from the same run profile that I use in ABP Studio, instead of guessing from the repository structure alone. + +## Container Tools + +Many ABP applications depend on infrastructure services while running locally. + +![ABP AI Coding Agent container tools](abp-agent-container-tools.png) + +A solution may need SQL Server, PostgreSQL, Redis, RabbitMQ, OpenIddict-related services, or other containers depending on the template and modules. When something fails, the cause is not always in application code. Sometimes a required container is not running. Sometimes the application cannot reach a dependency. Sometimes the runtime error is only a symptom of an infrastructure problem. + +Container tools give the agent a way to include that part of the environment in the investigation. + +Instead of asking the agent to guess why a database connection fails, I can let it check the container context that ABP Studio already has. The agent can then distinguish between: + +* a code problem, +* a configuration problem, +* a missing or stopped container, +* or a dependency that is running but unhealthy. + +This is one of the places where ABP Studio integration is especially valuable. General coding agents can help with Docker files or connection strings, but they usually do not know the current ABP Studio container state unless I copy it into the prompt. ABP AI Coding Agent can work closer to the actual local development environment. + +## Task Tools + +ABP Studio tasks are another part of the development workflow that should not live outside the agent. + +![ABP AI Coding Agent task tools](abp-agent-task-tools.png) + +Tasks can represent common solution actions. They may run commands, scripts, or workflow steps that are already configured for the solution. If the team uses ABP Studio tasks to standardize local development, the agent should be able to understand and use that same layer. + +That means I can ask for a workflow instead of a raw command: + +```text +Use the available ABP Studio tasks to validate this change. +``` + +The agent can work with the task names and outputs rather than asking me to remember the exact command. This is helpful in larger solutions where the correct validation step is not obvious from a single project file. + +It also keeps the agent aligned with the team's development path. If ABP Studio has the task, the agent can follow that path instead of inventing a one-off command. + +## Build Tools + +Build tools close the loop after an implementation or fix. + +![ABP AI Coding Agent build tools](abp-agent-build-tools.png) + +The agent should not only change files. It should also help verify that the change still builds. + +In a generic coding agent flow, validation often depends on shell access and manually chosen commands. That can work, but it leaves more room for guessing. Which project should be built? Which solution file should be used? Is there an ABP Studio-specific build action already configured? + +With ABP Studio build tools, the agent can use the build context exposed by the platform. That makes prompts like this more natural: + +```text +Apply the fix and run the available build validation. +``` + +For small changes, this may simply confirm that the project compiles. For larger changes, it can become part of a broader loop with tasks, application checks, and monitoring tools. + +The important part is that the agent can move from implementation to validation without requiring me to manually transfer output between tools. + +## A Practical Tool Access Walkthrough + +Let me make this more concrete with a small debugging scenario. + +In the sample application, I deliberately added a runtime exception to the `UpdateAsync` method of `BookAppService.cs`: + +```csharp +throw new Exception("Sample exception for demonstrating integrated tools!"); +``` + +Then I started the application from ABP Studio and triggered the related request from the browser. The only purpose of this setup is to create a real runtime failure that ABP Studio Monitoring can capture. + +The interesting part is not the exception itself. The interesting part is how the agent behaves when the monitoring tool is disabled, and how that changes when the tool is enabled. + +### First, Without The Exception Tool + +For the first run, I kept the monitoring tool that retrieves exceptions disabled. + +![ABP AI Coding Agent with exception monitoring tool disabled](disabled-monitoring-tools.png) + +Then I asked: + +```text +Can you get the latest exception from ABP Studio Monitoring and explain what failed? +``` + +At this point, the agent did not have direct access to the exception tool. So it tried to reason around the problem in other ways. It looked for available information, tried to inspect logs from files, and checked the codebase to understand what might be happening. + +![ABP AI Coding Agent result without exception tool access](without-tool-result.png) + +That is still useful in some cases, but it is not the best debugging flow. The agent is spending time and context trying to reconstruct a runtime failure indirectly. It may eventually find the suspicious code, but it is working from weaker evidence. + +This is exactly why tool access matters. Without the monitoring tool, the agent can reason from files. With the monitoring tool, it can inspect the actual runtime failure. + +### Then, With The Exception Tool Enabled + +For the second run, I enabled the monitoring tool that can retrieve exceptions, such as `get_exceptions`. + +![ABP AI Coding Agent with exception monitoring tool enabled](enabled-monitoring-tools.png) + +Then I asked: + +```text +Now the get_exceptions tool is enabled. Please get the latest exception from ABP Studio Monitoring, identify the failing code path, and suggest the smallest fix. +``` + +This time, the behavior was very different. The agent directly used the integrated exception tool, retrieved the exception details from ABP Studio Monitoring, and connected the runtime error to the failing code path. + +![ABP AI Coding Agent using get_exceptions result](with-tool-result-1.png) + +It also used the enabled logging tool to verify the surrounding context instead of guessing from the source code alone. + +![ABP AI Coding Agent correlating exception with logs](with-tool-result-2.png) + +That is the workflow I want from an integrated coding agent. It does not only say "this code looks suspicious." It checks the exception, follows the evidence, finds the source of the problem, and proposes the smallest fix. + +It is also faster and more efficient. The agent does not need to spend as many tokens searching for indirect clues because ABP Studio can provide the runtime signal directly. + +### Adding Logs And Requests + +After that, I asked the agent to use the available monitoring tools together: + +```text +Use the available monitoring tools to check the related logs and recent requests for this failure. Tell me whether they confirm the same root cause. +``` + +At this point, the agent used the enabled tools for exceptions, requests, and logs. + +![ABP AI Coding Agent checking monitoring tools together](step-3-1.png) + +![ABP AI Coding Agent reviewing exception, request, and log details](step-3-2.png) + +This is where the ABP Studio integration becomes even more valuable. A runtime problem is rarely just one line of code. There is usually a request, a log entry, an exception, and a running application context around it. + +When these tools are available together, the agent can correlate them. It can say, "this request caused this exception, the logs confirm it, and this code path is responsible." + +That is much better than asking the developer to copy each piece manually into the chat. + +### Validating The Fix + +Finally, I asked the agent to validate the result: + +```text +Run the available build or application validation tools and confirm that the problem is fixed. +``` + +The agent used ABP Studio tools to stop the application, build it, and run it again. + +![ABP AI Coding Agent validating the fix with ABP Studio tools](validate-the-fix.png) + +This completes the loop. The agent did not only identify the problem. It used the integrated tools to move through the full flow: + +1. Read the runtime exception. +2. Correlate it with logs and requests. +3. Find the failing code path. +4. Apply or suggest the focused fix. +5. Validate the application again. + +That is the difference I want to highlight in this article. ABP AI Coding Agent is not only a model that can edit files. When ABP Studio tools are enabled, it can participate in the same development workflow I use: observing the running application, understanding the failure, fixing the code, and validating the result. + +## Why This Is Different From Generic Coding Agents + +Tools like Cursor, Claude Code, and Codex are powerful. They can read code, edit files, run commands, and help with many software projects. + +**ABP AI Coding Agent has a different advantage: it is built for the ABP development experience.** + +It is aware of ABP concepts, ABP solution structure, ABP Studio run profiles, application metadata, containers, monitoring, tasks, and build actions. It is also backed by the ABP Platform: ABP Framework, ABP Commercial, ABP Suite, ABP Studio, and the workflows that connect them. + +That platform context matters. When I am building an ABP solution, I do not only want a model that can write C# or TypeScript. I want an assistant that understands the way ABP applications are structured and the way ABP Studio runs them. + +**That makes the starting point simple:** _open the ABP solution, use ABP Studio, [choose the right mode](https://abp.io/community/articles/deep-dive-on-abp-ai-agent-1-agent-plan-and-ask-modes-62wteg9t), enable the tools needed for the task, and work with the agent inside the platform._ + +## Conclusion + +Integrated tools make ABP AI Coding Agent more than a chat window next to the code. They give it a controlled way to work with the same ABP Studio context I already use: **applications**, **containers**, **monitoring**, **tasks**, and **build actions**. + +-> **That helps the agent move from "I think this might be the problem" to "I checked the runtime evidence, found the related code, applied the fix, and validated it."** + +That is the real value of this part of ABP Studio AI. It brings the coding agent closer to the full development experience, from understanding the solution to running it, observing it, fixing it, and checking the result. + +As ABP Studio evolves, more tools can be added to this workflow. That means the agent can become more useful over time without changing the basic idea: _ABP AI Coding Agent works best when it is not isolated from the platform, but integrated into it._ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-application-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-application-tools.png new file mode 100644 index 0000000000..34845eac5b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-application-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-build-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-build-tools.png new file mode 100644 index 0000000000..d1d4559ca1 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-build-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-container-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-container-tools.png new file mode 100644 index 0000000000..ac9c9f9e5a Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-container-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-monitoring-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-monitoring-tools.png new file mode 100644 index 0000000000..73fc3d5355 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-monitoring-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-task-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-task-tools.png new file mode 100644 index 0000000000..89239146fc Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-task-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-tools-overview.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-tools-overview.png new file mode 100644 index 0000000000..5d666173ba Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/abp-agent-tools-overview.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/cover-image.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/cover-image.png new file mode 100644 index 0000000000..7cf80f3905 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/cover-image.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/disabled-monitoring-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/disabled-monitoring-tools.png new file mode 100644 index 0000000000..cc84d05d25 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/disabled-monitoring-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/enabled-monitoring-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/enabled-monitoring-tools.png new file mode 100644 index 0000000000..d2dd9df33b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/enabled-monitoring-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/step-3-1.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/step-3-1.png new file mode 100644 index 0000000000..f6eb90c3ec Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/step-3-1.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/step-3-2.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/step-3-2.png new file mode 100644 index 0000000000..d457d9f220 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/step-3-2.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/tools-with-agent.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/tools-with-agent.png new file mode 100644 index 0000000000..7b0f88ac73 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/tools-with-agent.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/validate-the-fix.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/validate-the-fix.png new file mode 100644 index 0000000000..99a46deb60 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/validate-the-fix.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/with-tool-result-1.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/with-tool-result-1.png new file mode 100644 index 0000000000..ca86fcb3d4 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/with-tool-result-1.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/with-tool-result-2.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/with-tool-result-2.png new file mode 100644 index 0000000000..843b4ac856 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/with-tool-result-2.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/without-tool-result.png b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/without-tool-result.png new file mode 100644 index 0000000000..b381c31fba Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-4-tools/without-tool-result.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/add-mcp-server.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/add-mcp-server.png new file mode 100644 index 0000000000..766e0f55a2 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/add-mcp-server.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/add-seo-analyzer-mcp-server.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/add-seo-analyzer-mcp-server.png new file mode 100644 index 0000000000..1e98f568da Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/add-seo-analyzer-mcp-server.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/cover-image.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/cover-image.png new file mode 100644 index 0000000000..f4baa39fd2 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/cover-image.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/mcp-servers-empty.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/mcp-servers-empty.png new file mode 100644 index 0000000000..ea4785c009 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/mcp-servers-empty.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/post.md b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/post.md new file mode 100644 index 0000000000..a28a96edcd --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/post.md @@ -0,0 +1,315 @@ +# Deep Dive on ABP AI Agent #5: MCP (Model Context Protocol) + +When I use an AI coding agent inside ABP Studio, most of the work starts inside the solution. + +The agent can read the code. It can edit files. It can use the ABP Studio tools I enabled. It can build the solution, run tasks, start applications, inspect containers, and check monitoring data when something fails. [In the previous article of this](https://abp.io/community/articles/deep-dive-on-abp-ai-agent-4-integrated-abp-studio-tools-be2xa2om) series, we focused on exactly that: the integrated tools that connect the agent to the ABP Studio development environment. + +But there is another point where plain solution awareness is not enough. + +Sometimes the information I need is not in the repository. It is not in the running application. It is not in the build output, container state, or ABP Studio monitoring screen. + +It may be in a live Prometheus workspace. It may be in Google Search Console. It may be in an SEO analyzer, a documentation system, a database, a customer support tool, or another service that has nothing to do with the ABP solution itself. + +That is where **MCP** becomes useful. + +MCP, short for **Model Context Protocol**, gives ABP AI Coding Agent a standard way to reach external tools and data sources. It does not replace the built-in ABP Studio tools. It extends the agent beyond the solution boundary when the task depends on something outside that boundary. + +> **Note:** ABP AI Coding Agent is available directly to ABP license holders. License holders have predefined credits so they can try the integrated experience without setting up a separate AI workflow first. MCP is one of the ways this experience can be extended when the agent needs access to systems outside ABP Studio. + +## Why MCP Matters + +Most coding agents start from the same place: **the files they can see**. + +That is a good start, but real development work often needs context from somewhere else: production metrics, SEO data, analytics, search performance, customer records, internal APIs, or product knowledge. + +If the agent cannot reach those systems, the developer becomes the bridge: + +- Copy this metric result. +- Paste that SEO report. +- Export this analytics data. +- Summarize this dashboard. +- Explain which production signal matters. +- Tell the agent what the external system says. + +That works, but it is not the best workflow. The more information I manually copy into the chat, the easier it is to miss something, simplify too much, or give the agent an outdated snapshot. + +MCP reduces that manual work. It lets the agent ask an external system for context directly, within the permission boundary I configure. + +The important detail is that MCP is not "the agent can do anything now." It is a controlled extension point. A server is connected. Its tools are listed. Individual tools can be enabled or disabled. The agent can only use what is available in the current session. + +That gives me a practical balance: the ABP-aware development experience stays in ABP Studio, and external context can be added only when the task needs it. + +## What MCP Actually Is + +MCP is an open standard for connecting external tools and data to AI agents. + +Instead of every tool inventing its own integration for every agent, MCP defines a common shape. A program that exposes tools or resources through this standard is called an **MCP server**. An agent that understands MCP can connect to that server and use what it provides. + +For me, the easiest way to think about it is simple: + +- ABP Studio tools connect the agent to the ABP development environment. +- MCP servers connect the agent to systems outside that environment. + +The server might expose a tool for querying Prometheus, checking Search Console data, running an SEO audit, inspecting a database, or calling an internal API. The exact capability depends on the server. + +MCP is not a prompt, a rule, a skill, or a lesson. Those shape what the agent knows or how it behaves. MCP changes what the agent can reach. + +That makes MCP more like equipment than instruction. It gives the agent access to a tool or data source that was previously outside its working area. + +There are already many community and vendor MCP servers for different systems. A useful place to discover examples is the [awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers) repository, which collects MCP servers across many categories. + +For concrete examples, there are MCP servers for [Prometheus metrics](https://github.com/pab1it0/prometheus-mcp-server), [AWS Managed Prometheus](https://github.com/awslabs/mcp/tree/main/src/prometheus-mcp-server), and [SEO analysis](https://github.com/g-battaglia/mcp-seo). The exact server you choose depends on your environment, but the pattern is the same: expose a focused external capability to the agent through MCP. + +## When You Actually Need It + +Most of the time, I would not start with MCP. + +For normal ABP development, the built-in tools are usually the right first layer. The agent can already work with the solution, run profiles, applications, containers, tasks, builds, monitoring data, and documentation. If the task is completely inside the ABP solution, MCP may not add anything. + +MCP becomes useful when the task crosses the solution boundary. + +For example, I may ask: + +```text +Run an SEO audit for https://abp.io, summarize the weak areas, and suggest which content or technical improvements would matter most. +``` + +Without MCP, I would need to open a separate SEO tool, run the audit, copy the results, and paste them into the chat. With the right MCP server enabled, the agent can call the SEO tool directly and reason from the structured result. + +Or I may ask: + +```text +Connect to our Prometheus MCP server, check the error rate and p95 latency for the AuthServer over the last 30 minutes, and tell me whether the last deployment changed anything. +``` + +That is a very different kind of task. The answer is not in the source code alone. It lives in a monitoring system. Prometheus MCP servers exist for this kind of workflow; for example, some expose tools for instant PromQL queries, range queries, metric discovery, and target inspection. + +Another realistic prompt could be: + +```text +Use Search Console data to find pages that lost traffic this month, then inspect the related docs pages and suggest focused improvements. +``` + +Again, the important part is not that the agent magically knows everything. The important part is that I can connect a specific external system, expose a specific set of tools, and let the agent use them when they are relevant. + +If the work starts outside the codebase but ends inside the codebase, MCP can help connect those two parts of the workflow. + +## Setting Up An MCP Server + +MCP servers are configured under **Settings > MCP Servers**. + +When no server is connected, the page is intentionally simple. It is an empty list waiting for the first server. That is important because MCP should be explicit. If I have not connected a server, the agent should not behave as if it has access to that external system. + +![The MCP Servers page in ABP Studio, before any server is added](mcp-servers-empty.png) + +When I add a server, ABP Studio asks how it should connect. + +There are two main connection types: + +- **Stdio:** ABP Studio runs the MCP server as a local process. I provide the command, arguments, and environment variables the server needs. +- **HTTP:** ABP Studio connects to an MCP server over the network. I provide the URL and any required headers. + +![Adding an MCP server: pick stdio or HTTP, then provide the command, arguments, and environment variables](add-mcp-server.png) + +This is useful because different MCP servers are packaged in different ways. Some are local command-line programs. Some are hosted services. Some need environment variables for tokens or configuration. + +In the example below, I am adding an HTTP MCP server named **SEO Analyzer**. It exposes a small set of SEO-related tools through a remote MCP endpoint. + +![Adding the SEO Analyzer MCP server over HTTP](add-seo-analyzer-mcp-server.png) + +ABP Studio does not require every server to be created manually from scratch. If I already use MCP elsewhere, I can import existing configuration from tools like Cursor, Claude, VS Code, Windsurf, or a plain MCP server JSON file. It can also export configuration in the standard `mcpServers` JSON shape. + +That matters because MCP is an open ecosystem. The same server definition can often move between tools. ABP Studio becomes another place where I can use that server, but now in the context of an ABP-aware agent. + +## What A Connected Server Shows + +After a server is connected, ABP Studio shows the important parts of the connection. + +I can see whether the server is connected, how many tools it exposes, which tools are available, and whether it provides resources. I can also inspect resources directly from the settings page. + +![A connected SEO Analyzer MCP server and its available tools](seo-analyzer-mcp-tools.png) + +This visibility matters because an MCP server is not just a checkbox. It is a surface area. It may expose one tool or many. Some tools may be read-only, and some may perform actions outside the repository. + +The agent does not need me to call those tools manually. I describe the task, and if the tool is available and relevant, the agent can decide to use it. That is why a prompt can stay natural: + +```text +Read the product requirement from the connected knowledge base and compare it with the current implementation. +``` + +The prompt does not need to become a tool invocation script. The agent still owns the reasoning loop. MCP only gives it a new place to look. + +## Tool Access: What The Agent Is Allowed To Use + +The most important part of MCP in ABP Studio is not only connecting servers. It is controlling what the agent is allowed to use. + +Individual tools can be disabled. If a tool is disabled, it is not offered to the agent. The agent should not plan around it, call it, or assume it has access to it. + +That is useful for safety, but also for quality. + +![Disabling individual tools exposed by the SEO Analyzer MCP server](seo-analyzer-mcp-tools-disabled-indivually.png) + +Every enabled tool becomes part of the menu the agent considers. If a server exposes ten tools but the current task only needs two, I usually prefer to enable only those two. A shorter tool list is easier for the agent to choose from. It also makes the session easier for me to reason about. + +The permission model is simple: + +- If a server is not connected, the agent cannot use it. +- If a server is disabled, the agent cannot use its tools. +- If an individual tool is disabled, the agent cannot use that tool. +- If the session is not in Agent mode, MCP tools are not available. + +That last point is important. MCP tools are available in **Agent mode only**. + +Plan and Ask modes are read-only by design. They are useful for understanding, planning, and discussing changes, but they do not receive MCP tools. If I want the agent to call an external MCP tool, I need to work in Agent mode. + +This keeps the model consistent with the rest of the ABP AI Coding Agent experience. The mode determines what kind of work the agent is allowed to perform. + +## Keeping MCP Safe + +MCP is powerful because it lets an AI agent reach systems outside the solution. + +That is also why it needs care. + +An MCP server is a program. It may read files, call APIs, query databases, send requests, or perform actions depending on how it was built. The agent is not inventing those capabilities. It is using what the server exposes. + +So trust matters at two levels. + +First, I need to trust the actions. If a tool can update an issue, send a message, change a record, or trigger a workflow, that is a real side effect. + +Second, I need to trust the descriptions. When an MCP server connects, the names and descriptions of its tools become part of what the model can read. A careless or hostile server can use that text to influence the model before I even write my prompt. + +That means MCP safety is not only about "what can this tool do?" It is also about "what instructions or descriptions does this server place in front of the model?" + +Two habits keep this manageable: + +- Connect only servers I trust. +- Disable tools the agent should not use for the current task. + +This sits on top of the agent's normal guardrails. Shell commands, URL fetches, downloads, and other sensitive actions can still require permission. Also, the tool list is fixed for a running session, so changing a server in the middle of a session does not quietly alter the tool surface already given to that session. + +That is the behavior I want from this kind of integration. MCP can extend the agent, but it should do so through visible, intentional configuration. + +## A Practical MCP Walkthrough + +Let me make this more concrete with a small scenario. + +Imagine I am working on the public website or documentation side of an ABP-based product. The task is not a compiler error or a failing unit test. I want to understand how the website looks from an SEO perspective and what I would improve first. + +Without MCP, I would open a separate SEO tool, run the audit, wait for the result, copy the score, paste the table into the chat, and then ask the agent to interpret it. That is not terrible, but it turns me into a copy-paste integration layer. + +With an SEO Analyzer MCP server connected, I can start from a better prompt: + +```text +Please rate the SEO work on the https://abp.io website on a scale of 1 to 10. I'd also like to know what you would do if you were in charge. +``` + +At that point, the agent can use the SEO Analyzer MCP tool instead of guessing from general SEO knowledge alone. It can call the audit tool, read the structured result, and turn it into a concrete improvement plan. + +![ABP AI Coding Agent using the SEO Analyzer MCP server](using-mcp.png) + +The value is not only that the agent saved me a few seconds. The value is that the agent starts from live tool output instead of a manually summarized report. + +### First, Without The MCP Server + +If no SEO MCP server is connected, the agent cannot run the audit directly. + +It may still give a reasonable answer from general knowledge. It may say that the site should have good titles, descriptions, headings, performance, backlinks, structured data, and content targeting. That advice can be useful, but it is generic. + +For example, I might write: + +```text +Review the SEO of abp.io and tell me what to improve. +``` + +The agent can reason about common SEO practices, but it does not have a fresh audit result. It does not know which categories scored well, which areas are weak, or whether the live page data confirms the concern. + +This is similar to debugging without monitoring tools. The agent can reason, but it is reasoning from weaker evidence. + +### Then, With The MCP Server Enabled + +Now imagine the SEO Analyzer MCP server is connected, enabled, and the relevant audit tool is enabled. + +I can ask: + +```text +Run an audit on https://abp.io, rate the result, and suggest the highest-impact improvements. +``` + +This time the agent can gather the external context first. In the screenshot, the server exposes tools like `run_audit_anonymous`, `run_audit`, `get_audit`, `list_audits`, `get_audit_pdf`, and `get_organic_traffic`. The agent uses the audit tool and then explains the result in human terms. + +That changes the workflow: + +1. Run the external SEO audit. +2. Read the score and category breakdown. +3. Identify the weakest areas. +4. Suggest concrete technical or content improvements. +5. If needed, inspect the related website or documentation files. + +The agent is no longer guessing from best practices alone. It can connect the outside report to the actual website and then, if the relevant files are in the solution, help improve them. + +### Another Example: Live Monitoring + +SEO is only one example. Monitoring is another strong fit for MCP. + +Prometheus MCP servers can expose tools for PromQL instant queries, range queries, metric discovery, targets, and server information. That means I can ask operational questions in natural language while the agent queries the monitoring system through MCP. + +For example: + +```text +Use the Prometheus MCP server to check p95 latency, request rate, and error rate for the public web application after the last deployment. If something changed, identify the most likely area to inspect in the ABP solution. +``` + +That does not mean the agent should blindly change production behavior. It means the agent can start from live evidence: metrics, trends, targets, and recent changes. Then it can use ABP Studio context to inspect the related application, module, or configuration. + +### Combining MCP With ABP Studio Tools + +The best part is that MCP does not replace the ABP Studio tools from the previous article. It works beside them. + +In a real workflow, MCP may provide the outside signal, and ABP Studio tools may provide the inside development loop: + +1. MCP brings in the external context. +2. ABP-aware reasoning maps it to the solution. +3. ABP Studio tools help implement and validate the change. + +That is where the integration becomes more useful than a generic MCP checkbox. The agent is connected to an external tool while still understanding the ABP development environment. + +## How MCP Fits With Everything Else + +MCP is an open standard, so connecting an MCP server is not an ABP-only idea. + +Tools like Cursor, Claude, VS Code extensions, and other AI coding environments can also use MCP. That is part of its appeal. Teams can build or adopt a server once and use it across different tools. + +What makes MCP especially useful in ABP Studio is the company it keeps. ABP AI Coding Agent already understands the ABP solution structure, application layers, modules, migrations, proxies, build actions, run profiles, containers, monitoring data, and official ABP documentation. MCP adds the outside world to that picture. + +So I do not need to choose between: + +- an agent that understands ABP, +- and an agent that can reach my team's external systems. + +The better experience is both together, under visible configuration. Put simply: + +- Built-in ABP Studio tools handle the solution and runtime environment. +- MCP servers handle external tools and data sources. +- Rules, skills, and lessons shape how the agent behaves. +- Agent mode decides whether tool use is available for the session. + +Each part has a different role. Keeping those roles clear makes the agent easier to trust. This is also why I would start small in a real team workflow: one low-risk, high-value server, probably a read-only analytics, documentation, or monitoring server, with only the tools that support a clear workflow enabled. + +MCP by itself is a protocol. The difference in ABP Studio is that MCP becomes part of an ABP development session. The agent can read an external SEO report or monitoring signal, but it can also understand which ABP application, module, page, or configuration owns the behavior and validate the change with ABP Studio tools. + +Most real tasks are not only "call an external tool." They are more like: + +1. Understand the requirement from outside the repository. +2. Find where that behavior lives in the ABP solution. +3. Make the smallest correct change. +4. Validate it in the same development environment. + +MCP helps with the first part. ABP Studio AI helps connect the rest. + +**That makes MCP valuable not because it is another tool list, but because it extends the ABP AI Coding Agent beyond the repository without disconnecting it from the ABP workflow.** + +## Conclusion + +MCP is the agent's connection to everything that is not already inside the ABP solution. + +You will not need it for every task. But when the work depends on SEO data, live metrics, external documentation, internal services, or team knowledge, MCP gives the agent a standard way to reach that context. \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/seo-analyzer-mcp-tools-disabled-indivually.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/seo-analyzer-mcp-tools-disabled-indivually.png new file mode 100644 index 0000000000..e1b631f99c Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/seo-analyzer-mcp-tools-disabled-indivually.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/seo-analyzer-mcp-tools.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/seo-analyzer-mcp-tools.png new file mode 100644 index 0000000000..2160b389e0 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/seo-analyzer-mcp-tools.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/using-mcp.png b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/using-mcp.png new file mode 100644 index 0000000000..68810c6915 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-5-mcp/using-mcp.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/ai-agent-panel.png b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/ai-agent-panel.png new file mode 100644 index 0000000000..c0fcd9a3e7 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/ai-agent-panel.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/auth-identity-scope.png b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/auth-identity-scope.png new file mode 100644 index 0000000000..1fb92ec9ca Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/auth-identity-scope.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/post.md b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/post.md new file mode 100644 index 0000000000..346aaf23ed --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/post.md @@ -0,0 +1,159 @@ +# Deep Dive on ABP AI Agent #7: Scopes + +When I use an AI coding agent in a real ABP solution, I do not always want it to see everything. + +That may sound strange at first. More context usually feels better. But in a large solution, more context can also mean more noise, more unrelated files, and more chances for the agent to drift into an area that is not part of the task. + +If I am working on the public side of a modular application, I do not want the agent to redesign the admin side. If I am changing a Catalog module, I do not want it to spend half the session reasoning about Identity, SaaS, or Payment code. If I am fixing one microservice, I do not want the agent to treat the whole platform as editable surface area. + +That is where **AI Scopes** become one of the most important control features in **ABP Studio AI Coding Agent**. + +![ABP AI Coding Agent panel](scopes-openning.png) + +## Why Scopes Matter? + +Most AI coding agents are very good at reading a folder and making changes. That is useful, but an ABP solution is rarely just a folder. + +An ABP solution can contain modules, packages, applications, gateways, background workers, database projects, shared contracts, UI projects, and infrastructure configuration. In a microservice solution, the repository may contain multiple independently meaningful services. In a modular monolith, a single solution may still have clear business boundaries. + +-> In those situations, the question is not only: **Can the agent understand the solution?** ❌ + +-> The better question is: **Which part of the solution should the agent be allowed to work with for this task?** ✅ + +AI Scopes answer that question directly. + +They let me choose the accessible area before the session starts. The agent can then focus on the relevant module, package, solution area, or external folder instead of treating the entire repository as equally relevant. + +For me, that changes the feeling of using an AI agent. It is no longer "here is my whole codebase, please be careful." It becomes "here is the part of the system this task belongs to, work inside that boundary." + +## What An AI Scope Controls? + +An AI Scope **restricts which directories the agent can access during a session**. + +![Auth and Identity scope configuration in ABP Studio](auth-identity-scope.png) + +Depending on the task, a scope can include: + +* the whole solution, +* selected modules, +* selected packages, +* selected external folders, +* or a focused combination of these. + +The important part is that this is not only a prompt suggestion. It is part of the session context and file access boundary. File paths used by the agent are validated against the resolved scope. If a file is outside the accessible directories, the agent should not treat it as part of the editable workspace. + +Scopes also work together with `.abpignore`. Even if a file is under an accessible directory, files excluded by `.abpignore` remain blocked. That gives teams two useful layers: + +* **Scopes** decide which solution areas are relevant to the task. +* **`.abpignore`** protects files that should stay inaccessible, such as secrets, certificates, environment files, or other sensitive local data. + +This is a practical control model. I can narrow the agent's working area without pretending that the repository is smaller than it really is. + +## Scope Is Locked To The Session + +Another detail I like is that scope belongs to the AI Agent session. + +The first message of a session locks the configuration that affects the system prompt, including the active AI Scope. If a background session continues running and I change the foreground scope later, that running session does not silently change its context. + +That matters when multiple sessions are active. + +![Selected AI scope shown in the agent session panel](selected-scope.png) + +Imagine I have one session working on a Catalog module and another session answering questions about the whole solution. Those sessions should not accidentally share a changing boundary. Each one should keep the scope it started with. + +This makes scopes more predictable. I can choose the scope intentionally at the beginning of the work and trust that the session is tied to that decision. + +## Focused Autonomy + +Scopes are not about making the agent weaker. They are about making autonomy more focused. + +When I narrow the scope, I am not saying the agent is less capable. I am saying the task has a boundary. + +For example: + +```text +Use the Public AI Scope. +Add a small validation improvement to the public product search flow. +Do not inspect or change the Admin side unless you find a direct contract dependency. +``` + +That kind of prompt becomes much stronger when the selected scope already matches the instruction. The agent receives both the natural-language task and the platform-level boundary. + +This is especially useful for ABP because ABP applications are built around clear concepts: modules, layers, packages, application services, repositories, DTOs, permissions, localization resources, DbContexts, and run profiles. A scope can follow those boundaries instead of relying only on a long prompt. + +## What Scopes Help Prevent? + +Scopes help reduce a few common AI-agent failure modes. + +- First, they reduce **unrelated exploration**. The agent does not need to spend time discovering files that have nothing to do with the task. +- Second, they reduce **accidental edits**. When a task belongs to one module, the agent should not casually change another module just because it found a similar type there. +- Third, they improve **reviewability**. If I scoped the task to `Catalog`, and the diff changes `Identity`, that is immediately suspicious. The boundary makes the review easier. +- Fourth, they support **parallel work**. Different sessions can be scoped to different areas, which is useful when independent tasks are running in the same solution. + +This is one of the places where ABP AI Coding Agent feels different from a generic coding tool. The feature is not only "the model can read fewer files." It is integrated into ABP Studio's understanding of the solution. + +## Scopes And ABP Solution Architecture + +ABP already encourages clear boundaries. + +In a layered module, the Domain layer should not depend on the Application layer. HTTP API projects should depend on contracts, not implementation projects. Entity Framework Core and MongoDB integrations should stay behind the domain abstractions. A reusable module should be understandable as a module, not only as a set of files. + +AI Scopes fit naturally into that mindset. + +If I am working on a Domain change, I can keep the scope close to the module and its required shared contracts. If I am working on UI behavior, I can include the UI package and the related contract package. If I am working on a microservice, I can scope the agent to that service and only add external folders when they are truly required. + +That means the agent's working area can follow the same mental model I already use as an ABP developer: + +```text +What bounded area owns this change? +Which packages are needed to make it safely? +Which parts of the system should stay out of this session? +``` + +## Scopes And Workflows Work Better Together + +- Scopes define **where** the agent can work. +- Workflows define **what deterministic actions** should happen around that work. + +![ABP AI Coding Agent panel showing scopes and workflows combined](scopes-openning.png) + +That combination is powerful. For example, I can scope the agent to the `Catalog` module and use a workflow that builds the affected package, regenerates proxies if contracts changed, and restarts the related application. + +The scope keeps the coding session focused. The workflow keeps the verification loop repeatable. + +This is the larger ABP Studio AI story. It is not only an AI chat window. It is an agent inside a platform that already understands ABP solutions, run profiles, tools, workflows, Git state, and runtime signals. + +## Why This Is Different From Generic Coding Agents? + +Tools like Cursor, Claude Code, Codex, and Windsurf are strong general-purpose coding tools. They can read files, edit code, run shell commands, and help with many projects. + +**ABP AI Coding Agent is different because it is built around ABP Studio's view of an ABP solution.** + +Scopes are a good example of that difference. + +In a generic tool, I can try to simulate scope with a prompt: + +```text +Only work in this folder. +``` + +That is helpful, but it is still mostly an instruction. In ABP Studio, scope is part of the agent session and file access model. It can be selected intentionally before the work starts, stored with the session, and combined with `.abpignore`, workflows, tools, plans, and run profile context. + +For professional ABP teams, that matters. The goal is not to give an AI agent unlimited access and hope the prompt is clear enough. The goal is to create a controlled development loop where the agent understands the system, works in the right area, uses the right tools, and produces a diff that is easier to trust. + +## Conclusion + +AI Scopes make ABP Studio AI Coding Agent feel more deliberate. + +They let me say: + +* this is the part of the solution that matters, +* this is the boundary for the current session, +* this is the context the agent should focus on, +* and everything else should stay outside unless we intentionally expand the scope. + +That is exactly the kind of control I want when using AI in real ABP solutions. + +The agent can still be powerful. It can still plan, edit, build, run tools, and iterate. But with scopes, that power is pointed at the right part of the system. + +That is the real value: **not just more AI autonomy, but better-shaped AI autonomy for ABP development.** diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/scopes-openning.png b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/scopes-openning.png new file mode 100644 index 0000000000..6297b817e8 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/scopes-openning.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/selected-scope.png b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/selected-scope.png new file mode 100644 index 0000000000..b9039e28c2 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-7-scopes/selected-scope.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/ai-agent-panel.png b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/ai-agent-panel.png new file mode 100644 index 0000000000..c0fcd9a3e7 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/ai-agent-panel.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/post.md b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/post.md new file mode 100644 index 0000000000..49c4f51e60 --- /dev/null +++ b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/post.md @@ -0,0 +1,205 @@ +# Deep Dive on ABP AI Agent #9: Workflows + +There is a pattern I see in almost every real development session. + +The interesting part is the code change, but the repeated part is everything around it: + +* start the containers, +* build the affected packages, +* add a migration if the model changed, +* regenerate proxies if the API contract changed, +* restart the application, +* run the validation task, +* check the logs when something fails... + +When I work alone, I can do those steps manually. When I work with an AI coding agent, I do not want to keep pasting the same checklist into every prompt. I want the tool to understand that this solution has a normal way of preparing, validating, and recovering after changes. + +That is the point of **ABP Studio AI Agent Workflows**. + +![ABP Studio AI Agent Workflows overview](workflow.jpg) + +Workflows let me define repeatable actions around an agent run. The model can focus on the ambiguous part, understanding the requirement and changing the code, while ABP Studio handles the deterministic parts that should happen before or after the work. + +That combination is one of the clearest differences between **ABP AI Coding Agent** and a generic coding assistant. It is not only an editor with a chat panel. It is an agent inside a platform that **already knows how to build, run, migrate, generate proxies, manage containers, execute tasks, and inspect runtime signals.** + +## Why Workflows Matter? + +LLMs are powerful because they can reason through unclear requirements and modify code across files. But many development steps should not be creative. + +If the team always builds a package after an application service change, that should be predictable. If API contract changes require proxy generation, that should not depend on whether I remembered to mention it. If a local run needs containers before the application starts, that setup should not be reinvented in every prompt. + +Workflows give ABP Studio a place to encode those repeatable steps. + +![Opening the workflow settings panel in ABP Studio](workflows-openning.png) + +![ABP AI Agent workflow settings](workflow-settings.png) + +For me, the value is not only automation. It is also consistency. + +Instead of asking: + +```text +Please implement this, and then build, and maybe regenerate proxies, +and restart the app, and also add a migration if needed. +``` + +I can configure the workflow once and let the agent session carry that context. + +![Sample ABP AI Agent workflow configuration](sample-workflow-1.png) + +That makes the prompt cleaner: + +```text +Add the missing status filter to the order list. +Use the selected workflow for validation after the change. +``` + +The workflow becomes part of the development environment, not a long instruction I repeat manually. + +## Before And After The Agent Works + +An AI Agent workflow has two sides: + +* **Before steps** prepare the environment before the agent starts coding. +* **After steps** guide the validation and follow-up work after the main task is complete. + +Before steps run automatically in **Agent** mode. They are useful for setup actions that should happen before the model receives control, such as starting containers or running a preparation task. + +Plan and Ask modes are read-only, so they do not execute before steps. That separation is important. If I am only asking a question or asking for a plan, I do not want Studio to start applications or run mutation-oriented tooling. + +After steps are injected into the agent instructions as post-task guidance. The agent is expected to run the relevant post-steps after completing the work, but it can skip actions that do not apply. + +For example, if my workflow includes "Add Migration" but the change does not touch the EF Core model, the agent should not create an empty migration just because the workflow exists. The workflow gives deterministic options, but the agent still uses the actual change to decide what is relevant. + +## What A Workflow Can Do? + +Workflow actions are built around the things ABP developers already do in ABP Studio. + +![ABP AI Agent workflow actions](workflow-actions.png) + +The supported actions include: + +* **Build** the solution, selected modules, selected packages, or configured targets. +* **Start Application** for selected applications, folders, or all runnable applications. +* **Stop Application** when validation needs a clean state. +* **Restart Application** after code changes. +* **Start Containers** for databases, caches, message brokers, or other dependencies. +* **Stop Containers** when the workflow needs to clean up. +* **Run Task** for configured ABP Studio tasks. +* **Add Migration** when entity changes require a database migration. +* **Generate C# Proxies** after contract changes. +* **Generate Angular Proxies** after API changes consumed by Angular clients. + +That list is very ABP-specific. + +A generic coding tool can run shell commands, and that is useful. But ABP Studio workflows know about ABP Studio concepts: applications, containers, run profile tasks, packages, modules, migrations, and proxy generation. The agent can use those as first-class actions instead of trying to infer everything from terminal commands. + +## Personal And Shared Workflows + +Workflows can be personal or shared. + +![Sharing an ABP AI Agent workflow with the team via run profile](shared-with-team.png) + +- A **personal workflow** is stored locally under the solution workspace. It is useful for my own development habits. Maybe I like restarting a specific app after each agent turn. Maybe I have a local task that only makes sense on my machine. +- A **shared workflow** is stored with the active run profile. That makes it suitable for source control and team usage. + +This is where workflows become more than a convenience feature. A team can encode its normal AI-agent validation path into the solution itself. + +For example: + +```text +Before: +- Start required containers +- Run the prepare-local-environment task + +After: +- Build affected packages +- Generate Angular proxies when contracts changed +- Restart the Web and API applications +- Run the smoke-test task +``` + +Every developer using that run profile can work with the same repeatable loop. The workflow does not replace code review or testing, but it raises the baseline for what happens after the agent touches code. + +## Workflows Make AI More Deterministic Where It Should Be + +- I do not want the model to creatively decide whether my team usually runs proxy generation. I want the workflow to encode that. +- I do not want every prompt to include a long validation checklist. I want the workflow to carry it. +- I do not want an agent to guess which applications belong to the local run. I want ABP Studio's run profile to provide that context. + +This is why workflows are such a strong fit for ABP Studio AI Coding Agent. The model stays flexible where flexibility helps, and the platform stays deterministic where determinism matters. + +The result is a cleaner division of responsibility: + +| Responsibility | Best handled by | +| --- | --- | +| Understanding the requirement | AI Agent | +| Finding and editing relevant code | AI Agent | +| Starting known containers | Workflow | +| Running known tasks | Workflow | +| Adding migrations when needed | Agent using workflow action | +| Generating proxies when contracts change | Agent using workflow action | +| Building affected packages | Workflow / Studio tools | +| Investigating runtime failures | Agent using monitoring tools | + +## Workflows And Scopes Together + +Workflows are even better when combined with AI Scopes. + +Scopes define where the agent can work. Workflows define what repeatable actions should happen around that work. + +For example, I can select a `Catalog` scope and a workflow that: + +* builds the `Catalog` package, +* regenerates proxies if contracts changed, +* restarts the public web app, +* checks recent exceptions after restart. + +That is a focused agent loop. The agent does not need the whole repository, and the validation path does not need to be invented from scratch. + +This is the kind of full flow that makes ABP AI Coding Agent feel different from tools that only operate at the file-and-terminal level. + +## Why This Is Different From Generic Coding Agents + +Generic coding agents can be excellent: Cursor, Claude Code, Codex, Windsurf, and similar tools can read code, edit files, run shell commands, and help across many kinds of projects. + +But ABP Studio AI Coding Agent is built for a different experience: **It works inside ABP Studio, where the solution already has run profiles, applications, containers, tasks, modules, packages, migrations, proxy generation, monitoring, Git integration, and ABP-aware analysis.** + +Workflows use that platform context. + +Instead of saying: + +```text +Run whatever commands seem appropriate. +``` + +I can say: + +```text +Use the selected ABP Studio workflow. +``` + +That is a very different contract. The workflow is _visible, configurable, repeatable, and tied to the solution_. + +For ABP teams, this matters because the development process is not only code generation. It is code generation plus build, migration, proxy generation, application restart, runtime observation, and review. + +ABP Studio AI Coding Agent is designed for that full loop. + +## Conclusion + +Workflows make ABP Studio AI Coding Agent more practical for real development. + +They let me move repeated setup and validation steps out of my prompt and into the platform: + +* start what needs to be running, +* build what needs to be built, +* generate what needs to be regenerated, +* migrate when a model change requires it, +* restart the relevant apps, +* and continue the debugging loop with runtime evidence. + +That is why I see workflows as one of the features that makes ABP AI Coding Agent feel complete. + +The agent is not isolated from the development environment. It works inside ABP Studio, with the same solution structure, run profile, tools, and team workflow that I already use. + +That is the difference: **not just AI-generated code, but an AI-assisted ABP development flow from change to validation.** diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/sample-workflow-1.png b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/sample-workflow-1.png new file mode 100644 index 0000000000..b834ee979b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/sample-workflow-1.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/shared-with-team.png b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/shared-with-team.png new file mode 100644 index 0000000000..08257f286b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/shared-with-team.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow-actions.png b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow-actions.png new file mode 100644 index 0000000000..08d906d188 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow-actions.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow-settings.png b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow-settings.png new file mode 100644 index 0000000000..e539c3cf65 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow-settings.png differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow.jpg b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow.jpg new file mode 100644 index 0000000000..f7b8e5774b Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflow.jpg differ diff --git a/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflows-openning.png b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflows-openning.png new file mode 100644 index 0000000000..6297b817e8 Binary files /dev/null and b/docs/en/Community-Articles/2026-06-18-deep-dive-9-workflows/workflows-openning.png differ diff --git a/latest-versions.json b/latest-versions.json index ccd634c25c..5b6987a34f 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,13 @@ [ + { + "version": "10.4.1", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "5.4.1" + } + }, { "version": "10.4.0", "releaseDate": "",