Browse all documentation

Plugins

Install reusable features or build a plugin with Go behavior, new field types, and admin components.

Plugins add reusable features to your application, such as rich text, SEO fields, or a GraphQL API. A plugin has a Go package and may also include a Svelte/TypeScript package for its admin components. Both become part of your application when you build it.

If you want to change an input, add a dashboard panel, or customize another part of the admin, start with Custom components. You can register application components directly. The guide below is for installing or building plugins that add server behavior or new field types. For database connections and file storage, use adapters.

Install plugins you trust: their Go code runs with the same permissions as your application. Choose package versions explicitly and test updates before deploying them.

Install a plugin

If a plugin includes admin components, install its Go and admin packages together:

terminal
ridu plugin add color \
  --go-package example.com/acme/ridu-color \
  --go-version v1.2.0 \
  --admin-package @acme/ridu-color-admin \
  --admin-version '^1.2.0'

ridu add is the shorter alias. The command installs dependencies, updates ridu.plugins.json, registers the Go and admin packages, and regenerates application types. If field types come from a different npm package, install that package at the project root too. Failed setup or generation restores the changed files.

Use --constructor when the Go package exports a zero-argument constructor other than New. Use --no-install when you have already installed the dependencies; Ridu still checks compatibility.

Keep the generated installedPlugins() call in your Go config. The CLI updates the function when you add or remove a plugin:

content/config.go
func Config() ridu.Config {
  return ridu.Config{
    Name: "Acme Editorial",
    // Load the plugins registered by ridu plugin add.
    Plugins:     installedPlugins(),
    Collections: []ridu.Collection{Users, Posts},
  }
}

Rebuild and deploy your application after changing its plugins.

Choose what your plugin adds

Every plugin implements Key() string to give it a unique name. Add the interfaces needed for the features you want to provide:

I want to… Use What it does
Declare versions and generated types DescriptorProvider Describes supported Ridu versions, field types, and the admin package.
Add collections or change application settings ConfigTransformer Changes the Go config before Ridu validates it.
Add or change fields FieldGraphTransformer Edits a collection or global’s fields, including their access rules, hooks, and validation.
Check field configuration FieldGraphValidator Checks the final field definitions after plugins have changed them.
Run code when documents change HookProvider Adds collection hooks after the application’s own hooks.
Validate a custom field’s data FieldValidatorProvider Checks the value being saved and reports errors at the affected field.
Add an HTTP endpoint EndpointProvider Registers a method and path below /api/plugins/<key>/.
Add an API such as GraphQL TransportProvider Registers an API at a fixed path such as /api/graphql.
Add admin components The descriptor’s Admin property Loads the plugin’s fields, pages, dashboard panels, and other components.

Plugins run in the order of Config.Plugins. Descriptors are public: include package and type information, but keep executable functions, credentials, and secrets in Go code or server settings.

Describe compatibility and generated types

Implement Descriptor() when a plugin supplies generated types, an admin package, endpoints, or other features that need build configuration. For a custom color field, it describes which types the application will use:

plugin.go
func (plugin) Descriptor() ridu.PluginDescriptor {
  return ridu.PluginDescriptor{
    Version:    "1.2.0",
    GoPackage:  "example.com/acme/ridu-color",
    APIVersion: ridu.PluginAPIVersion,
    Ridu: ridu.RiduCompatibility{
      Minimum: ridu.FrameworkVersion,
    },
    // Name the types that generated Go and TypeScript code imports.
    FieldTypes: []ridu.PluginFieldType{{
      Key:               "color",
      TypeScriptPackage: "@acme/ridu-color-admin",
      TypeScriptOutput:  "ColorValue",
      TypeScriptInput:   "ColorInput",
      TypeScriptWhere:   "ColorWhere",
      GoPackage:         "example.com/acme/ridu-color",
      GoType:            "Value",
      JSONSchema:        colorJSONSchema,
    }},
  }
}

TypeScriptOutput names the type returned when reading a document. TypeScriptInput names the type accepted when creating or updating it, and TypeScriptWhere describes its query filters. GoPackage and GoType name the Go value type. If you omit both Go settings, generated Go code uses encoding/json.RawMessage.

JSONSchema describes the value in OpenAPI. Ridu checks these settings, plugin versions, and route conflicts before the application starts. Build a field plugin shows a complete descriptor, editor, and validator working together.

Add endpoints

Plugin endpoints declare an exact HTTP method and a relative path. Ridu mounts them below /api/plugins/<key>/; a known path with the wrong method returns 405.

The handler receives the HTTP request, response writer, and information about the signed-in user. Use EndpointContext.Local to read or change content with Ridu’s normal permissions, validation, and hooks.

Set MaxBodyBytes to limit request size, or leave it at zero to use the application’s limit. A negative value disables that limit; a streaming handler must then enforce its own limits. If your endpoint handles authentication, call AdmitAuthAttempt before checking credentials or performing other expensive work. See the endpoint context reference.

Use TransportProvider for an API with a fixed top-level path, such as GraphQL. Set it up once when the application starts. The GraphQL plugin shows this approach.

Connect the admin package

Set the descriptor’s Admin property to identify the JavaScript package and its exported plugin object. APIVersion must match ridu.AdminPluginAPIVersion. PairingVersion is your own version number for the connection between the Go and admin packages: increase it when an older version of either package would no longer work with the other.

Ridu generates the imports and checks that both packages agree on their key, versions, fields, routes, and assets. Publish compatible Go and admin packages together.

Create the JavaScript export with defineAdminPlugin. Use definePluginField for each new field type’s default editor, or defineFieldComponent for an alternative editor selected by an application. Each reference page lists its options and includes a working registration example. Build a field plugin connects them to a Go plugin from start to finish.

Plugin pages require sign-in. Their API requests still need server access rules, just like requests from the built-in admin.

Store plugin data

Store plugin data in ordinary collections and fields. Add them through ConfigTransformer to get Ridu’s permissions, validation, transactions, API, generated types, and admin. The active adapter plans their migrations together with application collections. See Migrations.

Add a loader to a plugin

A plugin can supply Go-backed page data for its dashboard panels, custom routes, or collection/global replacement views.

Start with a paired plugin. Put the loader’s Go function and input/output structs in that plugin’s package, not in the consuming application. For this example, change its NewAdminLoader key to editorial-post-summary to avoid collisions with other loaders. The loader guide explains valid keys and types.

Append the definition in the plugin’s existing TransformConfig method. For the PostSummary definition from that guide:

go
func (plugin) TransformConfig(config ridu.Config) (ridu.Config, error) {
  config.Admin.Loaders = append(config.Admin.Loaders, PostSummary)
  return config, nil
}

Keep any other changes your transformer already makes. Applications now receive the loader when they install the Go plugin; they do not register the function separately.

For a published admin package, generate against a small development application that installs your Go plugin. With that application in example/ and the admin package in admin/, run these commands from the plugin repository:

terminal
(cd example && ridu generate)
mkdir -p admin/src/generated
cp example/generated/ridu.generated.ts admin/src/generated/ridu.generated.ts

This copies the complete generated client, unchanged. Ridu does not currently generate a separate plugin-only loader file. Keep the development app small, and include this copy step in your package’s build process so changes to the Go types reach the admin package. Do not handwrite the loader schema or import files from a consuming application’s directory.

The generated client imports @riducms/sdk. Add it to both peerDependencies and devDependencies in admin/package.json, using the same exact Ridu version already listed for @riducms/plugin. If you reuse the guide’s component, add @riducms/admin and @riducms/ui there too: it imports their Link and Button. Then run bun install --cwd admin. Declare these direct imports even if your development app already installs the packages.

Your package’s components and registration now import their own generated module. For example, after moving post-summary.svelte into admin/src/, export a loaded dashboard panel:

ts
import { defineAdminPlugin } from '@riducms/plugin/authoring/v1';
import { withAdminLoader } from '@riducms/plugin';
import { adminLoaders } from './generated/ridu.generated';
import PostSummary from './post-summary.svelte';

export const editorialAdminPlugin = defineAdminPlugin({
  key: 'editorial',
  pairingVersion: 1,
  dashboardPanels: [
    {
      key: 'post-summary',
      position: 'before',
      ...withAdminLoader(
        adminLoaders['editorial-post-summary'],
        PostSummary
      )
    }
  ]
});

In the component, also change the generated import to ./generated/ridu.generated and the loader/type key to editorial-post-summary. Export editorialAdminPlugin from your package’s entry point and name it in the Go descriptor’s Admin.Export. Keep key and pairingVersion matched to the Go descriptor, as described in Connect the admin package. The same pairing works in routes or coreViews; declare route paths in the Go descriptor when adding plugin pages.

The loader result reaches your component through data, and refresh() runs that loader again. Ridu reads through ctx.List, ctx.Find, and ctx.Global use the signed-in user’s permissions and current locale. A frontend-only plugin needs a Go counterpart for this server-loaded data.

Publish the Go loader and its generated TypeScript reference together when their inputs or results change. There is no separate backend route map to maintain for these reads.

Remove a plugin

terminal
ridu plugin remove color
ridu migrate create --name remove-color

Removal updates the generated imports and types, removes the Go dependency, and removes the admin package if no remaining plugin uses it. Remove any separately installed type package yourself after checking that nothing else imports it. The CLI restores changed files if removal fails; it does not change the database.

Review which tables and data the generated migration will remove. Back up the database and try the migration on a copy before applying it in production. Export or migrate any data you need to keep while the plugin is still installed.

Create and test a plugin

terminal
ridu plugin new ./ridu-color \
  --key color \
  --module example.com/acme/ridu-color \
  --admin-package @acme/ridu-color-admin

The scaffold includes a Go module, field helper, descriptor, validator, Svelte field, exported value type, and tests.

Call plugintest.Run from an external-package Go test:

plugin_test.go
func TestPluginConformance(t *testing.T) {
  plugintest.Run(t, plugintest.Fixture{
    Plugin: color.New(),
    Fields: field.Fields{
      color.Field("accent"),
    },
    // Exercise acceptance and rejection through the plugin test suite.
    ValidData:   store.Values{"accent": store.String("#663399")},
    InvalidData: store.Values{"accent": store.String("not-a-color")},
    Compatibility: []plugintest.CompatibilityCase{
      {RiduVersion: ridu.FrameworkVersion, Compatible: true},
    },
  })
}

The test checks that the plugin loads, generates its declared types, accepts valid data, and rejects invalid data through both the local API and REST. It also checks the Ridu versions you list under Compatibility.

Compile a generated application that uses the plugin and run its admin checks. If the plugin adds database tables, test applying and reversing its migrations on every supported adapter.

Choose version numbers

Version Changes when
Plugin semantic Version Every plugin publication.
PluginAPIVersion Ridu makes a breaking change to its Go plugin API.
AdminPluginAPIVersion Ridu makes a breaking change to its admin plugin API.
Plugin PairingVersion This plugin’s backend and admin packages are no longer mutually compatible.

Test every Ridu version you claim to support, along with versions just outside that range.

Official plugins include Rich text, SEO, Form Builder, and GraphQL. For a complete new field type, follow Build a field plugin.