Skip to main content

Overview

The app.bsky.labeler namespace provides lexicons for declaring and querying labeling services. Labelers apply content moderation labels to posts, profiles, and other content.

Key Concepts

  • Labeler: A service that applies content labels
  • Labels: Metadata tags indicating content properties (NSFW, spam, etc.)
  • Labeling Policies: Which labels a service can apply
  • Custom Labels: Service-specific label definitions
  • Subscriptions: Users subscribing to labeler services

Record Types

service

Labeler service declaration. Record Structure:
policies
object
required
Labeling policies including supported label values
labels
union
Self-applied labels
createdAt
string
required
Service creation timestamp
Example:
await agent.com.atproto.repo.putRecord({
  repo: agent.session.did,
  collection: 'app.bsky.labeler.service',
  rkey: 'self',
  record: {
    $type: 'app.bsky.labeler.service',
    policies: {
      labelValues: ['porn', 'sexual', 'graphic-media', 'custom-label'],
      labelValueDefinitions: [
        {
          identifier: 'custom-label',
          severity: 'alert',
          blurs: 'content',
          defaultSetting: 'warn',
          adultOnly: false,
          locales: [
            {
              lang: 'en',
              name: 'Custom Label',
              description: 'Content flagged by our custom filter'
            }
          ]
        }
      ]
    },
    createdAt: new Date().toISOString()
  }
})

Queries

getServices

Get information about labeler services. Endpoint: app.bsky.labeler.getServices
dids
array
required
Array of labeler service DIDs (max 25)
detailed
boolean
Return detailed view with policies
Response:
views
array
required
Array of labeler service views
Example:
const response = await agent.app.bsky.labeler.getServices({
  dids: ['did:plc:labeler123'],
  detailed: true
})

for (const labeler of response.data.views) {
  console.log('Labeler:', labeler.creator.handle)
  console.log('Like count:', labeler.likeCount)
  
  if (labeler.policies) {
    console.log('Supported labels:', labeler.policies.labelValues)
  }
}
curl "https://bsky.social/xrpc/app.bsky.labeler.getServices?dids=did:plc:labeler123&detailed=true"

Type Definitions

labelerView

Basic labeler service view.
uri
string
required
AT-URI of the labeler service record
cid
string
required
CID of the service record
creator
object
required
Profile of the labeler service operator
likeCount
integer
Number of users subscribed to this labeler
viewer
object
Viewer’s relationship to the labeler
indexedAt
string
required
When the service was indexed
labels
array
Labels applied to the labeler service itself

labelerViewDetailed

Detailed labeler service view with policies. Includes all fields from labelerView plus:
policies
object
required
Labeling policies
reasonTypes
array
Report reason types this service accepts
subjectTypes
array
Subject types this service accepts reports for (account, record, chat)
subjectCollections
array
Record collection NSIDs this service labels

labelerPolicies

Labeling policies for a service.
labelValues
array
required
Array of label value strings this service can apply
labelValueDefinitions
array
Definitions for custom labels created by this service
Example:
{
  labelValues: [
    'porn',
    'sexual',
    'graphic-media',
    'spam',
    'custom-spoilers'
  ],
  labelValueDefinitions: [
    {
      identifier: 'custom-spoilers',
      severity: 'inform',
      blurs: 'content',
      defaultSetting: 'warn',
      adultOnly: false,
      locales: [
        {
          lang: 'en',
          name: 'Spoilers',
          description: 'Content contains spoilers for recent media'
        }
      ]
    }
  ]
}

labelerViewerState

Viewer’s relationship to a labeler.
like
string
AT-URI of like record if viewer has subscribed to this labeler

Using Labelers

Labelers are managed through user preferences in app.bsky.actor.defs#labelersPref.

Subscribe to a Labeler

// Get current preferences
const { data } = await agent.app.bsky.actor.getPreferences()

// Find or create labelers preference
let labelersPref = data.preferences.find(
  p => p.$type === 'app.bsky.actor.defs#labelersPref'
)

if (!labelersPref) {
  labelersPref = {
    $type: 'app.bsky.actor.defs#labelersPref',
    labelers: []
  }
  data.preferences.push(labelersPref)
}

// Add labeler
labelersPref.labelers.push({
  did: 'did:plc:labeler123'
})

// Save preferences
await agent.app.bsky.actor.putPreferences({
  preferences: data.preferences
})

Unsubscribe from a Labeler

// Get current preferences
const { data } = await agent.app.bsky.actor.getPreferences()

// Find labelers preference
const labelersPref = data.preferences.find(
  p => p.$type === 'app.bsky.actor.defs#labelersPref'
)

if (labelersPref) {
  // Remove labeler
  labelersPref.labelers = labelersPref.labelers.filter(
    l => l.did !== 'did:plc:labeler123'
  )
  
  // Save preferences
  await agent.app.bsky.actor.putPreferences({
    preferences: data.preferences
  })
}

Check Subscribed Labelers

const { data } = await agent.app.bsky.actor.getPreferences()

const labelersPref = data.preferences.find(
  p => p.$type === 'app.bsky.actor.defs#labelersPref'
)

if (labelersPref) {
  const labelerDids = labelersPref.labelers.map(l => l.did)
  
  // Get detailed info about subscribed labelers
  const labelers = await agent.app.bsky.labeler.getServices({
    dids: labelerDids,
    detailed: true
  })
  
  for (const labeler of labelers.data.views) {
    console.log('Subscribed to:', labeler.creator.handle)
    console.log('Labels:', labeler.policies?.labelValues)
  }
}

Creating a Labeler Service

To create a labeler service:
  1. Declare the Service: Create a app.bsky.labeler.service record
  2. Implement Label Emission: Emit labels via com.atproto.label.subscribeLabels
  3. Configure DID Document: Add labeler service endpoint to DID document
  4. Accept Reports (optional): Implement com.atproto.moderation.createReport handling

Example Service Declaration

const service = await agent.com.atproto.repo.putRecord({
  repo: agent.session.did,
  collection: 'app.bsky.labeler.service',
  rkey: 'self',
  record: {
    $type: 'app.bsky.labeler.service',
    policies: {
      labelValues: [
        // Global labels
        'porn',
        'sexual',
        'nudity',
        'graphic-media',
        
        // Custom labels
        'spoilers',
        'politics',
        'sports-scores'
      ],
      labelValueDefinitions: [
        {
          identifier: 'spoilers',
          severity: 'inform',
          blurs: 'content',
          defaultSetting: 'warn',
          adultOnly: false,
          locales: [
            {
              lang: 'en',
              name: 'Spoilers',
              description: 'Content may contain spoilers'
            }
          ]
        },
        {
          identifier: 'politics',
          severity: 'inform',
          blurs: 'none',
          defaultSetting: 'show',
          adultOnly: false,
          locales: [
            {
              lang: 'en',
              name: 'Political Content',
              description: 'Content related to politics'
            }
          ]
        }
      ]
    },
    createdAt: new Date().toISOString()
  }
})

console.log('Labeler service created:', service.uri)

Label Severity Levels

When defining custom labels, you can set severity:
  • inform: Informational only, content not hidden by default
  • alert: Warning shown, content blurred by default
  • none: No special handling

Label Blur Settings

Controls what gets blurred when label is applied:
  • content: Blur the content itself
  • media: Blur embedded media only
  • none: Don’t blur anything

Default Settings

How labels are treated by default:
  • hide: Content hidden by default
  • warn: Content shown with warning
  • show: Content shown normally
  • ignore: Label ignored

Common Use Cases

Find Labelers to Subscribe

// Search for labelers
const results = await agent.app.bsky.actor.searchActors({
  q: 'labeler'
})

// Check if they're running a labeler service
for (const actor of results.data.actors) {
  const labelers = await agent.app.bsky.labeler.getServices({
    dids: [actor.did],
    detailed: true
  })
  
  if (labelers.data.views.length > 0) {
    const labeler = labelers.data.views[0]
    console.log('Found labeler:', actor.handle)
    console.log('Provides labels:', labeler.policies?.labelValues)
  }
}

Configure Label Preferences

Combine with content label preferences:
const { data } = await agent.app.bsky.actor.getPreferences()

// Subscribe to labeler
let labelersPref = data.preferences.find(
  p => p.$type === 'app.bsky.actor.defs#labelersPref'
)
if (!labelersPref) {
  labelersPref = { $type: 'app.bsky.actor.defs#labelersPref', labelers: [] }
  data.preferences.push(labelersPref)
}
labelersPref.labelers.push({ did: 'did:plc:labeler123' })

// Configure how to handle their custom labels
data.preferences.push({
  $type: 'app.bsky.actor.defs#contentLabelPref',
  labelerDid: 'did:plc:labeler123',
  label: 'spoilers',
  visibility: 'warn'
})

data.preferences.push({
  $type: 'app.bsky.actor.defs#contentLabelPref',
  labelerDid: 'did:plc:labeler123',
  label: 'politics',
  visibility: 'hide'
})

await agent.app.bsky.actor.putPreferences({
  preferences: data.preferences
})

Resources

Build docs developers (and LLMs) love