Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rivenmedia/riven-ts/llms.txt

Use this file to discover all available pages before exploring further.

Every plugin must export at least one GraphQL resolver. Resolvers let your plugin expose queries, mutations, and field extensions that clients — such as the Riven frontend or a custom dashboard — can call directly. Riven merges all plugin resolvers into a single executable schema at startup using type-graphql.

Required imports

import { PluginContext, PluginDataSource } from '@repo/util-plugin-sdk';
import { Arg, FieldResolver, Mutation, Query, Resolver, Root } from 'type-graphql';
import { ObjectType, Field } from 'type-graphql';
Never define an @ObjectType() class with the same name as one that already exists in the SDK or another plugin. GraphQL requires globally unique type names — duplicate names will crash schema building at startup.

Parameter decorators

Riven provides two custom parameter decorators that tap into the plugin context stored in the GraphQL execution context object.

PluginContext(symbol)

Injects the entire plugin context for the given plugin symbol. The context object is whatever your plugin’s optional context() factory function returns — typically a settings bag or pre-computed values.
import { PluginContext } from '@repo/util-plugin-sdk';
import { pluginConfig } from '../my-plugin.config.ts';
import type { MyPluginContext } from '../my-plugin-context.ts';

@Resolver()
export class MyResolver {
  @Query(() => String)
  async greeting(
    @PluginContext(pluginConfig.name) ctx: MyPluginContext,
  ): Promise<string> {
    return `Hello from ${ctx.instanceName}`;
  }
}

PluginDataSource(symbol, DataSourceClass)

Injects a specific BaseDataSource instance from the plugin context’s DataSourceMap. This is the standard way for a resolver to reach an HTTP API client.
import { PluginDataSource } from '@repo/util-plugin-sdk';

@Query(() => Boolean)
async myServiceIsValid(
  @PluginDataSource(pluginConfig.name, MyServiceAPI) api: MyServiceAPI,
): Promise<boolean> {
  return api.validate();
}
PluginDataSource throws at runtime if the requested DataSource constructor was not listed in the plugin’s dataSources array, because it won’t be present in the DataSourceMap.

Basic resolver

The minimal resolver every plugin should provide is a connectivity check — it re-uses the validate() method that is already required by BaseDataSource:
import { PluginDataSource } from '@repo/util-plugin-sdk';
import { Query, Resolver } from 'type-graphql';

import { pluginConfig } from '../comet-plugin.config.ts';
import { CometAPI } from '../datasource/comet.datasource.ts';

@Resolver()
export class CometResolver {
  @Query(() => Boolean)
  public async cometIsValid(
    @PluginDataSource(pluginConfig.name, CometAPI) api: CometAPI,
  ): Promise<boolean> {
    return api.validate();
  }
}

Custom return types

Define custom GraphQL object types with the @ObjectType() and @Field() decorators from type-graphql:
import { Field, ObjectType, Query, Resolver } from 'type-graphql';
import { PluginDataSource } from '@repo/util-plugin-sdk';
import { pluginConfig } from '../tvdb-plugin.config.ts';
import { TvdbAPI } from '../datasource/tvdb.datasource.ts';

@ObjectType()
export class TvdbSeriesSummary {
  @Field(() => String)
  public id!: string;

  @Field(() => String)
  public name!: string;

  @Field(() => String, { nullable: true })
  public overview?: string;
}

@Resolver()
export class TvdbResolver {
  @Query(() => TvdbSeriesSummary, { nullable: true })
  public async tvdbSeries(
    @Arg('tvdbId') tvdbId: string,
    @PluginDataSource(pluginConfig.name, TvdbAPI) api: TvdbAPI,
  ): Promise<TvdbSeriesSummary | null> {
    const data = await api.getSeries(tvdbId);
    if (!data) return null;
    return { id: String(data.id), name: data.name ?? '', overview: data.overview ?? undefined };
  }
}

Settings resolver pattern

Every plugin should also extend the global Settings type so that clients can discover the plugin’s configuration keys through GraphQL introspection. The Settings base type is exported from the SDK.
import { Settings } from '@repo/util-plugin-sdk';
import { FieldResolver, Resolver } from 'type-graphql';
import { CometSettings } from './types/comet-settings.type.ts';

@Resolver(() => Settings)
export class CometSettingsResolver {
  @FieldResolver(() => CometSettings)
  public comet(): CometSettings {
    return {
      apiKey: 'comet-api-key',
    };
  }
}
The matching GraphQL object type lives in lib/schema/types/:
import { Field, ObjectType } from 'type-graphql';

@ObjectType()
export class CometSettings {
  @Field(() => String)
  public apiKey!: string;
}
Register both resolvers in your plugin’s resolvers array:
export const plugin: RivenPlugin = {
  // ...
  resolvers: [CometResolver, CometSettingsResolver],
};

Common type-graphql decorators

@Resolver()

Marks a class as a GraphQL resolver. Pass an object type (e.g. @Resolver(() => MyType)) to make it a field resolver class for that type.

@Query(() => T)

Exposes a read-only GraphQL query field. The lambda must return the GraphQL output type.

@Mutation(() => T)

Exposes a write GraphQL mutation field.

@FieldResolver(() => T)

Adds a computed field to an existing @ObjectType. Used to extend the global Settings type.

@Arg('name')

Declares a GraphQL argument on a query or mutation method.

@Root()

Injects the parent object instance in a @FieldResolver.

@ObjectType()

Declares a GraphQL output type. Fields are annotated with @Field().

@InputType()

Declares a GraphQL input type for mutation arguments.

Full example

The following is the complete resolver pair used by the Comet plugin:
import { PluginDataSource } from '@repo/util-plugin-sdk';
import { Query, Resolver } from 'type-graphql';

import { pluginConfig } from '../comet-plugin.config.ts';
import { CometAPI } from '../datasource/comet.datasource.ts';

@Resolver()
export class CometResolver {
  @Query(() => Boolean)
  public async cometIsValid(
    @PluginDataSource(pluginConfig.name, CometAPI) api: CometAPI,
  ): Promise<boolean> {
    return api.validate();
  }
}

Registering resolvers in a plugin

Pass all resolver classes to the resolvers array in your plugin definition. The array must have at least one element (validated at startup by the RivenPlugin Zod schema).
import type { RivenPlugin } from '@repo/util-plugin-sdk';

export const plugin: RivenPlugin = {
  name: pluginConfig.name,
  version: packageJson.version,
  dataSources: [CometAPI],
  resolvers: [CometResolver, CometSettingsResolver],
  hooks: { /* ... */ },
  settingsSchema: CometSettings,
  async validator({ dataSources }) {
    return dataSources.get(CometAPI).validate();
  },
};

Build docs developers (and LLMs) love