# Database Schema for Inbounds and Outbounds in S-UI: Complete Technical Reference

> Explore the S-UI database schema for inbounds and outbounds. Learn how GORM models and SQLite store routing configurations with fixed columns and JSON blobs.

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

---

**S-UI stores routing configurations in a SQLite database using GORM models, with `inbounds` and `outbounds` tables that combine fixed relational columns and JSON blobs for protocol-specific settings.**

S-UI is a web-based management interface for V2Ray/Xray proxies built by Alireza. Understanding the database schema for inbounds and outbounds in S-UI is essential for developers extending the platform, migrating configurations, or troubleshooting routing logic. The schema follows a hybrid relational/JSON design that balances query performance with the flexibility needed to support diverse proxy protocols.

## Inbounds Table Structure

The `inbounds` table, defined in [`database/model/inbounds.go`](https://github.com/alireza0/s-ui/blob/main/database/model/inbounds.go), stores incoming connection configurations:

| Column | Go Type | SQLite Type | Description |
|--------|---------|-------------|-------------|
| `id` | `uint` | `INTEGER PRIMARY KEY AUTOINCREMENT` | Primary key generated by GORM |
| `type` | `string` | `TEXT` | Protocol identifier (e.g., `vmess`, `trojan`, `vless`) |
| `tag` | `string` | `TEXT UNIQUE` | Human-readable unique identifier |
| `tls_id` | `uint` | `INTEGER` | Foreign key referencing `tls.id` |
| `addrs` | `json.RawMessage` | `BLOB` | JSON-encoded listening addresses |
| `out_json` | `json.RawMessage` | `BLOB` | Embedded outbound reference as JSON |
| `options` | `json.RawMessage` | `BLOB` | Protocol-specific configuration blob |

### TLS Relationship

The `tls_id` column establishes a **one-to-one relationship** with the `tls` table. This allows each inbound to reference dedicated TLS settings stored separately in the database. When `tls_id` equals `0`, the inbound operates without TLS encryption.

## Outbounds Table Structure

Located in [`database/model/outbounds.go`](https://github.com/alireza0/s-ui/blob/main/database/model/outbounds.go), the `outbounds` table uses a simplified schema optimized for egress routing:

| Column | Go Type | SQLite Type | Description |
|--------|---------|-------------|-------------|
| `id` | `uint` | `INTEGER PRIMARY KEY AUTOINCREMENT` | Primary key |
| `type` | `string` | `TEXT` | Outbound protocol (e.g., `direct`, `freedom`, `socks`) |
| `tag` | `string` | `TEXT UNIQUE` | Unique identifier for routing rules |
| `options` | `json.RawMessage` | `BLOB` | JSON-encoded server configuration |

Unlike inbounds, outbounds do not require foreign key relationships. All protocol-specific data—including server addresses, authentication credentials, and transport settings—resides in the `options` JSON column.

## GORM Model Definitions

Both tables are implemented as Go structs with GORM annotations. In [`database/model/inbounds.go`](https://github.com/alireza0/s-ui/blob/main/database/model/inbounds.go), the `Inbound` struct implements custom marshaling:

```go
type Inbound struct {
    ID      uint            `gorm:"primaryKey"`
    Type    string          `gorm:"type:text"`
    Tag     string          `gorm:"type:text;uniqueIndex"`
    TlsId   uint            `gorm:"column:tls_id"`
    Addrs   json.RawMessage `gorm:"type:blob"`
    OutJson json.RawMessage `gorm:"type:blob"`
    Options json.RawMessage `gorm:"type:blob"`
}

```

The `Outbound` struct in [`database/model/outbounds.go`](https://github.com/alireza0/s-ui/blob/main/database/model/outbounds.go) follows a similar pattern but omits the TLS and address fields, reflecting the asymmetric configuration requirements between inbound listeners and outbound proxies.

## Database Initialization and Migration

S-UI automatically creates these tables during startup. In [`database/db.go`](https://github.com/alireza0/s-ui/blob/main/database/db.go), the `AutoMigrate` function ensures schema consistency:

```go
db.AutoMigrate(&model.Inbound{}, &model.Outbound{}, …)

```

This migration also seeds a default entry. If the `outbounds` table is empty, S-UI inserts a `direct` outbound automatically, ensuring basic connectivity exists immediately after installation.

## JSON Serialization Strategy

The schema relies heavily on custom `MarshalJSON` and `UnmarshalJSON` methods. These methods separate fixed relational fields from variable protocol configurations:

- **Fixed fields** (`id`, `type`, `tag`, `tls_id`) map to database columns for querying and indexing
- **Dynamic fields** pack into `json.RawMessage` blobs, allowing S-UI to support new V2Ray-compatible protocols without schema migrations

When the API serves configuration data, the `MarshalFull()` method (implemented on the `Inbound` struct) merges the fixed fields with the JSON blobs to produce complete configuration objects.

## Practical Query Examples

### Creating an Inbound

To insert a new VMess inbound with specific options:

```go
in := model.Inbound{
    Type:    "vmess",
    Tag:     "vmess-inbound",
    TlsId:   0,
    Addrs:   json.RawMessage(`["0.0.0.0:1080"]`),
    OutJson: json.RawMessage(`{"tag":"direct"}`),
    Options: json.RawMessage(`{
        "clients": [{"id":"uuid-here","alterId":0}]
    }`),
}
if err := database.GetDB().Create(&in).Error; err != nil {
    // handle error
}

```

### Reading Outbound Configuration

To retrieve and decode an outbound's settings:

```go
var out model.Outbound
if err := database.GetDB().Where("tag = ?", "direct").First(&out).Error; err != nil {
    // handle error
}

var opts map[string]interface{}
json.Unmarshal(out.Options, &opts)
// opts now contains the full protocol configuration

```

### Full JSON Marshaling

To generate complete configuration output including all fields:

```go
// Standard marshaling (fixed fields only)
b, _ := json.Marshal(in)

// Full marshaling (includes id, tls_id, addrs, out_json, and options)
full, _ := in.MarshalFull()

```

## Summary

- S-UI uses **SQLite** with **GORM** models for configuration persistence
- The `inbounds` table includes **seven columns**: metadata fields plus `addrs`, `out_json`, and `options` JSON blobs
- The `outbounds` table uses **four columns**: `id`, `type`, `tag`, and an `options` blob
- **Foreign key**: `inbounds.tls_id` references `tls.id` for encryption settings
- **Auto-migration** occurs in [`database/db.go`](https://github.com/alireza0/s-ui/blob/main/database/db.go) with default `direct` outbound seeding
- **Custom JSON marshaling** preserves fixed columns while supporting flexible protocol extensions

## Frequently Asked Questions

### How does S-UI handle protocol-specific settings without database schema changes?

S-UI stores variable configuration data in `json.RawMessage` blobs within the `options` column (and `addrs`/`out_json` for inbounds). Custom `MarshalJSON` methods pack protocol-specific fields into these BLOB columns, while fixed fields remain as queryable database columns. This hybrid approach accommodates new V2Ray protocols without requiring ALTER TABLE operations.

### What is the relationship between inbounds and TLS configurations?

The `inbounds` table includes a `tls_id` column that functions as a **foreign key** to the `tls` table. This creates a one-to-one relationship where each inbound optionally references TLS settings (certificates, encryption modes) stored in a separate table. A `tls_id` value of `0` indicates the inbound uses no TLS encryption.

### Where does S-UI initialize the database schema?

Database initialization occurs in [`database/db.go`](https://github.com/alireza0/s-ui/blob/main/database/db.go) through GORM's `AutoMigrate` function, which creates the `inbounds` and `outbounds` tables if they don't exist. The same file handles **default data seeding**, inserting a `direct` outbound entry automatically when the database is first created.

### Can I query inbound listening addresses using SQL?

While the `addrs` column stores data as a BLOB containing JSON (e.g., `["0.0.0.0:1080"]`), SQLite can query JSON content using the `json_extract` function. However, S-UI typically reads these values into Go structs and unmarshals them via `json.RawMessage` rather than performing complex SQL queries on the JSON blobs.