npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@merkur/plugin-validation

v1.0.3

Published

Merkur plugin for schema validation (props and for future more). Library-agnostic - works with @esmj/schema, zod, and other schema libraries.

Readme

@merkur/plugin-validation

@merkur/plugin-validation is a Merkur plugin for schema-based validation. It provides library-agnostic validation for widget props that works with any schema library implementing the safeParse() interface, including:

Installation

npm install @merkur/plugin-validation @esmj/schema

Requirements

This plugin requires @merkur/plugin-component to be installed and registered before it.

Basic Usage

With @esmj/schema

import { s } from '@esmj/schema';
import { defineWidget } from '@merkur/core';
import { componentPlugin } from '@merkur/plugin-component';
import { validationPlugin } from '@merkur/plugin-validation';

const propsSchema = s.object({
  userId: s.string(),
  count: s.number().optional(),
  config: s.object({
    theme: s.enum(['light', 'dark']),
  }),
});

export default defineWidget({
  name: 'my-widget',
  version: '1.0.0',
  $plugins: [
    componentPlugin,
    validationPlugin({
      props: propsSchema,
    }),
  ],
  // ... widget implementation
});

With zod

import { z } from 'zod';
import { defineWidget } from '@merkur/core';
import { componentPlugin } from '@merkur/plugin-component';
import { validationPlugin } from '@merkur/plugin-validation';

const propsSchema = z.object({
  userId: z.string(),
  count: z.number().optional(),
  config: z.object({
    theme: z.enum(['light', 'dark']),
  }),
});

export default defineWidget({
  name: 'my-widget',
  version: '1.0.0',
  $plugins: [
    componentPlugin,
    validationPlugin({
      props: propsSchema,
    }),
  ],
  // ... widget implementation
});

For detailed benchmarks and feature comparisons, see the @esmj/schema documentation on npm.

:::note If you're already using zod in your project, you can continue using it with @merkur/plugin-validation. The plugin is library-agnostic and works with any schema library that implements the safeParse() method. But @esmj/schema is recommended. :::

Options

props (required)

The schema object used for props validation. Must implement:

  • safeParse(value) - Returns { success: boolean, data?, error? }

onError (optional)

Custom error handler function. Default: null (throws validation error)

| Value | Description | |-------|-------------| | null | Throws the validation error (default) | | function | Custom handler (widget, result) => void |

Custom error handler example

validationPlugin({
  props: propsSchema,
  onError: (widget, result) => {
    // Send to error tracking service
    errorTracker.captureException(result.error, {
      widget: widget.name,
    });
    
    // Or log with custom formatting
    console.warn(`Widget ${widget.name} received invalid props:`, result.error);
  },
});

When Validation Runs

The plugin validates props at two points:

  1. On mount - Initial props are validated when the widget mounts
  2. On setProps - Props are validated each time widget.setProps() is called

Validation is run against the merged props (existing props + new props), ensuring the complete props object is valid.

On successful validation, the result.data (transformed/coerced values) replaces the original props. This means schemas can also perform data transformation, not just validation.

Custom Element Integration

When using with @merkur/integration-custom-element, the validation plugin provides a cleaner approach for parsing HTML attributes to widget props. Instead of defining individual attributesParser functions, you can define a single schema that handles both validation and type coercion.

The @esmj/schema library supports type coercion, which automatically converts string attributes to the correct types:

import { registerCustomElement } from '@merkur/integration-custom-element';
import { componentPlugin } from '@merkur/plugin-component';
import { validationPlugin } from '@merkur/plugin-validation';
import { s } from '@esmj/schema';

// Schema with coercion - automatically converts strings to correct types
const propsSchema = s.object({
  title: s.string(),
  theme: s.string(),
  count: s.cast.number(),  // Casts "42" → 42
  enabled: s.cast.boolean(), // Casts "true" → true
  config: s.cast.json(s.object({ apiUrl: s.string() })), // Parses JSON strings automatically
});

const widgetDefinition = {
  name: 'my-widget',
  version: '1.0.0',
  $plugins: [
    componentPlugin,
    validationPlugin({ props: propsSchema }),
  ],
  // ... widget implementation
};

registerCustomElement({
  widgetDefinition,
  observedAttributes: ['title', 'theme', 'count', 'enabled', 'config'],
});
<my-widget 
  title="Hello World"
  theme="dark"
  count="42"
  enabled="true"
  config='{"apiUrl": "https://api.example.com"}'
></my-widget>

Benefits over attributesParser

| Feature | attributesParser | validationPlugin + schema | |---------|-------------------|------------------------------| | Type coercion | Manual per attribute | Automatic via schema | | Validation | None built-in | Full validation with errors | | Type safety | No | Yes (with TypeScript) | | Reusability | Limited | Schema can be reused | | Error handling | Manual | Configurable via onError | | Default values | Not supported | Via schema defaults |

TypeScript

Full TypeScript support is included:

import { s, type Infer } from '@esmj/schema';
 import { defineWidget } from '@merkur/core';
 import { componentPlugin } from '@merkur/plugin-component';
 import { validationPlugin, type ValidationPluginOptions } from '@merkur/plugin-validation';

const propsSchema = s.object({
  userId: s.string(),
  count: s.number().optional(),
});

type Props = Infer<typeof propsSchema>;

const options: ValidationPluginOptions<Props> = {
  props: propsSchema,
};

export default defineWidget({
  $plugins: [
    componentPlugin,
    validationPlugin(options),
  ],
});