Mandarin + Forage - An admin tool for phoenix

The README at has been updated with the correct instructions.

1 Like

Thanks for the quick update. It would be good also to update #10

Add a Function  resource:  mix mandarin.gen.html Admin Function functions name:string --binary-id

instead of

Add a  Function  resource:  mix mandarin.gen.html Admin Department departments name:string

Fixed it.

Hey, when do you estimate that this project will be production ready?

I think there is a bigger demand than we think when it comes to admin generators, especially a standardized one that becomes a default recommendation. This would be positive in many ways, as in several contributors, more knowledge spread around and of course a great product.

I wish I could contribute but I don’t think I have the elixir experience yet to be useful, but I will fork and test the repo when I get home from work and try it out for feedback purposes :slight_smile:

I don’t really have an estimate for when I have the docs done. Mandarin is mostly ready for production use now. You can create an admin backend using the generators and pretty much never touch the code (except to add authentication and authorization to the pipeline and things will work. The problem is that appart from the “tutorial” in the example repo there are no docs.

But even worse is the fact that Forage doesn’t have 100% code coverage yet, and docs are also pretty sparse. There’s also the problem that Forage web doesn’t yet support disjunction (the OR operator) in queries yet, but that’s not very important in my opinion.

Hm… I don’t know if thde demand for admin generators is that big. I think people would prefer an admin framework that worked based on DSLs and macros. Mandarin has been criticised for generating a lot of code, when much of it could be simplified with. The controller could be something like this:

defmodule App.Admin.Employee do
  use Mandarin.Controller, resource: App.Admin.Employee
  # with defoverridable functions, of course

It’s actually much more work to generate a file with the controller code. I’m doing it because I believe it’s easier to customize if all code is explicit (and you’ll want to customize it sooner or later…).

1 Like

Just trying to get some feedback :slight_smile: Has anyone actually tried this admin interface in a user-facing app, even if for an internal app? I’ve been quiet for a long time but I’m thinking on going back to it (I have a project that might use this)

Nope, not yet, never got around it. Was wondering between this one and a few others but I am more open to try some of what they call them today “low-code” or “no-code” internal app builders (of which there’s a lot).

Hm… what is a good example of a “low code” app builder? I’m not very familiar with those.

Here are a few lists:

EDIT: And a HN thread where a number of such tools are shown as well:

Ask HN: Best low-/no-code solution for simple web-based database frontends

I see. My approach is the exact opposite of "no code " tools then. It dumps a lot f code into your project in order for it to be maximally customizable.

But I’ll rise up to the challenge! What if it was possible to do something like this:

defmodule MyAppWeb.BackOffice do
  use Mandarin.Magik,
    repo: MyApp.Repo

  # The modules under the Admin scope are normal ecto schemas
  # created inside the Admin context (of course even the Magik version
  # of Mandarin uses contexts! We are note barbarians...)
  alias MyAppWeb.Admin.{


and then add the following to your router.ex file:

require MyAppWeb.BackOffice

scope "/back_office ", MyAppWeb.BackOffice, as: :back_office do

That would make it similar to many Python admin solutions, which are pretty low code (I believe Rails has some “low code” admin frameworks, but I’m not familiar enough to comment on those).

This is not something Mandarin currently implements, but I’m fairly confident I can implement this and it would be even simpler than the current architecture.


That would actually be great! Very “low-code” indeed and it looks like you can do a lot of stuff happen with it.

My inspiration is this: Introduction To Flask-Admin — flask-admin 1.5.3 documentation

If Python can do it, then I can do it too :slight_smile:

So I’ve made some progress. I can generate automatically the Controllers, the Views and the code to access the resources from the database. The only thing I haven’t solved yet how to handle templates. I’m trying to make this work without generating any files to the disk, but I want users to be able to supply their own templates for maximum customization.

Currently the following code generates a fully fledged admin interface that works with the templates generated by the Mandarin generators (only the templates, no need for views, controllers, etc.). I’m actually re-implementing a demo application with the new system by deleting the files I don’t need as I replace them with macros.

First, the code that creates an admin interface (aptly called Mandarin.Magik, but I’m open to new naming suggestions):

defmodule MandarinOfficeWeb.ExampleAdmin do
  alias MandarinOffice.Admin

  use Mandarin.Magik,
    app_web: MandarinOfficeWeb,
    scope: MandarinOfficeWeb.Admin,
    repo: MandarinOffice.Repo


Now, the router (the magik part is at the end):

defmodule MandarinOfficeWeb.Router do
  use MandarinOfficeWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers

  pipeline :api do
    plug :accepts, ["json"]

  scope "/", MandarinOfficeWeb do
    pipe_through :browser

    get "/", PageController, :index

  require MandarinOfficeWeb.ExampleAdmin, as: ExampleAdmin

  ExampleAdmin.scope "/back_office" do
    # You can (and should!) reuse your normal pipelines
    # inside the admin interface scope.

The Admin.Employee, Admin.Department, Admin.Function and Admin.Benefit are completely ordinary Ecto modules which Mandarin.Magik introspects to generate the code (plus some small decorations).

I still haven’t published these changes because I’m still not sure on how to solve the template problem (I’ll probably just copy what Phoenix does when searching for the templates, and use the “default templates” if it doesn’t find any. I’ll post a video soon.


Short video of me playing with the admin interface:


Not sure I am following you here so apologies if my comment is misguided.

As a guy who worked with a number of non-tech and half-tech people, absolutely do NOT do this. They’ll only see it as “this thing works but it you will not be able to just run a command; you’ll have to get your hands dirty”, at which point they’ll bail.

In other words, don’t put speed bumps on the way.

Hm… I don’t understand. The goal is to be able to write the code above and everything just works.

But if you “outgrow” the default template and want to customize some details, I’d like to allow you to provide different templates. As a completely optional feature.

That way “half-track people” could just write the code above and more “techy people” could ask mandarin to generate template files which you’d customize.

Is it clearer now?


Ah, then I misread you. Yes, that’s what I have in mind: have a CMS app generated that works mostly OK without customizations but being able to customize it down the line.

Sorry, my bad for misunderstanding.

1 Like

Ok, I’ve implemented the “low code” solution as described above and I’m actually very unhappy with it. It’s not customizable and I couldn’t get it to play well with any practical real-world application. I’m back to the generators.

My generators use “feature folders” or “vertical slices”, as discussed here, instead of splitting a “feature” into a controller folder, a view folder and a templates folder. I’ve settled on this architectures because it’s my favorite filesystem layout for “normal” (that is, non-mandarin) phoenix applications. Nonetheless, mandarin plays well with already existing applications which split the controllers, views and templates into their own directories.

I now have a Mandarin.Designer module with is a fancy programmatic interface to the command-line Mandarin and Phoenix generators. It gives you some tools to generate HTML, contexts and schemas in a phoenix app in a proper Elixir program, instead of painstakingly run the generators manually in the command line. I call it the Designer because it’s usefull to design your application’s data layer upfront and then generate it all at once. It is also very useful to generate multiple admin interfaces/CRUD pages for the same project.

Speaking of CRUD pages, I think Mandarin is now very usable for “normal” CRUD pages with fancy features like (multi-)select witgets which support Ecto associations (including many-to-many relations, although those take some manual work to get right). The biggest catch is that you have to be ok with using bootstrap templates. I couldn’t find a way of generating templates that don’t depend heavily on a CSS framework.

I’m quite happy with mandarin and forage right now, and I might relase a v1.0 soon for both packages.

1 Like

There are now pre-1.0 releases for:

  • Mandarin - the set of generators that builds the admin interface
  • Forage - HTML widgets for the admin interface and facilities to create dynmaic ecto queris from query parameters

There is now a mandarin guide that shows how to get basic functionality going: Backoffice Demo — mandarin v0.5.0


Now I believe I’m getting really close to 1.0. The main new features (already reflected in the GitHub repo) are the following:

  • Updated the generators in order to be more compatible with what Phoenix 1.6 generates

  • Made sure all tests pass (the generators weren’t buggy themselves, but the tests hadn’t been updated to reflect changes in the generators)

  • Use “vertical feature folders” which keep your controllers, views and templates together in the same directory, which makes it easy to modify related parts of the source code

  • As a consequence of the above, there is now a mix mandarin.uninstall MyContext which completely purges all the context diurectories and files and all references to that context from the router. This is very useful when your database design is still in flux and you might want to tear down the whole mandarin files and build them again

  • There is now a programatic interface to mandarin generators using the Mandarin.Design module, which allows you to generate the interface for a bunch of schemas from the comfort of an Elixir script instead of having to fiddle with the command line. To keep the design close to the normal Phoenix generators, I’m going to keep the command line interface as the main form of interaction with mandarin, but that might change in a putative 2.0 version

  • Mandarin now includes a mandarin.gen.auth AuthContext User users modelled on the phx.gen.auth generator but with support for “vertical feature folders” and support for prettier forms using forage widgets and a boostrap template by default

Things that haven’t changes:

  • Forage is still coupled to bootstrap; that’s pretty much unavoidable for a library of frontend widgets. The widgets must be styled with something, and bootstrap is still one of the easiest frameworks to use and customize, particularly for something like CRUD pages

  • Mandarin still dumps a bunch of files in your application. I’ve decided that mandarin will continue to focus on code generators instead of macros or other use of compile/runtime metaprogramming. No mandarin code runs at runtime.

  • No integration with LiveView. Simple CRUD pages are easy to do with normal controllers + minimal AJAX and more complex CRUD pages are outside of Mandarin’s scope. I’ve had some succes in integrating AlpineJS into mandarin pages (although neither Mandarin nor Forage contain any direct support for it)

The main problems I’ve had in getting to this stage are the fact that Phoenix continues to change the generators (for the better, of course!) on each minor version, and I took som etime to catch up to it. Although my generators are mostly copied from Phoenix, they have some important customizations, so thet job of updating them is not exactly simple. Note that this is not a case of Phoenix changing in backward-incompatible ways; it’s just a case of generators being different.