Is there a good Tutorial on how to create an Admin Panel in Meteor?

Hi, Im new to Meteor. I have already followed some tutorials and books to learn the basics of Meteor. But is there a good tutorial on how to create an admin panel which is restricted to the normal users?

Thanks in advance :smile:

Easier than you’d think

Start by using a library like alanning:roles to put users in roles. Eg:

Meteor.startup(function () {
  const admins = ['', ''].map(email => Accounts.findUserByEmail(email));
  Roles.addUsersToRoles(admins, 'admin', Roles.GLOBAL_GROUP);

Here’s a small catch: They can render the admin page, but since meteor is data driven, you just publish information to the admins. They may render the page, but they won’t have any information.

Meteor.publish('adminData', function () {
  if (Roles.userIsInRole(this.userId, 'admin', Roles.GLOBAL_GROUP)) {
    return SecretData.find();
  } else {
    this.error(new Meteor.Error(403, "Access Denied"));

Cool! Thats’s great news. But according to what I understand, meteor loads all the files at first. But what if we want to stop loading the admin related js and html files from loading to normal users? Is it possible to do this?

Also is it possible to restrict a specific route and all it’s sub routes to normal users? I’m planning to use the FlowRouter.

You can’t, really, you just restrict the data that they receive and what they can do.

Sure, they can render the admin page, but you can just make it not show anything of value. Or even better, make an access denied screen.

  if isInRole 'admin'
    // your template data here
    h1 Invalid Permissions.

I use the Roles package, and have added a few layers of security based around that.

  1. Router-level: You can’t restrict the route directly based around role, but if your using the useraccounts package, you can require the user be logged in, at which point you can THEN check their role. Example (using FlowRouter and Blaze):

    FlowRouter.route(’/dashboard/’, {
    triggersEnter: [AccountsTemplates.ensureSignedIn],
    action: function() {
    BlazeLayout.render(“layout”, {main: “dashboard”});

  2. Template-level security: In my primary Layout template I have a block that builds upon the above route based security. It (again) checks for login, and then checks for role. If the user is of a desirable role, the layout will be rendered. If not, access denied. Example:

         {{#if authLoggedIn}}
             {{#if isInRole 'superadmin,admin,user'}}
  3. Publication-level: The above is all client based security. Of course, some malicious users may be able to find a way past that security - our job is to make it inconvenient, which we did, but we have to plan for them to bypass it. We can do that by limiting what data the server sends to the client. In my example I’m making sure the user is logged in, and then making sure they are not a “deactivated” role, but in your case you can edit as you like:

    Meteor.publish(‘suppliers’, function() {

     // if user is not logged in, do not return anything
         return null;
     // if user is deactivated, do not return anything
     if (Roles.userIsInRole(this.userId, ['deactivated']))
         return null;
     return SuppliersCollection.find();


  4. Method-level: Aside from publications, all functions/API that are ran on the server should also verify role similarly. Example:

    // check permissions
    if(!Roles.userIsInRole(this.userId, [‘superadmin’,‘admin’])) {
    return {
    insufficientPermission: true

Following these 4 steps should leave your application pretty solid. It will be a real pain for them to bypass the layers of client security, and even if they do, they will not have any data and will not be able to perform any functions, unless they are both logged in & in the proper role to access the data/functionality!

Good luck! =)


You might want to look into Meteor Candy, it might be the most “modern” way to build admin panels. It sits right in your app, so you can leverage your existing login session and even roles system. A big part of what makes this approach reasonable is dynamic imports, which keeps the package from adding significant weight to the client.