Field type

A field is an abstraction that defines how table data is stored per column. More information can be found on the database plugin page. This is a tutorial about how to create your own custom table field type for Baserow via a plugin. We are going to create a integer field which displays as “hello world”. Of course a number field with the more features already exists, this is just for example purposes. In the end the user can create an integer field that only shows a hello world message. We expect that you are using the plugin boilerplate.

Backend

We are going to start by creating an IntegerField model that extends the Field model. It will have a property to indicate if a negative number is allowed which can be set for each field that is created.

Create plugins/my_baserow_plugin/backend/src/my_baserow_plugin/models.py

from django.db import models

from baserow.contrib.database.fields.models import Field


class IntegerField(Field):
    integer_negative = models.BooleanField(default=False)

Depending on the field model instance a model and serializer field must be returned. The model field is used when generating the table’s model that is used to select and update the data. The serializer field is used when exposing the data via the REST API to the web-frontend. For more information about the properties and methods related to the field type you can check the backend/src/baserow/contrib/database/fields/registries.py::FieldType class in the Baserow repository.

Create plugins/my_baserow_plugin/backend/src/my_baserow_plugin/field_types.py

from django.db import models
from django.core.exceptions import ValidationError

from rest_framework import serializers

from baserow.contrib.database.fields.registries import FieldType

from .models import IntegerField


class IntegerFieldType(FieldType):
    type = 'integer'
    model_class = IntegerField
    allowed_fields = ['integer_negative']
    serializer_field_names = ['integer_negative']

    def prepare_value_for_db(self, instance, value):
        if value and not instance.integer_negative and value < 0:
            raise ValidationError(f'The value for field {instance.id} cannot be '
                                  f'negative.')
        return value

    def get_serializer_field(self, instance, **kwargs):
        return serializers.IntegerField(required=False, allow_null=True)

    def get_model_field(self, instance, **kwargs):
        return models.IntegerField(null=True, blank=True)

Create plugins/my_baserow_plugin/backend/src/my_baserow_plugin/config.py

from django.apps import AppConfig

from baserow.core.registries import plugin_registry
from baserow.contrib.database.fields.registries import field_type_registry


class PluginNameConfig(AppConfig):
    name = 'my_baserow_plugin'

    def ready(self):
        from .plugins import PluginNamePlugin
        from .field_types import IntegerFieldType

        plugin_registry.register(PluginNamePlugin())
        field_type_registry.register(IntegerFieldType())

Finally, lets start the dev environment and use it to apply the migrations because we have created a new model.

# Set these env vars to make sure mounting your source code into the container uses
# the correct user and permissions.
export PLUGIN_BUILD_UID=$(id -u)
export PLUGIN_BUILD_GID=$(id -g)
docker-compose run my-baserow-plugin /baserow.sh backend-cmd manage makemigrations
docker-compose run my-baserow-plugin /baserow.sh backend-cmd manage migrate

Web frontend

Because the backend and web-frontend are two separate applications that only communicate via a REST API with each other, the web-frontend does not yet know about the existence of the integer field type. We can add this in a similar way. Add/modify the following files in the web-frontend part of the plugin.

plugins/my_baserow_plugin/web-frontend/fieldTypes.js

import { FieldType } from "@baserow/modules/database/fieldTypes";

import SubFormIntegerField from "@my-baserow-plugin/components/SubFormIntegerField";
import GridViewIntegerField from "@my-baserow-plugin/components/GridViewIntegerField";
import RowEditIntegerField from "@my-baserow-plugin/components/RowEditIntegerField";

export class IntegerFieldType extends FieldType {
    static getType() {
        return "integer";
    }

    getIconClass() {
        return "iconoir-numbered-list-left";
    }

    getName() {
        return "Integer";
    }

    getFormComponent() {
        return SubFormIntegerField;
    }

    getGridViewFieldComponent() {
        return GridViewIntegerField;
    }

    getRowEditFieldComponent(field) {
        return RowEditIntegerField;
    }
}

plugins/my_baserow_plugin/web-frontend/plugin.js

import { PluginNamePlugin } from "@my-baserow-plugin/plugins";
import { IntegerFieldType } from "@my-baserow-plugin/fieldTypes";

export default (context) => {
    const { app } = context;
    app.$registry.register("plugin", new PluginNamePlugin(context));
    app.$registry.register("field", new IntegerFieldType(context));
};

The GridViewIntegerField component is returned by the getGridViewFieldComponent method of the IntegerFieldType class which means that this component is added for each data row field that has the integer type. For now we only add “Hello World” for example purposes so it doesn’t actually display the number, but there are plenty of examples in the Baserow repository in the directory web-frontend/modules/database/components/view/grid.

plugins/my_baserow_plugin/web-frontend/components/GridViewIntegerField.vue

<template>
    <div class="grid-view__cell" :class="{ active: selected }">
        <div>Hello World</div>
    </div>
</template>

<script>
import gridField from "@baserow/modules/database/mixins/gridField";

export default {
    mixins: [gridField],
};
</script>

The RowEditIntegerField component is returned by the getRowEditFieldComponent method of the IntegerFieldType class which means that this component is added in the popup row form. This form is shown when the expand icon on the left side of row has been clicked by the user. For now, we only add “Hello World” for example purposes, but there are plenty of examples in the Baserow repository in the directory web-frontend/modules/database/components/row.

plugins/my_baserow_plugin/web-frontend/components/RowEditIntegerField.vue

<template>
    <div class="control__elements">Hello World</div>
</template>

<script>
import rowEditField from "@baserow/modules/database/mixins/rowEditField";

export default {
    mixins: [rowEditField],
};
</script>

The SubFormIntegerField component will be added to the context menu form when creating or editing a field. If there are extra properties defined in the related model in the backend then they can be added to the form using this component.

plugins/my_baserow_plugin/web-frontend/components/SubFormIntegerField.vue

<template>
    <div>
        <div class="control">
            <div class="control__elements">
                <Checkbox v-model="values.integer_negative"
                    >Allow negative</Checkbox
                >
            </div>
        </div>
    </div>
</template>

<script>
import form from "@baserow/modules/core/mixins/form";

export default {
    name: "SubFormIntegerField",
    mixins: [form],
    data() {
        return {
            allowedValues: ["integer_negative"],
            values: { integer_negative: false },
        };
    },
    methods: {
        isFormValid() {
            return true;
        },
    },
};
</script>

After adding all the files you should be able to create a field with the newly created integer field type.