Fields

Customise Tensei resources with fields

A field represents a database column.

Each Tensei resource has a fields method. This method takes in an array of fields that define the columns on the table this resource maps to.

Fields are much more than just columns. They can represent any way of collecting data from the user. Tensei ships with a variety of fields out of the box, including fields for text inputs, booleans, dates, file uploads, Markdown, Wysiwyg editors, Rich editors and more.

Defining Fields

To create a field, first you need to identify the type of data this field would be storing. Let's take a Post resource for example. A Post might have a Title field and a Published field. The title field would be a varchar column, and the published field would be a boolean column.

If you're using a NoSQL database, the title would be a String field, and published would be a Boolean field.

import { tensei, resource, text, boolean } from '@tensei/core'

export default tensei()
    .resources([
        resource('Post')
            .fields([
                text('Title'),
                boolean('Published')
            ])
    ])

Field Column Conventions

Tensei will "snake case" the name of the field to determine the underlying database column. For example, the text('Example Title') field would connect to the example_title column. However, if necessary, you may pass the column name as the second argument to the field's method:

text('Title', 'custom_title_column')

Showing / Hiding Fields on dashboard

As mentioned above, the defined fields would not only be used to determine the database columns, but also the types of input components to display on the dashboard.

Often, you will only want to display a field in certain situations. For example, there is typically no need to show a Password field on a resource index listing. Likewise, you may wish to only display a Created At field on the creation / update forms. Tensei makes it a breeze to hide / show fields on certain screens.

The following methods may be used to show / hide fields based on the display context:

  • showOnIndex
  • showOnDetail
  • showOnCreate
  • showOnUpdate
  • hideOnIndex
  • hideOnDetail
  • hideOnCreate
  • hideOnUpdate
  • onlyOnForms
  • exceptOnForms

You may chain any of these methods onto your field's definition in order to instruct Tensei where the field should be displayed:

resource('Post')
    .fields([
        text('Title')
            .hideOnIndex()
            .hideOnUpdate()
    ])

The Title field would be hidden from the resource index table because we called hideOnIndex(), and will be hidden when updating a Post because we called hideOnUpdate().

Showing / Hiding fields from API

Sometimes you want to completely prevent a field from being exposed to the API. You may use a combination of these methods to control where fields show up in the API:

  • hideFromCreateApi()
  • hideFromUpdateApi()
  • hideFromDeleteApi()
  • hideFromFetchApi()
  • hideFromShowApi()

Let's say you want to prevent a secret token field from being exposed to all data fetching endpoints, but be exposed to the create and update endpoints:

text('Secret')
    .hideFromFetchApi()
    .hideFromShowApi()

You may also use the hideFromApi() method to completely hide a field from showing up in any calls to the API.

Sortable Fields

When attaching a field to a resource, you may use the sortable method to indicate that the resource index may be sorted by the given field:

resource('Post')
    .fields([
        text('Title')
            .sortable()
    ])

Searchable Fields

You may use the searchable method to indicate that this field can be searched. When the search box on the resource index is used, only the searchable fields would be queried. All searchable fields would also be indexed in the database.

resource('Post')
    .fields([
        text('Title')
            .searchable()
    ])

Field Types

This portion of the documentation only discusses non-relationship fields. To learn more about relationship fields, check out their documentation.

Tensei ships with a variety of field types. So, let's explore all of the available types and their options:

Boolean Field

The Boolean field may be used to represent a boolean / "tiny integer" column in your database. For example, assuming your database has a boolean column named active, you may attach a Boolean field to your resource like so:

import { resource, boolean } from '@tensei/core'

resource('Customer')
    .fields([
        boolean('Active')
    ])

A Boolean field would show up as a checkbox on dashboard forms.

Date Field

The Date field may be used to store a date value (without time).

import { resource, date } from '@tensei/core'

resource('Employee')
    .fields([
        date('Birthday')
    ])

Default date time

You can default this field to the current time using .defaultToNow() method. If a default value is not defined when creating a resource, this value would default to the current date.

date('Birthday')
    .defaultToNow()

Date Formats

You may customize the display format of your Date fields using the format method. This format won't affect how the date is saved to the database. The format must be a format supported by Day.js:

date('Birthday')
    .format('do MMM yyyy, hh:mm a'),

DateTime Field

The DateTime field may be used to store a date-time value. It will also show a date/time picker on dashboard forms.

import { resource, dateTime } from '@tensei/core'

resource('Employee')
    .fields([
        dateTime('Started On')
    ])

You may customize the display format of your DateTime fields using the format method. This format won't affect how the date is saved to the database. The format must be a format supported by Day.js:

dateTime('Started On')
    .format('do MMM yyyy, hh:mm a'),

Timestamp Field

The Timestamp field may be used to store a timestamp value. It will also show a date/time picker on dashboard forms.

import { resource, timestamp } from '@tensei/core'

resource('Employee')
    .fields([
        timestamp('Last Logged In At')
    ])

Just like the date and dateTime fields, you can format timestamps for the dashboard using the .format() method.

Integer Field

The Integer field provides an input control with a type attribute of number:

import { resource, integer } from '@tensei/core'

resource('Book')
    .fields([
        integer('Price')
    ])

You may use the min, max, and step methods to set their corresponding attributes on the generated input control. This also applies to other number types such as bigInteger, float, and double.

integer('Price').min(1).max(1000).step(0.01)

Big Integer Field

The bigInteger field provides an input control with a type attribute of number. It will also store the data in a bigInt database column. It defaults to a number column if the database being used does not support bigint.

import { resource, bigInteger } from '@tensei/core'

resource('Book')
    .fields([
        bigInteger('Price')
    ])

Double Field

The double field provides an input control with a type attribute of number. It will also store the data in a double database column.

import { resource, double } from '@tensei/core'

resource('Book')
    .fields([
        double('Price')
    ])

Float Field

The float field provides an input control with a type attribute of number. It will also store the data in a float database column.

import { resource, float } from '@tensei/core'

resource('Book')
    .fields([
        float('Price')
    ])

Select Field

The Select field may be used to generate a drop-down select menu. The select menu's options may be defined using the options method:

import { select } from '@tensei/core'

resource('Book')
    .fields([
        select('Category')
            .options([{
                label: 'Postgresql',
                value: 'pg'
            }, {
                label: 'SQL database',
                value: 'sql'
            }])
    ])

Text Field

The Text field provides an input control with a type attribute of text:

import { text } from '@tensei/core'

resource('Book')
    .fields([
        text('Title')    
    ])

Text fields may be customized further by setting any attribute on the field. This can be done by calling the htmlAttributes method:

text('Title')
    .htmlAttributes({
        placeholder: 'Enter your email'
    })

Slug Field

The Slug field provides a read-only input control that automatically generates a url-friendly slug based on another field.

slug('Slug')
    .from('Title')
    .type('date')

The above slug would be automatically generated from() the Title field. As you type the title on the CMS, you should see this change reflect.

You may call the .editable() method if you do not want to slug to be read-only. You'll be able to update it to whatever you need.

Textarea Field

The Textarea field provides a textarea control:

import { textarea } from '@tensei/core'

resource('Book')
    .fields([
        textarea('Description')    
    ])

By default, Textarea fields will not display their content when viewing a resource on its detail page. It will be hidden behind a "Show Content" link, that when clicked will reveal the content. You may specify the Textarea field should always display its content by calling the alwaysShow method on the field itself:

textarea('Description').alwaysShow()

You may also specify the textarea's height by calling the rows method on the field:

textarea('Description').rows(7)

Textarea fields may be customized further by setting any attribute on the field. This can be done by calling the htmlAttributes method:

textarea('Description').htmlAttributes({
    placeholder: 'Some description of this book ...'
})

Customization

Nullable Fields

If you want to add a not nullable constraint on a column, use the .notNullable() method:

text('Title').notNullable()

If you want the opposite, you may call the .nullable() method to make a field nullable.

Unique fields

Sometimes you want to add a unique constraint to a database field. You may do so by calling the .unique() method on the field:

text('Email')
    .unique()

Default value

To set a database default value for a field, you may use the .default() method.

bigInt('Views')
    .default(1000)

If you want to set the default to a database function such as current_timestamp, use the .defaultRaw() method instead:

timestamp('Expires At')
    .defaultRaw('current_timestamp(3)')

Field Help Text

If you would like to place "help" text beneath a field on the create / update form, you may use the description method. This description will also show up on API documentation.

text('Description').description('Provide a clear description of this book.')

Modify on create

You can modify a field before it is inserted into the database. An example use case is hashing a password. You can pass a callback to the .onCreate() method that returns the modified value of the field.

text('Password')
    .onCreate((user) => Bcrypt.hashSync(user.password))

Modify on update

You can modify a field value before an update query is made to the database. An example use case is an updated_at timestamp. We may want this to update to the current date everytime the database row is updated.

field('Updated At')
    .onUpdate((user) => new Date())