go-admingo-admin
  • Guide
  • Development
    • Advanced
    • Commands
  • Advanced
  • Help
  • GitHub
  • Changelog
⌘ K
Standard Practice
Standard Module Development
Backend Basics
Backend Directory Structure
Backend Config File
Starting the Backend
Frontend Basics
Frontend Directory Structure
Frontend Config File
Starting the Frontend
Development Patterns
Actions Pattern
Hand-Written Pattern
First API Endpoint
Layered Development
API Layer
Service Layer
DTO Definitions
Model Definitions
Router Registration
Multi-Environment Config
Database Table Conventions
Data Permissions
Response Format
Code Generation
Pre-Generation Setup
Generating Business Code
One-Click Menu Generation
Binding APIs to the Menu
Configuring Role Permissions
Verifying the Feature
Generating Code with an LLM
Advanced Capabilities
Runtime Core API
Authentication & Authorization
Logging
Request Tracing
Cache
Queue
File Upload
Rate Limiting
Scheduled Jobs
Air Hot Reload
Swagger Docs
Code Generation Tool
Last updated:
Open-source MIT Licensed | Copyright © 2020-present
Powered by go-admin-team

TABLE OF CONTENTS

‌
‌
‌
‌

Data Permissions

Menu and API permissions decide whether you can reach a feature at all; data permissions decide which rows you see once you're in it. Open the same user list as an admin and you see everyone; as a department head you see your department; as a regular employee you see only what you created yourself — that's data permissions.

go-admin scopes data permissions by role, filtering on the create_by column of the data table.

The Five Data Scopes

Set in Role Management, per role. Values and behaviour:

ValueNameFilter rule
1All Datano filtering applied
2Custom Datalimited to data created by users in the departments configured for this role in sys_role_dept
3Own Departmentlimited to data created by users in the current user's department
4Own Department and Belowsame as 3, plus every department beneath it (matched against sys_dept.dept_path)
5Own Data Onlylimited to data the current user created themselves

The decision is made from the logged-in user: sys_user joined with sys_role yields data_scope, dept_id and role_id, which are used to build the query condition.

The Global Switch

Data permissions are gated by application.enabledp:

yml
settings:
application:
# data permission feature switch
enabledp: true

When it's off (the default, false), no filtering happens at all — every role sees every row regardless of its configured data scope. This is the first thing to check when "data permissions aren't working".

Enabling It in Your Own Module

Actions Pattern

Just hang actions.PermissionAction() on the route — the generic Action applies the filter automatically:

go
r := v1.Group("/demo-product").
Use(authMiddleware.MiddlewareFunc()).
Use(middleware.AuthCheckRole())
{
m := &models.DemoProduct{}
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
list := make([]models.DemoProduct, 0)
return &list
}))
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
return &models.DemoProduct{}
}))
}

PermissionAction() puts the current user's data-permission info into the request context; the Actions after it read it back out and fold it into the query. Skip this middleware and the filter never applies.

Hand-Written Pattern

Writing your own Handler and Service takes three steps.

The Api layer pulls the data permission out and passes it to Service:

go
// data permission check
p := actions.GetPermissionFromContext(c)
list := make([]models.SysApi, 0)
var count int64
err = s.GetPage(&req, p, &list, &count)

The Service method accepts it and adds it to Scopes:

go
func (e *SysApi) GetPage(c *dto.SysApiGetPageReq, p *actions.DataPermission, list *[]models.SysApi, count *int64) error {
var data models.SysApi
err := e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p),
).
Find(list).Limit(-1).Offset(-1).
Count(count).Error
return err
}

The route still needs actions.PermissionAction() on it.

What It Requires in the Schema

Data permissions depend on these fields and tables — all of them:

ObjectPurpose
the business table's create_bythe filter key, records who created the row
sys_user.dept_idthe user's department
sys_role.data_scopethe role's configured data scope
sys_dept.dept_paththe department hierarchy path; "own department and below" depends on it
sys_role_deptmaps role to department for "custom data"

As long as a business table follows the database table conventions and includes the common fields, create_by is filled in automatically by the framework on write — no manual assignment needed.

WARNING

The filter runs on create_by, so data imported straight into the database, or written by code that bypasses the framework, can end up with an empty create_by — and that data becomes invisible to everyone under any scope except "All Data".

Backfill create_by when importing historical data.

Checklist for "The Data's There but I Can't See It"

The most common symptom once data permissions are active is "there's clearly data, but the query returns nothing." Check, in this order:

  1. Is enabledp actually true? — When it's off, everyone sees everything; if the symptom is "seeing things I shouldn't", this is where to look;
  2. What data scope does the current account's role have? — Check in Role Management; scope 5 only shows what that user created themselves;
  3. What's the target row's create_by? — Query the database directly to confirm whether it's empty or belongs to the expected user;
  4. Is actions.PermissionAction() on the route? — Skip it and filtering doesn't apply — the symptom then runs the other way: everyone sees everything;
  5. Is the department hierarchy right? — With "own department and below", check that sys_dept.dept_path is correct; a wrong path means subordinate departments won't match.

INFO

While debugging, e.Orm.Debug() in the Service temporarily prints the actual SQL being run — seeing the assembled filter condition directly is much faster than guessing your way through each layer.

WARNING

Where to get help:

If anything in this guide is unclear, please open an issue.