← Back to recipes

Create volunteer rotas that work for everyone

service-deliveryadvancedproven

The problem

You've got 30 volunteers, 50 shifts to fill, and everyone has different availability, preferences, and skills. Some shifts need specific qualifications. You want to be fair about who gets the good shifts. Building the rota takes hours and someone always ends up unhappy or unavailable. There must be a better way.

The solution

You'll use a constraint solver to generate rotas automatically. You tell it the rules (who's available when, what skills each shift needs, how to be fair) and it finds a rota that satisfies all the constraints. It's the same approach airlines use for crew scheduling, just applied to your volunteer team. Google's free OR-Tools library does the heavy lifting.

What you get

A complete rota that respects everyone's availability, matches skills to shifts, and spreads the workload fairly. You can generate alternatives if you don't like the first one, or add more constraints and re-run. The output is a spreadsheet you can share with your team.

Before you start

  • A list of volunteers with their availability (days/times they can work)
  • A list of shifts that need filling with any skill requirements
  • Basic Python knowledge, or willingness to adapt example code with AI help
  • Ideally, someone who can maintain the system once it's set up
  • DATA PROTECTION: Handle volunteer data (names, contact info, availability) according to your data policy. If uploading to Google Colab, ensure your organisation permits cloud processing of this data.

When to use this

  • You've got more than 20 volunteers and complex constraints
  • Building rotas manually takes hours and still has problems
  • You need to balance fairness with operational requirements
  • You're scheduling recurring shifts, not one-off events

When not to use this

  • You've only got a handful of volunteers. A simple spreadsheet is easier
  • Shifts don't have complex constraints. Use Doodle or When2meet instead
  • Nobody in your team is comfortable maintaining code. Consider a commercial tool
  • Your scheduling is genuinely unpredictable week to week. This works better for regular patterns

Steps

  1. 1

    Map out your constraints

    Before touching any code, write down all the rules your rota needs to follow. Hard constraints are things that must be true (volunteers only work when they're available, first aiders on every shift). Soft constraints are preferences (spread weekend shifts fairly, respect seniority for popular shifts). Be specific.

  2. 2

    Structure your data

    Create two spreadsheets. One lists volunteers with their availability (which days/times they can work) and skills (first aid, DBS checked, etc.). The other lists shifts with their requirements (date, time, skills needed, number of volunteers). Export both as CSVs.

  3. 3

    Set up the basic solver

    Open Google Colab and install OR-Tools. The example code below shows how to set up a basic scheduling problem. Start simple: just match volunteers to shifts based on availability. Get this working before adding complexity.

  4. 4

    Add your hard constraints

    Add constraints one at a time. First, volunteers can only work shifts they're available for. Then, shifts with skill requirements only get volunteers with those skills. Test after each addition. If the solver returns "no solution", your constraints are too tight.

  5. 5

    Add fairness rules

    Define what fair means for you. Maybe everyone should work a similar number of shifts, or weekend shifts should be spread evenly. These are often "soft" constraints: the solver tries to satisfy them but can relax them if needed. The example shows how to do this.

  6. 6

    Generate and review rotas

    Run the solver and export the result. Review it for anything that looks wrong. You can ask the solver for multiple different solutions if you want options. If something's not right, add or adjust constraints and re-run.

  7. 7

    Build it into your routine(optional)

    Once you're happy with the setup, make it easy to run regularly. Keep your volunteer availability up to date in the spreadsheet, update the shifts each period, and re-run the solver. The hard work is upfront; ongoing maintenance is minimal.

Example code

Basic volunteer scheduling with OR-Tools

This sets up a simple scheduling problem. Volunteers are assigned to shifts respecting their availability. It's a starting point you can extend with more constraints.

# Install OR-Tools first: !pip install ortools

from ortools.sat.python import cp_model
import pandas as pd

# Load your data
volunteers = pd.read_csv('volunteers.csv')  # name, mon_am, mon_pm, tue_am, ...
shifts = pd.read_csv('shifts.csv')  # shift_id, day, time, volunteers_needed

model = cp_model.CpModel()

# Create a variable for each (volunteer, shift) pair
# x[v, s] = 1 if volunteer v is assigned to shift s
assignments = {}
for v_idx, volunteer in volunteers.iterrows():
    for s_idx, shift in shifts.iterrows():
        var_name = f"assign_{volunteer['name']}_{shift['shift_id']}"
        assignments[(v_idx, s_idx)] = model.NewBoolVar(var_name)

# Constraint: only assign volunteers to shifts they're available for
for v_idx, volunteer in volunteers.iterrows():
    for s_idx, shift in shifts.iterrows():
        # Check if volunteer is available for this shift's day/time
        availability_col = f"{shift['day']}_{shift['time']}"
        if availability_col in volunteer and volunteer[availability_col] == 0:
            model.Add(assignments[(v_idx, s_idx)] == 0)

# Constraint: each shift gets exactly the right number of volunteers
for s_idx, shift in shifts.iterrows():
    model.Add(
        sum(assignments[(v_idx, s_idx)] for v_idx in range(len(volunteers)))
        == shift['volunteers_needed']
    )

# Constraint: each volunteer works at most one shift per day
for v_idx in range(len(volunteers)):
    for day in ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']:
        day_shifts = [s_idx for s_idx, s in shifts.iterrows() if s['day'] == day]
        model.Add(sum(assignments[(v_idx, s_idx)] for s_idx in day_shifts) <= 1)

# Soft constraint: try to give everyone a similar number of shifts
# We minimise the maximum difference from the average
shifts_per_volunteer = []
for v_idx in range(len(volunteers)):
    total = sum(assignments[(v_idx, s_idx)] for s_idx in range(len(shifts)))
    shifts_per_volunteer.append(total)

# This is a simplification; for true fairness you'd use a more complex objective

# Solve
solver = cp_model.CpSolver()
status = solver.Solve(model)

if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    print("Rota found!")
    results = []
    for s_idx, shift in shifts.iterrows():
        assigned = [volunteers.iloc[v_idx]['name']
                   for v_idx in range(len(volunteers))
                   if solver.Value(assignments[(v_idx, s_idx)]) == 1]
        results.append({
            'shift': shift['shift_id'],
            'day': shift['day'],
            'time': shift['time'],
            'assigned': ', '.join(assigned)
        })
    pd.DataFrame(results).to_csv('rota.csv', index=False)
    print("Rota saved to rota.csv")
else:
    print("No solution found. Check your constraints aren't too tight.")

Tools

Google OR-Toolslibrary · free · open source
Visit →
Google Colabplatform · freemium
Visit →
Pythonlibrary · free · open source
Visit →

Resources

At a glance

Time to implement
weeks
Setup cost
free
Ongoing cost
free
Cost trend
stable
Organisation size
small, medium, large
Target audience
operations-manager, volunteer-coordinator, it-technical

The tools are free. The main investment is time to set it up and learn how it works. Once running, generating new rotas takes seconds.