Last edit: Oct 26, 2022

You develop your site (write code) in your codebase in your local environment.

Required directory structure

In order to correctly communicate with the platformOS engine and API, your codebase should be organized into a specific directory structure. The root directory of your project should contain the app directory.

Directory/file File type Explanation Learn more
.pos Configuration file that specifies available endpoints. Usually, you want to have at least two endpoints – staging and production. Development Workflow
assets asset Directory for static assets, like fonts, images, stylesheets, and scripts. Assets
authorization_policies Liquid Directory for .liquid files that contains Authorization Policy configuration files. Authorization Policies define rules that control whether a user has access to a form or page. Authorization Policies
schema YAML Directory for .yml files that define Tables. Tables define custom objects, including their fields and associations and allow you to create persistence containers for your custom forms. Records
emails Liquid Directory for .liquid files that define Email notifications. Notifications
api_calls Liquid Directory for .liquid files that define API calls notifications. Notifications
smses Liquid Directory for .liquid files that define SMS notifications. Notifications
graphql GraphQL Directory for .graphql files, each defining one GraphQL Query. GraphQL queries retrieve information from or insert information into the databases. Retrieved information is made available in Liquid. GraphQL
views/pages Liquid Directory for files that define pages, the most essential building blocks of a platformOS site. Views
views/layouts Liquid Directory for layouts. Layouts are wrappers around your page content. They ensure consistent outer content for similarly designed pages. Views
views/partials Liquid Directory for partials – reusable snippets of Liquid code usually used to render HTML. Views
translations YAML Directory for .yml files of Translations for multilingual sites, also used to define date format, or flash messages. Each file is a map of translations which can be used in your pages via Liquid. Translations
user_profile_types YAML Directory for .yml files that define User Profiles. Each instance includes one user role, called ‘default’, so each instance needs to include the default.yml file inside this directory. Users
user.yml YAML A yml file containing properties for all users Users
config.yml YAML A yml file containing configuration flags for backwards incompatible changes Config

Files in your codebase

Views: pages, layouts, and partials

Views are divided into three categories in your codebase, each with a mandatory subdirectory: layouts, pages, and partials.

Pages are the most essential building blocks of a platformOS site, that define content displayed at a given path. Each page is represented by a single file with a .liquid extension.

platformOS allows Liquid pages to be used to create many different types of endpoints beyond HTML, such as JavaScript, JSON, PDF, RSS, XML, etc. The page type can be specified in the page configuration.

Layouts are Liquid views that store code that would normally repeat on a lot of pages and is surrounding page content (e.g. header, footer). Without layouts, pages would share a lot of duplicated code, and changing anything would become a very time consuming and error prone process. You can create as many layouts as you need, and decide which page uses which layout.

Partials (partial templates) allow you to easily organize and reuse your code by extracting pieces of code to their own files. They help you improve code readability and follow the principle of DRY (Don’t Repeat Yourself). You can parameterize partials and use them in various places, e.g. layouts, pages, Authorization Policies, Forms.

Example directory structure of views with sample files:

└── views
    ├── layouts
    │   └── 1col.html.liquid
    ├── pages
    │   ├── index.html.liquid
    │   └── unauthorized.html.liquid
    └── partials
        └── layout
            ├── constants.liquid
            ├── foot.liquid
            ├── footer.liquid
            ├── head.liquid
            └── header.liquid

Developer guide icon

Follow our step-by-step tutorials to learn more about pages and layouts.


Assets are files that can be served by an HTTP web server without any backend/server processing. They are usually Javascript files, stylesheets (CSS), documents (HTML, pdf, doc), fonts, or media (audio, video) files.

Although only the assets directory is required and you can put your assets there, we recommend you further organize your assets into subdirectories inside the assets directory, e.g. images, scripts for Javascript files, styles for CSS files, etc. This is also how the pos-cli init command will create the codebase.

Example directory structure of assets with sample files:

└── assets
    ├── fonts
    ├── images
    │   ├── favicon.ico
    ├── scripts
    │   ├── app.js
    │   ├── vendor.js
    └── styles
        ├── app.css
        └── vendor.css

Developer guide icon

Follow our step-by-step tutorials to learn more about assets.


Just like in every web application, HTML forms are essential in platformOS. Because using plain HTML forms can get difficult to maintain with complex data structures, we provide multiple tools that make working with forms much easier.

Forms are the main tool for rendering forms, persisting data, and sending notifications (email/SMS/API) in a secure and customizable way.

They give you full control when defining:

  • which fields for a defined resource can be persisted
  • what authorization rules apply to be able to submit the form (i.e. if you want to edit a comment, you might want to specify that only the creator or the administrator is able to do it)
  • what should happen when the form is submitted successfully (i.e. without validation errors), e.g. send an email/SMS Notifications or API call to a third party system
  • where the user should be redirected

On top of that, you can define callbacks (either synchronous or asynchronous) for further modifications to the system using GraphQL mutations. For example, you can define a signup form that creates User records, and if the user input is valid, also creates a few sample products for them, so that they don’t have to start from scratch.

Example directory structure of forms with sample files:

└── forms
    ├── create_contact_request_form.liquid
    └── feedback_form.liquid

Developer guide icon

Follow our step-by-step tutorials to learn more about forms.


Users are accounts that any of your users can have. Users are identified by their unique email addresses. You can define properties for all users in the user.yml file.

User Profiles are roles in the application. Each User Profile can be associated with any number of Properties and Tables. All users are assigned a User Profile named Default. The difference between User Profile and Table is that each user can have maximum one profile of a given name (but can have many different profiles), whereas they can have many custom records with the same name. From a practical perspective, it is very simple to create an upsert operation on a UserProfile (i.e. create user profile, or if it exists, update it).

Example directory structure of user_profile_types with sample files:

├── user.yml
└── user_profile_types
    ├── developer.yml
    └── client.yml

Developer guide icon

Follow our step-by-step tutorials to learn more about users.


Table is an object that describes all Record objects that belong to it. Think of Tables as a custom database table, which allows you to build highly customized features. Use them to group Properties, and allow the user to provide multiple values for each of them.

Properties are fields that you attach to a User Profile, Table, etc. Think of them as custom database columns (though complex types, like attachments and images should be treated as separate database tables). We also provide some Properties to jumpstart your development.

Example directory structure of schema with sample files:

└── schema
    └── blog_post.yml

Developer guide icon

Follow our step-by-step tutorials to learn more about Tables and Properties.


Notifications are messages sent to platformOS users (including admins) when something happens. A message can be an email, SMS, or programmatic call to a 3rd party API.

Notifications can be delayed, and you can use Liquid, GraphQL, and trigger conditions to decide if a notification should be sent. They are a powerful mechanism used for example to welcome new users, follow up after they've added their first item, or reach out to them if they have been inactive for a while.

Each notification has its own directory:

  ├── api_calls
  │   └── ping_example_com_on_user_sign_up.liquid
  ├── emails
  │   └── welcome_user.liquid
  └── smses
      └── welcome_user.liquid

Developer guide icon

Follow our step-by-step tutorials to learn more about notifications.

Authorization Policies

Authorization Policies allow you to restrict access to forms and pages in a flexible way. Each form or page can have multiple policies attached to it.

Each policy is parsed using Liquid, and the system checks them in order of their appearance in the code. Depending on policy configuration, it redirects the user to a URL provided by the developer if the condition is not met or renders error status, for example 403. You can also add a flash message for the user who failed authorization.

Example directory structure of authorization_policies with sample files:

└── authorization_policies
    └── example_policy.liquid

Developer guide icon

Follow our step-by-step tutorials to learn more about Authorization Policies.


You can use platformOS to build sites in any language, and each site can have multiple language versions. Translations are yml files used for multilingual sites but also used to define date formats, flash messages or system-wide default error messages like "can't be blank".

Example directory structure of translations with sample files:

└── translations
    ├── en.yml
    └── pl.yml

Developer guide icon

Follow our step-by-step tutorials to learn more about translations.


Config is used to control the behavior of the application developed. It is a file located in app/config.yml.

flag default value Explanation
do_not_add_return_to_to_authorization_policies false By default, if the authorization policy fails and is set up to redirect, it automatically appends the return_to parameter. By setting the flag to true, the return_to parameter will not be appended
escape_output_instead_of_sanitize false By default, any variable is sanitized before the output and treated as html and corrected, which is unideal as it can change the expected output. By setting this flag to true, the output will be escaped, not sanitized. We highly recommend setting this flag to true.
liquid_raise_mode false By default, Liquid errors are displayed in line and the whole code is executed. By setting this flag, whenever there's any Liquid error - like parsing json fails, or GraphQL query receives invalid arguments, etc, a 500 page is raised instead and the rest of the code is not executed. This is generally the desired and expected behavior and we recommend setting this flag to true.
skip_elasticsearch false If you do not use the keyword argument in records/users queries or customizations/people queries, you can increase performance by avoiding indexing data in the ELasticSearch by setting this flag to true.
require_table_for_record_delete_mutation false The argument table for the record_delete mutation is optional by default, which can lead to security issues if the type of the record is not explicitly checked before executing the mutation. By setting the flag to true, you effectively make the table attribute required to avoid such vulnerability.
safe_translate false By default, the translate filter (t is an alias for it) marks the output as html_safe. This can lead to XSS vulnerability, if you provide user input as a variable, which can contain JavaScript. In such scenario, you should explicitly use another filter, translate_escape (or t_escape). By setting this flag to true, the system will automatically use the equivalent of t_escape if you provide any variable to the translation key, making your application safer by default. We highly recommend setting this flag to true.
slug_exact_match false By default, a page with slug abc will match not only example.com/abc, but also URLs like example.com/abc/1, example.com/abc/1/x and to control this behavior, you should use max_deep_level. By setting this flag to true, only example.com/abc will be matched. Additionally, you will be able to use named parameters in the URLss, like /abc/:id. We highly recommend setting this flag to true.
graphql_argument_type_mismatch_mode warning Defines behavior when the argument type in the GraphQL query is wrong. Possible values:
warning - display warning message in log
error - raise error, do not allow to deploy query
ignore - silently ignore error


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

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

Module code is split into 2 directories to protect IP. To create a module, split module code into public and private folders, and place all that code into the modules/MODULE_NAME directory.

These directories have the same structure as the standard app folder, but if developers try to download files after the module has been deployed to an Instance (pos-cli pull), they will only have access to the files from the public folder.

Example directory structure of modules with sample files:

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

Developer guide icon

Follow our step-by-step tutorials to learn more about Modules.

Contribute to this page

Github Icon


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

contact us