# How S-UI Handles Multi-Client and Multi-Inbound Configurations

> Learn how S-UI handles multi-client and multi-inbound configurations by storing inbound IDs in a JSON array and using atomic GORM transactions for synchronization. Discover efficient management techniques.

- Repository: [Alireza Ahmadi/s-ui](https://github.com/alireza0/s-ui)
- Tags: deep-dive
- Published: 2026-05-22

---

**S-UI manages multi-client and multi-inbound configurations by storing inbound IDs as a JSON array in the `clients.inbounds` column, using the `ClientService.Save` method in [`service/client.go`](https://github.com/alireza0/s-ui/blob/main/service/client.go) to detect changes, regenerate links via `util.LinkGenerator`, and synchronize relationships atomically within GORM transactions.**

Handling complex proxy topologies requires flexible many-to-many relationships between users and entry points. The alireza0/s-ui repository implements a sophisticated mapping system that allows any client to connect to any number of inbounds while maintaining strict data consistency and minimizing service disruption.

## The JSON-Based Many-to-Many Relationship

Unlike traditional relational designs that rely on join tables, S-UI stores the client-to-inbound mapping directly within the client record. The `clients.inbounds` column uses the `json.RawMessage` type to hold a JSON-encoded array of inbound IDs.

When processing client data, the system unmarshals this field using `json.Unmarshal(client.Inbounds, &inboundIds)` to extract the slice of uint identifiers. This approach eliminates the need for separate junction tables while supporting unlimited inbound assignments per client.

## Processing Client Changes with ClientService.Save

The orchestration logic for all multi-client and multi-inbound operations resides in [`service/client.go`](https://github.com/alireza0/s-ui/blob/main/service/client.go). The `ClientService.Save` method handles creation, editing, bulk addition, and bulk editing through a unified pipeline.

### Detecting Inbound Differences

For edit operations, the service invokes `findInboundsChanges` to perform a diff between the stored inbound ID array and the new submission. This comparison identifies precisely which inbounds were added or removed, enabling targeted updates rather than full rewrites.

### Regenerating Access Links

After determining the inbound changes, `updateLinksWithFixedInbounds` rebuilds the local link list for each affected client. For every inbound assigned to the client, the function calls `util.LinkGenerator` to create protocol-specific URIs (such as V2Ray or Trojan links). The system preserves non-local links, including custom URLs, ensuring that manual configurations remain intact during automated updates.

## Inbound-Side Synchronization Hooks

When administrators modify inbounds directly, S-UI propagates these changes to affected clients through three specialized helpers in the client service:

- **UpdateClientsOnInboundAdd** – Automatically appends the new inbound ID to each specified client's `inbounds` array and generates the corresponding local links for immediate connectivity.

- **UpdateClientsOnInboundDelete** – Removes the obsolete inbound ID from all client records and strips the associated local links to prevent stale configuration entries.

- **UpdateLinksByInboundChange** – Recomputes local links when inbound properties such as port, protocol type, or tag change, while maintaining any non-local custom links.

These methods are triggered from [`service/inbounds.go`](https://github.com/alireza0/s-ui/blob/main/service/inbounds.go) during the inbound lifecycle, ensuring the many-to-many mapping remains consistent regardless of which side initiates the change.

## Transaction Safety and Bulk Operations

All client-inbound mapping operations execute within a single GORM transaction (`tx *gorm.DB`). This guarantees atomicity during bulk operations, preventing partial updates when processing hundreds of clients simultaneously.

The `Save` method returns a slice of affected inbound IDs (`[]uint`), which the broader system uses to restart only the modified inbounds. This targeted approach avoids the performance penalty of reloading the entire proxy configuration.

## Implementation Examples

```go
// Attach a new inbound to specific clients by ID
svc := service.NewClientService()
err := svc.UpdateClientsOnInboundAdd(db, "1,3,7", newInboundID, "example.com")

```

```go
// Clean up references when deleting an inbound
svc := service.NewClientService()
err := svc.UpdateClientsOnInboundDelete(db, obsoleteInboundID, "old-tag")

```

```go
// Bulk edit clients with automatic link regeneration
svc := service.NewClientService()
changedInboundIDs, err := svc.Save(db, "editbulk", bulkDataJSON, "example.com")

```

## Summary

- S-UI implements multi-client and multi-inbound configurations using a JSON array in the `clients.inbounds` column rather than traditional join tables.
- The `ClientService.Save` method in [`service/client.go`](https://github.com/alireza0/s-ui/blob/main/service/client.go) handles all client modifications with full transaction safety.
- `findInboundsChanges` calculates precise differences between old and new inbound assignments to minimize processing overhead.
- `util.LinkGenerator` creates protocol-specific URIs while preserving existing non-local custom links.
- Inbound lifecycle hooks maintain synchronization when entry points are added, deleted, or modified.
- The architecture returns only affected inbound IDs to optimize service restarts and reduce downtime.

## Frequently Asked Questions

### How does S-UI store relationships between multiple clients and inbounds?

S-UI stores these relationships as a JSON array of inbound IDs within the `clients.inbounds` column, typed as `json.RawMessage`. This design allows each client to reference any number of inbounds without requiring a separate join table, simplifying the schema while maintaining flexibility.

### What happens to client links when an inbound is modified?

When an inbound's configuration changes, the `UpdateLinksByInboundChange` method recomputes the local links for all affected clients by invoking `util.LinkGenerator`. Non-local links such as custom URLs are preserved, ensuring that manual overrides remain intact while protocol-specific URIs update automatically.

### Are bulk client operations atomic in S-UI?

Yes. Whether performing bulk addition or bulk editing, the `ClientService.Save` method wraps all database operations in a GORM transaction. This ensures that all client-inbound mappings update simultaneously or roll back entirely, preventing configuration inconsistencies during large-scale changes.

### How does S-UI minimize service restarts during configuration updates?

The system tracks exactly which inbound IDs are modified during client or inbound operations. The `Save` method returns these IDs as `[]uint`, allowing S-UI to restart only the specific inbounds that changed rather than reloading the entire proxy service, significantly reducing downtime.