Last edit: Oct 26, 2022

Modules allow code reuse and sharing, while protecting the IP of creators.

Directory structure

In your codebase, the modules directory needs to be at the same level as the app directory.

Module code can be split into 2 directories to protect IP (Intellectual Property). To create a module, split module code into public and private folders, and place all that code into the modules/MODULE_NAME directory.

    ├── public
    │   └── assets
    └── private
        └─ assets

public and private directories have the same structure as the standard app folder, but if developers try to download or preview files after the module has been deployed to an Instance, they will only be able to access the files from the public folder.

In general, when referencing files that are part of a module, platformOS uses a prefix which does not include any public or private folders (for example: modules/admin/app.css instead of modules/admin/private/app.css). This means that creators should not create files with the same name in both folders, as one will overwrite the other. This behavior gives creators flexibility: if they want to change the scope of the file, they can move it between the private and public folders without changing the code.

You can create an Instance and deploy the code as you would usually do, and you still can have the app folder on the top level (this code will not be part of the module but you can use it while developing).

This mechanism allows creators to share their module code, make it configurable (by code in the public folder), but still protect the IP in the private folder.

Example: Module directories

An example of module code split into two directories, inside the modules directory:

├── private
│   └── graphql
│       ├── get_records.graphql
│       └── get_pages.graphql
└── public
    └── views
        └── pages
            └── admin.liquid

Namespacing rules

Configuration files placed in a module are treated a bit differently than regular files.
To avoid conflicts between the code of a module and regular code (or between modules) a namespacing strategy is used.

The general rule of accessing files in modules is to prefix paths with modules/MODULE_NAME. Keep in mind that skipping resource type applies just like it does without modules. This means that when you access a partial, you type a path to it as if you were in the partials directory.

When you use app: app/views/partials/users/settings/name.liquid
Is accessed by path: users/settings/name.liquid

When you use module: modules/admin/public/views/partials/users/settings/name.liquid
Is accessed by path: modules/admin/users/settings/name.liquid

The only difference is the prefix.



You can access assets placed at assets directory in a module using asset_url filter.

Paths for those files are prefixed with modules/MODULE_NAME


<link rel="stylesheet" media="screen" href="{{ 'modules/admin/app.css' | asset_url }}">


<script src="{{ 'modules/admin/admin.js' | asset_url }}"></script>


Partials can be referenced with their shorter name - the same as the regular ones.
For example, to include partial saved as modules/admin/private/views/partials/comments.liquid, you should use:

{% include 'modules/admin/comments' %}


Layout name has the same prefix when referenced within a page:

slug: admin/settings
layout: modules/admin/settings


query get_resumes {
    per_page: 5
    filter: {
      table: { value: "modules/admin/resume" }
  ) {
    results {

Authorization Policies


name: can_view_blog_posts
redirect_to: /blog
flash_alert: Please log in to access this page.

Within page/form it is referenced with a prefix:

  - modules/admin/can_view_blog_posts
graphql Query

Query looks like any other query:

query get_blog_instance(
  $current_user_id: ID
  $slug: String
  $scope: String
) {
) {

But it is referenced with the prefix:

{%- graphql bi = 'modules/admin/get_blog_instance' -%}


Translations YML files for module should be placed in:


But when used in the module code they should be prefixed:

{{ 'modules/admin/strings.sample_string' | t }}


We are always happy to help with any questions you may have.

contact us