Skip to main content
This tutorial walks you through building a simple Redmine plugin called Polls that adds a basic polling feature to projects. By the end you will have a working plugin with a controller, views, routes, a menu item, and a database migration.
All commands run from your Redmine root directory. Your Redmine installation must be working before you begin.

Prerequisites

  • Redmine 5.0 or higher installed and running
  • Bundler and Ruby available on your $PATH
  • Basic familiarity with Ruby on Rails

Step 1 — Generate the plugin scaffold

1

Run the plugin generator

Redmine includes a Rails generator that creates the full directory structure and required starter files.
bundle exec rails generate redmine_plugin polls
This creates the following layout under plugins/polls/:
plugins/polls/
├── init.rb
├── README.rdoc
├── app/
│   ├── controllers/
│   ├── helpers/
│   ├── models/
│   └── views/
├── assets/
│   ├── images/
│   ├── javascripts/
│   └── stylesheets/
├── config/
│   ├── locales/
│   │   └── en.yml
│   └── routes.rb
├── db/
│   └── migrate/
└── test/
2

Review the generated init.rb

The generator creates a minimal init.rb with placeholder values:
# plugins/polls/init.rb
Redmine::Plugin.register :polls do
  name 'Polls plugin'
  author 'Author name'
  description 'This is a plugin for Redmine'
  version '0.0.1'
  url 'http://example.com/path/to/plugin'
  author_url 'http://example.com/about'
end
Update the metadata with your own values.

Step 2 — Create a database migration

1

Generate the migration

bundle exec rails generate redmine_plugin_migration polls create_polls
Open the generated migration file in plugins/polls/db/migrate/ and define the table:
class CreatePolls < ActiveRecord::Migration[7.0]
  def change
    create_table :polls do |t|
      t.string  :question, null: false
      t.integer :yes,      default: 0
      t.integer :no,       default: 0
    end
  end
end
2

Run the migration

bundle exec rake redmine:plugins:migrate
To migrate only your plugin: bundle exec rake redmine:plugins:migrate NAME=polls

Step 3 — Create the model

Create plugins/polls/app/models/poll.rb:
class Poll < ActiveRecord::Base
  validates :question, presence: true

  def vote(answer)
    increment answer == 'yes' ? :yes : :no
    save
  end
end

Step 4 — Generate a controller

1

Run the controller generator

bundle exec rails generate redmine_plugin_controller polls polls index vote
This creates:
  • plugins/polls/app/controllers/polls_controller.rb
  • plugins/polls/app/helpers/polls_helper.rb
  • plugins/polls/app/views/polls/index.html.erb
  • plugins/polls/app/views/polls/vote.html.erb
  • plugins/polls/test/functional/polls_controller_test.rb
2

Implement the controller

Edit plugins/polls/app/controllers/polls_controller.rb:
class PollsController < ApplicationController
  def index
    @polls = Poll.all
  end

  def vote
    @poll = Poll.find(params[:id])
    @poll.vote(params[:answer])
    redirect_to polls_path
  end
end

Step 5 — Create views

Edit plugins/polls/app/views/polls/index.html.erb:
<h2>Polls</h2>

<% @polls.each do |poll| %>
  <p>
    <%= poll.question %><br />
    <%= link_to "Yes (#{poll.yes})", vote_poll_path(poll, answer: 'yes'), method: :post %>
    &nbsp;
    <%= link_to "No (#{poll.no})",  vote_poll_path(poll, answer: 'no'),  method: :post %>
  </p>
<% end %>

Step 6 — Add routes

Edit plugins/polls/config/routes.rb:
# Plugin's routes
# See: http://guides.rubyonrails.org/routing.html

resources :polls, only: [:index] do
  member do
    post :vote
  end
end

Step 7 — Add a menu item

Update plugins/polls/init.rb to add an application-level menu entry:
Redmine::Plugin.register :polls do
  name        'Polls plugin'
  author      'Jane Smith'
  description 'Adds polls to Redmine'
  version     '0.0.1'

  requires_redmine version_or_higher: '5.0.0'

  menu :application_menu, :polls,
       { controller: 'polls', action: 'index' },
       caption: 'Polls'
end
To add the item to the project sidebar instead, use :project_menu. Redmine automatically appends the project id parameter to project menu URLs:
menu :project_menu, :polls,
     { controller: 'polls', action: 'index' },
     caption: 'Polls',
     after: :activity

Step 8 — Add permissions

Wrap your menu registration with a project_module block to let project administrators enable or disable the feature per project:
Redmine::Plugin.register :polls do
  # ... metadata ...

  project_module :polls do
    permission :view_polls, { polls: [:index] },       public: true
    permission :vote_polls, { polls: [:vote] },        require: :loggedin
  end

  menu :project_menu, :polls,
       { controller: 'polls', action: 'index' },
       caption: 'Polls'
end
Then protect your controller actions:
class PollsController < ApplicationController
  before_action :find_project
  before_action { authorize }

  def index
    @polls = Poll.all
  end

  def vote
    @poll = Poll.find(params[:id])
    @poll.vote(params[:answer])
    redirect_to polls_path
  end

  private

  def find_project
    @project = Project.find(params[:project_id])
  rescue ActiveRecord::RecordNotFound
    render_404
  end
end

Step 9 — Restart and verify

bundle exec rails server
Navigate to Administration > Plugins to confirm your plugin appears in the list. Then visit the Polls menu item.
During development, restart the server after any change to init.rb. View and controller changes are picked up automatically in development mode.

Next steps

Hook system

Respond to Redmine events and inject content into existing views.

Plugin settings

Add a configuration page so administrators can tune your plugin.