> ## 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.

# Entities

> Create and manage content entities, reactions, and drafts

export const SpaceReputationParams = ({variant = "context", field = "path"}) => {
  const isUser = variant === "user";
  return <>
      <ParamField {...{
    [field]: "spaceReputation",
    type: "object"
  }}>
        Opt-in. Adds a <code>spaceReputation</code> number to each returned user,
        scoped to a single space. Requires the <code>reputation</code> bundle.
        The <code>reputation</code> total is always present regardless;{" "}
        <code>spaceReputation</code> is purely additive and only appears when you
        pass this object. <strong>This object is the primary form</strong> — it
        replaces the deprecated flat props below. See the{" "}
        <a href="/v8/data-models/reputation">Reputation data model</a> for the
        full contract.

        <Expandable title="spaceReputation">
          {isUser ? <ParamField name="spaceId" type="string | &quot;none&quot;" required>
              Which bucket to read. A space <strong><code>&lt;uuid&gt;</code></strong> reads that
              space's bucket; <strong><code>"none"</code></strong> reads the
              project-general (space-less) bucket.{" "}
              <strong><code>"context"</code> is rejected (400)</strong> on this
              endpoint — there is no space context to derive. The empty string
              and the legacy <code>general</code> / <code>null</code> aliases are
              also rejected. Missing buckets read as <code>0</code>.
            </ParamField> : <ParamField name="spaceId" type="string | &quot;none&quot; | &quot;context&quot;" required>
              Which bucket to read. One of: a space <strong><code>&lt;uuid&gt;</code></strong>{" "}
              (that space's bucket), <strong><code>"none"</code></strong> (the
              project-general, space-less bucket), or{" "}
              <strong><code>"context"</code></strong> (the space derived from the
              request context — resolved per-row on lists, or the single space on
              member/team/conversation surfaces). The empty string and the legacy{" "}
              <code>general</code> / <code>null</code> aliases are rejected (400).
              Missing buckets read as <code>0</code>.
            </ParamField>}
          <ParamField name="includeDescendants" type="boolean">
            Only honored when <code>spaceId</code> is an explicit space <code>&lt;uuid&gt;</code>.
            When <code>true</code>, <code>spaceReputation</code> is the{" "}
            <strong>subtree sum</strong> — the named space plus all of its
            descendant spaces (the root space's own bucket included). Ignored for{" "}
            <code>"none"</code>; not allowed with <code>"context"</code>.
          </ParamField>
        </Expandable>
      </ParamField>

      {isUser ? <ParamField {...{
    [field]: "spaceReputationId",
    type: "string",
    deprecated: true
  }}>
          <strong>Deprecated</strong> — use the <code>spaceReputation</code>{" "}
          object instead. The flat form of <code>spaceReputation.spaceId</code>.
          Still accepted (the object wins if both are passed); removal is a later
          major version. Accepts a space <code>&lt;uuid&gt;</code> or <code>"none"</code> only —{" "}
          <code>"context"</code> is rejected (400) on this endpoint.
        </ParamField> : <ParamField {...{
    [field]: "spaceReputationId",
    type: "string",
    deprecated: true
  }}>
          <strong>Deprecated</strong> — use the <code>spaceReputation</code>{" "}
          object instead. The flat form of <code>spaceReputation.spaceId</code>.
          Still accepted (the object wins if both are passed); removal is a later
          major version. Accepts a space <code>&lt;uuid&gt;</code>, <code>"none"</code>, or{" "}
          <code>"context"</code>.
        </ParamField>}

      <ParamField {...{
    [field]: "spaceReputationDescendants",
    type: "boolean",
    deprecated: true
  }}>
        <strong>Deprecated</strong> — use{" "}
        <code>spaceReputation.includeDescendants</code> instead. The flat form,
        only honored alongside an explicit <code>spaceReputationId</code> <code>&lt;uuid&gt;</code>.
      </ParamField>
    </>;
};

<Note>
  **Requires the `entities` bundle.** A project without the entities bundle returns `403 database/tables-not-available` for entity calls. See [Bundles](/v8/bundles).
</Note>

Entities are the central content objects in Sublay — posts, articles, products, or any item in your application that users interact with. The `entities` module gives you full control over their lifecycle, reactions, and drafts. All actions are performed on behalf of the logged-in user.

***

### createEntity

Creates a new entity, optionally as a draft or without attributing it to the logged-in user.

```typescript theme={null}
const entity = await sublay.entities.createEntity({
  foreignId: "post-42",
  title: "My First Post",
  content: "Hello world",
  keywords: ["intro", "welcome"],
  metadata: { category: "blog" },
});
```

<ParamField body="foreignId" type="string">
  Your application's identifier for this entity. Used for idempotent lookups via `fetchEntityByForeignId`.
</ParamField>

<ParamField body="sourceId" type="string">
  An optional secondary identifier for grouping or filtering entities.
</ParamField>

<ParamField body="spaceId" type="string">
  The space this entity belongs to.
</ParamField>

<ParamField body="title" type="string">
  Title text for the entity.
</ParamField>

<ParamField body="content" type="string">
  Body/content text for the entity.
</ParamField>

<ParamField body="attachments" type="Record<string, any>[]">
  File or media attachments.
</ParamField>

<ParamField body="keywords" type="string[]">
  Tags or keywords used for filtering and discovery.
</ParamField>

<ParamField body="mentions" type="Mention[]">
  User mentions embedded in the entity.
</ParamField>

<ParamField body="location" type="object">
  Geographic coordinates `{ latitude: number; longitude: number }`.
</ParamField>

<ParamField body="metadata" type="Record<string, any>">
  Arbitrary public metadata.
</ParamField>

<ParamField body="nsfw" type="boolean">
  The entity's own NSFW flag. Defaults to `false`. Effective NSFW also inherits from the entity's space. See [Entity NSFW flagging](/v8/data-models/entity#nsfw-flagging).
</ParamField>

<ParamField body="isDraft" type="boolean">
  When `true`, the entity is created as an unpublished draft.
</ParamField>

<ParamField body="excludeUserId" type="boolean">
  When `true`, creates the entity without attributing it to the logged-in user (authorless).
</ParamField>

**Returns** — `Promise<Entity>`

***

### fetchEntity

Fetches a single entity by its Sublay ID.

```typescript theme={null}
const entity = await sublay.entities.fetchEntity({ entityId: "ent_xyz789" });
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID.
</ParamField>

<ParamField body="include" type="string">
  Comma-separated list of associations to populate: `"user"`, `"space"`, `"topComments"`, `"saved"`, `"files"`, `"grants"`. `"topComments"` adds the entity's best top-level comments as full `Comment` objects — see [Top comments](/v8/data-models/entity#top-comments).
</ParamField>

<ParamField body="topCommentsLimit" type="number">
  With `include` containing `"topComments"`: how many top comments per entity. Server default `1`, maximum `5`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

<ParamField body="topCommentsSortByReaction" type="ReactionType">
  With `include` containing `"topComments"`: the reaction whose raw count ranks the top comments. Server default `"upvote"`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

<SpaceReputationParams variant="context" field="body" />

**Returns** — `Promise<Entity>`

***

### fetchEntityByForeignId

Fetches an entity by your application's own identifier. Optionally creates it if not found.

```typescript theme={null}
const entity = await sublay.entities.fetchEntityByForeignId({
  foreignId: "post-42",
  createIfNotFound: true,
});
```

<ParamField body="foreignId" type="string" required>
  Your application's entity identifier.
</ParamField>

<ParamField body="createIfNotFound" type="boolean">
  When `true`, creates a stub entity if no match is found.
</ParamField>

<ParamField body="include" type="string">
  Comma-separated list of associations to populate: `"user"`, `"space"`, `"topComments"`, `"saved"`, `"files"`, `"grants"`. `"topComments"` adds the entity's best top-level comments as full `Comment` objects — see [Top comments](/v8/data-models/entity#top-comments).
</ParamField>

<ParamField body="topCommentsLimit" type="number">
  With `include` containing `"topComments"`: how many top comments per entity. Server default `1`, maximum `5`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

<ParamField body="topCommentsSortByReaction" type="ReactionType">
  With `include` containing `"topComments"`: the reaction whose raw count ranks the top comments. Server default `"upvote"`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

**Returns** — `Promise<Entity>`

***

### fetchEntityByShortId

Fetches an entity by its short, human-readable ID (used in share URLs).

```typescript theme={null}
const entity = await sublay.entities.fetchEntityByShortId({ shortId: "abc12" });
```

<ParamField body="shortId" type="string" required>
  The entity's short ID.
</ParamField>

<ParamField body="include" type="string">
  Comma-separated list of associations to populate: `"user"`, `"space"`, `"topComments"`, `"saved"`, `"files"`, `"grants"`. `"topComments"` adds the entity's best top-level comments as full `Comment` objects — see [Top comments](/v8/data-models/entity#top-comments).
</ParamField>

<ParamField body="topCommentsLimit" type="number">
  With `include` containing `"topComments"`: how many top comments per entity. Server default `1`, maximum `5`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

<ParamField body="topCommentsSortByReaction" type="ReactionType">
  With `include` containing `"topComments"`: the reaction whose raw count ranks the top comments. Server default `"upvote"`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

**Returns** — `Promise<Entity>`

***

### fetchManyEntities

Fetches a filtered, paginated list of entities (feeds). Supports rich filtering by metadata, keywords, location, and more.

```typescript theme={null}
const { data, pagination } = await sublay.entities.fetchManyEntities({
  spaceId: "spc_abc123",
  sortBy: "hot",
  page: 1,
  limit: 20,
  metadataFilters: { includes: { category: "blog" } },
});
```

<ParamField body="sourceId" type="string">
  Filter by source ID.
</ParamField>

<ParamField body="spaceId" type="string">
  Filter to entities within a specific space.
</ParamField>

<ParamField body="sortBy" type="string">
  Sort order: `"createdAt"` (chronological — honors `sortDir`), `"hot"` (trending), `"top"` (highest scored), or `"controversial"`. A `"metadata.<prop>"` value is also accepted to sort by a metadata field.
</ParamField>

<ParamField body="sortDir" type="string">
  Sort direction: `"asc"` or `"desc"`.
</ParamField>

<ParamField body="sortType" type="string">
  How to interpret a metadata sort value: `"auto"`, `"numeric"`, `"text"`, `"boolean"`, or `"timestamp"`.
</ParamField>

<ParamField body="sortByReaction" type="string">
  Which reaction `top` ranks by: `"upvote"`, `"downvote"`, `"like"`, `"love"`, `"wow"`, `"sad"`, `"angry"`, or `"funny"`. Defaults to `"upvote"`. Only valid with `sortBy: "top"` — the server rejects it with any other sort.
</ParamField>

<ParamField body="page" type="number">
  Page number (1-indexed).
</ParamField>

<ParamField body="limit" type="number">
  Results per page.
</ParamField>

<ParamField body="include" type="string">
  Comma-separated list of associations to populate: `"user"`, `"space"`, `"topComments"`, `"saved"`, `"files"`, `"grants"`. `"topComments"` adds the entity's best top-level comments as full `Comment` objects — see [Top comments](/v8/data-models/entity#top-comments).
</ParamField>

<ParamField body="topCommentsLimit" type="number">
  With `include` containing `"topComments"`: how many top comments per entity. Server default `1`, maximum `5`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

<ParamField body="topCommentsSortByReaction" type="ReactionType">
  With `include` containing `"topComments"`: the reaction whose raw count ranks the top comments. Server default `"upvote"`. Rejected (`400`) without `"topComments"` in `include`.
</ParamField>

<ParamField body="timeFrame" type="string">
  Time window to restrict results to: `"hour"`, `"day"`, `"week"`, `"month"`, or `"year"`.
</ParamField>

<ParamField body="userId" type="string">
  The author to filter by, also used as the viewing user to resolve `followedOnly`.
</ParamField>

<ParamField body="followedOnly" type="&#x22;true&#x22; | &#x22;false&#x22;">
  When `"true"`, returns only entities from users the given `userId` follows.
</ParamField>

<ParamField body="keywordsFilters" type="object">
  Filter by keyword tags: `{ includes?: string[]; doesNotInclude?: string[] }`.
</ParamField>

<ParamField body="metadataFilters" type="object">
  Filter by metadata fields: `{ includes?: object; includesAny?: object[]; doesNotInclude?: object; exists?: string[]; doesNotExist?: string[] }`.
</ParamField>

<ParamField body="titleFilters" type="object">
  Filter by title: `{ hasTitle?: "true" | "false"; includes?: string | string[]; doesNotInclude?: string | string[] }`.
</ParamField>

<ParamField body="contentFilters" type="object">
  Filter by content: `{ hasContent?: "true" | "false"; includes?: string | string[]; doesNotInclude?: string | string[] }`.
</ParamField>

<ParamField body="attachmentsFilters" type="object">
  Filter by attachments: `{ hasAttachments?: "true" | "false" }`.
</ParamField>

<ParamField body="locationFilters" type="object">
  Filter within a geographic radius: `{ latitude: string; longitude: string; radius: string }`.
</ParamField>

<ParamField body="nsfwFilter" type="&#x22;include-all&#x22; | &#x22;exclude&#x22; | &#x22;only&#x22;">
  Filter by effective NSFW (`entity.nsfw OR its space's nsfwEffective`). `"include-all"` (default) adds no predicate; `"exclude"` drops effective-NSFW entities; `"only"` returns only them. See [Entity NSFW flagging](/v8/data-models/entity#nsfw-flagging).
</ParamField>

<ParamField body="blockedFilter" type="&#x22;exclude&#x22; | &#x22;include-outbound-blocked&#x22;">
  How to treat entities authored by users the viewer is [block-edged](/v8/sdk/moderation/overview#blocking) with. `"exclude"` (default) hides them; `"include-outbound-blocked"` re-includes only the viewer's own outbound-blocked authors — never content from users who blocked the viewer. No effect without the `moderation` bundle.
</ParamField>

<SpaceReputationParams variant="context" field="body" />

**Returns** — `Promise<PaginatedResponse<Entity>>`

***

### updateEntity

Updates fields on an existing entity.

```typescript theme={null}
const entity = await sublay.entities.updateEntity({
  entityId: "ent_xyz789",
  title: "Updated Title",
  metadata: { featured: true },
});
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID to update.
</ParamField>

<ParamField body="title" type="string">
  New title.
</ParamField>

<ParamField body="content" type="string">
  New content body.
</ParamField>

<ParamField body="attachments" type="Record<string, any>[]">
  Updated attachments list.
</ParamField>

<ParamField body="keywords" type="string[]">
  Updated keywords list.
</ParamField>

<ParamField body="mentions" type="Mention[]">
  Updated user mentions list.
</ParamField>

<ParamField body="location" type="object">
  Updated coordinates `{ latitude: number; longitude: number }`.
</ParamField>

<ParamField body="metadata" type="Record<string, any>">
  Updated metadata. Merged with existing values.
</ParamField>

<ParamField body="nsfw" type="boolean">
  Updated NSFW own-flag. A space moderator flagging another user's entity uses `spaces.setSpaceEntityNsfw` instead. See [Entity NSFW flagging](/v8/data-models/entity#nsfw-flagging).
</ParamField>

**Returns** — `Promise<Entity>`

***

### deleteEntity

Permanently deletes an entity and its associated data.

```typescript theme={null}
await sublay.entities.deleteEntity({ entityId: "ent_xyz789" });
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID to delete.
</ParamField>

**Returns** — `Promise<void>`

***

### fetchDrafts

Fetches the logged-in user's unpublished draft entities.

```typescript theme={null}
const { data, pagination } = await sublay.entities.fetchDrafts({
  page: 1,
  limit: 20,
});
```

<ParamField body="page" type="number">
  Page number (1-indexed).
</ParamField>

<ParamField body="limit" type="number">
  Results per page.
</ParamField>

<ParamField body="sourceId" type="string">
  Filter drafts by source ID.
</ParamField>

<ParamField body="spaceId" type="string">
  Filter drafts to a specific space.
</ParamField>

<ParamField body="include" type="DraftIncludeParam">
  Associations to populate: `"user"`, `"space"`, `"files"`, `"grants"` (a single value or an array).
</ParamField>

**Returns** — `Promise<PaginatedResponse<Entity>>`

***

### publishDraft

Publishes a draft entity, making it publicly visible.

```typescript theme={null}
const entity = await sublay.entities.publishDraft({ entityId: "ent_xyz789" });
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID of the draft to publish.
</ParamField>

**Returns** — `Promise<Entity>`

***

### addReaction

Adds the logged-in user's reaction to an entity and returns the updated entity.

```typescript theme={null}
const entity = await sublay.entities.addReaction({
  entityId: "ent_xyz789",
  reactionType: "like",
});
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID.
</ParamField>

<ParamField body="reactionType" type="ReactionType" required>
  One of: `"upvote"`, `"downvote"`, `"like"`, `"love"`, `"wow"`, `"sad"`, `"angry"`, `"funny"`.
</ParamField>

**Returns** — `Promise<Entity>`

***

### removeReaction

Removes the logged-in user's reaction from an entity and returns the updated entity.

```typescript theme={null}
const entity = await sublay.entities.removeReaction({
  entityId: "ent_xyz789",
});
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID.
</ParamField>

**Returns** — `Promise<Entity>`

If the entity's author has blocked the user, this fails with `404 entity/not-found` and nothing changes.

***

### fetchReactions

Fetches a paginated list of reactions on an entity, optionally filtered by reaction type.

```typescript theme={null}
const { data, pagination } = await sublay.entities.fetchReactions({
  entityId: "ent_xyz789",
  reactionType: "like",
  page: 1,
  limit: 20,
});
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID.
</ParamField>

<ParamField body="reactionType" type="ReactionType">
  Filter to a specific reaction type.
</ParamField>

<ParamField body="page" type="number">
  Page number (1-indexed).
</ParamField>

<ParamField body="limit" type="number">
  Results per page.
</ParamField>

<ParamField body="sortDir" type="string">
  Sort direction: `"asc"` or `"desc"`.
</ParamField>

<SpaceReputationParams variant="context" field="body" />

**Returns** — `Promise<{ data: Reaction[]; pagination: { page: number; limit: number; total: number; totalPages: number; hasMore: boolean } }>`

***

### getUserReaction

Gets the logged-in user's reaction on an entity. Returns `null` if there is no reaction.

```typescript theme={null}
const { reactionType } = await sublay.entities.getUserReaction({
  entityId: "ent_xyz789",
});
// reactionType is "like" | "upvote" | ... | null
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID.
</ParamField>

**Returns** — `Promise<{ reactionType: ReactionType | null }>`

***

### isEntitySaved

Checks whether the logged-in user has saved an entity, and in which collections.

```typescript theme={null}
const { saved, collections } = await sublay.entities.isEntitySaved({
  entityId: "ent_xyz789",
});
```

<ParamField body="entityId" type="string" required>
  The Sublay entity ID.
</ParamField>

**Returns** — `Promise<{ saved: boolean; collections: { id: string; name: string }[] }>`


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