Modules Introduction
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 }}