# System Schema

A UserSchema is a schema created by users through the Admin UI.

Source: https://fastschema.com/docs/framework/database/system-schema

## Userschema

A UserSchema is a schema created by users through the Admin UI. It is stored in JSON format, can be used to generate database table and is't represented by a Go struct.

For more information, see [Schema](/docs/concepts#schema).

From now on, we will refer to the `UserSchema` as `Schema`.

## SystemSchema

A System Schema is a Schema generated from a Go struct, designed to store system-level data such as users, roles, and permissions.

A System Schema can also be used to define any Schema from within the Go code.

System Schemas are created exclusively through Go code and cannot be deleted. However, they can be extended by users via the Admin UI.

**Example:**

<Tabs items={["Go Struct","Output Schema"]}>
<Tab value="Go Struct">

```go
type Category struct {
  Name  string `json:"name"`
  Slug  string `json:"slug"`
}
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "category",
  "namespace": "categories",
  "label_field": "name",
  "fields": [
    {
      "type": "string",
      "name": "name",
      "label": "Name"
    },
    {
      "type": "string",
      "name": "slug",
      "label": "Slug"
    }
  ]
}
```

</Tab>
</Tabs>

## Schema generation

A `System Schema` itself is just a Go Struct, and is not a valid `schema.Schema` object. FastSchema will generate an actual Schema object from the Go struct, following the rules below.

- `name`: Created using the snake case of the struct name. For example,

  `Category` -> `category`.

- `namespace`: Created using the plural form of the schema name. For example,

  `category` -> `categories`.
- `label_field`: Using the first string field in the struct.
- `fields`: An array of fields in the struct.
  - `type`: Field type is created based on the Go type of the field.
  - `name`: Field name is created using the snake case of the field name.
  - `label`: Field label is created using the field name.

<Callout type="warn">

The `label_field` is required for all system schemas. If the struct does not have a string field, an error will be thrown.

Only the exported fields of the struct are considered for the schema fields.

Field that has a tag `json:"-"` will be ignored.

</Callout>

## Customize schema

A Go struct contains very limited information for the schema generation. To provide more information about the schema, you can use one of the following methods:

### Customize schema using an unexported field with tag `fs`

```go {2}
type Category struct {
  _           any    `json:"-" fs:"label_field=slug"`
  Name        string `json:"name"`
  Slug        string `json:"slug"`
}
```

`fs` tag is a string that contains a list of properties separated by a semicolon. Each property is in the format `key=value`.

**Using the `fs` tag, you can customize the schema with the following properties:**

- `name`: Custom the schema name.
- `namespace`: Custom the schema namespace.
- `label_field`: Custom the label field.
- `disable_timestamp`: Disable the timestamp fields.
- `db`: Custom the DB index and unique constraint for the table.

**Example:**

<Tabs items={["Go Struct","Output Schema"]}>
<Tab value="Go Struct">

```go
type Category struct {
  _     any    `json:"-" fs:"name=cat;namespace=cats;label_field=slug"`
  Name  string `json:"name"`
  Slug  string `json:"slug"`
}
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "cat",
  "namespace": "cats",
  "label_field": "slug",
  "fields": [
    {
      "type": "string",
      "name": "name",
      "label": "Name"
    },
    {
      "type": "string",
      "name": "slug",
      "label": "Slug"
    }
  ]
}
```

</Tab>
</Tabs>

**Additionally, you can use the `fs.db` tag to customize the DB index and unique constraint for the table.**
<Callout type="warn">

The value of `fs.db` tag must be a valid [HJSON](https://github.com/hjson/hjson-go) string and satisfy the `schema.SchemaDB` type.

</Callout>

<Tabs items={["fs.db tag","Output Schema"]}>
<Tab value="fs.db tag">

```go
`fs.db:"{'indexes':[{'name':'idx_name','columns':['name'],'unique':true}]}"`
```

</Tab>
<Tab value="Output Schema">

```json
{
  "db": {
    "indexes": [
      {
        "name": "idx_name",
        "columns": ["name"],
        "unique": true
      }
    ]
  }
}
```

</Tab>
</Tabs>

### Customize schema using the `Schema()` method

A struct that implements the `CustomizableSchema` interface can return a custom schema information. These information will be merged with the generated schema.

<Tabs items={["schema.CustomizableSchema"]}>
<Tab value="schema.CustomizableSchema">

```go {2}
type CustomizableSchema interface {
	Schema() *Schema
}
```

</Tab>
</Tabs>

Using the `Schema()` method brings more flexibility to customize the schema. You can customize any part of the schema, such as the schema name, namespace, label field, DB, etc.

- `Name`: Custom the schema name
- `Namespace`: Custom the schema namespace
- `LabelField`: Custom the label field
- `Fields`: Custom the fields

<Callout type="warn" title="Customize Fields using Schema method">

The fields in the schema method will only be used to override the existing fields in the struct.

The updated fields in the schema method will be matched with the fields in the struct based on the field name.

If there are any fields returned by the schema method that are not in the struct, they will be ignored.

If you want to add new fields, you need to include them in the struct.

</Callout>

**Example:**

<Tabs items={["Go Struct","Output Schema"]}>
<Tab value="Go Struct">

```go
type Category struct {
  Name  string `json:"name"`
  Slug  string `json:"slug"`
}

func (c Category) Schema() *Schema {
  return &Schema{
    Name: "cat",
    Namespace: "cats",
    LabelField: "slug",
    Fields: []*Field{
      {
        Type: "string",
        Name: "name",
        Label: "Category Name",
      },
      {
        Type: "string",
        Name: "slug",
        Label: "Category slug",
      },
    },
  }
}
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "cat",
  "namespace": "cats",
  "label_field": "slug",
  "fields": [
    {
      "type": "string",
      "name": "name",
      "label": "Category Name"
    },
    {
      "type": "string",
      "name": "slug",
      "label": "Category Slug"
    }
  ]
}
```

</Tab>
</Tabs>

<Callout type="error" title="IMPORTANT">

A System Schema can be customized using both the `fs` tag and the `Schema()` method. If both are used, the `Schema()` method will override the `fs` tag.

</Callout>

## Customize field

A Go struct property includes only the field name and type, which is quite limited.

In contrast, a Schema Field contains additional properties that allow for greater customization (see [Field](/docs/concepts#field)).

FastSchema enables you to provide more detailed information about the field using `struct tags` for customization.

### Customize with tag `json`

`json` tag is used to marshal and unmarshal the field value to/from JSON string.

FastSchema uses the `json` tag to customize the field `name`.

### Customize with tag `fs`

`fs` tag is a string that contains a list of properties separated by a semicolon. Each property is in the format `key=value`.

**Using the `fs` tag, you can customize the field with the following properties:**

- `label_field`: Select the field as the schema label field.
- `type`: Custom the field type, refer to [Field](/docs/concepts#field) for the list of available types.
- `label`: Custom the field label.
- `size`: Custom the field size.
- `multiple`: Custom the field to accept multiple values. Only field with type `file` supports this property.
- `unique`: Flag the field as unique.
- `optional`: Allow the field to be optional.
- `sortable`: Allow the field to be sortable through the API.
- `filterable`: Allow the field to be filterable through the API.
- `default`: Set the default value for the field.

**Example:**

<Tabs items={["Go Struct","Output Schema"]}>
<Tab value="Go Struct">

```go
type Category struct {
  Slug  string `json:"slug" fs:"unique;size=255;label=Url"`
  Name  string `json:"name" fs:"label_field;filterable`
  Views int    `json:"views" fs:"type=uint64;optional;sortable"`
  Note  string `json:"note" fs:"optional;default=Empty Note"`
  Image any    `json:"image" fs:"type=file;multiple;optional"`
}
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "category",
  "namespace": "categories",
  "label_field": "name",
  "fields": [
    {
      "type": "string",
      "name": "slug",
      "label": "Url",
      "size": 255,
      "unique": true
    },
    {
      "type": "string",
      "name": "name",
      "label": "Name",
      "filterable": true
    },
    {
      "type": "uint64",
      "name": "views",
      "optional": true,
      "sortable": true
    },
    {
      "type": "string",
      "name": "note",
      "optional": true,
      "default": "Empty Note"
    },
    {
      "type": "file",
      "name": "image",
      "multiple": true,
      "optional": true
    }
  ]
}
```

</Tab>
</Tabs>

### Customize with tag `fs.enums`

`fs.enums` tag is used to define the list of enum values for the field if the field type is `enum`.

The value of `fs.enums` tag must meet the following criteria:

- A valid [HJSON](https://github.com/hjson/hjson-go) string.
- Represent the `[]*schema.FieldEnum` type.

**Example:**

<Tabs items={["Go Struct","Formatted HJSON","Output Schema"]}>
<Tab value="Go Struct">

```go
type Category struct {
  Status string `json:"status" fs:"type=enum" fs.enums:"[{'value':'active','label':'Active'},{'value':'inactive','label':'Inactive'}]"`
}
```

</Tab>
<Tab value="Formatted HJSON">

```json
[
  {
    'value': 'active',
    'label': 'Active'
  },
  {
    'value': 'inactive',
    'label': 'Inactive'
  }
]
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "category",
  "namespace": "categories",
  "label_field": "name",
  "fields": [
    {
      "type": "enum",
      "name": "status",
      "enums": [
        {
          "value": "active",
          "label": "Active"
        },
        {
          "value": "inactive",
          "label": "Inactive"
        }
      ]
    }
  ]
}
```

</Tab>
</Tabs>

### Custom with tag `fs.relation`

`fs.relation` tag is used to define the relation between the field and another schema.

The value of `fs.relation` tag must meet the following criteria:

- A valid [HJSON](https://github.com/hjson/hjson-go) string.
- Represent the `*schema.Relation` type.

**Example:**

<Tabs items={["Go Structs","Formatted HJSONs","Output Schemas"]}>
<Tab value="Go Structs">

```go
type Tag struct {
  Name  string  `json:"name"`
  Blogs []*Blog `json:"blogs" fs.relation:"{'type':'m2m','schema':'blog','field':'tags','owner':true}"`
}

type Blog struct {
  Name  string `json:"name"`
  Tags  []*Tag `json:"tags" fs.relation:"{'type':'m2m','schema':'tag','field':'blogs'}"`
}
```

</Tab>
<Tab value="Formatted HJSONs">

```json
// tag.blogs
{
  'type': 'm2m',
  'schema': 'blog',
  'field': 'tags',
  'owner': true
}

// blog.tags
{
  'type': 'm2m',
  'schema': 'tag',
  'field': 'blogs'
}
```

</Tab>
<Tab value="Output Schemas">

```json
// data/schemas/tag.json
{
  "name": "tag",
  "namespace": "tags",
  "label_field": "name",
  "fields": [
    {
      "type": "string",
      "name": "name"
    },
    {
      "type": "m2m",
      "name": "blogs",
      "relation": {
        "type": "m2m",
        "schema": "blog",
        "field": "tags",
        "owner": true
      }
    }
  ]
}

// data/schemas/blog.json
{
  "name": "blog",
  "namespace": "blogs",
  "label_field": "name",
  "fields": [
    {
      "type": "string",
      "name": "name"
    },
    {
      "type": "m2m",
      "name": "tags",
      "relation": {
        "type": "m2m",
        "schema": "tag",
        "field": "blogs"
      }
    }
  ]
}
```

</Tab>
</Tabs>

### Customize with tag `fs.renderer`

`fs.renderer` tag is used to define the frontend renderer for the field.

The value of `fs.renderer` tag must meet the following criteria:

- A valid [HJSON](https://github.com/hjson/hjson-go) string.
- Represent the `*schema.FieldRenderer` type.

**Example:**

<Tabs items={["Go Struct","Formatted HJSON","Output Schema"]}>
<Tab value="Go Struct">

```go
type Blog struct {
  Name    string `json:"name"`
  Content string `json:"content" fs.renderer:"{'class':'editor','settings':{'height':500}}"`
}

```

</Tab>
<Tab value="Formatted HJSON">

```json
{
  'class': 'editor',
  'settings': {
    'height': 500
  }
}
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "blog",
  "namespace": "blogs",
  "label_field": "name",
  "fields": [
    {
      "type": "string",
      "name": "name"
    },
    {
      "type": "string",
      "name": "content",
      "renderer": {
        "class": "editor",
        "settings": {
          "height": 500
        }
      }
    }
  ]
}
```

</Tab>
</Tabs>

### Customize with tag `fs.db`

`fs.db` tag is used to define the database properties for the field.

The value of `fs.db` tag must meet the following criteria:

- A valid [HJSON](https://github.com/hjson/hjson-go) string.
- Represent the `*schema.FieldDB` type.

**Example:**

<Tabs items={["Go Struct","Formatted HJSONs","Output Schema"]}>
<Tab value="Go Struct">

```go
type Blog struct {
  Name    string `json:"name" fs.db:"{'collation':'utf8mb4_unicode_ci','key':'PRIMARY'}"`
}
```

</Tab>
<Tab value="Formatted HJSONs">

```json
{
  'collation': 'utf8mb4_unicode_ci',
  'key': 'PRIMARY'
}
```

</Tab>
<Tab value="Output Schema">

```json
{
  "name": "blog",
  "namespace": "blogs",
  "label_field": "name",
  "fields": [
    {
      "type": "string",
      "name": "name",
      "db": {
        "collation": "utf8mb4_unicode_ci",
        "key": "PRIMARY"
      }
    }
  ]
}
```

</Tab>
</Tabs>

<Callout type="info">

A field can also be customized using [Customize schema using a struct method](/docs/framework/database/system-schema#customize-schema-using-the-schema-method)

</Callout>

### Customize with tag `fs.setter`

<Callout type="warn">

`fs.setter` and `fs.getter` tags must by a valid [expr](https://github.com/expr-lang/expr) expression that return the desired value.

The expression can access the following variables:
- $db: Provides access to the database instance, enabling queries.
- $context: The context object.
- $args: Includes custom arguments passed to the rule:
    - Schema: The schema object.
    - Entity: The entity object.
    - Value: The field value before applying the setter/getter.
    - Exist: A boolean value that indicates whether the field value exists.

</Callout>

`fs.setter` tag is used to define the setter method for the field. This method will be called before the schema object is saved to the database.

Using the `fs.setter` tag, you will be able to customize the field value before saving it to the database.

Some common use cases for the setter method are:

- Setting the default value for the field.
- Formatting the field value before saving it to the database.
- Encrypting the field value before saving it to the database.

**Example:**

*Set the author field to the current user ID*

```go title="Go Struct"
type Blog struct {
  Author *Author* `json:"author" fs.setter:"$context.Value('user').ID"`
}
```

*Set the encrypted password field*

```go title="Go Struct"
type User struct {
  Password string `json:"password" fs.setter:"fs.setter:"$args.Exist && $args.Value != '' ? $hash($args.Value) : $undefined"`
}
```

### Customize with tag `fs.getter`

`fs.getter` tag is used to define the getter method for the field. This method will be called before the schema object is returned to the client.

Using the `fs.getter` tag, you will be able to customize the field value before returning it to the client.

Some common use cases for the getter method are:

- Hiding sensitive information from the client.
- Formatting the field value before returning it to the client.
- Decrypting the field value before returning it to the client.

**Example:**

*Hide the password field*

```go title="Go Struct"
type User struct {
  Password string `json:"password" fs.getter:"$undefined"`
}
```
