[GH-ISSUE #4481] [BUG]: Custom plugin fails with Cannot read properties of undefined (reading 'params') #2852

Closed
opened 2026-02-22 18:31:32 -05:00 by yindo · 5 comments
Owner

Originally created by @pslotwinski-eng on GitHub (Oct 3, 2025).
Original GitHub issue: https://github.com/Mintplex-Labs/anything-llm/issues/4481

How are you running AnythingLLM?

Docker (local)

What happened?

I'm trying to create a custom plugin for AnythingLLM using the documented structure. The plugin is visible in the UI, activates correctly, and is attached to the agent. However, when triggered, it fails with:
Cannot read properties of undefined (reading 'params')
TypeError: fn.handler is not a function

Folder structure:
/app/server/storage/plugins/agent-skills/ReturnSourcePDF/
├── plugin.json
└── handler.js

plugin.json:
JSON{ "name": "ReturnSourcePDF", "hubId": "ReturnSourcePDF", "description": "Returns the original PDF file uploaded to the thread when requested.", "author": "Słotwiński Przemysław", "version": "1.0.0", "type": "tool", "tags": ["pdf", "file", "download"], "entry": "handler.js", "params": [], "imported": true, "active": true}Show more lines

handler.js:
JavaScriptconst path = require("path");async function handler(context = {}) { const threadFiles = Array.isArray(context.threadFiles) ? context.threadFiles : []; const pdfFiles = threadFiles.filter(file => typeof file === "string" && file.toLowerCase().endsWith(".pdf")); if (pdfFiles.length === 0) return "No PDF files found in this thread."; const firstPdf = pdfFiles[0]; const fileName = path.basename(firstPdf); const downloadUrl = /files/${fileName}; return You can download the original PDF here: ${downloadUrl};}module.exports = handler;Show more lines
Environment:

AnythingLLM Docker container
Ollama backend: gemma2:latest
Plugin path confirmed inside container

Are there known steps to reproduce?

Image
Image

Originally created by @pslotwinski-eng on GitHub (Oct 3, 2025). Original GitHub issue: https://github.com/Mintplex-Labs/anything-llm/issues/4481 ### How are you running AnythingLLM? Docker (local) ### What happened? I'm trying to create a custom plugin for AnythingLLM using the documented structure. The plugin is visible in the UI, activates correctly, and is attached to the agent. However, when triggered, it fails with: Cannot read properties of undefined (reading 'params') TypeError: fn.handler is not a function Folder structure: /app/server/storage/plugins/agent-skills/ReturnSourcePDF/ ├── plugin.json └── handler.js plugin.json: JSON{ "name": "ReturnSourcePDF", "hubId": "ReturnSourcePDF", "description": "Returns the original PDF file uploaded to the thread when requested.", "author": "Słotwiński Przemysław", "version": "1.0.0", "type": "tool", "tags": ["pdf", "file", "download"], "entry": "handler.js", "params": [], "imported": true, "active": true}Show more lines handler.js: JavaScriptconst path = require("path");async function handler(context = {}) { const threadFiles = Array.isArray(context.threadFiles) ? context.threadFiles : []; const pdfFiles = threadFiles.filter(file => typeof file === "string" && file.toLowerCase().endsWith(".pdf")); if (pdfFiles.length === 0) return "No PDF files found in this thread."; const firstPdf = pdfFiles[0]; const fileName = path.basename(firstPdf); const downloadUrl = `/files/${fileName}`; return `You can download the original PDF here: ${downloadUrl}`;}module.exports = handler;Show more lines Environment: AnythingLLM Docker container Ollama backend: gemma2:latest Plugin path confirmed inside container ### Are there known steps to reproduce? ![Image](https://github.com/user-attachments/assets/0a681284-d188-4ef7-85b0-908636873683) ![Image](https://github.com/user-attachments/assets/1861b08c-be80-40a9-bd23-0b3671580c66)
yindo added the possible bug label 2026-02-22 18:31:32 -05:00
yindo closed this issue 2026-02-22 18:31:32 -05:00
Author
Owner

@timothycarambat commented on GitHub (Oct 3, 2025):

Can you paste exactly the full contents of plugin.json and handler.js in formatted code blocks? This is likely a very simple fix - closing since this is for sure custom code releated and not a bug with AnythingLLM directly

obviously omit any credentials if they are present in config/code

@timothycarambat commented on GitHub (Oct 3, 2025): Can you paste exactly the full contents of `plugin.json` and `handler.js` in formatted code blocks? This is likely a very simple fix - closing since this is for sure custom code releated and not a bug with AnythingLLM directly _obviously omit any credentials if they are present in config/code_
Author
Owner

@pslotwinski-eng commented on GitHub (Oct 4, 2025):

const path = require("path");

async function handler(context = {}) {
console.log(" ReturnSourcePDF tool triggered");

const threadFiles = Array.isArray(context.threadFiles) ? context.threadFiles : [];

const pdfFiles = threadFiles.filter(file => typeof file === "string" && file.toLowerCase().endsWith(".pdf"));

if (pdfFiles.length === 0) {
return "No PDF files found in this thread.";
}

const firstPdf = pdfFiles[0];
const fileName = path.basename(firstPdf);
const downloadUrl = /files/${fileName};

return You can download the original PDF here: ${downloadUrl};
}

module.exports = handler;

@pslotwinski-eng commented on GitHub (Oct 4, 2025): const path = require("path"); async function handler(context = {}) { console.log("✅ ReturnSourcePDF tool triggered"); const threadFiles = Array.isArray(context.threadFiles) ? context.threadFiles : []; const pdfFiles = threadFiles.filter(file => typeof file === "string" && file.toLowerCase().endsWith(".pdf")); if (pdfFiles.length === 0) { return "No PDF files found in this thread."; } const firstPdf = pdfFiles[0]; const fileName = path.basename(firstPdf); const downloadUrl = `/files/${fileName}`; return `You can download the original PDF here: ${downloadUrl}`; } module.exports = handler;
Author
Owner

@pslotwinski-eng commented on GitHub (Oct 4, 2025):

{
"name": "ReturnSourcePDF",
"hubId": "ReturnSourcePDF",
"description": "Returns the original PDF file uploaded to the thread when requested.",
"author": "Słotwiński Przemysław",
"version": "1.0.0",
"type": "tool",
"tags": ["pdf", "file", "download"],
"entry": "handler.js",
"params": [],
"imported": true,
"active": true
}

@pslotwinski-eng commented on GitHub (Oct 4, 2025): { "name": "ReturnSourcePDF", "hubId": "ReturnSourcePDF", "description": "Returns the original PDF file uploaded to the thread when requested.", "author": "Słotwiński Przemysław", "version": "1.0.0", "type": "tool", "tags": ["pdf", "file", "download"], "entry": "handler.js", "params": [], "imported": true, "active": true }
Author
Owner

@timothycarambat commented on GitHub (Oct 9, 2025):

At a minimum your code need to look like this

// handler.js
module.exports.runtime = {
  handler: async function (context = { threadFiles: '' }) {
    const callerId = `${this.config.name}-v${this.config.version}`;
    try {
      const fs = require("fs");
      console.log("✅ ReturnSourcePDF tool triggered");

      const threadFiles = context.threadFiles.split(",").map(file => file.trim());
      const pdfFiles = threadFiles
        .filter(file => file !== "")
        .filter(file => typeof file === "string" && file.toLowerCase().endsWith(".pdf"))
        .filter(file => fs.existsSync(file));

      this.logger(`${callerId} found ${pdfFiles.length} PDF files in this thread.`);
      if (pdfFiles.length === 0) return "No PDF files found in this thread.";

      let output = "";
      for (const pdf of pdfFiles) output += `You can download the original PDF here: ${pdf}\n`;
      return output;
    } catch (e) {
      this.introspect(
        `${callerId} failed to get PDF files. Reason: ${e.message}`
      );
      this.logger(
        `${callerId} failed to get PDF files`,
        e.message
      );
      return `Failed to get PDF files. Error: ${e.message}`;
    }
  }
};
{
  "active": true,
  "hubId": "ReturnSourcePDF",
  "name": "ReturnSourcePDF",
  "description": "Returns the original PDF file uploaded to the thread when requested.",
  "version": "1.0.0",
  "author": "Słotwiński Przemysław",
  "license": "MIT",
  "type": "tool",
  "tags": [
    "pdf",
    "file",
    "download"
  ],
  "setup_args": {},
  "entrypoint": {
    "file": "handler.js",
    "params": {
      "threadFiles": {
        "description": "file names in list of strings separated by commas",
        "type": "string",
      },
    }
  }
}

Beyond that I dont know what this.context.threadFiles was supposed to do, so i made some changes I thought made sense

@timothycarambat commented on GitHub (Oct 9, 2025): At a minimum your code need to look like this ```js // handler.js module.exports.runtime = { handler: async function (context = { threadFiles: '' }) { const callerId = `${this.config.name}-v${this.config.version}`; try { const fs = require("fs"); console.log("✅ ReturnSourcePDF tool triggered"); const threadFiles = context.threadFiles.split(",").map(file => file.trim()); const pdfFiles = threadFiles .filter(file => file !== "") .filter(file => typeof file === "string" && file.toLowerCase().endsWith(".pdf")) .filter(file => fs.existsSync(file)); this.logger(`${callerId} found ${pdfFiles.length} PDF files in this thread.`); if (pdfFiles.length === 0) return "No PDF files found in this thread."; let output = ""; for (const pdf of pdfFiles) output += `You can download the original PDF here: ${pdf}\n`; return output; } catch (e) { this.introspect( `${callerId} failed to get PDF files. Reason: ${e.message}` ); this.logger( `${callerId} failed to get PDF files`, e.message ); return `Failed to get PDF files. Error: ${e.message}`; } } }; ``` ```json { "active": true, "hubId": "ReturnSourcePDF", "name": "ReturnSourcePDF", "description": "Returns the original PDF file uploaded to the thread when requested.", "version": "1.0.0", "author": "Słotwiński Przemysław", "license": "MIT", "type": "tool", "tags": [ "pdf", "file", "download" ], "setup_args": {}, "entrypoint": { "file": "handler.js", "params": { "threadFiles": { "description": "file names in list of strings separated by commas", "type": "string", }, } } } ``` Beyond that I dont know what `this.context.threadFiles` was supposed to do, so i made some changes I thought made sense
Author
Owner

@pslotwinski-eng commented on GitHub (Oct 10, 2025):

Thank you Timothy for correcting the code.
Now, when I invoke the agent, there's no longer an error.
However, I can't get a link to download the file.
Perhaps I can explain my problem further. I want to use Anything-LLM for the RAG system.
I loaded a single file, "andersen-basnie.pdf," into a test workspace.
My idea was to be able to download the original PDF files (from the workspace, not uploaded ad-hoc in the thread), which were returned in the thread as citations.
I don't know if it's possible to add such a skill to the agent?
Now I see that the new skill is invoked but doesn't return a link. The log says it found 0 files in the current thread.

Image
Image
Image

@pslotwinski-eng commented on GitHub (Oct 10, 2025): Thank you Timothy for correcting the code. Now, when I invoke the agent, there's no longer an error. However, I can't get a link to download the file. Perhaps I can explain my problem further. I want to use Anything-LLM for the RAG system. I loaded a single file, "andersen-basnie.pdf," into a test workspace. My idea was to be able to download the original PDF files (from the workspace, not uploaded ad-hoc in the thread), which were returned in the thread as citations. I don't know if it's possible to add such a skill to the agent? Now I see that the new skill is invoked but doesn't return a link. The log says it found 0 files in the current thread. ![Image](https://github.com/user-attachments/assets/417381ac-502e-49f3-8755-f509a16a8ef7) ![Image](https://github.com/user-attachments/assets/efcb417d-b71c-4f57-bad1-41ee647befab) ![Image](https://github.com/user-attachments/assets/d475e0e4-9132-4ee9-a573-1efcee409630)
yindo changed title from [BUG]: Custom plugin fails with Cannot read properties of undefined (reading 'params') to [GH-ISSUE #4481] [BUG]: Custom plugin fails with Cannot read properties of undefined (reading 'params') 2026-06-05 14:48:54 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Mintplex-Labs/anything-llm#2852