Homepage

Setting Up an Endpoint and Understanding Files Structure

Last edit: Apr 22, 2025

In the previous step, we created the schema definition file contact.yml and the GraphQL mutation file create.graphql, and we confirmed that data can be successfully saved using the mutation. At this stage, your app\ directory structure should look like this:

app
├── graphql
│   └── contacts
│       └── ✅ create.graphql
├── schema
│   └── ✅ contact.yml
└── views
    └── pages
        └── index.html.liquid

Now we’re going to set up an endpoint that to handle form submissions and utilize the GraphQL mutation.

We first need to create the /contacts/create endpoint. This involves setting up the /contacts/create page.

Create the contacts directory

In the app/views/pages directory, create a new directory named contacts:


mkdir app/views/pages/contacts

Create the create.liquid file

Navigate into the contacts directory and create a file named create.liquid. Add the following content to the file:

app/views/pages/contacts/create.liquid

---
method: post
---

Hello {{ context.params }}

{% graphql result = "contacts/create",
  variables = {
    email: context.params.contact.email,
    body: context.params.contact.body
  }
%}

This file sets up a POST endpoint and displays the parameters submitted through the form.
It also triggers the create.graphql mutation, with an explicit mapping of values from {{ context.params }} to the required GraphQL variables.
We will improve this implementation in the upcoming steps, but for the sake of consistency, we want to have a fully working version that utilizes all the components we've created so far.

We are going also to explain what {{ context.params }} is doing soon, in the Understanding context.params chapter.

Test the endpoint

To test if the endpoint is working correctly, go to the homepage of your Contact Us form. Enter test data into the fields and click on the Send button.


Testing the Contact Us form with initial test data

You should see a response similar to the following:

Hello {"authenticity_token":"rPROtKzFREOlK2WE89sNi0SOySB65jawjwWdxiEsKrDWK-kP6SbXDkgpK2COHCYjBpxVJ7lLADn-6hJCnbuNVQ","contact":{"email":"[email protected]","body":"This is my test message"},"slug":"contacts","slug2":"create","format":"html"}

In the response, you will see authenticity_token and contact objects, which include the email and body provided in the form.
This response comes from {{ context.params }} in our create.liquid.

Understanding file structure

Let's stop here to explain how in PlatformOS, file names and their structure aren't random — they follow a convention-based approach to connect features automatically without extra configuration.
Here's a breakdown of why the file names must correspond:

Convention Over Configuration

Instead of writing lots of configuration files to connect parts of your app, PlatformOS assumes standard locations and names work together automatically.
PlatformOS knows that a POST request to the URL /contacts/create should be handled by the file app/views/pages/contacts/create.liquid because the file path matches the URL and the file contains method: post in its frontmatter.
Similarly, the GraphQL mutation finds the contact data structure because table: contact in create.graphql matches the filename app/schema/contact.yml.
You don't need a separate model mapping file.

Automatic Variable Mapping

PlatformOS automatically takes incoming data (like from form submissions) and makes it available as variables within your code.
When the form in index.html.liquid is submitted, the fields named contact[email] and contact[body] are sent. In create.liquid, this data automatically becomes available in the {{ context.params }} object.
This allows you to easily access context.params.contact.email and context.params.contact.body to pass them to the GraphQL mutation create.graphql which expects $email and $body variables.

Routing and Page Views

The structure of your app/views/pages/ directory directly defines the URL routes for your application.
The existence of app/views/pages/contacts/create.liquid automatically creates a route accessible at /contacts/create.
There's no need for a separate routes file; the directory structure is the routing configuration.

Directory Structure Overview

Let's take a look at our current directory structure:

app
├── graphql
│   └── contacts
│       └── 📄 create.graphql     ← Defines how contact data is saved
├── schema
│   └── 📄 contact.yml            ← Defines data structure (email, body)
└── views
    └── pages
        ├── contacts
        │   └── 📄 create.liquid  ← Handles POST requests from the form
        └── 📄 index.html.liquid  ← Renders the contact form

How These Files Work Together

  • index.html.liquidContact Form

This file renders the initial contact form.

<form action="/contacts/create" method="post">

It defines action="/contacts/create" and method="post".

This tells the browser that when the form is submitted, it should send a POST request to the URL /contacts/create.
It also defines two input fields for the email and message (body):

<input type="text" name="contact[email]" id="email">
<textarea name="contact[body]"></textarea>
  • /contacts/create - Routing the Request

PlatformOS receives the POST request for /contacts/create.
It looks inside app/views/pages/ for a structure matching the URL path contacts/create.
It finds the directory contacts and the file create.liquid.
The URL path /contacts/create maps directly to the file path app/views/pages/contacts/create.liquid.

  • /contacts/create.liquidRequest Handler

PlatformOS checks create.liquid to see if it's configured to handle POST requests.
The frontmatter --- method: post --- explicitly tells that this file is the handler for POST requests to its corresponding URL /contacts/create.
The method: post in the frontmatter matches the method="post" from the HTML form. If the method didn't match (e.g., the form sent a GET request), this file wouldn't be executed for that request.
When a POST request is made, the form fields are automatically passed into the {{ context.params }} global object.
Then, the GraphQL mutation defined in graphql/contacts/create.graphql is explicitly triggered using variables extracted from this object.

{% graphql result = "contacts/create",
  variables = {
    email: context.params.contact.email,
    body: context.params.contact.body
  }
%}

  • contact.ymlSchema Definition

This file defines what a contact record looks like. It specifies that it has email and body properties.
The filename contact.yml defines the name of the data model or table as contact.
This contact name is used elsewhere (specifically in the GraphQL mutation) to refer to this specific data structure.

  • create.graphqlGraphQL Mutation

This file contains the logic to create a new contact record in the database.
The location graphql/contacts/create.graphql logically groups this operation under "contacts" and the action "create". While not strictly enforced by routing like the views/pages files, it's the standard convention to easily find the relevant mutation.
The mutation itself explicitly references the contact table and its data structure defined in contact.yml.
It expects variables ($email, $body) and maps them to properties (name: "email", name: "body"), which correspond to the properties defined in contact.yml.
The form field names contact[email] and contact[body] in index.html.liquid use a convention (model_name[property_name]).
When submitted, they are automatically mapped to the variables expected by the GraphQL mutation.

Summary

Component File Name Why It Matters
Schema contact.yml Defines contact table for the DB
Mutation create.graphql Mutates records in contact table
Form Action Handler create.liquid Handles POST request to /contacts/create
Form Page index.html.liquid Calls correct form action

Developer guide icon

Read more about the Directory structure.

Questions?

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

contact us