mastra-ai/mastra · error · Error

must contain a plugins array

Error message

${PLUGIN_MANIFEST_FILE} must contain a plugins array

What it means

After successful JSON parsing, loadPluginManifest() requires the root object to have a `plugins` array. If the top level is not an object or `plugins` is missing/not an array, it throws this error. This catches manifests with the wrong shape rather than failing later with confusing undefined errors.

Solutions

  1. Make the top level an object with a `plugins` array: `{ "plugins": [ ... ] }`.
  2. Check the key spelling is exactly `plugins`.
  3. Regenerate the file programmatically via savePluginManifest to guarantee the correct shape.
  4. Diff against a known-good `.mastracode-plugin.json` from a working project.

Example fix

// before
[{ "id": "a", "entry": "./a.ts" }]
// after
{ "plugins": [{ "id": "a", "entry": "./a.ts" }] }
Defensive patterns

Strategy: type-guard

Validate before calling

import fs from 'node:fs';
const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (!parsed || typeof parsed !== 'object' || !Array.isArray((parsed as { plugins?: unknown }).plugins)) {
  throw new Error('.mastracode-plugin.json must be an object with a plugins array');
}

Type guard

function isPluginManifest(value: unknown): value is { plugins: unknown[] } {
  return (
    !!value && typeof value === 'object' && Array.isArray((value as { plugins?: unknown }).plugins)
  );
}

Try / catch

try {
  const manifest = loadPluginManifest(rootDir);
} catch (error) {
  if (error instanceof Error && error.message.includes('must contain a plugins array')) {
    console.error('Manifest shape wrong — expected { "plugins": [...] }');
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: `.mastracode-plugin.json` exists in rootDir, parses as valid JSON, but is e.g. a top-level array, `null`, `{}`, or has `"plugins": {...}` or a misspelled key like `"plugin": [...]`.

Common situations: Authoring the manifest from scratch with the wrong schema; confusing this file with another plugin config format; a tool rewriting the file with a different top-level structure; typos in the `plugins` key.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/c38c711c547aea81. Report an issue: GitHub.

Appendix: source

Thrown at mastracode/sdk/src/plugins/manifest.ts:30

export type PluginManifest = {
  plugins: PluginManifestEntry[];
};

export function loadPluginManifest(rootDir: string): PluginManifest | undefined {
  const manifestPath = path.join(rootDir, PLUGIN_MANIFEST_FILE);
  if (!fs.existsSync(manifestPath)) return undefined;

  let parsed: unknown;
  try {
    parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
  } catch (error) {
    throw new Error(
      `Could not parse ${PLUGIN_MANIFEST_FILE}: ${error instanceof Error ? error.message : String(error)}`,
    );
  }

  if (!parsed || typeof parsed !== 'object' || !Array.isArray((parsed as { plugins?: unknown }).plugins)) {
    throw new Error(`${PLUGIN_MANIFEST_FILE} must contain a plugins array`);
  }

  return {
    plugins: (parsed as { plugins: unknown[] }).plugins.map((entry, index) => validateManifestEntry(entry, index)),
  };
}

export function savePluginManifest(rootDir: string, manifest: PluginManifest): void {
  fs.writeFileSync(path.join(rootDir, PLUGIN_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
}

export function upsertPluginManifestEntry(rootDir: string, entry: PluginManifestEntry): void {
  const manifest = loadPluginManifest(rootDir) ?? { plugins: [] };
  const validatedEntry = validateManifestEntry(entry, manifest.plugins.length);
  const existingIndex = manifest.plugins.findIndex(plugin => plugin.id === validatedEntry.id);
  if (existingIndex >= 0) {
    manifest.plugins[existingIndex] = validateManifestEntry(validatedEntry, existingIndex);
  } else {

View on GitHub (pinned to 75dd419e61)