Build on Rails

Build an ATS on Rails

Run your hiring pipeline from your Rails app: candidates, stages, interview notes, and resumes on the models you already have, behind the auth you already trust.

250+ paying companies · 2.7M downloads of the avo gem

01

01 · the hand-built version

What building an ATS by hand actually costs

Hand-rolling an applicant tracking system starts simple and then quietly turns into a second product. The parts that hurt are rarely the ones you scoped.

  • 01

    The pipeline board is never just a status column

    Moving a candidate from Screen to Onsite means recording who moved them, when, and why, then keeping the board fast once you have a few hundred applicants across a dozen roles. That is a real feature, not a CSS afterthought.

  • 02

    Resumes and notes pile up as attachments and free text

    Every candidate carries a resume, a cover letter, and a running log of interview feedback. Wiring up Active Storage, preview links, and a notes timeline by hand is the plumbing you rebuild on every internal tool.

  • 03

    Recruiters and hiring managers should not see the same thing

    A hiring manager sees their own reqs, a recruiter sees everything, a coordinator can schedule but not reject. Get that wrong and you leak salary expectations and candidate feedback to the wrong people.

  • 04

    GDPR deletion is a promise you have to keep

    Candidates ask to be removed, and deleting the row is not enough when a resume lives in object storage and feedback is scattered across tables. You need a deletion path you can actually run and prove.

02

02 · the Avo version

Your models are already the hard part. Avo is the rest.

Avo turns the models you would build anyway, Candidate, Job, Stage, and InterviewNote, into a working back office. You point resources at your existing tables, and hiring runs on them the same afternoon. No separate service, no data leaving your app.

A pipeline your team can actually work

Show each candidate's stage as a colored badge and let recruiters move them with a select field, or wire a custom action like Advance to onsite that also stamps who did it. Filter the Index view by role, stage, or recruiter to answer who is stuck where in one click.

Resumes and interview notes in one place

A file field on Candidate handles resume and cover-letter uploads through Active Storage, and a has_many of interview notes gives you a feedback timeline per candidate without building an attachment pipeline from scratch.

Roles enforced by policies, not by hiding buttons

Pundit policies decide who sees salary bands, who can reject, and who only schedules. Because Avo reads the same policies your app already uses, a coordinator literally cannot open a candidate they should not.

Email touchpoints logged against the candidate

Model each outreach as a record and expose it as a has_many, so we emailed them twice, no reply becomes data on the page instead of tribal knowledge in someone's inbox.

Deletion you can run and prove

A destroy action on Candidate, guarded by a policy, with your model cascading to attachments and notes, gives you a clean, one-step removal you can point to when a candidate asks, instead of a manual hunt across tables.

A Candidate resource with pipeline stage, resume, and interview notes:

class Avo::Resources::Candidate < Avo::BaseResource
  self.title = :full_name
  self.includes = [:job, :interview_notes]
  self.search = {
    query: -> { query.ransack(full_name_cont: params[:q], email_cont: params[:q], m: "or").result(distinct: false) }
  }

  def fields
    field :id, as: :id
    field :full_name, as: :text, required: true
    field :email, as: :text
    field :job, as: :belongs_to
    field :stage, as: :badge, only_on: [:index, :show], options: {
      info: "applied",
      warning: %w[screening onsite],
      success: "hired",
      danger: "rejected"
    }
    field :stage, as: :select, hide_on: [:index, :show],
      options: { "Applied": "applied", "Screening": "screening", "Onsite": "onsite", "Hired": "hired", "Rejected": "rejected" }
    field :resume, as: :file, accept: "application/pdf"
    field :interview_notes, as: :has_many
  end
end
03

03 · the timeline

From first resource to the team running on it

Hour one

Generate resources for the models you already have with bin/rails generate avo:resource candidate, and you are looking at a real pipeline populated with real applicants, no seed-data theater.

Day one

Drop your Pundit policies in so recruiters and hiring managers see only what they should, put it behind your existing authentication, and let the recruiting team start moving live candidates through stages.

End of the week

Interview notes, resume uploads, email touchpoints, and a policy-guarded deletion action are all in place, and the spreadsheet you were tracking candidates in is retired.

Prefer to prompt?

Hand this to Claude Code, Cursor, or whatever agent you build with, and review what it ships like any other pull request.

Scaffold a brand-new applicant tracking system as a fresh Ruby on Rails app with Avo 4 as the management UI (https://avohq.io, docs at https://docs.avohq.io/4.0/). Nothing exists yet: no repo, no database, no models, so build it from zero.

1. Run rails new to generate the application with sensible defaults, then set up sign-in with Rails' built-in authentication generator (bin/rails generate authentication). Add the avo gem to the Gemfile, bundle, run bin/rails generate avo:install, and mount Avo at /avo so it sits behind the authentication you just generated.
2. Work out the data model from first principles before you write a single migration. Reason about what recruiting genuinely requires: the people applying, the openings they apply to, where each person sits in a hiring pipeline, and the feedback that accumulates as they move through it. Decide which of those are their own tables and which are attributes, whether a pipeline stage is best expressed as an enum on a record or as its own table with a history of moves, and how everything associates. Sketch the full schema, then generate the migrations and models to match. Treat resumes and cover letters as Active Storage attachments and outreach touchpoints as records you can list per candidate. Nothing here is a fixed list to copy; it is the ground your design has to cover.
3. Generate an Avo resource per model with bin/rails generate avo:resource. On the record recruiters open most, set self.title to its most recognizable attribute, give self.search a ransack query across the fields people are looked up by such as name and email, and set self.includes to preload the associations the Index and Show views render. Match field types to the columns you chose: a badge for the pipeline stage on Index and Show with a select for editing it on the form, a file field for the resume attachment, belongs_to for the opening, and has_many for the feedback timeline.
4. Express the pipeline's verbs as Avo Actions. Because the app is new, put the real work in model methods or small service objects and have each action call them: an advance-stage action moves the record forward and stamps who did it and when, a reject action records the reason. Add filters and scopes over the stages and associations you modeled so a recruiter can narrow the list to one opening, one stage, or one owner and see who is stuck where.
5. Build a dashboard that surfaces what a hiring team watches: counts per stage, open roles, and recent activity (https://docs.avohq.io/4.0/dashboards.html). Add filters and scopes to the resources to back it.
6. Authorize with Pundit policies covering real hiring roles so a recruiter sees the whole pipeline, a hiring manager sees only the openings they own, a coordinator can schedule without rejecting, and sensitive fields like compensation render only for the roles cleared to read them (https://docs.avohq.io/4.0/authorization.html). Add a policy-guarded destroy action on the candidate that cascades to its attachments and feedback, so a GDPR deletion request is one provable step.
7. Seed the database with realistic sample data: several openings, candidates spread across every pipeline stage, resumes attached, and a few rounds of interview feedback, so every screen and dashboard has something real to show.
8. Boot the app, open /avo, and move a seeded candidate forward a stage. Confirm the move is stamped, the dashboard counts shift, and a lower-privileged role is blocked from the fields and actions its policy denies.

04 · start today

Stand up an ATS this week

Avo is per app with unlimited users, so the whole team works from it without the bill scaling with headcount. When you hit an edge Avo doesn't cover, you drop to plain Rails instead of filing a feature request.

More things teams run from their Rails app

Frequently asked questions

Can I build an applicant tracking system with Ruby on Rails?
Yes. An ATS is candidates, jobs, pipeline stages, and notes, which are ordinary Rails models with associations. Avo turns those models into a working recruiting back office so you skip the admin UI and keep the domain logic. Avo runs inside your Rails app, so there is no separate service to host and your candidate data never leaves your database.
Do I have to move my candidate data into another tool?
No. Avo reads and writes your existing tables directly. Your Candidate, Job, and InterviewNote models stay where they are, and Avo builds the Index, Show, and Edit views on top of them. When you outgrow a default, you drop to plain Rails instead of filing a feature request and waiting.
How do I control who sees which candidates and salary details?
Authorization is handled with Pundit policies, the same ones your Rails app already uses. A CandidatePolicy decides who can view a candidate, who can reject, and which fields such as salary expectations are visible per role. See the authorization docs.
Can candidates request deletion under GDPR?
Yes. You can expose a deletion action, guarded by a policy, that removes the candidate along with their resume attachments and interview notes in one step. A please-delete-my-data request becomes a button you can run and prove, not a manual hunt across tables.
How long does it take to get a working ATS?
You can have a real pipeline on your own data in an afternoon: generate resources for your existing models and you are moving candidates through stages the same day. Putting it behind your authentication and policies is a day-one task, not a multi-sprint project.
What does Avo cost for a recruiting team?
Avo 4 is priced per app with unlimited users, so adding every recruiter, coordinator, and hiring manager does not change the bill. Over 250 paying companies run on Avo, and the avo gem has 2.7M downloads.