Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Entity Access

Entity access rules control which module roles can create, read, write, and delete objects of a given entity. Rules can restrict access to specific attributes and apply XPath constraints to limit which rows are visible.

GRANT on Entities

GRANT <rights> ON ENTITY <Module>.<Entity> TO <Module>.<Role> [, ...] [WHERE [<xpath>]];

The <rights> list is a comma-separated combination of:

RightSyntaxDescription
CreateCREATEAllow creating new objects
DeleteDELETEAllow deleting objects
Read allREAD *Read access to all attributes and associations
Read specificREAD (<attr>, ...)Read access to listed members only
Write allWRITE *Write access to all attributes and associations
Write specificWRITE (<attr>, ...)Write access to listed members only

Examples

Full Access

Grant all operations on all members:

GRANT CREATE, DELETE, READ *, WRITE * ON ENTITY Shop.Customer TO Shop.Admin;

Read-Only Access

GRANT READ * ON ENTITY Shop.Customer TO Shop.Viewer;

Selective Member Access

Restrict read and write to specific attributes:

GRANT READ (Name, Email, Status), WRITE (Email) ON ENTITY Shop.Customer TO Shop.User;

Members Added Later

A rule also carries a default for members added after it was written, and MDL derives that default from the grant rather than stating it: WRITE * gives ReadWrite, READ * gives ReadOnly, and a grant written purely as member lists leaves it at None.

So an attribute added later is granted None on a member-listed rule. The model is complete and correct — every rule gets an entry for the new member, and the build reports no errors — but the attribute renders blank for that role:

GRANT READ (Name, Email) ON ENTITY Shop.Customer TO Shop.User;
-- later:
--   alter entity Shop.Customer add attribute Phone: String;
-- Phone is granted None to Shop.User. Nothing is broken; it is simply not visible.

alter entity … add attribute reports this and prints the GRANT that fixes it:

Added attribute 'Phone' to entity Shop.Customer
Warning: Shop.Customer.Phone is not readable by Shop.User
  ...
    grant read (Phone) on entity Shop.Customer to Shop.User;

What decides this is the rule’s default, not how narrow its member list is. A rule granted READ *, WRITE (Email) is narrower than READ *, WRITE * and still sees new members, because READ * set its default to ReadOnly. If you want a role to pick up future members automatically, give its rule a READ * or WRITE * and narrow from there with REVOKE.

XPath Constraints

Limit which objects a role can see or modify using an XPath expression in the WHERE clause:

mdl 1;
-- Users can only access their own orders
GRANT READ *, WRITE * ON ENTITY Shop.Order TO Shop.User
  WHERE [Sales.Order_Customer/Sales.Customer/Name = $currentUser];

-- Only open orders are editable
GRANT READ *, WRITE * ON ENTITY Shop.Order TO Shop.User
  WHERE [Status = 'Open'];

Note that single quotes inside XPath expressions must be doubled (''), since the entire expression is wrapped in single quotes.

Additive Behavior

GRANT is additive. If a role already has an access rule on the entity, the new rights are merged in without removing existing permissions:

mdl 1;
-- Initial grant
GRANT READ (Name, Email) ON ENTITY Shop.Customer TO Shop.User;

-- Add Notes access — Name and Email are preserved
GRANT READ (Notes) ON ENTITY Shop.Customer TO Shop.User;
-- Result: READ (Name, Email, Notes)

-- Upgrade Email to writable — existing reads preserved
GRANT WRITE (Email) ON ENTITY Shop.Customer TO Shop.User;
-- Result: READ (Name, Notes), WRITE (Email)

Merging only ever widens access — rights rank None < ReadOnly < ReadWrite, and a GRANT takes whichever is higher. To take access away, use REVOKE; that is what makes the two commands inverses rather than two spellings of “set”.

One Rule per Constraint

The constraint is part of a rule’s identity. Two GRANTs for the same role with the same WHERE update one rule; with different constraints they produce two, which is how row-level security is normally written:

mdl 1;
-- Two separate rules for one role
GRANT READ * ON ENTITY Shop.Order TO Shop.User WHERE [Status = 'Open'];
GRANT READ (Total) ON ENTITY Shop.Order TO Shop.User WHERE [Owner = $currentUser];

Mendix combines them at runtime: a role’s effective access is the union of every rule naming it. An unconstrained rule is distinct from a constrained one, so adding a WHERE to an existing GRANT creates a second rule rather than narrowing the first — and because a rule is matched on its constraint, re-running a script updates the same rules instead of accumulating new ones.

Multiple Roles on the Same Entity

Each GRANT creates a separate access rule. An entity can have rules for multiple roles:

mdl 1;
GRANT CREATE, DELETE, READ *, WRITE * ON ENTITY Shop.Order TO Shop.Admin;
GRANT READ *, WRITE * ON ENTITY Shop.Order TO Shop.User WHERE [Status = 'Open'];
GRANT READ * ON ENTITY Shop.Order TO Shop.Viewer;

REVOKE on Entities

Remove an entity access rule entirely or revoke specific rights:

-- Full revoke (removes entire rule)
REVOKE <Module>.<Role> ON <Module>.<Entity>;

-- Partial revoke (downgrades specific rights)
REVOKE <Module>.<Role> ON <Module>.<Entity> (<rights>);

Examples:

mdl 1;
-- Remove all access for Viewer
REVOKE ALL ON ENTITY Shop.Customer FROM Shop.Viewer;

-- Remove read access on a specific attribute
REVOKE READ (Notes) ON ENTITY Shop.Customer FROM Shop.User;

-- Downgrade write to read-only on Email
REVOKE WRITE (Email) ON ENTITY Shop.Customer FROM Shop.User;

A full REVOKE (without rights list) removes the entire access rule. A partial REVOKE downgrades specific rights: REVOKE READ (x) sets member x to no access, REVOKE WRITE (x) downgrades from ReadWrite to ReadOnly.

Viewing Entity Access

-- See which roles have access to an entity (the two spellings are synonyms)
LIST ACCESS ON ENTITY Shop.Customer;
LIST ACCESS ON Shop.Customer;

-- Full matrix across a module
DESCRIBE SECURITY MATRIX IN Shop;

See Also