> For the complete documentation index, see [llms.txt](https://docs.sparkframework.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sparkframework.dev/documentation/developer-guide/core-systems/database-entries.md).

# Database Entries

Every piece of game data in Spark is a `SparkDatabaseEntry`. This page covers how to create custom entry types, use nested data, and access entries at runtime.

## Anatomy of a SparkDatabaseEntry

`SparkDatabaseEntry` is an abstract class that extends Unity's `ScriptableObject`. It provides these base fields:

| Field         | Type     | Description                                                  |
| ------------- | -------- | ------------------------------------------------------------ |
| `id`          | `string` | Unique identifier. Auto-generated, read-only after creation. |
| `entryName`   | `string` | Internal name for organization in the editor.                |
| `displayName` | `string` | Player-facing name (shown in UI).                            |
| `description` | `string` | Player-facing description (tooltips, info panels).           |
| `icon`        | `Sprite` | Visual representation in UI elements.                        |

All fields are serialized with `[SerializeField] private` and exposed through public getters. This is the standard pattern for Spark entries.

## Creating a Custom Entry Type

To define a new database entry, inherit from `SparkDatabaseEntry`:

```csharp
using UnityEngine;

[CreateAssetMenu(
    fileName = "New Mount",
    menuName = "Spark/Mounts/Mount Entry")]
public class MountEntry : SparkDatabaseEntry
{
    [Section("Movement")]
    [SerializeField] private float speed = 10f;
    [SerializeField] private float sprintMultiplier = 1.5f;

    [Section("Appearance")]
    [SerializeField] private GameObject mountPrefab;
    [SerializeField] private RuntimeAnimatorController animatorController;

    [Section("Requirements")]
    [DatabaseEntryDropdown(typeof(ClassEntry))]
    [SerializeField] private string requiredClassId;

    [ConditionalField("requiredClassId", "")]
    [SerializeField] private int requiredLevel = 1;

    public float Speed => speed;
    public float SprintMultiplier => sprintMultiplier;
    public GameObject MountPrefab => mountPrefab;
    public RuntimeAnimatorController AnimatorController => animatorController;
    public string RequiredClassId => requiredClassId;
    public int RequiredLevel => requiredLevel;
}
```

Key points:

* Use `[Section("Title")]` to group related fields under collapsible headers.
* Use `[DatabaseEntryDropdown(typeof(T))]` to create a dropdown selector for entries of a specific type.
* Use `[ConditionalField("fieldName", value)]` to show/hide fields based on another field's value.
* Use `[ReadOnly]` to make a field visible but not editable.
* Always use `[SerializeField] private` with public getter properties.

## Entry Storage

Entries must be stored in a `Resources/` folder so they can be loaded at runtime. The standard location is:

```
YourPlugin/Runtime/Resources/YourPluginData/
```

For example, mount entries would go in:

```
Mounts/Runtime/Resources/MountData/
```

The Spark Editor handles creating entries in the correct location based on your plugin manifest configuration.

## Accessing Entries at Runtime

`SparkDatabaseRegistry` provides O(1) access to any entry:

```csharp
// Get a single entry by ID
MountEntry mount = SparkDatabaseRegistry.GetEntry<MountEntry>("war_horse");

// Get all entries of a type
List<MountEntry> allMounts = SparkDatabaseRegistry.GetAllEntries<MountEntry>();

// Check existence
bool exists = SparkDatabaseRegistry.HasEntry("war_horse");

// Get total entry count across all types
int total = SparkDatabaseRegistry.GetTotalEntryCount();
```

The registry caches entries on first access. In the editor, it uses `AssetDatabase` for discovery. In builds, it loads from `Resources/` folders.

## Nested Data

For complex entries that contain lists of sub-objects, use `SparkDatabaseEntryNestedData`:

```csharp
[System.Serializable]
public class MountAbilityData : SparkDatabaseEntryNestedData
{
    [SerializeField] private string abilityName;
    [SerializeField] private float cooldown = 5f;
    [SerializeField] private Sprite abilityIcon;

    public string AbilityName => abilityName;
    public float Cooldown => cooldown;
    public Sprite AbilityIcon => abilityIcon;
}
```

Then reference it from your entry:

```csharp
public class MountEntry : SparkDatabaseEntry
{
    [Section("Abilities")]
    [NestedData]
    [SerializeField] private List<MountAbilityData> abilities = new List<MountAbilityData>();

    public List<MountAbilityData> Abilities => abilities;
}
```

`SparkDatabaseEntryNestedData` tracks its parent entry and data type name for editor tooling. The `[NestedData]` attribute tells the Spark Editor to render it as an inline editable list.

## Validation

Override `OnValidate()` to add custom validation to your entries:

```csharp
protected override void OnValidate()
{
    base.OnValidate();

    if (speed < 0) speed = 0;
    if (sprintMultiplier < 1f) sprintMultiplier = 1f;
}
```

This runs in the editor whenever a field is changed, providing immediate feedback.

## Available Attributes

Here is a quick reference of attributes you can use on entry fields:

| Attribute                               | Purpose                                       |
| --------------------------------------- | --------------------------------------------- |
| `[Section("Title")]`                    | Group fields under a collapsible header       |
| `[Section("Title", collapsible: true)]` | Same, with explicit collapsible flag          |
| `[DisplayNameTooltip("Label", "Tip")]`  | Custom label and tooltip                      |
| `[ReadOnly]`                            | Non-editable field                            |
| `[ConditionalField("field", value)]`    | Show only when another field matches a value  |
| `[ConditionalMultiField(...)]`          | Show based on multiple field conditions       |
| `[DatabaseEntryDropdown(typeof(T))]`    | Dropdown picker for entries of type T         |
| `[SparkDatabaseEntrySelector]`          | Generic entry picker (any type)               |
| `[ScriptableObjectDropdown(typeof(T))]` | Dropdown for ScriptableObject assets          |
| `[InlineField]`                         | Render nested object fields inline            |
| `[NestedData]`                          | Render as editable nested data list           |
| `[TagField]`                            | Unity tag dropdown                            |
| `[Switch("field", "value1", "value2")]` | Switch field rendering based on another field |

See [Custom Attributes Reference](/documentation/developer-guide/core-systems/custom-attributes.md) for the full details on each attribute.

## Best Practices

* Keep entry classes focused. If an entry has too many fields, consider splitting into a base entry and extensions.
* Use `[Section]` generously to keep the editor organized.
* Use conditional fields to hide irrelevant options. This reduces clutter for users.
* Always validate serialized data in `OnValidate()`.
* Never store runtime state in entries. Entries are configuration data, not runtime data.
* Use meaningful ID prefixes. The auto-generated ID format uses the entry name, but you can influence it by naming entries clearly.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.sparkframework.dev/documentation/developer-guide/core-systems/database-entries.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
