Access Logic (Security Definitions)

Security Definitions

Security definitions are the logic behind your security policies. They use JavaScript expressions to determine whether a user has access to a form, submission, resource, or platform feature.

You can think of security definitions as dynamic rules that say things like:

"This user can access the form if they're on the HR team, or if they're the assigned manager."


What Are Security Definitions?

A security definition is a reusable expression that evaluates to true or false. It's attached to a security policy at the Form, Kapp, Space, or Workflow level.

If the definition evaluates to true → access is allowed.
If it evaluates to false → access is denied and the user sees your custom message.


How Do Security Definitions Work?

Security definitions rely on dynamic data like the current user’s identity, their teams, or the submission values to determine access. That data is accessed using built-in bindings.

Bindings are functions like identity(), values(), or submission() that retrieve live information about:

  • The user trying to access something
  • The form or submission they’re interacting with
  • The space, kapp, or workflow where it’s happening

You’ll use these bindings throughout your definitions to write logic like:

identity('teams').includes('IT::Managers')

 

or

values('Requested For') === identity('username')

You can find a full breakdown of binding types and usage later in this documentation.


Where to Create and Use Them

You can create definitions from either:

  • Space > Definitions > Security
  • Kapp > Definitions > Security

Once created, these definitions are available in drop-downs when assigning policies to:

  • Forms
  • Submissions
  • Kapps
  • Workflows
  • Teams & Users

💡

Definitions created at the Space level can be used across all kapps.

Space vs. Kapp Security Definitions

The creation process for security definitions is the same everywhere, but the types of definitions you can create depend on where you are in the platform.

Created FromTypes Available
Space levelSpace, File Resource, Team, User
Kapp levelKapp, Form, Submission

🔍

You must create the definition from the correct context in order for it to appear as an option when assigning a policy.

For example:

  • If you're securing a user management action, create the definition from the Space > Definitions > Security section and choose User as the type.
  • If you're securing access to a form or submission, go to the Kapp > Definitions > Security section and choose the appropriate Form or Submission type.

Selecting the Right Type

When creating a new definition, you'll select a Type that determines which objects it can secure.

Each type corresponds to a specific area of the platform:

  • Space: Controls access to global features like user creation or file resources.
  • File Resource: Secures access to uploaded files in the platform.
  • Team / User: Controls permissions for modifying or viewing teams and users.
  • Kapp: Governs access to an entire kapp or its configuration.
  • Form: Secures form-level access (e.g., display or modification).
  • Submission: Applies to individual submission records (e.g., view/edit logic).

🛑

If you don’t see the type you need, check whether you're in the correct level (Space vs. Kapp) when creating the definition.


Writing a Security Rule

A rule must be a single JavaScript expression that returns a boolean (true or false). Most rules use built-in platform functions like:

  • identity('username') – returns the logged-in user's username
  • identity('teams') – returns a list of team names the user belongs to
  • values('Assigned Individual') – pulls a value from the submission
  • submission('createdAt') – retrieves submission metadata

Example Rules

// Only users on the HR team
identity('teams').includes('Department::HR')
 
// Only allow if Assigned Individual matches the logged-in user
values('Assigned Individual') === identity('username')
// Allow if user is on the assigned team OR is the submitter
hasIntersection(values('Assigned Team'), identity('teams')) ||
values('Requested By') === identity('username')

Adding a Message

Every security definition includes a Message field. This is the text the user will see when they fail the access check. In other words, when the rule evaluates to false.

Good messages reduce confusion, prevent support tickets, and guide users to the next step.

When Is the Message Displayed?

  • When a user tries to load a form they aren’t allowed to view
  • When they attempt to view or update a submission they don’t have access to
  • When a workflow or platform feature is restricted

Examples of Effective Messages

"This form is only available to members of the HR department. Please contact HR if you believe you need access."
"You can only view submissions you created. Contact your team lead for historical records."
"Access to this workflow is restricted to authorized reviewers."

Tips for Writing Messages

  • Be specific: Tell the user what the restriction is about
  • Be polite: Avoid blame or accusatory language
  • Give direction: If appropriate, mention what they can do next.

If no message is provided, users will see a generic access error. Use this field to improve clarity and user trust.


Common Helper: hasIntersection

When comparing two values or lists (like a user’s teams and a field value), it’s helpful to use a reusable helper function that handles edge cases and ensures type consistency.

Here’s a more robust version of the commonly used hasIntersection helper:

(function() {
  // Helper method: Checks if two lists have any shared values
  var hasIntersection = function(obj1, obj2) {
    // Ensure the objects are not empty
    obj1 = (obj1 === null || obj1 === undefined) ? [] : obj1;
    obj2 = (obj2 === null || obj2 === undefined) ? [] : obj2;

    // Wrap single values in arrays if needed
    var list1 = (obj1 instanceof Array) ? obj1 : [obj1];
    var list2 = (obj2 instanceof Array) ? obj2 : [obj2];

    // Return whether any intersecting values were found
    return list1.find(function(value) {
      return hasValue(list2, value);
    }) !== undefined;
  };

  //  Helper method: Verifies a value exists in a list
  var hasValue = function(list, value) {
    return (list instanceof Array) && list.indexOf(value) !== -1;
  };

  // Example: Check if the user belongs to the 'Employee' role
  return hasIntersection(identity('teams'), ['Role::Employee']);
})()

 

What This Does:

  • Converts null or undefined inputs into empty arrays
  • Wraps single values into arrays so comparisons don't fail
  • Uses hasValue() to safely check membership
  • Returns true if any value overlaps between the two lists

Why It's Useful

This pattern avoids fragile, one-off expressions and can be dropped into any security definition. You can reuse it to check:

  • If the user belongs to a required team or role
  • If the user matches a value in a multi-select field
  • If any overlap exists between submission values and user attributes

💡

You can copy and paste this directly into any Security Definition for a reliable list comparison


What Are Bindings?

Bindings are the dynamic values you can use inside your security definitions to evaluate who the user is, what they’re trying to access, and the context around it. They work like pre-built functions that return real-time information.

Think of bindings as your data source when writing access logic.


Common Binding Functions

BindingWhat It ReturnsExample Usage
identity()Info about the current user (username, teams, attributes)identity('username') === 'mary.manager'
values()Field values from the current submissionvalues('Department') === 'HR'
submission()Metadata about the submission (creator, timestamps)submission('createdBy')
space()Space-level propertiesspace('slug') === 'prod'
kapp()Kapp-level properties (e.g., name or slug)kapp('name') === 'Services'
form()Form-level properties (e.g., name, slug)form('slug') === 'onboarding-request'
team() / user()Info about a specific team or user (Space-level only)team('slug') === 'admins'

Binding Scope by Definition Type

Some bindings are only available depending on the type of security definition you’re writing. For example:

Definition TypeAvailable Bindings
Spaceidentity(), space(), team(), user()
Kappidentity(), space(), kapp()
Formidentity(), space(), kapp(), form()
Submissionidentity(), space(), kapp(), form(), values(), submission()
Team/Useridentity(), team(), user()

Example: Using Bindings Together

// Only allow if the user is on the assigned team OR is the submitter
hasIntersection(values('Assigned Team'), identity('teams')) ||
submission('createdBy') === identity('username')

 

This rule combines:

  • values() to pull from the form
  • identity() to check the current user
  • submission() to confirm submitter meta

Optional Defaults in Bindings

Some binding functions accept a default value if the requested value is undefined. For example:

identity('attribute:Manager', ['nobody'])

This will return a nobody if the user has no Manager attribute.


Final Tips for Building Great Security Definitions

Crafting effective security definitions isn’t just about writing the right expression — it’s about thinking through the experience for both the builder and the end user.

Here are a few tips to bring it all together:

  • Start Simple: Begin with straightforward rules like team checks or ownership comparisons. You can layer complexity as your needs grow.
  • Test As a Non-Admin: Space Admins bypass security definitions. Always use a standard test account to validate your logic in real conditions.
  • Reuse and Name Clearly: Create reusable definitions like Is Submitter or HR Only, and use consistent naming so your team can quickly identify what each one does.
  • Combine with Security Policies Thoughtfully: Remember: definitions power policies — make sure you apply them at the right level (Form, Kapp, Space), and understand the fallback hierarchy.
  • Don’t Skip the Message: A well-written denial message helps end users understand why they don’t have access and reduces support overhead.
  • Use Helpers (like hasIntersection):Abstracting logic into small helpers makes complex definitions easier to read, maintain, and debug.

With the right security definitions in place, you can offer tailored, secure access throughout your platform, without relying on rigid role-based access or hardcoded logic.

When in doubt, start with what you know, test in context, and iterate from there.