alejolcc

alejolcc

Oban Chore provides an Oban plugin to generate a dashboard to run Oban jobs

Motivation

In previous jobs, I often had to run Oban jobs manually for things like backfills or specific support actions. Doing this meant SSH-ing into production and manually typing commands to run the job, which carries inherent security risks.

Additionally, direct access to the Elixir node isn’t always available. While we could use the Oban dashboard, we often don’t want to give full access to this dashboard to non-technical staff. This unfortunately turns developers into a bottleneck for customer support and operations teams.

Oban Chore solves this by giving you a visual interface to handle these tasks directly from the browser, empowering non-technical users to safely run jobs.

Highlights

Zero-Boilerplate UI Generation: Define input schemas directly inside your worker, and let ObanChore generate the form components automatically.
Live Execution Streaming: Leveraging Phoenix PubSub and Telemetry, the dashboard streams logs and status updates from the process directly to the user’s browser in real-time.
Idempotency Check: Automatically detects if a job with the same arguments is already running.
Unique Execution Toggle: Manually enforce single-job execution via the dashboard UI. (you can override this from the job definition)
Validation: Full Ecto-backed validation for all chore arguments.

:rocket: Quick Example

  1. Configure Oban
    # config/config.exs                                                                                                                  
    config :my_app, Oban,                                                                                                                
      repo: MyApp.Repo,                                                                                                                  
      plugins: [                                                                                                                         
        {ObanChore.Plugin, otp_app: :my_app, pubsub_server: MyApp.PubSub}                                                                
      ],                                                                                                                                 
      queues: [default: 10]
  1. Define your Chore Worker:
defmodule MyApp.Chores.UserBackfill do                                                                                               
  use ObanChore.Worker,                                                                                                              
    name: "User Data Backfill",                                                                                                      
    description: "Backfills historical data for a specific user.",                                                                   
    fields: [                                                                                                                        
      user_id: [type: :integer, required: true, label: "Target User ID"],                                                            
      reason: [type: :string, default: "Manual correction", label: "Reason"]                                                         
    ]                                                                                                                                
                                                                                                                                     
  @impl Oban.Worker                                                                                                                  
  def perform(%Oban.Job{args: %{"user_id" => user_id, "reason" => reason}} = job) do                                                       
    ObanChore.log(job, "Starting user backfill for user #{user_id}")                                                                                 
    # Your execution logic here...                                                                                                   
    ObanChore.log(job, "Done!")                                                                                                      
    :ok                                                                                                                              
  end                                                                                                                                
                                                                                                                                     
  # Optional: Customize changeset validation                                                                                         
  @impl ObanChore.Worker                                                                                                             
  def custom_changeset(changeset) do                                                                                                 
    Ecto.Changeset.validate_number(changeset, :user_id, greater_than: 0)                                                             
  end                                                                                                                                
end                                                                                                                                  
  1. Mount the Dashboard:
# lib/my_app_web/router.ex                                                                                                           
defmodule MyAppWeb.Router do                                                                                                         
  use MyAppWeb, :router                                                                                                              
  import ObanChore.Router                                                                                                            
                                                                                                                                     
  scope "/" do                                                                                                                       
    pipe_through :browser                                                                                                            
    oban_chore_dashboard "/ops/chores"                                                                                               
  end                                                                                                                                
end                                                                                                                                  

Status & Feedback

Please note that this library is currently under active development. As this is an early release, any feedback, architecture suggestions, or ideas for new use cases are welcome!

Links

• Hex Package: oban_chore | Hex
• Documentation: ObanChore 🎭 — ObanChore v0.4.0

Showing Posts 1 to 10

jan-mb-me

jan-mb-me

@alejolcc, nice! Thanks for sharing.

webofbits

webofbits

hey, nice work. i’m trying to understand the intended boundary here.

if I am not mistaken Oban Web has authorization/access control via Oban.Web.Resolver.

from the README/thread, it looks like Oban Chore is more about exposing a curated set of predefined workers as validated forms for support/ops users: backfills, reruns, manual syncs, etc. that makes sense as a different use case from Oban Web, which already has authorization but is still primarily a full job/queue inspection and management dashboard.

a few things i’m curious about:

1. how do you see authorization being handled per chore/worker?
2. is there an audit trail for who ran what, with which args, and why?
3. do you expect chores to be idempotent by convention, or does the library enforce enough around duplicate execution?
4. would you recommend using this alongside Oban Web, with Oban Web reserved for developers/admins and Oban Chore for support/ops?

alejolcc

alejolcc OP

Hey! Thanks for the questions. As I mentioned before, this is still under heavy development. The core intention behind it is to provide a safe, user-friendly way for non-technical users to manually trigger background jobs.

Right now, to keep things simple, the dashboard relies on the host application’s router pipeline for access control (such as a generic live_session block or a basic auth plug). However, I want to introduce more granular access at the chore level. The plan is to add an optional, declarative auth/2 or authorized?/1 callback inside the ObanChore.Worker behaviour. This will allow developers to evaluate the current user session against a specific chore module before it even renders in the UI or permits execution.

This is on the backlog, but I don’t have a finalized plan for the underlying architecture just yet.

To make it a true, resilient audit trail, the data needs to be persistent, so an in-memory solution like ETS is out. My current thinking is to introduce a dedicated audit log table within the same Ecto repo that Oban uses.

But to keep installation friction as low as possible, I want this feature to be completely optional. If a developer wants to enable audit tracking, they would run a generator command—like a mix task or a native Igniter installer—to automatically inject all the required changes.

I’m very open to hearing how others have solved this type of optional, cross-cutting persistence in their own backend tools!

By convention, background jobs should always be idempotent, but that ultimately remains the developer’s responsibility. At the library level, ObanChore focuses on providing runtime guardrails in the UI to catch human error before it hits your business logic.

The dashboard enforces uniqueness by default by dynamically appending Oban’s native unique options (fields: [:args], states: [:available, :scheduled, :executing]) over a safe window. If a human double-clicks “Execute”, the dashboard blocks the duplicate insertion, and returns a clean warning banner in the UI. However, if a developer explicitly configures custom unique options directly inside their worker module, the default respects their specific settings. I also provide an “Allow concurrent executions” checkbox in the UI to serve as a conscious escape hatch if an operator intentionally needs to force a duplicate run.

Absolutely, this is NOT an Oban Web replacement. This is intended to safely run manual jobs defined by developers who know the logic.

Oban Web is an excellent engineering tool, but (I think) It’s built for low-level infrastructure inspection, monitoring system health, pausing queues, and debugging failed jobs. It belongs in the hands of developers and DevOps engineers who understand the technical internals of the system.

Oban Chore acts as a high-level business interface. It abstracts the raw job system entirely and exposes safe, user-friendly, and schema-validated forms. It belongs in the hands of support teams, account managers, and operations users who just need to trigger business processes (like running a report, onboarding a special client, or syncing an account) without ever seeing a stack trace or raw JSON payloads.

christhekeele

christhekeele

I would consider handling this at the router level, by allowing intentional collection of Chores into a route, ex:

oban_chore_dashboard "/ops", chores: [MyApp.Chores.FrontendDeploy]
oban_chore_dashboard "/customer_success", chores: [MyApp.Chores.UserBackfill]

Or perhaps via a tag system:

use ObanChore.Worker,                                                                                                              
    name: "User Data Backfill",                                                                                                      
    tag: :backfill

oban_chore_dashboard "/product", tags: [:backfill]

Developers could handle granular access via composition. This would allow ObanChore to stay light, neutral about auth, and most powerfully, work with any existing plug/pipe auth mechanism. The callbacks might be useful for advanced scenarios, but I’d start simpler!


I would also avoid building your own solution here, and consider what the lightest possible thing could be done to enable it. Optional lifecycle hooks in the worker module could get you very far, only invoked when triggered from ObanChore’s dashboards, which would allow people to plug in their own DB or telemetry based solutions. Something as simple as

use ObanChore.Worker

@impl ObanChore.Worker                                                                                                             
def on_chore_trigger(changeset)

would get you all you need to support auditing. Give it access to the conn to enable plug-drived auth, and with extra return value semantics, it could be the starting point for per-job auth as well, ex

def on_chore_trigger(conn, changeset) do
  if MyApp.Auth.Roles.ops?(conn.assigns.current_user) do
    {:ok, conn, changeset}
  else
    {:error, "Not authorized"}
  end
end
sorentwo

sorentwo

Oban Core Team

Interesting project. A few clarifications on what’s possible with Oban Web.

  1. As of v2.12 there’s a “New Job” sidebar where you can insert new jobs.
  2. There are roll based access to restrict CRUD operations to certain users, as well as fine grained access controls to restrict access to sensitive operations.
  3. Auditing is possible too, for all operations, by hooking into telemetry, or use the built-in audit logging.

Of course that doesn’t change whether it’s easy to identify some curated workers with default args and all that, but it’s possible to accomplish this with Oban Web.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Dude that’s awesome. Presumably if you define a new/2 function on the worker the you can do validations and stuff on the args?

webofbits

webofbits

maybe would be a good idea to select existing workers and pass arguments

sorenone

sorenone

Oban Core Team

Currently released versions, no.
On main and because you mentioned it..is now true and very much the case. :magic_wand:

alejolcc

alejolcc OP

Thanks!! This is fantastic feedback. I really like both of these suggestions, they align perfectly with keeping the library lightweight which is my intention :slightly_smiling_face:

alejolcc

alejolcc OP

Thanks Soren! Oban Web is fantastic and I love it.

My main goal with this lib is to focus strictly on the developer experience of turning workers into schema-validated HTML forms (instead of raw JSON fields). I want to provide a quick, out-of-the-box solution with a friendlier UI for non-technical users, making it easier for developers to hand off specific manual tasks safely.

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
rodloboz
I’ve started working on a new library to run SQL queries and do basic business intelligence. Think “Blazer for Elixir.” Currently it fe...
New

Other Trending Topics Top

CodeSync
The Everything App - Lars Wikman | ElixirConf EU 2026 https://www.youtube.com/watch?v=CsB0CroGHsk Comments welcome! View the <span cla...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews