# Building user profile pages in Kirby 3

**URL:** <https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457>\
**Category:** Questions\
**Tags:** v3\
**Created:** [January 25, 2019, 3:02pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457 "2019-01-25T15:02:35Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![tart2000](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/tart2000/32/518_2.png) [@tart2000](https://forum.getkirby.com/u/tart2000)\
**Post date:** [January 25, 2019, 3:02pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/1 "2019-01-25T15:02:35Z")

</div>

Hi !  
I’m trying to build automatic user pages with the new Kirby.  
From what I understand, they don’t come out of the box.  
I tried to add a route like so:

```auto
return [
  'routes' => [
    [
      'pattern' => 'profile/(:any)',
      'action' => function($user) {
        $site = kirby()->site(); 	
        tpl::load(kirby()->roots()->templates() . DS . 'authors.php', array('user' => $user, 'site' => $site), false);
      }
    ]
  ]
];

```

And I made a `authors.php` template that goes with it.  
But all I get is a 404 error…  
Any help is appreciated.  
Also, I think it would make a nice addition to the cookbook.  
Because I can’t believe I’m the only one trying to do that 😉  
Thank you very much!

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [January 25, 2019, 3:21pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/2 "2019-01-25T15:21:33Z")

</div>

Think you have to return the template:

```auto
return [
  'routes' => [
    [
      'pattern' => 'profile/(:any)',
      'action' => function($user) {
        $site = kirby()->site(); 	
        return tpl::load(kirby()->roots()->templates() . DS . 'authors.php', array('user' => $user, 'site' => $site), false);
      }
    ]
  ]
];

```

---

<div class="post-metadata">

**Author:** ![tart2000](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/tart2000/32/518_2.png) [@tart2000](https://forum.getkirby.com/u/tart2000)\
**Post date:** [January 25, 2019, 3:51pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/3 "2019-01-25T15:51:48Z")

</div>

Thanks.  
I tried but still no luck.  
I’m still getting an error page ☹

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [January 25, 2019, 3:59pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/4 "2019-01-25T15:59:53Z")

</div>

Is that a single or multi-language Kirby installation?

---

<div class="post-metadata">

**Author:** ![tart2000](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/tart2000/32/518_2.png) [@tart2000](https://forum.getkirby.com/u/tart2000)\
**Post date:** [January 25, 2019, 4:26pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/5 "2019-01-25T16:26:47Z")

</div>

No it’s single language (I started from the v3 starterkit).  
But I might have to switch to multi at some point…

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [January 25, 2019, 4:32pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/6 "2019-01-25T16:32:28Z")

</div>

Hm, the route works for me…

What is in your authors.php?

---

<div class="post-metadata">

**Author:** ![tart2000](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/tart2000/32/518_2.png) [@tart2000](https://forum.getkirby.com/u/tart2000)\
**Post date:** [January 25, 2019, 4:34pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/7 "2019-01-25T16:34:02Z")

</div>

Just that:  
\<?php snippet('header') ?\>  
\<?= $user ?\>  
\<?php snippet('footer') ?\>

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [January 25, 2019, 4:49pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/8 "2019-01-25T16:49:28Z")

</div>

Returning a template from a route was never such a great deal, actually, with having to return all the data down to the snippets (your current setup would throw errors, even if it didn’t return the 404 page). For example, if your header contains the typical

```auto
<?= $page->title() ?>

```

you will get an error, because the Page object doesn’t exist.

I’d create a parent `/profile` page and then create the children as virtual pages of that parent.

---

<div class="post-metadata">

**Author:** ![tart2000](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/tart2000/32/518_2.png) [@tart2000](https://forum.getkirby.com/u/tart2000)\
**Post date:** [January 25, 2019, 7:28pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/9 "2019-01-25T19:28:44Z")

</div>

It’s weird because I remember doing something similar in Kirby2…

So what you suggest is making a ‘real’ `/profile` page with a dedicated blueprint and all? And then launching a virtual page like shown here ([https://getkirby.com/docs/guide/routing#virtual-pages](https://getkirby.com/docs/guide/routing#virtual-pages))? How would you go about passing the user id?

Once the `/profile` page exists, I’m thinking I could just pass the user id through a param and then use it to find the user…

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [January 25, 2019, 7:32pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/10 "2019-01-25T19:32:11Z")

</div>

> [@tart2000](#):
>
> Once the `/profile` page exists, I’m thinking I could just pass the user id through a param and then use it to find the user…

Yes, that would also be an option… Instead of using the parameter, you can use the the route as before, but instead of trying to load a template, return the parent page with the user as additional data for the template.

---

<div class="post-metadata">

**Author:** ![tart2000](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/tart2000/32/518_2.png) [@tart2000](https://forum.getkirby.com/u/tart2000)\
**Post date:** [January 25, 2019, 8:29pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/11 "2019-01-25T20:29:04Z")

</div>

OK I got it to work in the end by making a ‘real’ `/profile` page (with blueprint, but not listed in the panel) and passing the user id as a parameter. It’s not very ‘clean’ but it does the job… (if someone decides to erase the profile page through the panel, I’m toast…)

I’d be happy to find out if there’s a better way to do it at some point. Even though routing and stuff is a bit beyond my skillset…

Thanks a lot for bearing with me and trying to find a solution…

---

<div class="post-metadata">

**Author:** ![Perry](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/perry/32/1336_2.png) [@Perry](https://forum.getkirby.com/u/Perry)\
**Post date:** [May 16, 2020, 11:48am UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/12 "2020-05-16T11:48:17Z")

</div>

@texnixe there is now an easier way to display the user pages ?

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [May 16, 2020, 11:58am UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/13 "2020-05-16T11:58:25Z")

</div>

You can create them as virtual children of a parent as I suggested in one of the replies above. I think that’s a better option than working with routes.

See the docs: [https://getkirby.com/docs/guide/virtual-pages](https://getkirby.com/docs/guide/virtual-pages)

Only your virtual content wouldn’t be a database or csv files, but the users.

---

<div class="post-metadata">

**Author:** ![Perry](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/perry/32/1336_2.png) [@Perry](https://forum.getkirby.com/u/Perry)\
**Post date:** [May 16, 2020, 12:51pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/14 "2020-05-16T12:51:59Z")

</div>

I tried that:

**config.php**

```
  'routes' => [
          [
            'pattern' => 'mitglied/(:any)',
            'action' => function($user) {
              $site = kirby()->site(); 	
              return tpl::load(kirby()->roots()->templates() . DS . 'member.php', array('user' => $user, 'site' => $site), false);
            }
          ]
          ],

```

**template collection of users**

```
<?php foreach($kirby->users()->sortBy('name','asc') as $user):
		 
			if($user->member_type() == "member"): ?>
				<article class="grid-col-30">
				<a href="mitglied/"<? echo $user->id() ?> >
					<h2><?php echo $user->member_type() ?></h2>
					<h2><?php echo $user->name() ?></h2>
				</a>
				</article>
			<?php endif ?>

		<?php endforeach ?>

```

and added the dir **content/member**

what do I have to use the correct url to display a user page?  
**\<a href=“mitglied/”\<? echo $user-\>id() ?\> \>**

maybe it’s simpler (with my knowledge) to switch between user collection and singel user with JS

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [May 16, 2020, 1:06pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/15 "2020-05-16T13:06:01Z")

</div>

I suggested to use a model instead of this template stuff…

Create a members page in `/content` with a `members.txt`and then this model `members.php`

```auto
<?php

class MembersPage extends Page
{
  public function children()
  {
    $usersPages = [];
    $users = kirby()->users();
    foreach ($users as $key => $user) {
      $userPages[] = [
        'slug' => Str::slug($user->id()), // or username if unique
        'num' => $user->indexOf($users),
        'template' => 'member',
        'model' => 'member',
        'content' => [
          'title' => $user->username(),
           // more fields here
        ]
      ];
    }
    return Pages::factory($userPages, $this);

  }
}

```

Then you can list them in your member.php template and the children work like a normal page with their own url.

---

<div class="post-metadata">

**Author:** ![Perry](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/perry/32/1336_2.png) [@Perry](https://forum.getkirby.com/u/Perry)\
**Post date:** [May 16, 2020, 1:30pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/16 "2020-05-16T13:30:41Z")

</div>

Thank you very much, works wonderfully.

---

<div class="post-metadata">

**Author:** ![mickaeldurand](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/mickaeldurand/32/8022_2.png) [@mickaeldurand](https://forum.getkirby.com/u/mickaeldurand)\
**Post date:** [October 18, 2022, 4:04pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/17 "2022-10-18T16:04:43Z")

</div>

Hello,

I used the same principle and it works well for simple fields.

I can’t display a structure correctly.

I have an error:  
`Invalid structure data for "user" field on parent`

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [October 18, 2022, 5:19pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/18 "2022-10-18T17:19:50Z")

</div>

How did you add the structure to your content array? Please post your code!

---

<div class="post-metadata">

**Author:** ![mickaeldurand](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/mickaeldurand/32/8022_2.png) [@mickaeldurand](https://forum.getkirby.com/u/mickaeldurand)\
**Post date:** [October 19, 2022, 8:29am UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/19 "2022-10-19T08:29:42Z")

</div>

Hello,

I think I don’t know how to add the structure to the models `members.php`

My structure field is: `social_events_listing` (and to make it more complex, I have a substructure).

```auto
<?php

class MembersPage extends Page
{
  public function children()
  {
    $usersPages = [];
    $users = kirby()->users();
    foreach ($users as $key => $user) {
      $userPages[] = [
        'slug' => Str::slug($user->firstname() ." ". $user->namefamily()), // or username if unique
        'num' => $user->indexOf($users),
        'template' => 'member',
        'model' => 'member',
        'content' => [
          'user' => $user,
          'name' => $user->name(),
          'title' => $user->firstname() ." ". $user->namefamily(),
          'firstname' => $user->firstname(),
          'namefamily' => $user->namefamily(),
          'member' => $user->member(),
          'sex' => $user->sex(),
          'birthday' => $user->birthday(),
          'street' => $user->street(),
          'zip' => $user->zip(),
          'city' => $user->city(),
          'country' => $user->country(),
          'phone' => $user->phone(),
          'phonemobile' => $user->phonemobile(),
          'licence_date' => $user->licence_date(),
          'size' => $user->size(),
          'weight' => $user->weight(),
          'bio' => $user->bio(),
          'url_strava' => $user->url_strava(),
          'photo' => $user->file(),
          'social_events_listing' => $user->social_events_listing(),
          //'social_events_date' => $user->social_events_date(),
           // more fields here
        ]
      ];
    }
    return Pages::factory($userPages, $this);

  }
}

```

At the beginning I displayed all the data on a page which lists all the members with photo.

We click on the photo to display a modal with all the information.

But I preferred to create a page for each user.

Here is the part with the structure and substructure that works.

I thought it was enough to copy the same model and change something at first, but I don’t know how.

```auto
              <?php if ($user->social_events_listing()->length() > 0): ?>
              <h4 id="scrollspyHeading2-membre-<?= mb_strtolower(str_replace(' ', '', $user->name())) ?>">Participation
              </h4>

              <div class="accordion accordion-flush mb-5" id="accordionFlushExample">
                <?php 
                    $items = $user->social_events_listing()->toStructure()->flip();
                    foreach ($items as $item):   
                ?>
                <div class="accordion-item ps-0">
                  <h2 class="accordion-header" id="flush-headingOne">
                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                      data-bs-target="#flush-collapse-<?= $item->id(); ?>" aria-expanded="false"
                      aria-controls="flush-collapseOne">
                      <b>Année <?php echo $item->social_events_date() ?></b>
                    </button>
                  </h2>
                  <div id="flush-collapse-<?= $item->id(); ?>" class="accordion-collapse collapse"
                    aria-labelledby="flush-headingOne" data-bs-parent="#accordionFlushExample">
                    <div class="accordion-body">

                      <div class="alert alert-light" role="alert">
                        <?= $item->social_events_description()->kirbytext() ?>
                      </div>

                      <div class="card">
                        <ul class="list-group list-group-flush">
                          <?php
                            //Sous-liste
                            $listings = $item->social_events_listing2()->toStructure();
                            foreach($listings as $listing): 
                          ?>

                          <li class="list-group-item">
                            <div class="row">
                              <div class="col-6">
                                <h3><?= $listing->social_events_listing2_title(); ?></h3>
                              </div>
                              <div class="col-6 ms-auto text-end">
                                <div class="d-none d-lg-inline-flex tag">
                                  <span class="badge bg-light text-primary mx-1"><i class="far fa-calendar-alt"></i>
                                    <?= $listing->social_events_listing2_date()->toDate('%A %e %B %Y'); ?></span>
                                  <span class="badge bg-light text-primary mx-1"><i class="fas fa-map-marker-alt"></i>
                                    <?= $listing->social_events_listing2_city(); ?></span>
                                </div>
                              </div>
                              <div class="col-12 fst-italic">
                                <?= $listing->social_events_listing2_text()->kirbytext(); ?>
                              </div>
                              <div class="col-12">
                                <!-- Actualités ou Séjours en relation -->
                                <?php foreach($listing->social_events_listing2_related()->toPages() as $storierelated): ?>

                                <a class="btn btn-secondary text-start" href="<?= $storierelated->url() ?>">Lire
                                  <?= lcfirst($storierelated->title()) ?> <i class="fa-solid fa-chevron-right"></i></a>

                                <?php endforeach ?>
                                <!-- ./Actualités ou Séjours en relation -->
                              </div>
                            </div>
                          </li>
                          <?php endforeach ?>
                        </ul>
                      </div>

                    </div>
                  </div>
                </div>
                <?php endforeach ?>
              </div>
              <?php endif ?>

```

If it can help. This is what the file looks like: `blueprints/users/client.yml`

```auto
title: Membre
# Set page where you want to redirect the user to after login
#home: /

permissions:
  access:
    panel: false

tabs:
  profil:
    label: Mon profil
    icon: text
    columns:
      - width: 1/3
        sections:
          downloads:
            headline:
              label: Photo
            type: files
            layout: cards
            info: "{{ file.caption }}"
            image:
              ratio: 3/4
            max: 1 #maximum 1
            
      - width: 2/3
        # Kirby has many different field types, from simple text fields to the more complex structure field that contains subfields
        # All available field types: https://getkirby.com/docs/reference/panel/fields
        fields:
          firstname:
            label: Prénom
            type: text
            width: 1/2
          namefamily:
            label: Nom
            type: text
            width: 1/2
          member:
            label: Membre du bureau (facultatif)
            type: text
            placeholder: Président, Secrétaire, Trésorier ...
            width: 1/1
          sex:
            label: Sexe
            type: select
            default: sex_default
            options:
              sex_default: non défini
              sex_man: homme
              sex_woman: femme
            width: 1/2
          birthday:
            label: Date de naissance
            type: date
            display: DD/MM/YYYY
            width: 1/2

  contactdetails:
    label: Coordonnées
    icon: text
    sections:
      contactdetails:
        type: fields
        fields:
          street:
            label: Adresse
            type: text
          zip:
            label: Code postal
            type: tel
            icon: none
            width: 1/4
          city:
            label: Ville
            type: text
            width: 3/4
          country:
            label: Pays
            type: text
            default: France
            placeholder: France
          phone:
            label: Téléphone
            type: tel
            icon: phone
            maxlength: 10
            width: 1/4
          phonemobile:
            label: Téléphone portable
            type: tel
            icon: mobile
            maxlength: 10
            width: 1/4
            
  licence:
    label: Ma licence
    icon: text
    sections:
      licence:
        type: fields
        fields:
          licence_number:
            label: Numéro de licence
            type: tel
            icon: account
            width: 1/2
          licence_date:
            label: Date d'entrée au club
            type: date
            display: DD/MM/YYYY
            width: 1/2

  athlete:
    label: L'athlète
    icon: text
    sections:
      athlete:
        type: fields
        fields:
          size:
            label: Taille
            type: number
            after: cm
            width: 1/4
          weight:
            label: Poids
            type: number
            after: kg
            width: 1/4
          bio:
            label: Bio
            type: textarea
            width: 1/1

  social:
    label: Suivez-moi !
    icon: text
    sections:
      social:
        type: fields
        fields:
          url_strava:
            label: Lien Strava
            type: url
            width: 1/2
          # social_events:
          # label: Suivez-moi sur un événement
          # type: textarea
          # width: 1/1
          social_events_listing:
            label: Historique
            type: structure
            fields:
              social_events_date:
                label: Date
                type: number
                max: 2050
                width: 1/2
              social_events_description:
                label: Description
                type: textarea
                width: 1/1
              social_events_listing2:
                label: Sous-texte(s)
                type: structure
                fields:
                  social_events_listing2_title:
                    label: Titre
                    type: text
                    width: 1/1
                  social_events_listing2_date:
                    label: Date
                    type: date
                    display: D MMMM YYYY
                    width: 1/2
                  social_events_listing2_city:
                    label: Ville
                    type: text
                    size: small
                    width: 1/2
                  social_events_listing2_text:
                    label: Texte
                    type: textarea
                    size: small
                    width: 1/1
                  social_events_listing2_related:
                    label: Lien(s)
                    type: pages
                    query: site.find('sejours', 'actualites')
                    multiple: true

```

---

<div class="post-metadata">

**Author:** ![texnixe](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/texnixe/32/5754_2.png) [@texnixe](https://forum.getkirby.com/u/texnixe)\
**Post date:** [October 19, 2022, 7:20pm UTC](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457/20 "2022-10-19T19:20:44Z")

</div>

> [@mickaeldurand](#):
>
> ` 'social_events_listing' => $user->social_events_listing(),`

You need to convert the structure to an array:

```auto
'social_events_listing' => $user->social_events_listing()->toStructure()->toArray(),

```

should do the job.

[Next page](https://forum.getkirby.com/t/building-user-profile-pages-in-kirby-3/12457.md?page=2)
