Homepage

Modules Introduction

Last edit: Sep 19, 2024

Modules allow code reuse and sharing, while optionally protecting the Intellectual Property (IP) of creators by using the private folder. They can also serve as a namespace to group various files (GraphQL, pages, partials, etc.) related to a larger functionality of a project, all within one directory.

Note

By default, module files are not deleted during pos-cli deploy or sync to avoid breaking your application in cases where a module contains private files to which you might not have access. You can change this by adding the module name you want to synchronize with your codebase to modules_that_allow_delete_on_deploy in app/config.yml. This behavior is expected to be improved in the near future.

Partner Portal as a Module Registry

Modules can be uploaded to the Modules Marketplace in Partner Portal to make the process of installing, downloading, and updating them easier. Any module uploaded to Partner Portal can be installed via the pos-cli modules install command, and you will be able to download the source code (both to browse the source code and to leverage platformos-check built-in navigation).

Directory structure

In your codebase, the convention is that modules that are external to the application needs to be at the same level as the app directory. Applications tusing such a module should never modify any of the module's files. See Overwritting a module file to learn about modifying a module file.

Modules that are internal to the application, i.e., those that serve as a namespace, should be placed inside the app directory. We'll focus on external modules, meaning we'll assume that they are installed via the pos-cli modules install command, and we'll use app only for Overwriting a module file.

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

app
modules
└── MODULE_NAME
    ├── 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 that does not include any public or private folders (for example: modules/admin/app.css instead of modules/admin/private/app.css). This means that developers should not create files with the same name in both public and private 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 can still have the app folder at the top level (this code will not be part of the module but can be used during development).

This mechanism allows creators to share their module code, and 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:

app
└── views
   └── pages
       ├── index.liquid
modules
└── admincms
     ├── private
     │   └── graphql
     │       ├── foo.graphql
     │       └── bar.graphql
     └── public
         └── views
              └── pages
                  └── foo.liquid

Overwriting a module file

Although not ideal, in real-life scenarios, it is sometimes necessary to overwrite a module file. If you need to overwrite a module file, for example, modules/foo/public/views/pages/bar.liquid, you can do so by creating a new file with the exact same path and placing it inside the app directory:

app
└── modules
    └── foo
         └── public
             └── views
                  └── pages
                      └── bar.liquid
modules
└── foo
     └── public
         └── views
              └── pages
                  └── bar.liquid

On sync/deploy, platformOS will overwrite the file on the server side. However, in your repository, you will still have access to both files. When upgrading modules, you will be able to check which files you have overwritten and compare any changes.

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 for accessing files in modules is to prefix paths with modules/MODULE_NAME. Keep in mind that skipping the resource type applies just as it does without modules. This means that when you access a partial, you reference it as if you were in the partials directory.

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

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

The only difference is the prefix.

Examples

Assets

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

Paths for those files are prefixed with modules/MODULE_NAME

modules/admin/public/assets/app.css

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

modules/admin/public/assets/admin.js

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

Partials

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' %}

Layouts

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

modules/admin/public/views/layouts/settings.liquid
slug: admin/settings
layout: modules/admin/settings

Records

modules/admin/private/records/resume.yml
query get_resumes {
  records(
    per_page: 5
    filter: {
      table: { value: "modules/admin/resume" }
    }
  ) {
    results {
      id
    }
  }
}

Authorization Policies

modules/admin/public/authorization_policies/can_view_blog_posts.liquid

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

Within the page/form, it is referenced with a prefix:

authorization_policies:
  - modules/admin/can_view_blog_posts
graphql Query

The query looks like any other query:

modules/admin/public/graphql/get_blog_instance.graphql
query get_blog_instance(
  $current_user_id: ID
  $slug: String
  $scope: String
) {
  records(
  ...
) {
}

But it is referenced with the prefix:


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

Translations

Translations YML files for module should be placed in:

modules/admin/public/translations/

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


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

Questions?

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

contact us