Securing your files and folders
Contents
- Security rules
- Access rule structure
- Private media files and app bundles
- Custom security rules
- REST API endpoints
Security rules
Access to files and folders is secured via the File Security section in Fliplet Studio. Rules control who can read, create (upload), update, and delete files — enforced server-side for all app token requests. Rules can be configured through the Studio UI wizard without writing any code.
Key behaviors:
- Denied by default — access is denied when no rules exist on a resource or any of its ancestors. Note that apps come with a preset rule granting public read and logged-in upload access, which can be changed in Studio
- First-match wins — rules are evaluated top to bottom; the first rule that grants access is used and evaluation stops. If no rules match, access is denied
- Inheritance chain — rules are resolved by walking up the resource hierarchy until rules are found:
- The file or folder’s own rules
- Parent folder → grandparent folder → … (up the folder tree)
- The app’s root media access rules (final fallback)
- No rules found anywhere → access denied
A resource with its own rules completely replaces inherited rules (no merging). The system uses only the first set of rules it finds in this chain.
- Action-based — rules specify which operations they permit:
read,create,update,delete
Access rule structure
File security rules follow the same conventions as Data Source security rules, using the same allow schema, condition operators, and session handling. Each rule specifies who can access files and what operations they can perform.
| Property | Type | Required | Description |
|---|---|---|---|
type |
Array of strings | Yes | Operations this rule applies to: "read", "create", "update", "delete". Note: "create" is only valid on folder rules (not file rules) |
allow |
String or Object | Yes | Who can access: "all", "loggedIn", { "user": {...} }, { "tokens": [...] }, or { "dataSource": {...} } |
enabled |
Boolean | No | Whether the rule is active (defaults to true) |
appId |
Array of numbers | No | Restrict this rule to specific app IDs (applies to all apps if omitted) |
name |
String | No | Descriptive label for identifying the rule in Studio |
script |
String | No | Custom JavaScript code for advanced security logic. When present, overrides allow and type — see Custom security rules |
stop |
Boolean | No | If true and the rule does not match the request, evaluation stops immediately (explicit deny). Defaults to false. See Explicit deny with stop |
A maximum of 20 rules can be configured per file or folder.
Defining who can access
The allow property supports the same modes as Data Source rules. User filters support three operators (equals, notequals, contains) and can reference session data using Handlebars syntax:
[
{
"type": ["read", "create", "update", "delete"],
"allow": { "user": { "Role": { "equals": "Admin" } } },
"enabled": true
},
{
"type": ["read"],
"allow": {
"user": {
"Department": { "equals": "Engineering" },
"Status": { "notequals": "Inactive" }
}
},
"enabled": true
},
{
"type": ["read", "create"],
"allow": "loggedIn",
"enabled": true
}
]
To grant access to specific API tokens, use the tokens mode with an array of Fliplet API token IDs:
{
"type": ["read"],
"allow": { "tokens": [42857] },
"enabled": true
}
For the full allow reference and Handlebars templating details, see Data Source security rules.
Explicit deny with stop
Normally, when a rule does not match the current request it is skipped and evaluation continues with the next rule. Setting stop: true on a rule changes this behavior: if the rule does not match, evaluation halts immediately and access is denied.
This lets you create an explicit deny — a rule that says “if you don’t pass this check, stop here; don’t even look at the remaining rules.”
Example: only non-suspended logged-in users should be able to read files, even though a broader loggedIn rule exists further down:
[
{
"type": ["read"],
"allow": {
"user": { "Status": { "notequals": "Suspended" } }
},
"stop": true,
"enabled": true
},
{
"type": ["read"],
"allow": "loggedIn",
"enabled": true
}
]
A non-suspended user matches the first rule and is granted access. A suspended user fails the first rule — because stop is true, evaluation halts and access is denied before the second loggedIn rule is reached.
Note: The stop property is specific to file security rules. It is not available on Data Source security rules.
Data source ownership rules
File rules support an additional allow mode not available on Data Source rules: { "dataSource": {...} }. This grants access when a data source entry references the file and optionally matches user criteria. This mode is only available on file rules (not folder rules).
[
{
"type": ["read"],
"allow": {
"dataSource": {
"id": 123,
"fileColumn": "Attachments",
"where": {
"Owner": "{{user.[Email]}}"
}
}
},
"enabled": true
}
]
| Property | Type | Required | Description |
|---|---|---|---|
id |
Number | Yes | The data source ID to query |
fileColumn |
String | Yes | The column name that contains file references (checked for the file’s ID via substring match) |
where |
Object | No | Additional filter criteria applied to matching entries. Supports Handlebars templates for session data. If omitted, any entry referencing the file grants access |
Full example: department document library
This example shows a folder structure where public files are accessible to everyone, department folders are restricted to logged-in users in the matching department, and only admins can upload or delete.
Folder structure:
/public/
welcome.pdf
/engineering/
architecture.pdf
roadmap.xlsx
/marketing/
brand-guide.pdf
Rules on the /public/ folder:
[
{
"type": ["read"],
"allow": "all",
"enabled": true
}
]
Rules on the /engineering/ folder:
[
{
"type": ["read", "create", "update", "delete"],
"allow": { "user": { "Role": { "equals": "Admin" } } },
"enabled": true
},
{
"type": ["read"],
"allow": {
"user": {
"Department": { "equals": "Engineering" }
}
},
"enabled": true
}
]
The second rule uses a literal value ("Engineering") for the department check. Unlike Data Source rules which have require for query scoping, file security rules can only filter by the user’s identity via allow.user. Using a Handlebars self-reference like "equals": "{{user.[Department]}}" would compare the user’s department against itself — always true, granting access to any logged-in user regardless of department.
Rules on the /marketing/ folder would follow the same pattern with "Department": { "equals": "Marketing" }.
Access outcomes:
| User | Role | Department | /public/welcome.pdf read |
/engineering/roadmap.xlsx read |
/engineering/ upload |
|---|---|---|---|---|---|
| Anonymous | — | — | Granted | Denied | Denied |
| Bob | User | Engineering | Granted | Granted | Denied |
| Carol | User | Marketing | Granted | Denied | Denied |
| Alice | Admin | Engineering | Granted | Granted | Granted |
Rules are evaluated top to bottom — the admin rule matches first for Alice, the department rule matches for Bob. Carol is denied because her department does not match the literal value “Engineering”. Files in /engineering/ inherit the folder’s rules.
API calls that succeed
Bob reading files from /engineering/ — Bob is logged in (Role: User, Department: Engineering). The second rule matches because his department equals “Engineering”:
// JS API — list files in the engineering folder
Fliplet.Media.Folders.get({ folderId: 2 }).then(function (response) {
// response.files contains architecture.pdf and roadmap.xlsx
console.log('Engineering files:', response.files);
});
// REST API equivalent
GET /v1/media?folderId=2
Auth-token: <Bob's app token>
// Response (200 OK):
{
"files": [
{ "id": 10, "name": "architecture.pdf", "contentType": "application/pdf", ... },
{ "id": 11, "name": "roadmap.xlsx", "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ... }
],
"folders": []
}
Alice uploading to /engineering/ — Alice is logged in (Role: Admin). The first rule matches because her role equals “Admin”, granting create access:
// JS API — upload a file to the engineering folder
var formData = new FormData();
formData.append('files[0]', fileInput.files[0]);
Fliplet.Media.Files.upload({
data: formData,
folderId: 2 // engineering folder
}).then(function (files) {
console.log('Uploaded:', files);
});
// REST API equivalent
POST /v1/media/files?folderId=2
Auth-token: <Alice's app token>
Content-Type: multipart/form-data
// Response (201 Created):
{
"files": [{ "id": 12, "name": "new-doc.pdf", ... }]
}
Anonymous reading from /public/ — No login required. The rule uses "allow": "all":
// JS API — list files in the public folder
Fliplet.Media.Folders.get({ folderId: 1 }).then(function (response) {
// response.files contains welcome.pdf
});
// REST API — read file contents directly
GET /v1/media/files/9/contents/welcome.pdf
// No auth token needed — the rule grants public read access
// Response: file stream (200 OK)
API calls that fail
When a request is denied, the API responds with HTTP status 401 and a JSON error body (note: Data Source denials return HTTP 400 instead):
{
"error": "file.access",
"message": "You do not have permission to access this file"
}
In the JS API, the promise is rejected with this error.
Carol trying to read /engineering/ files — Carol is logged in (Role: User, Department: Marketing). The admin rule does not match (wrong role), and the department rule does not match (Marketing ≠ Engineering):
// JS API — Carol tries to read a file from engineering
Fliplet.Media.Folders.get({ folderId: 2 }).catch(function (error) {
// error: "You do not have permission to access this file"
});
// REST API equivalent
GET /v1/media?folderId=2
Auth-token: <Carol's app token>
// Response (401 Unauthorized):
{ "error": "file.access", "message": "You do not have permission to access this file" }
Bob trying to upload to /engineering/ — Bob is logged in (Role: User, Department: Engineering). He can read files, but the only rule granting create access requires Role: Admin:
// JS API — Bob tries to upload
Fliplet.Media.Files.upload({
data: formData,
folderId: 2
}).catch(function (error) {
// error: "You do not have permission to create files here"
});
// REST API equivalent
POST /v1/media/files?folderId=2
Auth-token: <Bob's app token>
Content-Type: multipart/form-data
// Response (401 Unauthorized):
{ "error": "file.access", "message": "You do not have permission to create files here" }
Anonymous trying to read /engineering/ files — No login. Neither rule matches: the admin rule requires a user session, and the department rule requires a logged-in user with a matching department:
// REST API
GET /v1/media/files/10/contents/architecture.pdf
// No auth token
// Response (401 Unauthorized):
{ "error": "file.access", "message": "You do not have permission to access this file" }
Private media files and app bundles
Files that do not have an unconditional public read rule (i.e., "allow": "all" for "read") are treated as private. This affects how files are included in app bundles:
- Public files are included in the app bundle as normal and are available offline
- Private files are excluded from the bundle — only metadata (file ID, name, content type) is included
- At runtime, the app must make authenticated API requests to access private file contents (e.g., via
Fliplet.Media.Files.get()or the/v1/media/files/:id/contentsendpoint) - Private files are not available offline unless your app explicitly fetches and caches them at runtime
This partitioning ensures that secured files are never exposed in the publicly downloadable app bundle.
Custom security rules
For advanced logic beyond what the standard rule properties support, you can write custom JavaScript security rules. This follows the same model as custom Data Source security rules — the script is evaluated at runtime in a sandboxed environment.
Important: When a rule has a custom script, the script is the sole determinant of access. Standard rule fields like allow and type are ignored — the script runs regardless of login status or operation type. Your script must perform its own identity and operation checks (e.g., if (!user) return { granted: false };). If the script does not return a value (e.g., an unhandled operation type falls through without a return), access is denied by default.
When writing a custom rule, these variables are available in the script context:
type(String) — the operation being attempted:read,create,update, ordeleteuser(Object) — the authenticated user’s session data, orundefinedif not logged in. For data-source passport sessions (e.g., Email Verification, Fliplet Login), this contains the flat column values from the user’s row in the authentication data source (e.g.,user.Email,user.Role). For SAML2 sessions, it contains the assertion attributes.file(Object) — the resource being accessed. This is the plain Sequelize model of either a file or a folder, depending on the operation. See Thefileobject for available properties.DataSources(Function) — server-side library for reading data from other data sources. See Reading data from other Data Sources.
Script execution has a 3-second timeout. Async operations (like DataSources queries) are supported via await.
Here is an example covering multiple scenarios:
// Check the operation type
if (type === 'read') {
// Any logged-in user can read
if (user) {
return { granted: true };
}
return { granted: false, message: 'You must be logged in to access this file' };
}
if (type === 'create') {
// Only editors can upload
if (user && user.Role === 'Editor') {
return { granted: true };
}
return { granted: false, message: 'Only editors can upload files' };
}
if (type === 'update' || type === 'delete') {
// Only the original uploader can modify or delete.
// file.userId is the Fliplet user ID of who uploaded the file.
// user.id is the authenticated user's Fliplet user ID (not a data source column).
if (user && file && file.userId === user.id) {
return { granted: true };
}
return { granted: false, message: 'You can only modify files you uploaded' };
}
// Deny by default if none of the conditions above matched
return { granted: false };
Granting access
Return { granted: true } to allow access, or { granted: false } to deny. You can include a message when denying for debugging purposes (e.g., { granted: false, message: 'Only managers can access report files' }).
Important: You must return an object — bare boolean values (e.g., return true) are not supported and will be treated as a denial. Always use return { granted: true } or return { granted: false }.
The file object
The file variable in custom scripts contains the plain Sequelize model of the resource being accessed — either a file or a folder. The available properties depend on the resource type.
For files:
{
id: 456,
name: "team-photo.jpg",
contentType: "image/jpeg",
size: [640, 480], // Image dimensions [width, height] — only populated for images
mediaFolderId: 12, // ID of the parent folder
appId: 789,
userId: 42, // ID of the user who uploaded the file
isEncrypted: false,
metadata: {},
accessRules: null, // This file's own rules (if any)
createdAt: "2025-06-15T10:30:00Z",
updatedAt: "2025-06-15T10:30:00Z"
}
For folders (when the operation targets a folder, e.g., create for uploading into a folder):
{
id: 12,
name: "2025",
parentId: 5, // ID of the parent folder
appId: 789,
metadata: {},
accessRules: null,
createdAt: "2025-01-10T08:00:00Z",
updatedAt: "2025-01-10T08:00:00Z"
}
Important: The file object does not include related records (folder details, app name, uploader email). To look up related data, use the DataSources library or match on the available IDs (userId, mediaFolderId, appId).
For create operations (file upload), file contains the metadata available from the upload request (e.g., name, contentType). Fields like id and createdAt are not yet available.
Restrict file types on upload
if (type === 'create') {
var allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (file && allowedTypes.indexOf(file.contentType) === -1) {
return { granted: false, message: 'Only JPEG, PNG, and PDF files are allowed' };
}
return { granted: true };
}
// This rule does not grant access to other operation types
Reading data from other Data Sources
Custom rules can query other data sources using the DataSources server-side library. The API is identical to data source custom rules — refer to that page for the full find and findOne reference, supported query operators, and usage examples.
These reads run at server level and bypass all security rules on the target data source. Connect using the data source ID (number) or name (string):
if (type === 'create') {
if (!user) {
return { granted: false, message: 'You must be logged in to upload files' };
}
var permission = await DataSources('Permissions').findOne({
where: { Email: user.Email, CanUpload: 'Yes' }
});
if (permission) {
return { granted: true };
}
return { granted: false, message: 'You do not have upload permissions' };
}
Use DataSources('Name') (data source name) instead of DataSources(123) (ID) in custom scripts. Data source names are preserved during app clone, so name-based lookups continue to work without manual remapping.
REST API endpoints
To manage access rules programmatically, see the Media access rules API for endpoints to get and set rules on files, folders, and apps.