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