Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Discourse
https://github.com/discourse/discourse
Admin
Discourse is a 100% open-source community platform offering robust features for discussion,
...
Tokens:
182,032
Snippets:
2,583
Trust Score:
9.7
Update:
2 days ago
Context
Skills
Chat
Benchmark
72.1
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Discourse Discourse is a 100% open-source community platform built with Ruby on Rails (backend API) and Ember.js (frontend). It provides discussion forums with real-time chat, customizable themes, and an extensible plugin system. The platform supports topics, posts, categories, user management, groups, badges, and advanced search capabilities, all exposed through a comprehensive REST API. The architecture follows a standard Rails MVC pattern with controllers handling JSON API responses, services encapsulating business logic, and models managing data persistence via PostgreSQL. The frontend uses Ember.js components that communicate with the Rails API. Discourse offers extensive customization through its Plugin API (JavaScript) for frontend modifications and Ruby plugins for backend extensions, making it highly adaptable for various community needs. ## REST API - Topics The Topics API allows creating, reading, updating, and managing discussion topics. Topics are the primary content containers in Discourse, each containing a title, category, and collection of posts. ```bash # Create a new topic curl -X POST "https://discourse.example.com/posts.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "title": "Welcome to our community", "raw": "This is the content of the first post in the topic.", "category": 5, "tags": [{"name": "welcome"}, {"name": "announcement"}] }' # Get a topic by ID curl "https://discourse.example.com/t/123.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Update topic title/category curl -X PUT "https://discourse.example.com/t/123.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "topic_id": 123, "title": "Updated Topic Title", "category_id": 10, "tags": [{"id": 1, "name": "updated"}] }' # Close/Open a topic curl -X PUT "https://discourse.example.com/t/123/status.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{"status": "closed", "enabled": true}' # Delete a topic curl -X DELETE "https://discourse.example.com/t/123.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Set topic timer (auto-close) curl -X POST "https://discourse.example.com/t/123/timer.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{"status_type": "close", "time": "2024-12-31T23:59:59Z"}' ``` ## REST API - Posts The Posts API manages individual posts within topics. Posts contain the actual content of discussions and support features like editing, deletion, recovery, and revision history. ```bash # Create a reply to a topic curl -X POST "https://discourse.example.com/posts.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "topic_id": 123, "raw": "This is a reply to the topic.", "reply_to_post_number": 1 }' # Get a single post curl "https://discourse.example.com/posts/456.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Update/Edit a post curl -X PUT "https://discourse.example.com/posts/456.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "post": { "raw": "Updated post content with edits.", "edit_reason": "Fixed typos" } }' # Delete a post curl -X DELETE "https://discourse.example.com/posts/456.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Recover a deleted post curl -X PUT "https://discourse.example.com/posts/456/recover.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Get post revisions curl "https://discourse.example.com/posts/456/revisions/2.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Get latest posts feed curl "https://discourse.example.com/posts.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" ``` ## REST API - Categories The Categories API manages topic categories which organize discussions. Categories support permissions, subcategories, custom colors, and various display settings. ```bash # List all categories curl "https://discourse.example.com/categories.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Get a specific category by slug curl "https://discourse.example.com/c/general/find_by_slug.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Create a new category curl -X POST "https://discourse.example.com/categories.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "name": "Announcements", "color": "0088CC", "text_color": "FFFFFF", "description": "Official announcements from the team", "permissions": {"everyone": 1}, "parent_category_id": null }' # Update a category curl -X PUT "https://discourse.example.com/categories/5.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Category Name", "color": "FF5733", "slug": "updated-slug" }' # Delete a category (must be empty) curl -X DELETE "https://discourse.example.com/categories/5.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Search categories curl -X POST "https://discourse.example.com/categories/search.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{"term": "general", "limit": 10}' ``` ## REST API - Users The Users API handles user account management including creation, profile updates, authentication, and user-specific data retrieval. ```bash # Get user by username curl "https://discourse.example.com/u/johndoe.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Create a new user curl -X POST "https://discourse.example.com/users.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe", "username": "johndoe", "email": "john@example.com", "password": "securepassword123", "active": true, "approved": true }' # Update user profile curl -X PUT "https://discourse.example.com/u/johndoe.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "name": "John Smith", "bio_raw": "Hello, I am a community member!" }' # Get user's summary/stats curl "https://discourse.example.com/u/johndoe/summary.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # List user's bookmarks curl "https://discourse.example.com/u/johndoe/bookmarks.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: johndoe" # Get user cards (multiple users) curl "https://discourse.example.com/u/cards.json?user_ids=1,2,3" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" ``` ## REST API - Groups The Groups API manages user groups for permissions, messaging, and community organization. Groups can be automatic (trust level based) or custom. ```bash # List all groups curl "https://discourse.example.com/groups.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Get a specific group curl "https://discourse.example.com/groups/moderators.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Create a new group (admin only) curl -X POST "https://discourse.example.com/admin/groups.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{ "group": { "name": "vip_members", "full_name": "VIP Members", "visibility_level": 0, "mentionable_level": 0, "messageable_level": 0 } }' # Add members to a group curl -X PUT "https://discourse.example.com/groups/5/members.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{"usernames": "johndoe,janedoe"}' # Remove member from group curl -X DELETE "https://discourse.example.com/groups/5/members.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" \ -H "Content-Type: application/json" \ -d '{"username": "johndoe"}' # Get group members curl "https://discourse.example.com/groups/moderators/members.json" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" ``` ## REST API - Search The Search API provides full-text search across topics, posts, users, categories, and tags with support for advanced search operators. ```bash # Basic search curl "https://discourse.example.com/search.json?q=ruby%20programming" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Search with filters curl "https://discourse.example.com/search.json?q=ruby%20category:programming%20status:open" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Header search (quick search) curl "https://discourse.example.com/search/query.json?term=help" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Search within a specific category curl "https://discourse.example.com/search.json?q=help&search_context[type]=category&search_context[id]=5" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Search within a user's posts curl "https://discourse.example.com/search.json?q=@johndoe%20tutorial" \ -H "Api-Key: YOUR_API_KEY" \ -H "Api-Username: system" # Advanced search operators: # category:name - filter by category # #tag - filter by tag # @username - filter by author # status:open/closed/archived # before:YYYY-MM-DD / after:YYYY-MM-DD # in:title/first/pinned/wiki # order:latest/likes/views ``` ## JavaScript Plugin API - Basic Setup The Plugin API allows themes and plugins to customize Discourse's frontend behavior. Plugins are initialized through API initializers that receive the `api` object with all customization methods. ```javascript // plugins/my-plugin/assets/javascripts/discourse/api-initializers/init-my-plugin.gjs import { apiInitializer } from "discourse/lib/api"; export default apiInitializer((api) => { // Get current user const currentUser = api.getCurrentUser(); console.log("Current user:", currentUser?.username); // Modify a class (add new behavior to existing components) api.modifyClass("controller:composer", { pluginId: "my-plugin", // Override or extend actions actions: { customAction() { console.log("Custom action triggered!"); } } }); // Listen for page changes (useful for analytics) api.onPageChange((url, title) => { console.log(`Page changed to: ${url} - ${title}`); }); // Listen for application events api.onAppEvent("topic:created", (topic) => { console.log("New topic created:", topic.id); }); }); ``` ## JavaScript Plugin API - Value Transformers Value transformers allow modifying data values at specific points in the application without replacing entire components. They're the preferred way to customize UI elements. ```javascript import { apiInitializer } from "discourse/lib/api"; export default apiInitializer((api) => { // Transform a value - modify post menu buttons api.registerValueTransformer("post-menu-buttons", ({ value, context }) => { const { post } = context; // Add a custom button to posts if (post.user_id === api.getCurrentUser()?.id) { value.push({ id: "my-custom-button", icon: "star", label: "my_plugin.favorite", action: "favoritePost" }); } return value; }); // Transform poster name icons api.registerValueTransformer("poster-name-icons", ({ value, context }) => { const { post } = context; if (post.user_custom_fields?.is_vip) { return [...value, { icon: "crown", className: "vip-badge", title: "VIP Member" }]; } return value; }); // Add tracked properties for automatic UI updates api.addTrackedPostProperties("custom_field", "special_status"); api.addTrackedTopicProperties("custom_topic_field"); }); ``` ## JavaScript Plugin API - Behavior Transformers Behavior transformers allow intercepting and modifying application behavior at specific points. They can run code before, after, or instead of default behavior. ```javascript import { apiInitializer } from "discourse/lib/api"; export default apiInitializer((api) => { // Intercept behavior - perform action before default behavior api.registerBehaviorTransformer("example-transformer", ({ next, context }) => { console.log("Running before default behavior", context); const result = next(); // Execute default behavior console.log("Running after default behavior"); return result; }); // Conditionally abort default behavior api.registerBehaviorTransformer("post-submit", ({ next, context }) => { const { composer } = context; // Prevent submission under certain conditions if (composer.raw?.includes("forbidden_word")) { alert("Your post contains forbidden content!"); return; // Don't call next() - aborts default behavior } return next(); // Continue with normal submission }); // Add behavior transformer name (must be in pre-initializer) // This registers a new transformer point that plugins can hook into api.addBehaviorTransformerName("my-custom-behavior-point"); }); ``` ## JavaScript Plugin API - UI Customization The Plugin API provides methods to customize the user interface including toolbar buttons, icons, decorators, and rendered content. ```javascript import { apiInitializer } from "discourse/lib/api"; import MyCustomComponent from "../components/my-component"; export default apiInitializer((api) => { // Add toolbar button to composer api.onToolbarCreate((toolbar) => { toolbar.addButton({ id: "insert-template", group: "extras", icon: "file-alt", action: "insertTemplate", title: "my_plugin.insert_template" }); }); // Add option to composer popup menu api.addComposerToolbarPopupMenuOption({ icon: "magic", label: "my_plugin.format_text", action: (toolbarEvent) => { toolbarEvent.applySurround("**", "**"); }, condition: (composer) => composer.editingPost }); // Decorate cooked (rendered) post content api.decorateCookedElement((element, decoratorHelper) => { // Add click handlers to custom elements element.querySelectorAll(".custom-element").forEach((el) => { el.addEventListener("click", () => { console.log("Custom element clicked"); }); }); // Return cleanup function (optional) return () => { element.querySelectorAll(".custom-element").forEach((el) => { el.removeEventListener("click", () => {}); }); }; }, { onlyStream: true }); // Replace icons globally api.replaceIcon("heart", "thumbs-up"); // Render a component in a plugin outlet api.renderInOutlet("user-profile-primary", MyCustomComponent); // Add keyboard shortcut api.addKeyboardShortcut("ctrl+shift+t", () => { console.log("Custom shortcut triggered!"); }, { global: true }); }); ``` ## JavaScript Plugin API - Admin & Navigation The Plugin API allows adding custom admin interfaces, sidebar sections, and navigation items. ```javascript import { apiInitializer } from "discourse/lib/api"; export default apiInitializer((api) => { // Add admin menu button for posts api.addPostAdminMenuButton((post) => { return { icon: "flag", className: "custom-flag-button", label: "my_plugin.custom_flag", action: () => { console.log("Custom flag action for post:", post.id); } }; }); // Add admin menu button for topics api.addTopicAdminMenuButton((topic) => { return { icon: "archive", className: "archive-topic", label: "my_plugin.archive_topic", action: () => { // Perform archive action } }; }); // Add navigation item api.addNavigationBarItem({ name: "my-custom-page", displayName: "Custom Page", href: "/custom-page" }); // Add sidebar section api.addSidebarSection((BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => { return class extends BaseCustomSidebarSection { get name() { return "my-plugin-section"; } get title() { return "My Plugin"; } get links() { return [ new (class extends BaseCustomSidebarSectionLink { get name() { return "custom-link"; } get title() { return "Custom Link"; } get href() { return "/custom-page"; } get icon() { return "star"; } })() ]; } }; }); }); ``` ## Ruby Plugin Development - Basic Structure Ruby plugins extend Discourse's backend functionality. A plugin requires a `plugin.rb` manifest file and can add controllers, models, jobs, and modify core behavior through events and modifiers. ```ruby # plugins/my-plugin/plugin.rb # name: my-plugin # about: A custom plugin for Discourse # version: 1.0.0 # authors: Your Name # url: https://github.com/yourname/my-plugin # required_version: 2.7.0 enabled_site_setting :my_plugin_enabled # Register plugin assets register_asset "stylesheets/my-plugin.scss" # Add site settings after_initialize do # Add custom fields to models Topic.register_custom_field_type("my_custom_field", :string) Post.register_custom_field_type("special_status", :boolean) User.register_custom_field_type("user_preference", :integer) # Whitelist custom fields for serialization add_to_serializer(:topic_view, :my_custom_field) do object.topic.custom_fields["my_custom_field"] end add_to_serializer(:post, :special_status) do object.custom_fields["special_status"] end # Register event listeners on(:post_created) do |post, opts, user| if post.raw.include?("#important") post.custom_fields["special_status"] = true post.save_custom_fields end end on(:topic_created) do |topic, opts, user| # Perform actions when topic is created Jobs.enqueue(:process_new_topic, topic_id: topic.id) end # Add permitted parameters to controllers PostsController.class_eval do def permitted_create_params super + [:my_custom_param] end end end ``` ## Ruby Plugin Development - Controllers and Routes Plugins can add custom controllers and routes to handle API endpoints and web pages. ```ruby # plugins/my-plugin/plugin.rb after_initialize do # Define the controller module ::MyPlugin class Engine < ::Rails::Engine engine_name "my_plugin" isolate_namespace MyPlugin end class MyController < ::ApplicationController requires_login def index items = MyPlugin::Item.where(user_id: current_user.id) render json: { items: items } end def create params.require(:item).permit(:name, :description) item = MyPlugin::Item.create!( user_id: current_user.id, name: params[:item][:name], description: params[:item][:description] ) render json: { item: item } end def show item = MyPlugin::Item.find(params[:id]) guardian.ensure_can_see!(item) render json: { item: item } end def destroy item = MyPlugin::Item.find(params[:id]) guardian.ensure_can_edit!(item) item.destroy! render json: success_json end end end # Add routes Discourse::Application.routes.append do mount ::MyPlugin::Engine, at: "/my-plugin" end MyPlugin::Engine.routes.draw do get "/" => "my#index" post "/" => "my#create" get "/:id" => "my#show" delete "/:id" => "my#destroy" end end ``` ## Ruby Plugin Development - Site Settings Plugins can define custom site settings that administrators can configure through the admin panel. ```yaml # plugins/my-plugin/config/settings.yml my_plugin: my_plugin_enabled: default: false client: true my_plugin_api_key: default: "" secret: true my_plugin_max_items: default: 10 min: 1 max: 100 my_plugin_feature_mode: default: "basic" type: enum choices: - basic - advanced - expert my_plugin_allowed_groups: default: "" type: group_list my_plugin_welcome_message: default: "Welcome to our community!" type: text my_plugin_categories: default: "" type: category_list ``` ```ruby # Using settings in plugin code after_initialize do if SiteSetting.my_plugin_enabled max_items = SiteSetting.my_plugin_max_items allowed_groups = SiteSetting.my_plugin_allowed_groups.split("|") # Check if user is in allowed groups def user_allowed?(user) return true if allowed_groups.empty? (user.groups.pluck(:name) & allowed_groups).any? end end end ``` ## Theme Settings Themes can define settings that site administrators can customize through the UI without modifying code. Settings are defined in YAML and accessible in JavaScript and CSS. ```yaml # themes/my-theme/settings.yml # Simple boolean setting show_banner: true # String setting banner_text: "Welcome to our community!" # Integer with limits max_featured_topics: type: integer default: 5 min: 1 max: 20 # Float setting content_opacity: type: float default: 0.9 # Enum (dropdown) setting theme_style: type: enum default: modern choices: - classic - modern - minimal # List setting (user can add/remove items) featured_categories: type: list default: "general|announcements" # Upload setting (for images) custom_logo: type: upload # Multi-language description primary_color: type: string default: "#0088CC" description: en: "The primary brand color for the theme" es: "El color principal de la marca" fr: "La couleur principale de la marque" ``` ```javascript // Accessing settings in JavaScript // themes/my-theme/javascripts/discourse/api-initializers/init-theme.gjs import { apiInitializer } from "discourse/lib/api"; export default apiInitializer((api) => { // Settings are available globally as `settings` object console.log("Theme settings:", settings); if (settings.show_banner) { console.log("Banner text:", settings.banner_text); } const maxTopics = settings.max_featured_topics; const themeStyle = settings.theme_style; }); ``` ```scss // Accessing settings in SCSS // themes/my-theme/common/common.scss // Settings are available as SCSS variables html { --primary-color: #{$primary-color}; --content-opacity: #{$content-opacity}; } .custom-banner { background-color: $primary-color; opacity: $content-opacity; } @if $show-banner { .banner-container { display: block; } } ``` ## Discourse Service Objects Service objects encapsulate complex business logic and provide a consistent pattern for operations that span multiple models. They use a contract-based approach with validation and step-based execution. ```ruby # Example service object pattern used in Discourse # app/services/example/create_item.rb module Example class CreateItem include Service::Base # Define the contract (input validation) params do attribute :name, :string attribute :description, :string attribute :category_id, :integer validates :name, presence: true, length: { maximum: 100 } validates :category_id, presence: true end # Define dependencies model :category model :user # Define policies (authorization) policy :can_create # Define the transaction transaction do step :create_item step :notify_users step :log_action end private def fetch_category(params:) Category.find_by(id: params.category_id) end def fetch_user guardian.user end def can_create(guardian:, category:) guardian.can_create_topic_on_category?(category) end def create_item(params:, category:, user:) context[:item] = Item.create!( name: params.name, description: params.description, category: category, user: user ) end def notify_users(item:) # Send notifications end def log_action(item:, user:) StaffActionLogger.new(user).log_item_creation(item) end end end # Using the service in a controller class ItemsController < ApplicationController def create Example::CreateItem.call(guardian:, params: item_params) do on_success { |item:| render json: { item: item } } on_failed_contract { |contract| render_json_error(contract.errors) } on_failed_policy(:can_create) { raise Discourse::InvalidAccess } on_failure { render json: failed_json } end end end ``` Discourse is primarily used to build online community forums, providing features like threaded discussions, real-time chat, user trust levels, and moderation tools. Common integrations include SSO authentication with external systems, webhook notifications for external services, and embedding Discourse comments into external websites. The platform excels at creating communities for open-source projects, customer support forums, internal team discussions, and knowledge bases. The extensibility model allows developers to create themes for visual customization, plugins for adding new features, and integrations with external services through the REST API. The JavaScript Plugin API enables frontend modifications without forking core code, while Ruby plugins can add new database models, background jobs, and server-side logic. This architecture makes Discourse suitable for both simple community forums and complex, highly-customized platforms with unique requirements.