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.

Riven plugins read their configuration exclusively through the PluginSettings utility — never directly from process.env. The framework strips all plugin-related environment variables from process.env before importing any plugin, ensuring no plugin can accidentally (or maliciously) read another plugin’s secrets. PluginSettings parses each plugin’s variables using a Zod schema, validates them at startup, then deep-freezes the result so settings cannot be mutated at runtime.

Defining a settings schema

Create a Zod object schema that describes every setting your plugin accepts. Add .describe() to each field — descriptions appear in generated documentation.
// lib/my-plugin-settings.schema.ts
import z from 'zod';

export const MyPluginSettings = z.object({
  apiKey: z
    .string()
    .min(1, 'API key is required')
    .describe('Your MyPlugin API key'),
  url: z
    .url()
    .default('https://api.myplugin.example')
    .describe('Base URL for the MyPlugin API'),
  timeoutMs: z.coerce
    .number()
    .int()
    .nonnegative()
    .default(5000)
    .describe('Request timeout in milliseconds'),
});

export type MyPluginSettings = z.infer<typeof MyPluginSettings>;

Environment variable naming convention

Each field in the schema maps to an environment variable following this pattern:
RIVEN_PLUGIN_SETTING__<PREFIX>__<fieldName>
The <PREFIX> is the configPrefix registered for your plugin — typically derived from the package name in SCREAMING_SNAKE_CASE. For a package called @repo/plugin-my-service the prefix would be REPO_PLUGIN_MY_SERVICE.
# apiKey → RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__apiKey
RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__apiKey="sk-abc123"

# url → RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__url
RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__url="https://custom.myplugin.example"

# timeoutMs → RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__timeoutMs
RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__timeoutMs="10000"
Variable names are case-sensitive. The fieldName portion must match the exact camelCase key in your Zod schema. Unrecognised keys are logged as warnings at startup.

Reading settings in plugin code

Call settings.get(YourSettingsSchema) anywhere you have access to a PluginSettings instance — inside hook handlers, validators, and the context() factory. The return type is fully inferred from the Zod schema.
async validator({ dataSources, settings }) {
  const { apiKey, url } = settings.get(MyPluginSettings);
  // apiKey: string, url: string, timeoutMs: number
  return dataSources.get(MyServiceAPI).validate();
}
Inside a hook handler:
hooks: {
  'riven.content-service.requested': async ({ dataSources, settings }) => {
    const { updateIntervalSeconds } = settings.get(MyPluginSettings);
    const api = dataSources.get(MyServiceAPI);
    // ...
    return { movies, shows, updateIntervalSeconds };
  },
},

The settings lifecycle

1

Scan environment variables

At startup PluginSettings is constructed with the list of all registered plugin prefixes. It scans process.env, matches each variable against the pattern, and groups raw string values by prefix. All matched variables are deleted from process.env immediately so they cannot be read by later code.
2

Parse with Zod (set)

The framework calls settings.set(configPrefix, YourSchema) for each registered plugin. This calls schema.parse() on the raw string values for that prefix — applying coercions, defaults, and validations defined in the Zod schema. The parsed object is stored in an internal map keyed by the schema object itself.
3

Plugin validator runs

Your plugin’s validator({ dataSources, settings }) function is called. Throw a FatalValidationError here if the settings are permanently wrong (e.g. the API key is malformed or connectivity fails permanently).
4

Lock settings

settings.lock() is called. Every settings object in the map is deep-frozen via Object.freeze(). Any subsequent call to settings.set() throws immediately. Unused raw variables (keys that didn’t match any schema field) are logged as warnings and discarded.

PluginSettings API reference

settings.get(schema)
(schema: ZodObject) => z.infer<typeof schema>
Returns the frozen, parsed settings object for the given schema. Throws Error: Schema not found in settings map if set() was never called for this schema — which indicates a plugin registration bug.
settings.set(configPrefix, schema)
(prefix: string, schema: ZodObject) => void
Parses all raw env values for configPrefix using schema and stores the result. Called internally by the Riven plugin registrar — do not call this from plugin code.
settings.lock()
() => void
Freezes all settings objects and discards unused raw values. Called once after all plugins are registered. Subsequent calls to set() throw.

Registering the settings schema in the plugin

Pass your settings schema as the settingsSchema field of your RivenPlugin export. The framework uses it to determine what prefix to call settings.set() with:
import type { RivenPlugin } from '@repo/util-plugin-sdk';
import { MyPluginSettings } from './my-plugin-settings.schema.ts';

export const plugin: RivenPlugin = {
  name: pluginConfig.name,
  version: packageJson.version,
  settingsSchema: MyPluginSettings,
  // ...
};

Exposing settings via GraphQL

Use the settings resolver pattern (described in detail in the Resolvers guide) to expose your plugin’s setting keys through GraphQL introspection. This allows the Riven UI to show which settings a plugin needs without the values ever leaving the server.
// lib/schema/types/my-plugin-settings.type.ts
import { Field, ObjectType } from 'type-graphql';

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

  @Field(() => String)
  public url!: string;
}
// lib/schema/my-plugin-settings.resolver.ts
import { Settings } from '@repo/util-plugin-sdk';
import { FieldResolver, Resolver } from 'type-graphql';
import { MyPluginSettingsType } from './types/my-plugin-settings.type.ts';

@Resolver(() => Settings)
export class MyPluginSettingsResolver {
  @FieldResolver(() => MyPluginSettingsType)
  public myPlugin(): MyPluginSettingsType {
    // Return placeholder key names — actual values never leave the server
    return {
      apiKey: 'my-plugin-api-key',
      url: 'my-plugin-url',
    };
  }
}

Real-world example: TVDB plugin settings

The TVDB plugin uses a minimal settings schema with a single apiKey field:
// packages/plugin-tvdb/lib/tvdb-settings.schema.ts
import z from 'zod';

export const TvdbSettings = z.object({
  apiKey: z
    .string()
    .default('6be85335-5c4f-4d8d-b945-d3ed0eb8cdce')
    .describe('The TVDB API key used to request a token.'),
});

export type TvdbSettings = z.infer<typeof TvdbSettings>;
The corresponding env var:
RIVEN_PLUGIN_SETTING__REPO_PLUGIN_TVDB__apiKey="your-tvdb-api-key"
Inside TvdbAPI the value is available as this.settings.apiKey:
this.#inFlightLoginRequest ??= this.post<unknown>('login', {
  body: {
    apikey: this.settings.apiKey,
  },
});

FatalValidationError

Throw FatalValidationError from your validator function when the settings are permanently wrong and retrying will not help:
import { FatalValidationError } from '@repo/util-plugin-sdk/errors/fatal-validation-error';

async validator({ dataSources, settings }) {
  const { apiKey } = settings.get(MyPluginSettings);

  if (apiKey.length < 32) {
    throw new FatalValidationError(
      'MyPlugin API key appears invalid — expected 32+ characters. ' +
      'Check RIVEN_PLUGIN_SETTING__REPO_PLUGIN_MY_SERVICE__apiKey.',
    );
  }

  return dataSources.get(MyServiceAPI).validate();
},
FatalValidationError prevents the plugin from loading entirely. Only throw it when you are certain that no amount of retrying will succeed (e.g. the key format is structurally wrong). For transient network failures, return false instead so the startup sequence can retry.

Build docs developers (and LLMs) love