Component

Write a custom svelte component

Getting started

Budibase components are written in Svelte.

If you are using VSCode for your development, you will most likely want to install the Svelte plugin to enable syntax highlighting.

📘

NodeJS version

Make sure you have node v16+ installed. You can run node --version to see if you need to upgrade.

Initialise component

Make sure you have the latest Budibase CLI installed. Even if you have previously installed the CLI, you may want to install it again to be sure you have the latest version.

Once that's done, we need to initialise a template for our component.

Navigate to the folder location you want to create your plugin, and execute the following command:

budi plugins --init component

Next you will receive a series of prompts to enter details about your component. You can press enter without providing a value if you are happy to use the placeholder.

Within the newly created custom component project you should see a tree structure as follows:

src:
  Component.svelte: The svelte code for your custom component. Do not rename.

package.json: The version, license and dependencies for your component.

schema.json: The meta data and available builder settings for your component. 

Building your component

After you have created your new plugin directory, execute the following:

cd my-component
yarn build

Assuming you have updated the server env variable you should now be able to see your component template in Budibase:


Component.svelte

This is where your svelte code will live for your component. You can use html tags, css styling and bindings as you would for any other svelte component.

To make sure your saved changes are passed through to your Budibase server, you must execute a yarn watch within your custom component project. This will allow your local Budibase app builder to see those changes in real-time, which is very handy for getting your component to look and behave just right.

🚧

The use:styleable={$component.styles} attribute must be included in your top-level component div. Removing this will cause issues when displaying your component.

SDK Context

The SDK context gives you access to the internal Budibase API.

const { styleable, API } = getContext("sdk")

The packages/server/src/api/routes directory provides all of the available endpoint definitions, and the implementations can be found in the packages/server/src/api/controllers directory.

This provides a large amount of integration with various aspects of the Budibase platform.


Context

The context key is a promise of what data bindings the component is going to provide. The builder will use this information to generate data bindings to display, but it's up to the components themselves to provide the data they say they will.
Components can provide many contexts, so the value of the context key may be either an object or an array of objects.

Each context definition supports the following configuration:

Key

Description

Possible values

type

The type of context provided

static
Static contexts define an array of explicit bindings which are provided by the component. This is used when you know each what data bindings your component provides.

schema
Schema contexts are generated dynamically based on settings. The data bindings provided will be the schema of a certain datasource. How this datasource is determined is by looking at the component settings and identifying a setting of type dataSource, table or schema. If the component has a setting of type dataProvider, the data provider stack will be traversed upwards until a matching setting is found. Data bindings are then generated for whatever matching datasource is found. The component is expected to provide some sort of row from some datasource if this context setting is used.

values [static contexts only]

An array of objects defining which static bindings are available

An array of objects containing key and label properties.

Defines the readable and runtime values for the binding, in the label and key fields respectively.

schema.json

On the next page we will discuss the intricacies of the component schema.json file - which defines the inputs and outputs of your component, which will be controlled through the component settings panel in the builder.

You can jump to the component schema page here.

Example: Custom form field component

In this example we will create a Star Rating component that can be added as a field to Budibase Forms.

📘

This example is taken from the Star Rating custom component

This example assumes you already have an empty component initialised and built. We're going to focus on the parts that allow this custom component to work as an input within the existing Form component.

Updating schema.json

The first step will be to define the settings that will be available to our form. The most important setting for any form field component is the field that we want the input to be for. In my case, I have a Number column in one of my tables called Rating that I want to be able to use my Star Rating component with.

We need to add a setting for Field to our component and we can do this by defining it in schema.json. On an empty component template there is a placeholder setting for Text. We will replace this with a setting for Field which will be of type field/number and make it a required field. While we're here we can also add a text field for the label.

component settings within schema.jsoncomponent settings within schema.json

component settings within schema.json

the new settings for our component now show up in the builderthe new settings for our component now show up in the builder

the new settings for our component now show up in the builder

You can view my full schema with extra settings here or view documentation for the component schema here.

Working with Component.svelte

The plugin template comes with a basic placeholder in Component.svelte and this is what we will modify to create our custom component. This is written in Svelte - if you are new to it you can check out the tutorial here.

The first thing we'll do is declare props for our settings values we created above. The syntax for this is export let [your-setting-key]; This means we can use the parameters set in the builder in our component.

Now that our props are added we can continue to build out the front end. Using JS, HTML and CSS we can create the basic interface for our component and then make it functional.

looks good, now let's make it useful!looks good, now let's make it useful!

looks good, now let's make it useful!

Integrating with the Form component

The first step here is to get the context from the parent Form component. We can then use that Form Context to register our component as a Field in that Form.

export let field;
export let label;

const component = getContext("component");
const formContext = getContext("form");
const formStepContext = getContext("form-step"); 
    
const formApi = formContext?.formApi;
$: formStep = formStepContext ? $formStepContext || 1 : 1;
$: formField = formApi?.registerField(field, "number", 0, false, null, formStep);

📘

formApi.registerField() parameters from our example

  • field name - we are using the value of field from our props
  • field data type - in this case, the field is for a number
  • field default value - we have set that to be 0 here
  • field disabled - ours is false here but you could make it configurable
  • field validation - ours is null in this case
  • form step - we use formStep derived from the formStepContext

Now that the field is registered, we have to set it to update the form's properties in the parent component every time the store changes. Using onDestroy we can deregister our field from our form whenever the component unmounts.

import { onDestroy } from "svelte";

let fieldApi;
let fieldState; 

$: unsubscribe = formField?.subscribe((value) => {
  fieldState = value?.fieldState;
  fieldApi = value?.fieldApi;
});

onDestroy(() => {
  fieldApi?.deregister();
  unsubscribe?.();
});

Just like that we have everything we need to set a value to our form using our component. We get the field's value within the component using fieldState.value and set it using fieldApi.setValue('new value here').

For this to work we also need to validate that the component is actually being used as a child of a form in our Budibase screen. To do this, I will add a Svelte if/else statement to our component so that it will render our field if it's the child of a form, or show a warning if it's not.

  {#if !formContext}
    <div class="placeholder">Form components need to be wrapped in a form</div>
  {:else}
    // our component goes here
  {/if}

Integrating with Field Groups

Remember we made a setting for the label above? Now we are going to make our component compatible with the Budibase Field Group component, so the label styling can come from that. In order to make this work we need to get context from the Field Group, just like we did above for the Form component.

<script>
    const fieldGroupContext = getContext("field-group");

        const labelPos = fieldGroupContext?.labelPosition || "above";

    $: labelClass =
        labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`;
</script>


<div class="spectrum-Form-item" use:styleable={$component.styles}>
    <label
      class:hidden={!label}
      for={fieldState?.fieldId}
      class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
    >
      {label || " "}
    </label>
    <div class="spectrum-Form-itemField">
            // component logic in here
        </div>
</div>

<style>
  label {
    white-space: nowrap;
  }
  label.hidden {
    padding: 0;
  }
  .spectrum-Form-itemField {
    position: relative;
    width: 100%;
  }
  .spectrum-FieldLabel--right,
  .spectrum-FieldLabel--left {
    padding-right: var(--spectrum-global-dimension-size-200);
  }
</style>

Handling errors

      {#if fieldState?.error}
        <div class="error">{fieldState.error}</div>
      {/if}
       
      <style>
        .error {
            color: var(--spectrum-semantic-negative-color-default,
                  var(--spectrum-global-color-red-500));
              font-size: var(--spectrum-global-dimension-font-size-75);
                margin-top: var(--spectrum-global-dimension-size-75);
                }
      </style>

Did this page help you?