EspoCRM Ebla List Pro Tutorial Customization

How to Create a Custom EspoCRM Record Card with Ebla List Pro

Eblasoft Team
Property-style EspoCRM record cards created with Ebla List Pro

Create a responsive image-based EspoCRM record card using Ebla List Pro, Layout Manager, and standard Custom files.

Before you begin

The fields and List Card layout can be prepared from the EspoCRM administration area. The custom HTML design requires file access to the EspoCRM installation. Complete and test the customization on a development or test installation before using it in production.

What you will create

  • A large image at the top of each card.
  • Status and price displayed over the image.
  • Category, location, and record name.
  • Three compact specification fields.
  • A short description and featured-record indicator.
  • A fallback image when a record does not have an uploaded image.

Requirements

  • EspoCRM with Ebla List Pro installed and enabled.
  • Administrator access to EspoCRM.
  • File access to the EspoCRM installation.

Example names used in this article

The example entity is called Test. Replace Test, test, and the field names with the internal names used by your entity. Internal names are case-sensitive.

Step 1: Prepare the fields

Open Administration → Entity Manager → Test → Fields and create the fields needed by your card. The following set is enough for this property-style example.

Label Internal name Field type Example
Main Image mainImage Image Uploaded property photo
Price price Currency $450,000
Availability availability Enum Available
Listing Type listingType Enum For Sale
Category category Enum Apartment
Location location Varchar Istanbul
Bedrooms bedroomCount Integer 3
Bathrooms bathroomCount Integer 2
Area area Float 165
Featured featured Boolean Yes

The standard name, description, and assignedUser fields are also used. Add all required fields to the Detail layout so users can enter and update the information.

Step 2: Enable List Pro card view

  1. Open Administration → Entity Manager → Test → Edit.
  2. Enable Card List View.
  3. Optionally enable Default View Mode Is Card.
  4. Save the entity.

EspoCRM Card List View and Default View Mode set to Card in Entity Manager

Enable Card List View. Enable the default card mode only when users should open this entity in card view automatically.

The listCard layout is the single place where you choose the fields used by the card. You do not need to repeat the field list in JavaScript.

Step 3: Create the List Card layout

Open Administration → Layout Manager → Test. If List Card is not shown in the layout menu, select Create and create listCard as a List layout. Open it and move the fields from Step 1 into the Enabled column.

Keep the layout in one place

List Pro passes the listCard layout to the card item automatically. Do not create a second field array in the JavaScript view.

The field order does not need to match the visual card because the template decides where each value is displayed.

List Card layout with property fields enabled in EspoCRM Layout Manager

Move every field used by the template into the List Card layout.

Step 4: Connect List Pro to your card item

Use EspoCRM's standard Custom directories. A separate module and Resources/module.json are not required.

First, point the Test card mode to your custom card-list view. If this file already exists, merge the recordViews.card value into its existing JSON.

custom/Espo/Custom/Resources/metadata/clientDefs/Test.json

{
    "recordViews": {
        "card": "custom:views/test/card-list"
    }
}

Register the card stylesheet in the standard Custom metadata.

custom/Espo/Custom/Resources/metadata/app/client.json

{
    "cssList": [
        "__APPEND__",
        "client/custom/css/test-card.css"
    ]
}

Why __APPEND__ matters

It adds your CSS file to the existing stylesheet list. Without it, other registered stylesheets could be replaced.

Now create the card-list view that tells Ebla List Pro which view should render each individual card.

client/custom/src/views/test/card-list.js

define(
    'custom:views/test/card-list',
    ['ebla-list-pro:views/record/card-list'],
    function (CardListView) {
        return CardListView.extend({
            itemViewName: 'custom:views/test/card-item',
        });
    }
);

Step 5: Create the card item view

The item view prepares the field views, record link, uploaded image URL, and fallback image. If your image field has another internal name, change every use of mainImage and mainImageId.

client/custom/src/views/test/card-item.js

define(
    'custom:views/test/card-item',
    ['ebla-list-pro:views/record/card-item'],
    function (CardItemView) {
        return CardItemView.extend({
            template: 'custom:test/card-item',

            data: function () {
                return Object.assign(
                    {},
                    CardItemView.prototype.data.call(this),
                    this.model.attributes,
                    {
                        imageUrl: this.getImageUrl(),
                        recordUrl: '#Test/view/' + this.model.id,
                    }
                );
            },

            getImageUrl: function () {
                var imageId = this.model.get('mainImageId');

                if (!imageId) {
                    return this.getFallbackImageUrl();
                }

                return this.getBasePath() + '?entryPoint=image&id=' +
                    encodeURIComponent(imageId) + '&size=large';
            },

            getFallbackImageUrl: function () {
                return this.getBasePath() +
                    'client/custom/images/test-placeholder.webp';
            },

            afterRender: function () {
                CardItemView.prototype.afterRender.call(this);

                this.$el.closest('.card-list-row').addClass('test-card-shell');

                this.$el.find('.test-card-image').one(
                    'error',
                    function (event) {
                        event.currentTarget.src = this.getFallbackImageUrl();
                    }.bind(this)
                );
            },
        });
    }
);

Step 6: Create the card template

EspoCRM creates a field view for every field in the listCard layout. Place those field views in the template with names such as priceField and availabilityField. Triple braces are required because EspoCRM field views return safe HTML.

client/custom/res/templates/test/card-item.tpl

<article class="test-card">
    <a class="test-card-media" href="{{recordUrl}}" aria-label="{{name}}">
        <img
            class="test-card-image"
            src="{{imageUrl}}"
            alt="{{name}}"
            loading="lazy"
        >

        <div class="test-card-status field" data-name="availability">
            {{{availabilityField}}}
        </div>

        <div class="test-card-price field" data-name="price">
            {{{priceField}}}
        </div>
    </a>

    {{#unless rowActionsDisabled}}
        <div class="item-menu-container test-card-menu">{{{itemMenu}}}</div>
    {{/unless}}

    <div class="test-card-content">
        <div class="test-card-tags">
            <div class="field" data-name="listingType">{{{listingTypeField}}}</div>
            <div class="field" data-name="category">{{{categoryField}}}</div>
        </div>

        <h3 class="test-card-title field" data-name="name">{{{nameField}}}</h3>

        <div class="test-card-location">
            <span class="fas fa-map-marker-alt" aria-hidden="true"></span>
            <div class="field" data-name="location">{{{locationField}}}</div>
        </div>

        {{#if description}}
            <p class="test-card-description">{{description}}</p>
        {{/if}}

        <div class="test-card-specs">
            <div>
                <span class="fas fa-bed" aria-hidden="true"></span>
                <span class="field" data-name="bedroomCount">{{{bedroomCountField}}}</span>
            </div>
            <div>
                <span class="fas fa-bath" aria-hidden="true"></span>
                <span class="field" data-name="bathroomCount">{{{bathroomCountField}}}</span>
            </div>
            <div>
                <span class="fas fa-ruler-combined" aria-hidden="true"></span>
                <span class="field" data-name="area">{{{areaField}}}</span>
                <span>m²</span>
            </div>
        </div>

        <div class="test-card-footer">
            {{#if featured}}
                <strong><span class="fas fa-star"></span> Featured</strong>
            {{else}}
                <span>Test</span>
            {{/if}}

            {{#if assignedUserName}}
                <div class="field" data-name="assignedUser">{{{assignedUserField}}}</div>
            {{/if}}
        </div>
    </div>
</article>

Step 7: Add the card styling

This CSS creates a responsive image card and stays scoped to the custom Test card. You can safely change its radius, shadow, spacing, and colors.

client/custom/css/test-card.css

.card-list-inner > .card-list-row.test-card-shell {
    overflow: hidden;
    padding: 0;
    border: 1px solid rgba(127, 127, 127, .16);
    border-radius: 14px;
    background: var(--panel-bg, #fff);
    box-shadow: 0 8px 24px rgba(28, 39, 49, .1);
}

.test-card {
    position: relative;
    min-width: 0;
}

.test-card .inline-edit-link {
    display: none;
}

.test-card-media {
    position: relative;
    display: block;
    overflow: hidden;
    aspect-ratio: 16 / 9;
    background: rgba(127, 127, 127, .12);
}

.test-card-media::after {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 55%;
    background: linear-gradient(to top, rgba(10, 16, 22, .72), transparent);
    content: "";
}

.test-card-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.test-card-status {
    position: absolute;
    z-index: 2;
    top: 12px;
    left: 12px;
}

.test-card-price {
    position: absolute;
    z-index: 2;
    right: 14px;
    bottom: 12px;
    color: #fff;
    font-size: 21px;
    font-weight: 700;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .45);
}

.test-card-menu {
    position: absolute;
    z-index: 4;
    top: 9px !important;
    right: 9px !important;
}

.test-card-content {
    padding: 15px 16px 14px;
}

.test-card-tags,
.test-card-location,
.test-card-footer {
    display: flex;
    align-items: center;
    gap: 7px;
}

.test-card-title {
    overflow: hidden;
    margin: 9px 0 6px;
    font-size: 17px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.test-card-location,
.test-card-description,
.test-card-footer {
    font-size: 12px;
    opacity: .7;
}

.test-card-description {
    display: -webkit-box;
    overflow: hidden;
    min-height: 38px;
    margin: 11px 0 12px;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.test-card-specs {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
    padding: 10px 0;
    border-top: 1px solid rgba(127, 127, 127, .16);
    border-bottom: 1px solid rgba(127, 127, 127, .16);
}

.test-card-specs > div {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    font-size: 12px;
    font-weight: 600;
}

.test-card-footer {
    justify-content: space-between;
    padding-top: 11px;
}

@media (max-width: 480px) {
    .test-card-price {
        font-size: 18px;
    }
}

Save a fallback image at client/custom/images/test-placeholder.webp. A landscape image with a 16:9 ratio works best. Keep the file small so cards load quickly.

Step 8: Apply the changes

Open Administration → Rebuild and run the rebuild from the EspoCRM interface. This makes EspoCRM discover the new Custom files and metadata.

When the rebuild finishes, clear the browser cache or perform a hard refresh. A normal page refresh may continue using an older JavaScript file.

Step 9: Test the result

Finished property-style EspoCRM card list in Ebla List Pro

The finished card view includes images, EspoCRM field formatting, record actions, and pagination. The highlighted button switches the list to card view.

  1. Open the Test list and select the card-view button.
  2. Confirm that records with images show the uploaded image.
  3. Confirm that records without images show the fallback image.
  4. Check that the price uses the configured currency symbol and number format.
  5. Open a card and confirm that it links to the correct record.
  6. Test desktop, tablet, and mobile widths.
  7. Test with a user who has limited field permissions.

You are finished when

The card view loads without errors, every card has an image, all values match the record, and the layout remains readable on a small screen.

Common problems

The card view keeps loading

Confirm that both JavaScript view names use the custom: namespace and that recordViews.card points to custom:views/test/card-list. Then run Administration → Rebuild and perform a hard refresh.

A value is empty

Add the field to the List Card layout in Layout Manager. Then confirm that the template uses the correct field-view name, for example {{{priceField}}} for the price field.

The image is broken

Check the image field's internal name. An image field called mainImage uses mainImageId. Also confirm that the fallback image exists at the path used by getFallbackImageUrl().

The CSS is not applied

Confirm that the CSS file is registered in custom/Espo/Custom/Resources/metadata/app/client.json, that the list starts with "__APPEND__", and that Administration → Rebuild was run afterward.

The card shows old JavaScript

Run Administration → Rebuild, clear the browser cache, and perform a hard refresh.

Safe customization tips

  • Keep all card CSS under a unique class such as .test-card.
  • Use EspoCRM field views for currency, enums, links, and translated values.
  • Use an image field instead of storing a public image URL in a text field.
  • Always include a fallback image.
  • Keep the customization under custom/Espo/Custom and client/custom.
  • Do not edit Ebla List Pro's original files.
  • Copy or package the Custom files when moving the card to another server.