formcord

Docs

Formcord

Universal Discord notifications with zero dependencies. Works in Edge, serverless, and Node runtimes using only Web APIs.

Get started

Install

bash
npm install formcord

Environment variables

ENV
FORMCORD_DISCORD_TOKEN=xxxx
FORMCORD_DISCORD_CHANNEL=yyyy

First message

TS
import { formcord } from "formcord";

await formcord.send({
  token: process.env.FORMCORD_DISCORD_TOKEN!,
  channelId: process.env.FORMCORD_DISCORD_CHANNEL!,
  data: {
    "Subject": "Hello",
    "Email": "me@example.com",
    "Message": "This is a test",
  }
});

Discord bot setup (quick and easy)

  1. Create an app and bot in the Discord Developer Portal.
  2. Copy the bot token.
  3. Invite the bot to your server with permission to send messages.
  4. Get the channel ID (enable Developer Mode, then copy ID).

Helpers

Built-in helpers to standardize your payloads. All custom fields should go inside the data object.

contact

Form submissions and inquiries.

  • data.subject - Subject of the contact message
  • data.email - Sender email address
  • data.message - Message body
  • text - Optional top message text
  • embed - Optional embed styling
TS
formcord.contact({
  token,
  channelId,
  throwOnError,
  text,
  embed,
  data: {
    subject,
    email,
    message,
  }
});

error

Runtime errors with optional context.

  • error - Error object or message
  • data.source - Where the error originated
  • data.environment - Runtime environment
  • text - Optional top message text
  • embed - Optional embed styling
TS
formcord.error({
  token,
  channelId,
  error,
  throwOnError,
  text,
  embed,
  data: {
    source,
    environment,
  }
});

deploy

Deployment notifications.

  • data.project - Project name
  • data.environment - Environment name
  • data.url - Deployment URL
  • data.commit - Commit SHA or ref
  • text - Optional top message text
  • embed - Optional embed styling
TS
formcord.deploy({
  token,
  channelId,
  throwOnError,
  text,
  embed,
  data: {
    project,
    environment,
    url,
    commit,
  }
});

feedback

User feedback and ratings.

  • data.rating - Rating value
  • data.message - Feedback text
  • text - Optional top message text
  • embed - Optional embed styling
TS
formcord.feedback({
  token,
  channelId,
  throwOnError,
  text,
  embed,
  data: {
    rating,
    message,
  }
});

bug

Bug reports with steps and context.

  • data.title - Bug title
  • data.steps - Steps to reproduce
  • data.browser - Browser or client info
  • text - Optional top message text
  • embed - Optional embed styling
TS
formcord.bug({
  token,
  channelId,
  throwOnError,
  text,
  embed,
  data: {
    title,
    steps,
    browser,
  }
});

Delivery status and debugging

Formcord is fire-and-forget by default. Every notification method resolves with a simple success status when Discord accepts or rejects the request.

TS
const result = await formcord.contact({
  token,
  channelId,
  data: { subject, email, message }
});

if (!result.success) {
  // Show a fallback, retry, or record the failed notification.
}

Set throwOnError: truewhen you need Discord's original response for debugging.

Media & Attachments

Attach images, PDFs, logs, or text files directly using the optional files array. Formcord handles standard Discord upload constraints automatically (max 25MB combined size, max 10 files) and normalizes standard Web API File / Blob objects dynamically.

TS
import { formcord } from "formcord";
import fs from "node:fs/promises";

await formcord.send({
  token,
  channelId,
  text: "Attached multiple files of different types",
  files: [
    // Type 1: Raw Web API File/Blob objects (from client-side inputs or server parsers)
    rawBrowserFileObject,

    // Type 2: Plain Text Strings (logs, CSVs, markdowns)
    {
      name: "system-logs.txt",
      data: "INFO: Task started\nERROR: Failed to save changes.",
      contentType: "text/plain"
    },

    // Type 3: Node Buffers / Uint8Arrays (local filesystem files)
    {
      name: "avatar.png",
      data: await fs.readFile("./public/avatar.png"),
      contentType: "image/png"
    },

    // Type 4: ArrayBuffers (remote asset fetch results)
    {
      name: "statement.pdf",
      data: await fetch("https://api.example.com/invoice.pdf").then(res => res.arrayBuffer()),
      contentType: "application/pdf"
    }
  ]
});

Custom Validation Helper

If you want custom constraints (e.g. limiting files to 5MB, setting a max count of 5, or enforcing all-or-nothing check policies), use the standalone validateFiles helper function:

TS
import { formcord, validateFiles } from "formcord";
import fs from "node:fs/promises";

// 1. Gather your files (Formcord File/Blob normalization runs automatically)
const attachments = [
  rawBrowserFileObject, 
  {
    name: "server_logs.txt",
    data: "DEBUG: Server running...",
    contentType: "text/plain"
  },
  {
    name: "invoice.pdf",
    data: await fetch("https://api.example.com/invoice.pdf").then(res => res.arrayBuffer()),
    contentType: "application/pdf"
  }
];

// 2. Validate the mixed list
const { valid, invalid } = validateFiles(attachments, {
  maxFileSize: "5mb",     // Max 5 MB per file
  maxTotalSize: "15mb",   // Max 15 MB combined total
  maxFileCount: 5,        // Max 5 files total
  ignoreInvalid: true,    // Keep valid files, skip bad ones (false = reject all on any failure)
  throwOnError: false,    // Return results gracefully (true = throw immediately)
  logWarnings: true       // Print warning logs to console
});

// 3. Handle validation errors
if (invalid.length > 0) {
  console.warn("Some files failed validation checks:", 
    invalid.map(i => `${i.file.name}: ${i.message}`)
  );
}

// 4. Send the verified valid files
if (valid.length > 0) {
  await formcord.send({
    token,
    channelId,
    text: "Notification submission with attachments.",
    files: valid
  });
}

Theming and custom fields

Add a top message with text, customize embeds with embed, and pass unlimited custom fields in data.

TS
formcord.contact({
  token,
  channelId,
  text: "New support request",
  embed: {
    title: "RenderCard Support Message",
    author: { name: "Anonymous User - 8f3a2d" },
    color: 0x5865f2,
    footer: { text: "System Notification" },
    timestamp: new Date().toISOString(),
  },
  data: {
    subject: "Hello",
    email: "me@example.com",
    message: "This is a test",
    "Extra Field": "Unlimited custom fields allowed"
  }
});

⚠️ Migration Guide (v1 to v2)

Version 2.0.0 standardizes field names to prevent confusion between top-level text, embed styling, and form fields.

Before (v1.x)

TS
formcord.contact({
  token,
  channelId,
  content: "Top message text",
  theme: { title: "My Title" },
  subject: "Hello",
  email: "me@example.com",
  message: "Test"
});

After (v2.x)

TS
formcord.contact({
  token,
  channelId,
  text: "Top message text",
  embed: { title: "My Title" },
  data: {
    subject: "Hello",
    email: "me@example.com",
    message: "Test"
  }
});

Notes

  • Uses only fetch, URL, and JSON.
  • Retries once on 429 rate limits.
  • Best effort delivery.
  • Requires a Discord bot token with permission to post.
  • Not a guaranteed delivery system for enterprise logging or auditing.