Complete guide to Laravel and Prepr personalization

Estimated duration: 20 minutes

This guide shows you how to connect Prepr to a Laravel project including styling and personalization. You can then customize this project to fit the requirements for your web app.

At the end of this guide, you’ll have a working website with personalization like the image below. On the left, you can see a site connected to Prepr with a home page. On the right is the same page that has been personalized for a specific target audience (Beginner bakers).

web app end result

If you can't wait and want to skip ahead, clone the repository on Github (opens in a new tab) to run the demo locally or visit our demo website (opens in a new tab) to see a personalization example in action. The code referenced in this guide is a simplified subset of the Page pattern in the repo so there'll be some differences.

Introduction

This is a step-by-step guide that shows you how to set up a working front-end app with personalization. This guide uses Laravel 11 as a PHP web app framework and connects to Prepr content that’s set up with personalization.

You are going to build this front end in the following steps:

Prerequisites

You need to have the following setup before you connect your Laravel project to Prepr.

Step 1: Create a new Laravel project

Before creating your first Laravel project, you should ensure that your local machine has PHP and Composer (opens in a new tab) installed. If you are developing on macOS, PHP and Composer can be installed via Homebrew (opens in a new tab). In addition, we recommend installing Node and NPM (opens in a new tab). If you want to learn more about how to install Laravel for your system, check out the Laravel docs (opens in a new tab).

This guide is based on Laravel 11. The instructions below will guide you on how to create an empty Laravel project for your app. If you have an existing Laravel 11 project then you can skip this step.

  1. Open a terminal and execute the following command to create a new Laravel project called prepr-personalization:
composer create-project laravel/laravel prepr-personalization
  1. When the project is successfully created, go to the prepr-personalization folder, the root directory of the project, and run the project with the following commands in the terminal:
cd prepr-personalization
php artisan serve
  1. You should now be able to view your app on your localhost, for example, http://localhost:8000/.
  2. Open your Laravel project with your preferred code editor.
  3. Go to the resources/views subfolder and update the welcome.blade.php file with the following code to display your app:
./resources/views/welcome.blade.php
<div>
    <h1>My home page</h1>
</div>

You should now see something like the image below on your localhost.

New project

Step 2: Install Tailwind

Tailwind is a CSS framework that simplifies front-end styling. We use a Laravel Tailwind module (opens in a new tab) for the styling in this project.

  1. Execute the following commands in the terminal to install Tailwind and to generate the config files:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
  1. Add the templates paths to the tailwind.config.js as follows:
./tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
 
  // Add template paths
  content: [
    "./resources/**/*.blade.php",
    "./resources/**/*.js",
    "./resources/**/*.vue",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. Go to the resources/css folder and update the app.css file with the Tailwind directives as follows:
./resources/css/app.css
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. Test the Tailwind installation by adding styling to the heading in the welcome.blade.php file as follows:
./resources/views/welcome.blade.php
<head>
 
    <!-- import app.css and app.js-->
    @vite('resources/css/app.css')
    @vite('resources/js/app.js')
</head>
<body>
  <div>
    <!-- Add tailwind class to include styling in the heading -->
    <h1 class="mb-4 bg-black text-3xl font-extrabold text-white md:text-6xl lg:text-6xl">
      My home page
    </h1>
  </div>
</body>

Vite (opens in a new tab) is a modern front-end build tool used for bundling CSS and JavaScript.

  1. Run the following command to compile the tailwind files.
npm run dev

Now, when you refresh the page, you should see a styled heading in your app like in the image below:

styled example

Step 3: Create a static page

In this step, you'll create a static version of the page including two front-end components. Later, we update them to fetch the corresponding data dynamically from Prepr.

As you can see in the image below, we have a Home page content item in the demo data in Prepr. The Home page has all the elements of the page in a Stack field. The stack makes it easy for an editor to set up their page content in a flexible structure for the front end. Check out the page pattern doc for more details on how to use the Stack field.

For this project, we are going to implement this page and two components: Page header and Image and text. In this step, we will first implement these as static components. Later in the guide, we'll retrieve them from Prepr to make them dynamic.

Home page content item

Create a route

To get the Laravel app to display a page, you need to create a route to this page. In Laravel, you can register web routes for your application in the web.php file in the routes folder. Update the web.php file with the route to your main page:

./routes/web.php
<?php
use Illuminate\Support\Facades\Route;
 
// Include the PageController class
use App\Http\Controllers\PageController;
 
// Add a route to the PageController function
Route::get('/', [PageController::class, 'index']);

Now that the route is created, construct the page.

Construct the page

  1. Create a controller to construct the page by executing the following command:
php artisan make:controller PageController
  1. Go to the subfolder App/Http/Controllers and update the PageController.php file with the following code to construct the page:
./App/Http/Controllers/PageController.php
<?php
 
namespace App\Http\Controllers;
 
class PageController extends Controller
{
    public function index()
    {
       // Return the page view
      return view('pages.index');   
    }
}

When you refresh the app in your browser, you see an error like the image below.

no view error

This is because you don't have a view defined for the layout of the page yet. In the next step, you'll create the view to resolve this error and display the page.

Create a view

Go to the resources/views subfolder and create a new folder called pages. Move the welcome.blade.php that you updated previously to this folder and rename it to index.blade.php. Update it with the following code to set static values.

./resources/views/pages/index.blade.php
<!doctype html>
<html>
  <head>
    @vite('resources/js/app.js')
    @vite('resources/css/app.css')
  </head>
  <body>
    <div class="min-h-screen pt-24 antialiased md:pt-0">
      
      <!-- Display the components with static data -->
       <x-page-header
          :data="[
            'heading' => 'Heading',
            'image'=> [
              [
                'url' => 'https://placehold.co/1320x400?text=Page%20header%20image'
              ]
            ],
            'text' => 'Lorem ipsum',
            'cta_label' => 'Label'
          ]"
      />
      <x-image-and-text 
        :data="[
          'title'=> 'Static text title',
          'text'=> 'Static text body',
          'image'=> [
            [
              'url'=> 'https://placehold.co/150x150?text=Image%20and%20Text%20image'
            ]
          ],
          'image_position'=> 'Right',
        ]"
      />
    </div>
  </body>
</html>

Now that static values are set, let's add the components.

Add components

Add the components to the front end as follows:

  1. Go to resources/views and create a components folder with new files called page-header.blade.php and image-and-text.blade.php.
  2. Copy the following code into the page-header.blade.php file to construct the page header:
./resources/views/components/page-header.blade.php
<!-- Set the background image URL to the value in the data variable -->
<section style="background-image: url({{ data_get($data,'image.0.url') }})" class="my-12 flex items-center h-[600px] bg-no-repeat bg-cover">
    <div class="container mx-auto md:px-0">
        <h1 class="mb-4 text-3xl font-extrabold leading-none tracking-tight text-white md:text-6xl md:text-5xl lg:text-6xl">
 
            <!-- Set the page header heading to the value in the data variable -->
            {{ data_get($data,'heading') }}
        </h1>
        <p class="mb-8 text-base font-normal text-white md:text-lg lg:text-xl">
 
            <!-- Set the Page header text to the value in the data variable-->
            {{ data_get($data,'text') }}
        </p>
        <a href="#" data-modal-target="popup-modal" data-modal-toggle="popup-modal" class="inline-flex items-center px-6 py-3.5 text-sm font-medium text-center text-white rounded-lg bg-violet-700 hover:bg-violet-800 focus:ring-4 focus:outline-none focus:ring-violet-300">
 
            <!-- Set the CTA label to the value in the data variable -->
            {{ data_get($data,'cta_label') }}
            <svg
                    aria-hidden="true"
                    class="w-5 h-5 ml-2 -mr-1"
                    fill="currentColor"
                    viewBox="0 0 20 20"
                    xmlns="http://www.w3.org/2000/svg"
            >
              <path
                      fill-rule="evenodd"
                      d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
                      clip-rule="evenodd"
              ></path>
            </svg>
        </a>
    </div>
</section>
  1. Copy the following code into the image-and-text.blade.php file to construct the Image and text component:
./resources/views/components/image-and-text.blade.php
<div class="container mx-auto md:px-0">
    <section class="bg-white">
        <div class="items-center max-w-screen-xl gap-8 px-4 py-8 mx-auto xl:gap-16 md:grid md:grid-cols-2 sm:py-16 lg:px-6">
 
             <!-- Check the image position and set the position and image URL from the data variable-->
            <img class="{{ data_get($data,'image_position') === 'Right' ? 'order-last' : '' }} object-cover w-full rounded-lg shadow-xl  h-60 md:h-96" src="{{ data_get($data,'image.0.url') }}" alt="dashboard image"/>
            <div class="mt-8 md:mt-4 md:mt-0">
                <h2 class="mb-4 text-xl font-extrabold tracking-tight text-center text-gray-900  md:text-left md:text-4xl">
 
                    <!-- Set the title to the value in the data variable -->
                    {{ data_get($data,'title') }}
                </h2>
                <p class="font-light text-center text-gray-500  md:mb-6 md:text-left md:text-lg">
 
                    <!-- Set the body text of the Image and text component to the value in the data variable-->
                    {{ data_get($data,'text') }}
                </p>
            </div>
        </div>
    </section>
</div>

Now that the components are built, refresh the page. You should now see something like the image below on your localhost.

Static site

Great work on getting your static web app working! Let's continue the following steps to make it dynamic by including data from Prepr.

Step 4: Install the Laravel GraphQL SDK

The Laravel GraphQL SDK (opens in a new tab) is an integration tool that helps to retrieve Prepr CMS data with GraphQL. The instructions below show you how to install the Laravel GraphQL SDK so that you can execute GraphQL queries to request data from the Prepr API.

  1. Execute the following command in the terminal:
composer require preprio/laravel-graphql-sdk
  1. Update the services.php file in the config folder to add the Prepr endpoint service:
./config/services.php
<?php
 
return [
 
  'postmark' => [
        'token' => env('POSTMARK_TOKEN'),
    ],
 
  'ses' => [
      'key' => env('AWS_ACCESS_KEY_ID'),
      'secret' => env('AWS_SECRET_ACCESS_KEY'),
      'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
  ],
 
  'resend' => [
      'key' => env('RESEND_KEY'),
  ],
 
  'slack' => [
      'notifications' => [
          'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
          'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
      ],
  ],
  /*
  Include the Prepr endpoint service
  */
  'prepr' => [
      'endpoint' => env('PREPR_ENDPOINT'),
      /*
        Optional timeout settings
      */
      'timeout' => env('PREPR_TIMEOUT'),
      'connect_timeout' => env('PREPR_CONNECT_TIMEOUT')
  ],
];
  1. Next, assign the URL of the endpoint. This URL includes the access token for the environment you want to query from. Get this URL by logging into your Prepr account:
    a. Go to Settings → Access tokens to view all the access tokens.
    b. Copy the API URL value from the GraphQL Production access token to only retrieve published content items.

    access token list

    Use the GraphQL Production API URL to request published content items for your live app and use the GraphQL Preview value to make a preview of unpublished content items for your content editors.

  2. Update the .env file in the root directory of your project by adding the Prepr endpoint below.

./.env
PREPR_ENDPOINT=<YOUR-PREPR-API-URL>
 
# Optional variables for custom timeout values, Default Laravel defaults are 30 and 10 
PREPR_TIMEOUT=<YOUR-TIMEOUT-IN-SECONDS>
PREPR_CONNECT_TIMEOUT=<YOUR-CONNECT-TIMEOUT-IN-SECONDS>

Now refresh the app in your browser, if your app runs without errors, then the setup above was done correctly. The next step is to fetch content from Prepr using the installed GraphQL SDK.

Step 5: Make the page dynamic

Now that the Laravel GraphQL SDK is installed and connected to Prepr, let's fetch the page content from Prepr.

First, create a query to get the content from Prepr as follows:

  1. In the app folder, create a Queries subfolder. In this folder, create a file named get-page-by-slug.graphql and add the following query to this file to retrieve a page by its slug:
./app/Queries/get-page-by-slug.graphql
query ($slug: String) {
  Page(slug: $slug) {
    _id
    title
    _slug
 
    # Retrieve the stack and two components: 
    # Page Header and Image and Text 
    stack {
      __typename
      ... on PageHeader  {
        heading
        cta_url
        cta_label
        image {
          url(width: 1600)
        }
        _id
        text
      }
      ... on ImageAndText {
        image {
          url(width: 800)
        }
        text
        title
        image_position
        _id
      }
    }
  }
}

You can create and test GraphQL queries using the Apollo explorer (opens in a new tab) from Prepr. Open the API Explorer from the Article content item or the access token that you copied previously.

  1. Test the query by going to App/Http/controllers and adding the following code to the PageController.php file. This code executes the query with a slug value and prints the query results to the front end.
./App/Http/controllers/PageController.php
<?php
 
namespace App\Http\Controllers;
 
// Include Http to make the query request
use Illuminate\Support\Facades\Http;
 
class PageController extends Controller
{
    public function index()
    {
        // Make the query request
        $response = Http::prepr([
            'query' => 'get-page-by-slug',
            'variables' => [
                'slug' => 'home',
            ],
        ]);
  
        // Print the query response to the browser
        dump(json_decode($response, true));
 
        // Return the page view with query response
        if (data_get($response->json(), 'data.Page')) {
            return view('pages.index', [
                'page' => data_get($response->json(), 'data.Page'),
            ]);
        }
    }
}

If you’re using preloaded demo data in your Prepr environment as mentioned above in the Prerequisites section, you should have one home page with the slug home. The query will retrieve the id, title, slug, and a stack containing a page header and two image and text blocks with their own fields.

When you refresh the page on the browser, the response to the query looks something like this:

query results in browser

Now that the query has been created and retrieves the data successfully, let's display the specific page content from the query results.

  1. Go to the resources/views/pages and update the index.blade.php to display the data retrieved from the query instead of the static data.
./resources/views/pages/index.blade.php
<head>
    @vite('resources/js/app.js')
    @vite('resources/css/app.css')
</head>
<body>
 
<!-- Loop through elements in the stack field and assign the matching components-->
  <div class="min-h-screen pt-24 antialiased md:pt-0">
    @if(data_get($page,'stack'))
      @foreach(data_get($page,'stack') as $element)
        @if(data_get($element,'__typename') === 'PageHeader')
          <x-page-header :data="$element"/>
        @elseif(data_get($element,'__typename') === 'ImageAndText')
          <x-image-and-text :data="$element"/>
        @endif
      @endforeach
    @endif
  </div>
</body>

Now when you refresh the page, you'll see something like the image below:

Dynamic home page

Congratulations! You have successfully connected your web app to Prepr. Go to your Home page content item in Prepr and make some content updates to see the effects on your web app immediately. For example, reorder the page header and the image and text blocks.

Step 6: Set up personalization

Now that you have a working web app with styling and content from Prepr, add personalization. Prepr lets you create personalized experiences with Adaptive content. With Adaptive content, content editors can make different versions of content for various customer segments. You can then show the right content to each visitor based on their segment giving them a personalized experience.

For this project, assume the scenario below for the baking community web app.

A user comes across a campaign for beginner bakers in a social post. They click the link in the post and get directed to your page. The URL link from this post includes a special UTM tag that identifies the specific campaign. Your web app uses the UTM tag to retrieve the adaptive content and display the personalized page for the beginner baker.

Start by personalizing the page header in Prepr.

Personalize the page header in Prepr

You want to show a different version of the page header when a beginner baker visits the page. The setup for personalization in Prepr is made up of two parts. First you need to create customer segments and then add the personalized content for that segment. If you are using the preloaded demo data then you already have some customer segments defined like in the image below.

Preloaded customer segments

For this example, use the Beginner bakers segment. For more details, check out the Customer segments doc.

Go to your Home page content item in Prepr and add personalization as follows:

  1. Go to the page header in the stack and click the icon on the page header to personalize the element.
  2. Duplicate the page header. The original page header will automatically be used as the fallback content and linked to All other customers.
  3. Update the image and text for the Beginner bakers page header.
  4. Click the Add segment link and choose the Beginner bakers segment for this header and save the page.

personalize header

Now that the adaptive content is ready, add some code to your front end.

Get the segment Id from the UTM tag

To fetch the personalized page, we need to get the UTM tag from the URL. In our example, the UTM tag matches our segment ID for Beginner bakers.

Update the PageController.php file with the following code to get the UTM campaign value and to fetch the personalized content.

./App/Http/Controllers/PageController.php
<?php
 
namespace App\Http\Controllers;
 
// Make Request available to get URL parameters
use Illuminate\Http\Request;
 
use Illuminate\Support\Facades\Http;
 
class PageController extends Controller
{
    // Include Request in the function to get URL parameters
    public function index(Request $request)
    {
        // Assign variable to the utm_campaign URL parameter
        $segment = $request->get('utm_campaign');
                
        // If no UTM parameter is set, then default the value to 'None'
        if (empty($segment)) {
            $segment = 'None';
        }
 
        $response = Http::prepr([
            'query' => 'get-page-by-slug',
            'variables' => [
                'slug' => 'home',
 
                // Include the segment in the query request
                'segment' => $segment,
            ],
        ]);
 
        // Print the query response to the console
        dump(json_decode($response, true));
 
        return view('pages.index', [
            'page' => data_get($response->json(), 'data.Page'),
        ]);
    }
}

Once that's done, update the query with the segment ID that you just defined.

Update the query

The current page query returns a page that is not personalized. We need to set a segment parameter in the query to get the personalized page. In our example, this value has to match the segment ID for Beginner bakers.

Update the query in the get-page-by-slug.graphql with this parameter and additional personalization fields as follows:

./app/Queries/get-page-by-slug.graphql
# Add variable for the segment
query ($slug: String, $segment: String!) {
  Page(slug: $slug) {
    _id
    title
    _slug
 
    # Retrieve a variant based on the provided segment
    stack (personalize_for_segments: [$segment]) {
      __typename
      ... on PageHeader  {
        heading
        cta_url
        cta_label
        image {
          url(width: 1600)
        }
        _id
        text
 
        # Retrieve system fields with personalization info
        # The _context fields hold info about the personalization:
        #  - "kind" shows that this component includes personalization
        #  - "group_id" is a unique id to identify a specific personalized grouping for multiple personalized components
        #  - "segments" is the segment that the variant is applicable to.
        #  - "countries" is filled if the variant is applicable to specific countries.
        _context {
          kind
          group_id
          segments
          countries
        }
      }
      ... on ImageAndText {
        image {
          url(width: 800)
        }
        text
        title
        image_position
        _id
      }
    }
  }
}

Now that the query has been updated to include personalized content, let's fetch the personalized data.

View in the browser

When you view the site as is, you should still see the page without personalized content. Refresh the page for the updated results.

personalized query results

Add the UTM tag to the URL, for example: http://localhost:8000/?utm_campaign=beginner-bakers

Now you can see the personalized content such as in the image below.

Personalized page end result

All done

Congratulations! You have a working Laravel web app connected to Prepr including personalization. This is a very simple example. You can personalize more elements like call-to-actions, featured articles and more. Also you can create dynamic segments based on what visitors do in your app. The options are limitless.

Next steps

To learn more on how to expand your project, check out the following resources:

Was this article helpful?

We’d love to learn from your feedback