> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sublay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Entity

> The Entity interface shape returned by the API and SDK

An entity is the central content unit in Sublay — it can represent a post, article, product listing, video, or any other piece of content in your application. Entities support reactions, comments, votes, file attachments, geo-location, and more.

<Note>
  Entities are provided by the optional **`entities`** bundle. A project without it returns `403 database/tables-not-available` for entity endpoints. See [Bundles](/v8/bundles).
</Note>

## Properties

| Property | Type | Description |
| - | - | - |
| `id` | `string` | Unique entity identifier (UUID). |
| `foreignId` | `string \| null` | Your system's content ID, if this entity maps to existing data. Can also be a static identifier like `"about-page"`. |
| `shortId` | `string` | Auto-generated short identifier, useful for sharing links. |
| `projectId` | `string` | The project this entity belongs to. |
| `sourceId` | `string \| null` | Groups entities by content type within a project. Useful when one project hosts multiple distinct categories of content (e.g. `"user-post"` vs `"blog-post"`), allowing you to filter and separate them without creating separate projects. |
| `spaceId` | `string \| null` | The space this entity belongs to, if any. |
| `space` | `Space \| null` | Populated space object when `include` contains `"space"`. |
| `userId` | `string \| null` | ID of the user this entity is associated with. `null` if the entity is not associated with any user. |
| `user` | `User \| null` | The author. Populated when `include` contains `"user"`. |
| `title` | `string \| null` | Optional title. |
| `content` | `string \| null` | Optional text content. |
| `mentions` | `Mention[]` | Array of user mentions embedded in the content. |
| `attachments` | `Record<string, any>[]` | Flexible array of media/attachment metadata objects. |
| `files` | `File[]` | System-managed file associations. Populated when `include` contains `"files"`. |
| `keywords` | `string[]` | Tags used for filtering and discovery. |
| `upvotes` | `string[]` | Legacy v6 field — array of user IDs that upvoted. Use `reactionCounts` instead. |
| `downvotes` | `string[]` | Legacy v6 field — array of user IDs that downvoted. Use `reactionCounts` instead. |
| `reactionCounts` | `ReactionCounts` | Counts for all 8 reaction types. See [Reaction](/v8/data-models/reaction). |
| `userReaction` | `ReactionType \| null` | The current user's reaction. Only present when the user is authenticated and has reacted to this entity. |
| `commentsCount` | `number` | How many comments the entity has, at any depth. Counts everything its thread contains — deleted, removed and unavailable comments included, since they stay in the thread as [placeholders](/v8/data-models/comment#placeholders) — so it's the same number for every user. |
| `sharesCount` | `number` | Share counter stored on the entity. `0` by default. |
| `views` | `number` | View count. Auto-incremented on every fetch. Can also be incremented manually via the service-key-only `PATCH /entities/:entityId/increment-views` endpoint. |
| `score` | `number` | Computed "hotness" score based on engagement activity. |
| `scoreUpdatedAt` | `Date` | When the score was last recalculated. |
| `location` | `GeoPoint \| null` | Optional geo-location stored as a GeoJSON point. |
| `metadata` | `Record<string, any>` | Custom key-value data (max 10 KB). |
| `topComments` | `Comment[]` | The entity's best top-level comments, as full [Comment](/v8/data-models/comment) objects. Populated when `include` contains `"topComments"`; `[]` when none qualify. See [Top comments](#top-comments). |
| `isSaved` | `boolean` | Whether the current user has saved this entity to a collection. Populated when `include` contains `"saved"`. |
| `grants` | `GrantSummary \| undefined` | Per-item [reputation-grant](/v8/data-models/reputation-grant) summary — `{ total, count, viewerTotal }` over **positive** grants. Populated when `include` contains `"grants"`. Zero-filled rather than omitted on a project with no grants, so `undefined` always means "nobody asked". |
| `isDraft` | `boolean \| null` | Whether this entity is in draft state. |
| `nsfw` | `boolean` | The entity's **own** NSFW flag. `false` by default. Set on create/update by the owner (or a service/master key), or by a space moderator via the [set-NSFW endpoint](/v8/hooks/spaces/use-set-space-entity-nsfw). See [NSFW flagging](#nsfw-flagging). |
| `nsfwEffective` | `boolean` | **Computed live, read-only.** `true` when the entity's own `nsfw` is `true` **or** its space is effectively NSFW: `entity.nsfw OR (space?.nsfwEffective ?? false)`. This is the value the `nsfwFilter` query param keys off. See [NSFW flagging](#nsfw-flagging). |
| `moderationStatus` | `"approved" \| "removed" \| null` | Current space moderation status. |
| `moderatedAt` | `Date \| null` | When the moderation action was taken. |
| `moderatedById` | `string \| null` | ID of the moderator who took the action. |
| `moderatedByType` | `"client" \| "user" \| null` | Whether a client (admin) or user performed moderation. |
| `moderationReason` | `string \| null` | Reason provided for moderation. |
| `createdAt` | `Date` | When the entity was created. |
| `updatedAt` | `Date` | When the entity was last updated. |
| `deletedAt` | `Date \| null` | Soft-delete timestamp. `null` for active entities. |

## Nested Types

### GeoPoint

| Property | Type | Description |
| - | - | - |
| `type` | `"Point"` | GeoJSON geometry type. Always `"Point"`. |
| `coordinates` | `[number, number]` | Longitude and latitude, in that order. |

## NSFW flagging

An entity carries its own `nsfw` boolean and exposes a live-computed `nsfwEffective`:

* **`nsfw`** — the entity's own flag. It defaults to `false` and is what the owner, a service/master key, or a space moderator toggles.
* **`nsfwEffective`** — never stored on the entity. It is computed on **every** read as:

  ```
  nsfwEffective = entity.nsfw OR COALESCE(space.nsfwEffective, false)
  ```

  So an entity reads as effectively NSFW when either its own flag is set **or** the space it lives in is effectively NSFW (which itself folds in the space's whole ancestor chain — see [Space → NSFW flagging](/v8/data-models/space#nsfw-flagging)). A space-less entity (`spaceId` is `null`) falls back to its own flag; on a project without the spaces bundle, the space contribution is simply absent and `nsfwEffective` equals `entity.nsfw`.

The effect is a live OR: clearing an entity's own `nsfw` **cannot** make it read safe while its space chain is still NSFW, and flagging a space instantly cascades to every entity inside it without stamping or backfilling entity rows.

To filter a list by effective NSFW, pass `nsfwFilter` to [Fetch many entities](/v8/hooks/entities/use-fetch-many-entities) (`include-all` default / `exclude` / `only`).

<Note>
  Sublay only **labels** (`nsfw`) and **filters** (`nsfwFilter`) content. It does **not** blur, age-gate, add interstitials, or store a per-viewer "show mature content" preference — how NSFW-flagged content is presented is entirely the consuming app's responsibility.
</Note>

## Top comments

`include=topComments` adds a preview of the entity's best comments — for example, the top comment under each entity in a feed, or a few rotating ones.

* Each item is a full [Comment](/v8/data-models/comment), with its author (`user`) always included.
* Only **top-level** comments, ranked by the raw count of one reaction (`topCommentsSortByReaction`, default `upvote`), ties newest first.
* `topCommentsLimit` sets how many per entity: a whole number of at least `1`, default `1`, maximum `5` (larger values are treated as `5`). `0`, a negative number or a non-integer value returns `400`.
* **Real content only:** deleted and moderator-removed comments are left out (for everyone, the author included), and so are comments whose author is block-edged with the requesting user, in either direction. So `isAuthorBlocked` and `isUnavailable` are always `false` here.
* `[]` when no comment qualifies, on a project without the `comments` bundle, and on an entity whose author has blocked the requesting user (for example, one they saved to a collection before the block).

Sending `topCommentsLimit` or `topCommentsSortByReaction` without `include=topComments` returns `400`.

To render a full discussion — including placeholders, so replies stay in place — use [Fetch Many Comments](/v8/api-reference/comments/fetch-many-comments).

## Includes

Pass `include` to populate optional fields. Each endpoint accepts its own list; an unknown value returns `400`.

| Include value | Populates | Entity fetches and lists | Collection entities | Drafts |
| - | - | :-: | :-: | :-: |
| `"user"` | `user` — the entity author | ✓ | ✓ | ✓ |
| `"space"` | `space` — the associated space | ✓ | ✓ | ✓ |
| `"topComments"` | `topComments` — the best top-level comments | ✓ | ✓ | |
| `"saved"` | `isSaved` — whether current user saved this entity | ✓ | | |
| `"files"` | `files` — managed file records | ✓ | ✓ | ✓ |
| `"grants"` | `grants` — the reputation-grant summary | ✓ | ✓ | ✓ |

"Entity fetches and lists" are [Fetch Entity](/v8/api-reference/entities/fetch-entity), [by foreign ID](/v8/api-reference/entities/fetch-entity-by-foreign-id), [by short ID](/v8/api-reference/entities/fetch-entity-by-short-id) and [Fetch Many Entities](/v8/api-reference/entities/fetch-many-entities). On collection entities every item is already saved (`isSaved: true`).

## See Also

* [Reaction](/v8/data-models/reaction)
* [File](/v8/data-models/file)
* [ReputationGrant](/v8/data-models/reputation-grant)
* [Space](/v8/data-models/space)
* [Fetch Entity](/v8/api-reference/entities/fetch-entity)
* [useEntity hook](/v8/hooks/entities/use-entity)
* [Set space entity NSFW hook](/v8/hooks/spaces/use-set-space-entity-nsfw)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.