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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@robojs/roadmap

v0.1.0

Published

Sync project roadmaps from Jira (or custom providers) to organized Discord forum channels.

Readme


@robojs/roadmap

Sync project roadmaps from Jira (or custom providers) to organized Discord forum channels, automatically creating and updating forum threads for each card while providing slash commands for seamless team collaboration.

GitHub license npm install size Discord All Contributors

📚 Documentation: Getting started

🚀 Community: Join our Discord server

✨ Features

  • 🔄 Automatic bidirectional sync between provider and Discord
  • 📋 Forum channels organized by workflow columns
  • 🏷️ Smart label tagging with autocomplete (up to 5 tags per thread)
  • 👥 Role-based authorization for card creation
  • 🔒 Public/private forum access modes
  • 🔍 Autocomplete for cards, columns, labels, and issue types
  • 📅 Date range filtering for programmatic queries
  • 🌐 Optional REST API for external integrations
  • 🔌 Extensible provider architecture (Jira, GitHub, Linear, custom)
  • Idempotent sync with automatic error recovery

⚡ Quick Happy Path

  1. npx robo add @robojs/roadmap in an existing Robo project.
  2. Set Jira env vars (JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN, JIRA_PROJECT_KEY) in your .env.
  3. Start Robo with npx robo dev.
  4. In Discord, run /roadmap setup to create roadmap forums.
  5. Run /roadmap sync to import cards.
  6. Click into the generated roadmap forums to collaborate on cards.

For a full walkthrough, see Getting Started and Provider Setup.

💻 Getting Started

npx robo add @robojs/roadmap

New to Robo.js? Start your project with this plugin pre-installed:

npx create-robo <project-name> -p @robojs/roadmap

Set Jira environment variables (JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN, JIRA_PROJECT_KEY) for the plugin to function. See Provider Setup for details.

💡 Install @robojs/server to enable REST API features for external integrations.

🚀 Quick Start

  1. Configure Jira credentials in your .env file:

    JIRA_URL="https://company.atlassian.net"
    JIRA_EMAIL="[email protected]"
    JIRA_API_TOKEN="your-api-token"
    JIRA_PROJECT_KEY="PROJ"
  2. Start your Robo:

    npx robo dev
  3. Run /roadmap setup in Discord to create forum channels

  4. Run /roadmap sync to sync your cards from Jira

  5. Cards appear as forum threads organized by column (Backlog, In Progress, Done, etc.)

During setup, the plugin creates a dedicated category with forum channels for each workflow column. Each card becomes a forum thread in its respective column channel, complete with labels, descriptions, and automatic updates.

💬 Discord Commands

The plugin provides four slash commands for managing your roadmap:

/roadmap setup

Creates the roadmap forum structure. Admin only.

  • Creates a category with forum channels for each workflow column
  • Shows interactive setup message with access toggle and role selector
  • Configures authorized creator roles

Example:

/roadmap setup

/roadmap sync

Manually syncs cards from provider to Discord. Admin only.

  • Options:

    • dry-run (boolean) - Preview sync without making changes
  • Shows sync statistics (created, updated, archived)

  • Cancellation: A cancel button appears during sync, allowing you to stop the operation. Only administrators or the user who started the sync can cancel. Partial results are preserved and shown (e.g., "Partial sync: 15/42 cards processed").

  • Idempotent: safe to run multiple times

  • When a card's column changes, the thread is moved to the new forum channel. The old thread is locked and archived to preserve discussion history, and the new thread links to it if there were user messages.

Example:

/roadmap sync dry-run:true

/roadmap add

Creates a new card. Authorized users.

  • Options:

    • title (required) - Card title
    • description - Card description
    • column (autocomplete) - Target column
    • labels (autocomplete, comma-separated) - Card labels
    • issue-type (autocomplete) - Issue type (Task, Bug, etc.)
  • Automatically syncs to Discord after creation

  • Supports autocomplete for columns, labels, and issue types

Example:

/roadmap add title:"Add dark mode" column:"Backlog" labels:"feature,ui"

/roadmap edit

Updates an existing card. Authorized users.

  • Options:

    • card (autocomplete, required) - Card to edit
    • title - New title
    • description - New description
    • column (autocomplete) - Move to column
    • labels (autocomplete, comma-separated) - Update labels
  • Updates both provider and Discord thread

  • Autocomplete searches cards by title and key

  • When changing a card's column, the Discord thread is moved to the new forum channel. The previous thread is locked and archived, and the new thread includes a link to the old discussion if it had user activity.

Example:

/roadmap edit card:"PROJ-123" column:"In Progress"

💡 All autocomplete fields are cached for performance and refresh every 5 minutes by default. You can change this by setting autocompleteCacheTtl (in milliseconds) in your roadmap plugin options; lower values refresh suggestions more often at the cost of more provider API calls. Labels support comma-separated values for multi-selection.

🔧 Provider Setup

Jira Provider

Jira is the built-in provider for syncing roadmaps from Jira Cloud.

Required Environment Variables:

  • JIRA_URL - Your Jira instance URL (e.g., https://company.atlassian.net)
  • JIRA_EMAIL - Email address for authentication
  • JIRA_API_TOKEN - API token for authentication (create one here)
  • JIRA_PROJECT_KEY - Project key to sync (e.g., PROJ)

Optional Environment Variables:

  • JIRA_JQL - Custom JQL query to filter issues
  • JIRA_MAX_RESULTS - Max results per page (1-1000, default 100)
  • JIRA_DEFAULT_ISSUE_TYPE - Default issue type for new cards (default 'Task')
  • JIRA_DISCORD_USER_ID_FIELD_ID - Custom field ID containing Discord User ID (e.g., customfield_10001)

Example .env file:

JIRA_URL="https://company.atlassian.net"
JIRA_EMAIL="[email protected]"
JIRA_API_TOKEN="ATATT3xFfGF0..."
JIRA_PROJECT_KEY="PROJ"
JIRA_JQL="labels = public"
JIRA_MAX_RESULTS="50"
JIRA_DEFAULT_ISSUE_TYPE="Story"
JIRA_DISCORD_USER_ID_FIELD_ID="customfield_10001"

Configuration File Example:

// config/plugins/robojs/roadmap.mjs
export default {
	provider: {
		type: 'jira',
		options: {
			url: process.env.JIRA_URL,
			email: process.env.JIRA_EMAIL,
			apiToken: process.env.JIRA_API_TOKEN,
			projectKey: process.env.JIRA_PROJECT_KEY,
			jql: 'labels = public',
			maxResults: 50
		}
	}
}

JQL Filtering Examples:

// Public cards only
jql: 'project = PROJ AND labels = public'

// Exclude private cards
jql: 'project = PROJ AND labels != private'

// Active cards only (not resolved)
jql: 'project = PROJ AND resolution = Unresolved'

// Cards updated in last 30 days
jql: 'project = PROJ AND updated >= -30d'

💡 JQL (Jira Query Language) allows powerful filtering. See Atlassian JQL documentation for advanced queries.

Discord User ID Custom Field

You can use a Jira custom field to directly assign Discord users to cards, bypassing the standard Jira assignee mapping. This is useful when you want to assign cards to Discord users who may not have Jira accounts or when you want to override the Jira assignee.

Setup:

  1. Create a text custom field in Jira (e.g., customfield_10001)
  2. Configure the field ID in your environment or config:
    JIRA_DISCORD_USER_ID_FIELD_ID="customfield_10001"
  3. In Jira issues, enter the Discord User ID (17-19 digit numeric string) in this custom field

Behavior:

  • Priority: Custom field Discord User ID takes priority over Jira assignee mapping
  • Fallback: If the custom field is empty or invalid, falls back to Jira assignee mapping
  • Avatar: When a custom field Discord User ID is present, that user's Discord avatar is displayed instead of the Jira assignee's avatar
  • Validation: Invalid Discord User ID formats (not 17-19 digits) are logged and ignored, falling back to Jira assignee

Example:

provider: new JiraProvider({
	type: 'jira',
	options: {
		// ... credentials ...
		discordUserIdFieldId: 'customfield_10001'
	}
})

[!NOTE] Configuration precedence: explicit config file > plugin options > environment variables. Environment variables provide the simplest setup for most use cases.

⚙️ Configuration

Column Mapping

✅ Default Behavior (Works Out of the Box):

The plugin automatically maps Jira status categories to three columns without any configuration:

  • "To Do" category → "Backlog" column
  • "In Progress" category → "In Progress" column
  • "Done" category → "Done" column

These defaults are always active - you can start syncing immediately without configuring anything!

🔧 Optional Customization:

If your Jira workflow uses different statuses or you want custom columns, you can override defaults at two levels:

Provider-Level (Default): Configure custom columns and status mappings in your provider config:

provider: new JiraProvider({
	type: 'jira',
	options: {
		// ... credentials ...
		columnConfig: {
			columns: [
				{ id: 'planning', name: 'Planning', order: 0 },
				{ id: 'development', name: 'Development', order: 1 },
				{ id: 'review', name: 'Review', order: 2 },
				{ id: 'done', name: 'Done', order: 3, archived: true }
			],
			statusMapping: {
				Backlog: 'Planning',
				'To Do': 'Planning',
				'In Progress': 'Development',
				'Code Review': 'Review',
				QA: 'Review',
				Done: 'Done',
				Closed: null, // Track but don't create forum thread
				"Won't Do": null
			}
		}
	}
})

Runtime-Level (Per-Guild): Override mappings per-guild using /roadmap setup or the settings API:

import { setColumnMapping } from '@robojs/roadmap'

// Map QA status to Development column
setColumnMapping(guildId, 'QA', 'Development')

// Track Blocked status without creating forum thread
setColumnMapping(guildId, 'Blocked', null)

Many-to-One Support: Multiple statuses can map to the same column (e.g., both "Code Review" and "QA" map to "Review").

Track-Only Mappings: Map a status to null to track it programmatically (useful for changelogs) without creating a Discord forum thread.

Plugin Options

Configure the plugin in config/plugins/robojs/roadmap.mjs (or .ts):

import { JiraProvider } from '@robojs/roadmap'

export default {
	// Provider instance or config object (required)
	provider: new JiraProvider({
		type: 'jira',
		options: {
			url: process.env.JIRA_URL,
			email: process.env.JIRA_EMAIL,
			apiToken: process.env.JIRA_API_TOKEN,
			projectKey: process.env.JIRA_PROJECT_KEY,
			jql: 'labels = public',
			maxResults: 100,
			defaultIssueType: 'Task'
		}
	}),

	// Cache duration for autocomplete suggestions (default: 300000 = 5 minutes)
	autocompleteCacheTtl: 300000,

	// Whether /roadmap add and /roadmap edit replies are ephemeral (true) or visible in-channel (false)
	ephemeralCommands: true,

	// Default template for Discord thread titles (can be overridden per-guild)
	threadTitleTemplate: '[{id}] {title}',

	// Reserved for future automatic sync feature
	autoSync: false,

	// Reserved for future sync interval configuration
	syncInterval: null
}

Available Options:

| Option | Type | Default | Description | | ---------------------- | ----------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- | | provider | RoadmapProvider \| ProviderConfig | required | Provider instance or config object | | autocompleteCacheTtl | number | 300000 | Cache duration in milliseconds for autocomplete | | ephemeralCommands | boolean | true | Controls whether /roadmap add and /roadmap edit replies are ephemeral | | threadTitleTemplate | string | undefined | Default template for formatting Discord thread titles (see Thread Title Templates) | | autoSync | boolean | false | Reserved for future automatic sync | | syncInterval | number \| null | null | Reserved for future sync interval |

Jira Configuration

Jira-Specific Options:

| Option | Type | Required | Description | | ------------------ | -------- | -------- | ------------------------------------------ | | url | string | yes | Jira instance URL | | email | string | yes | Email for authentication | | apiToken | string | yes | API token for authentication | | projectKey | string | yes | Project key to sync | | jql | string | no | Custom JQL query to filter issues | | maxResults | number | no | Max results per page (1-1000, default 100) | | defaultIssueType | string | no | Default issue type (default 'Task') |

Example with JQL Filtering:

export default {
	provider: {
		type: 'jira',
		options: {
			url: 'https://company.atlassian.net',
			email: '[email protected]',
			apiToken: process.env.JIRA_API_TOKEN,
			projectKey: 'PROJ',
			jql: 'project = PROJ AND labels = public AND resolution = Unresolved',
			maxResults: 50,
			defaultIssueType: 'Story'
		}
	}
}

🔐 Authorization & Permissions

Permission Levels

Admin Commands (/roadmap setup, /roadmap sync):

  • Require Administrator permission in Discord

Authorized User Commands (/roadmap add, /roadmap edit):

  • Require admin OR authorized creator role
  • Configure authorized roles via /roadmap setup interactive message or settings API

Required Discord Bot Permissions

| Permission | Required For | | ------------------------ | ---------------------------- | | View Channels | See forum channels | | Create Public Threads | Create card threads | | Send Messages in Threads | Post card content | | Manage Threads | Update and archive threads | | Manage Messages | Edit thread starter messages |

💡 Permissions inherit from category to forum channels. Set permissions on the roadmap category for easiest management.

Public vs Private Forums

  • Public mode: @everyone can view forum channels
  • Private mode: Only authorized roles can view forum channels

Configure access mode via /roadmap setup or the settings API.

🏷️ Thread Title Templates

Customize how Discord thread titles are formatted using template strings with placeholders.

Template Format

Templates support two placeholders:

  • {id} - The card ID (e.g., "ROBO-23", "PROJ-123")
  • {title} - The card title

Configuration

Global Default (Plugin Options):

Set a default template for all guilds in your plugin configuration:

// config/plugins/robojs/roadmap.ts
export default {
	provider: {
		/* ... */
	},
	threadTitleTemplate: '[{id}] {title}' // Default for all guilds
}

Per-Guild Override (Settings API):

Override the global default per-guild using the settings API:

import { updateSettings } from '@robojs/roadmap'

// Set custom template for a specific guild
updateSettings(guildId, {
	threadTitleTemplate: '{id} - {title}'
})

Examples

// Default format: "[ROBO-23] Lorem ipsum"
threadTitleTemplate: '[{id}] {title}'

// Alternative format: "ROBO-23 - Lorem ipsum"
threadTitleTemplate: '{id} - {title}'

// Title first: "Lorem ipsum (ROBO-23)"
threadTitleTemplate: '{title} ({id})'

// Just ID: "ROBO-23"
threadTitleTemplate: '{id}'

// Just title (same as undefined/empty)
threadTitleTemplate: '{title}'

Behavior

  • Character Limit: Discord thread names have a 100 character limit. The template system automatically truncates the {title} portion while preserving the template structure (including {id}).
  • Fallback: If no template is provided (or it's empty), thread titles use just the card title.
  • Precedence: Guild-specific settings override the global plugin default.
  • Sync Updates: Running /roadmap sync will update all existing thread titles to match the current template configuration.

Use Cases

  • Consistent formatting: Ensure all threads follow your team's naming convention
  • Easy identification: Include card IDs in thread titles for quick reference
  • Custom branding: Format titles to match your organization's style guide

🧰 Programmatic API

All functionality is available as importable functions for use in custom commands, events, or plugins.

Provider Access

import { getProvider, isProviderReady } from '@robojs/roadmap'

// Check if provider is ready before use
if (isProviderReady()) {
	const provider = getProvider()
	const cards = await provider.fetchCards()
}

Sync Operations

import { syncRoadmap } from '@robojs/roadmap'

// Full sync
const result = await syncRoadmap({
	guild,
	provider
})

// Dry run (preview without changes)
const preview = await syncRoadmap({
	guild,
	provider,
	dryRun: true
})

console.log(`Synced ${result.stats.total} cards`)

Thread Movement on Column Changes

When a card moves to a different column (e.g., from "Backlog" to "In Progress"), the sync engine automatically moves the Discord thread to the corresponding forum channel:

  • New thread created in the target forum with the same name, tags, and content
  • Old thread locked and archived to preserve discussion history
  • Conditional linking: If the old thread had user messages (more than just the starter message), the new thread includes a link at the top: "📜 See X messages in previous discussion: [link]"
  • Thread history tracked in settings for audit trail

This behavior applies to:

  • /roadmap sync operations
  • /roadmap edit command when changing the column
  • REST API PUT /api/roadmap/cards/:guildId/:cardId when updating the column

Example:

import { syncRoadmap } from '@robojs/roadmap'

// When a card moves from "Backlog" to "In Progress"
// The thread will be moved to the In Progress forum
const result = await syncRoadmap({ guild, provider })
// Old thread: locked and archived in Backlog forum
// New thread: active in In Progress forum with link to old thread

Card Operations

import { getProvider } from '@robojs/roadmap'
import type { CreateCardInput, UpdateCardInput } from '@robojs/roadmap'

const provider = getProvider()

// Create a card
const newCard: CreateCardInput = {
	title: 'Add dark mode',
	description: 'Implement dark theme support',
	column: 'Backlog',
	labels: ['feature', 'ui']
}
const result = await provider.createCard(newCard)

// Update a card
const cardId = 'PROJ-123'
const update: UpdateCardInput = {
	title: 'Add dark mode support',
	column: 'In Progress',
	labels: ['feature', 'ui', 'high-priority']
}
await provider.updateCard(cardId, update)

Settings Management

import { getSettings, updateSettings, canUserCreateCards } from '@robojs/roadmap'
import { PermissionFlagsBits } from 'discord.js'

// Get current settings
const settings = getSettings(guildId)

// Update settings
updateSettings(guildId, { isPublic: true })

// Check authorization
const userRoleIds = member.roles.cache.map((r) => r.id)
const isAdmin = member.permissions.has(PermissionFlagsBits.Administrator)
if (canUserCreateCards(guildId, userRoleIds, isAdmin)) {
	// User can create cards
}

Forum Management

import { createOrGetRoadmapCategory, toggleForumAccess, updateForumTagsForColumn } from '@robojs/roadmap'

// Create forum structure
const { category, forums } = await createOrGetRoadmapCategory({
	guild,
	columns
})

// Toggle access mode
await toggleForumAccess(guild, 'public')

// Update tags for a column
const columnName = 'Backlog'
const tagNames = ['feature', 'bug', 'enhancement', 'docs', 'design']
await updateForumTagsForColumn(guild, columnName, tagNames)

[!NOTE] For complete API reference with all parameters, types, and return values, see the auto-generated documentation.

📅 Date Range Filtering

Fetch cards by time range for reports, analytics, or incremental syncs.

Helper Functions

import {
	getCardsFromLastMonth,
	getCardsFromLastWeek,
	getCardsFromLastDays,
	getCardsFromDateRange
} from '@robojs/roadmap'

// Last calendar month
const lastMonth = await getCardsFromLastMonth(provider)

// Last 7 days
const lastWeek = await getCardsFromLastWeek(provider)

// Last 30 days
const last30Days = await getCardsFromLastDays(provider, 30)

// Custom date range
const cards = await getCardsFromDateRange(provider, new Date('2024-01-01'), new Date('2024-01-31'))

// Use 'created' instead of 'updated' (default)
const created = await getCardsFromLastWeek(provider, 'created')

Use Cases

  • Generate reports: Monthly activity summaries
  • Track recent activity: Cards updated in last 24 hours
  • Incremental syncs: Only sync cards changed since last run
  • Analytics: Trend analysis over time

[!NOTE] Date filtering requires provider support. Jira provider fully supports all date range queries via fetchCardsByDateRange().

🌐 REST API (Optional)

💡 Install @robojs/server to enable REST endpoints: npx robo add @robojs/server

The REST API provides HTTP endpoints for external integrations, custom dashboards, and automation.

Response Format

Success:

{
	"success": true,
	"data": {
		/* endpoint-specific data */
	}
}

Error:

{
	"success": false,
	"error": {
		"code": "ERROR_CODE",
		"message": "Human-readable error message",
		"hint": "Optional operator-friendly hint with a suggested fix"
	}
}

Common Error Codes: PROVIDER_NOT_READY, FORUM_NOT_SETUP, GUILD_NOT_FOUND, INVALID_REQUEST, SYNC_FAILED

Key Endpoints

Health & Provider

# Check health status
curl http://localhost:3000/api/roadmap/health

# Get provider info
curl http://localhost:3000/api/roadmap/provider/info

Forum Management

# Create forum structure
curl -X POST http://localhost:3000/api/roadmap/forum/:guildId/setup

# Toggle access mode
curl -X PUT http://localhost:3000/api/roadmap/forum/:guildId/access \
  -H "Content-Type: application/json" \
  -d '{"mode": "public"}'

Sync Operations

# Trigger sync
curl -X POST http://localhost:3000/api/roadmap/sync/:guildId

# Dry run
curl -X POST "http://localhost:3000/api/roadmap/sync/:guildId?dryRun=true"

# Get sync status
curl http://localhost:3000/api/roadmap/sync/:guildId/status

Card Management

# Create card
curl -X POST http://localhost:3000/api/roadmap/cards/:guildId \
  -H "Content-Type: application/json" \
  -d '{"title": "New feature", "column": "Backlog"}'

# Get card details
curl http://localhost:3000/api/roadmap/cards/:guildId/:cardId

⚠️ Security Warning: The card creation endpoint lacks authentication by default. Implement authentication middleware before exposing to the internet.

Use Cases

  • Custom dashboards: Monitor roadmap sync status across multiple guilds
  • External automation: Trigger syncs from CI/CD pipelines or webhooks
  • Monitoring tools: Track provider health and sync statistics
  • Multi-guild management: Manage roadmap configuration across servers

🔌 Custom Providers

Extend the roadmap to sync from GitHub Projects, Linear, or any custom backend.

Minimal implementation checklist

  • Extend RoadmapProvider with your own provider class.
  • Implement the required methods: fetchCards, getColumns, getCard, createCard, updateCard, and getProviderInfo.
  • Optionally implement helpers like getIssueTypes, getLabels, fetchCardsByDateRange, validateConfig, and init if your backend supports them.
  • Register your provider in config/plugins/robojs/roadmap.(mjs\|ts) via the plugin options.

Creating a Provider

import { RoadmapProvider } from '@robojs/roadmap'
import type {
	RoadmapCard,
	RoadmapColumn,
	ProviderInfo,
	CreateCardInput,
	UpdateCardInput,
	CreateCardResult,
	UpdateCardResult
} from '@robojs/roadmap'

class CustomProvider extends RoadmapProvider {
	// Required: Fetch all cards
	async fetchCards(): Promise<RoadmapCard[]> {
		// Implement your logic
		return []
	}

	// Required: Get workflow columns
	async getColumns(): Promise<RoadmapColumn[]> {
		return []
	}

	// Required: Get single card by ID
	async getCard(id: string): Promise<RoadmapCard | null> {
		return null
	}

	// Required: Create a new card
	async createCard(input: CreateCardInput): Promise<CreateCardResult> {
		// TODO: Implement card creation logic
		// Return placeholder for demonstration
		const card: RoadmapCard = {
			id: 'placeholder',
			title: input.title,
			description: input.description,
			column: input.column,
			labels: input.labels || [],
			assignees: input.assignees || [],
			url: 'https://example.com',
			updatedAt: new Date()
		}
		return { card, success: false, message: 'Not implemented' }
	}

	// Required: Update existing card (partial update - only provided fields are changed)
	async updateCard(cardId: string, input: UpdateCardInput): Promise<UpdateCardResult> {
		// TODO: Implement card update logic
		// Return placeholder for demonstration
		const card: RoadmapCard = {
			id: cardId,
			title: input.title || 'Unknown',
			description: input.description || '',
			column: input.column || 'Unknown',
			labels: input.labels || [],
			assignees: input.assignees || [],
			url: 'https://example.com',
			updatedAt: new Date()
		}
		return { card, success: false, message: 'Not implemented' }
	}

	// Required: Provider metadata
	async getProviderInfo(): Promise<ProviderInfo> {
		return {
			name: 'Custom Provider',
			version: '1.0.0',
			capabilities: ['cards', 'columns']
		}
	}

	// Optional: Validate configuration
	async validateConfig(): Promise<void> {
		// Throw error if config is invalid
	}

	// Optional: Initialize provider
	async init(): Promise<void> {
		// Setup connections, auth, etc.
	}

	// Optional: Get issue types
	async getIssueTypes(): Promise<string[]> {
		return ['Task', 'Bug', 'Feature']
	}

	// Optional: Get labels
	async getLabels(): Promise<string[]> {
		return []
	}

	// Optional: Date range filtering
	async fetchCardsByDateRange(
		startDate: Date,
		endDate: Date,
		dateField?: 'created' | 'updated'
	): Promise<RoadmapCard[]> {
		return []
	}
}

Registering a Custom Provider

// config/plugins/robojs/roadmap.mjs
import { CustomProvider } from './providers/custom.js'

export default {
	provider: new CustomProvider({
		// Your provider config
	})
}

💡 See AGENTS.md for detailed implementation guidance, best practices, and architecture details.

🛠️ Troubleshooting

Provider not configured

  • Check environment variables are set correctly
  • Verify Jira URL is accessible
  • Ensure API token has required permissions

Authentication failed

  • Verify Jira email and API token
  • Ensure token hasn't expired
  • Check token has project access

Missing permissions

Sync errors (content too long)

  • Card descriptions are automatically truncated to Discord's limits
  • 2000 characters for forum thread starter messages and message edits

Cards not appearing

  • Run /roadmap setup first to create forum channels
  • Check that provider columns match configured columns
  • Verify JQL query doesn't exclude all cards

Rate limit errors

  • Reduce maxResults setting
  • Increase time between syncs
  • Wait before retrying

Forum not set up

  • Run /roadmap setup before using other commands
  • Check bot has permissions to create channels

Autocomplete not working

  • Wait for cache refresh (default 5 minutes)
  • Restart bot to force cache refresh
  • Check provider is ready via isProviderReady()

How to cancel a running sync

  • Click the "Cancel Sync" button that appears during the sync progress.
  • Only administrators or the user who started the sync can cancel.
  • The sync stops after finishing the current card (typically < 1 second).
  • Partial results are preserved and shown in the cancellation message.
  • Stats reflect cards successfully processed before cancellation.

Sync canceled but shows partial results

  • This is expected behavior — cancellation preserves completed work.
  • The cancellation message shows "Partial sync: X/Y cards processed".
  • Stats (created, updated, archived, errors) reflect processed cards before cancellation.
  • Cards synced before cancellation remain in Discord.
  • Run /roadmap sync again to complete the sync.

Cancel button not working

  • Verify you are an administrator or the user who started the sync.
  • If the sync already completed, the cancel button won't work (sync already ended).
  • If you see a "Not authorized" message, you don't have permission to cancel this sync.
  • If the button appears disabled, the sync may have already finished.
  • Check bot logs for any errors related to cancellation.

Threads moved to different forums

  • This is expected behavior when a card's column changes
  • Old threads are locked and archived to preserve history
  • New threads link to old discussions if they had user activity
  • Thread history is tracked in settings

Old threads are locked

  • Threads are automatically locked when a card moves to a new column
  • This prevents confusion from discussions in multiple places
  • Users can still view locked threads and their history
  • The new thread includes a link to the old discussion

Thread links show "See X messages"

  • Links only appear when the old thread had user messages (not just the starter message)
  • Message count excludes the starter message (e.g., "See 5 messages" means 5 user replies)
  • This helps users find valuable prior discussions
  • Threads with only the starter message are not linked

Thread creation fails

  • Check bot has Create Public Threads permission
  • Verify forum channels exist
  • Ensure forum isn't at Discord's thread limit

💡 Enable debug logging by setting ROBO_LOG_LEVEL=debug in your .env file. Look for logs with roadmap: prefix for detailed diagnostics.

[!HELP] Still stuck? Enable debug logging and check for errors in the console. If you need further assistance, join our Discord community.

🤔 Got questions?

If you need help, hop into our community Discord. We love to chat, and Sage (our resident AI Robo) is always online to assist.

🚀 Community: Join our Discord server