<- All posts

How to Build an Engineering Change Management System in 5 Steps

Ronan McQuillan
19 min read · Feb 27, 2025

Managing changes to engineering projects can be an incredibly tricky process. Even seemingly minor changes must be accurately assessed for risks, costs, compliance issues, and potential impacts.

This applies to almost all kinds of engineering, including software, construction, and others.

The key challenges here include identifying a need for change, assessing the impact of potential actions, and applying consistent business rules to authorize new change initiatives.

However, engineering projects also introduce additional difficulties. For one, there’s a need to maintain a clear, auditable record of all decisions. We’ll often also have heightened security needs as well as more extensive integration requirements.

So, many teams opt for a custom solution.

Today, we’re exploring how to build a fully customizable engineering change management system in Budibase without writing a single line of code.

By the end, you’ll have a secure, performant tool that you can use on our cloud system - or roll out to your own infrastructure with a self-hosted Budibase instance.

What is an engineering change management system?

An engineering change management system is an internal tool that handles change requests for engineering projects.

Typically, this is built around an approval workflow with two or more user roles. So, one set of users can request changes while another can review and authorize them. In more advanced use cases, we might have more roles for different kinds of requestors or levels of approvers.

At the most basic level, this achieves two things:

  1. All change requests are documented consistently.
  2. Authorization for approval or rejection follows defined business logic.

In turn, this reduces administrative workloads in projects, saving time and helping to eliminate errors.

Some solutions might also have additional requirements.

For example, we may wish to create a record of how and when the status of change requests has changed as it progresses through our workflows for auditability. Or, we may wish to create more complex workflows with escalation or routing for different kinds of changes.

Some highly advanced solutions might even include functionality for automating aspects of change assessment, like impact analysis or cost estimation.

Often, change management systems also require integration with other types of platforms within our project stack, such as documentation or release management, in order to effectively action authorized changes.

What are we building?

We’re building a comparatively simple engineering change management system on top of an existing PostgreSQL database. As change request workflows will vary from one organization to the next, the goal is to provide a working solution with plenty of scope for customization.

Our database uses three tables called projects, change_requests, and change_status_tracker.

We’ll provide the queries you need to build a look-a-like database. We’re handling our data layer in NeonDB, a cloud-based Postgres tool. But, with Budibase, you have the option of using just about any SQL or NoSQL platform.

We’ve created our app around a two-role workflow. Here’s how this works:

  • Requestors can request changes to any project and monitor changes to their statuses.
  • Project Owners can perform CRUD actions on their own projects, as well as responding to change requests related to them.

Each group of users will have access to two screens, allowing them to take actions on the projects and change_requests tables that are appropriate for their role.

In the background, we’ll also set up an automation rule so that a new row is created on the change_status_tracker table each time the status of a change_request is updated.

Here’s what our app will look like when we’re finished.

Grab a coffee, and let’s get building.

How to build an engineering change management system in 5 steps

If you haven’t already, the first thing to do is sign up for a Budibase account to start building as many apps as you need for free.

Join 200,000 teams building workflow apps with Budibase

1. Setting up our data layer

The first thing we need to do is create a new Budibase application. We have the option of using a pre-built template or importing an existing app file, but today we’re starting from scratch.

When we choose this option, we’re prompted to give our app a name, which will also be used to populate our URL extension. We’re simply going with Engineering Change Management System.

Engineering change management system

Connecting to Postgres

Once our app has been created, we’ll be presented with a range of options for connecting to data. Budibase supports direct querying for a range of RDBMSs, NoSQL tools, spreadsheets, and APIs alongside our built-in low-code database.

Today, we’re going to hit PostgreSQL.

Data

We’ll then be prompted to enter our config details.

config

Next, we can choose which tables we’d like to fetch, making them queryable within Budibase. Budibase acts as a proxy for external databases, querying our data without storing it.

We’re fetching our database’s three constituent tables.

Fetch Tables

If you want to build along with this tutorial, you can use the following query to create our tables in your own Postgres database.

 1-- Create the projects table
 2
 3CREATE TABLE projects (
 4
 5  id SERIAL PRIMARY KEY,
 6
 7  project_name VARCHAR(255) NOT NULL,
 8
 9  description TEXT,
10
11  start_date TIMESTAMP,
12
13  end_date TIMESTAMP
14
15);
16
17-- Create the change_requests table
18
19CREATE TABLE change_requests (
20
21  id SERIAL PRIMARY KEY,
22
23  project_id INT NOT NULL,
24
25  change_title VARCHAR(255) NOT NULL,
26
27  change_type VARCHAR(50),
28
29  description TEXT,
30
31  impact_analysis TEXT,
32
33  status VARCHAR(50),
34
35  date_requested TIMESTAMP,
36
37  CONSTRAINT fk_project
38
39FOREIGN KEY (project_id)
40
41REFERENCES projects (id)
42
43ON DELETE CASCADE
44
45);
46
47-- Create the change_status_tracker table
48
49CREATE TABLE change_status_tracker (
50
51  id SERIAL PRIMARY KEY,
52
53  change_request_id INT NOT NULL,
54
55  old_status VARCHAR(50),
56
57  new_status VARCHAR(50),
58
59  status_change_date TIMESTAMP,
60
61  CONSTRAINT fk_change_request
62
63FOREIGN KEY (change_request_id)
64
65REFERENCES change_requests (id)
66
67ON DELETE CASCADE
68
69);
70
71-- Indexes for performance (optional but recommended)
72
73CREATE INDEX idx_project_id ON change_requests (project_id);
74
75CREATE INDEX idx_change_request_id ON change_status_tracker (change_request_id);

This query will populate our tables with dummy data:

 1-- Insert dummy projects
 2
 3INSERT INTO projects (project_name, description, start_date, end_date) VALUES
 4
 5('Project Alpha', 'Redesign of mechanical components', '2024-01-01', '2024-06-30'),
 6
 7('Project Beta', 'Software upgrade for control system', '2024-02-15', '2024-08-15'),
 8
 9('Project Gamma', 'New material testing initiative', '2024-03-10', '2024-09-30');
10
11-- Insert dummy change requests
12
13INSERT INTO change_requests (project_id, change_title, change_type, description, impact_analysis, status, date_requested) VALUES
14
15(1, 'Update CAD Model', 'Design', 'Modify CAD model to meet new safety standards', 'Medium cost, 2-week delay', 'Pending', '2024-02-01'),
16
17(1, 'Adjust Tolerance Specification', 'Process', 'Tighten tolerances for increased precision', 'Minor cost increase, potential scrap reduction', 'Approved', '2024-02-10'),
18
19(2, 'Migrate to New Database', 'Software', 'Upgrade from PostgreSQL 12 to PostgreSQL 15', 'Performance improvement, minor downtime', 'In Progress', '2024-03-05'),
20
21(2, 'Integrate New Sensor API', 'Component', 'Add support for a new sensor model', 'Requires firmware update, 3-week lead time', 'Rejected', '2024-03-12'),
22
23(3, 'Change Test Procedure', 'Process', 'Revise test sequence for more accurate failure detection', 'Minimal impact, improves data reliability', 'Pending', '2024-04-01');
24
25-- Insert dummy change status history
26
27INSERT INTO change_status_tracker (change_request_id, old_status, new_status, status_change_date) VALUES
28
29(1, 'Pending', 'Approved', '2024-02-05'),
30
31(2, 'Pending', 'Approved', '2024-02-12'),
32
33(3, 'Pending', 'In Progress', '2024-03-07'),
34
35(4, 'Pending', 'Rejected', '2024-03-15'),
36
37(5, 'Pending', 'Approved', '2024-04-05');

Once fetched, we can begin to manipulate our tables in Budibase’s Data section. Here’s what these will look like.

Data

Tweaks to existing columns

In Budibase, app-building is centered around the Data section. With our data-first experience, any changes that we make here will be reflected when we start generating UIs and automation rules.

So, we want to make a few adjustments to our data layer before we move on to the rest of our app. Firstly, we’re going to adjust a couple of columns so that the screens we autogenerate will better reflect the needs of our users.

Firstly, all textual data is stored in our database with Postgres’ TEXT type.

But, we’re going to want to provide users with more space to enter information for some fields than for others. These are description in projects, and description and impact_analysis in change_requests.

We’ll change the Type for these from Text to Long Form Text.

Long Form Text

Remember to repeat this process for all three of the columns we listed.

Our change_requests table has an attribute called status. We’re not going to want our users to enter these manually. Rather, we want to offer defined options.

So, we’ll change the Type from Text to Single Select, adding the following options:

  • Pending,
  • In Progress,
  • Approved,
  • Rejected.

options

We’re also doing the same for new_status and old_status on our change_request_tracker for visual consistency. Optionally, we can change the type of our change_type attribute to Single Select too, remembering to populate relevant options.

Adding default values

Next, there are some columns where we’d like to populate values without users having to enter these manually. With Budibase’s Default Values, we can specify values that we’d like to be automatically assigned to columns when a new row is created.

There are two columns on the change_requests table we want to do this for. The first is status.

We’ll use the dropdown to set this to Pending by default.

default

Now, a new change request will automatically be set to Pending.

We also want to default the date_requested to the current date and time when a new row is created. In other words, we need to set a dynamic value rather than a static one. To do this, we’ll hit the lightning bolt icon to open our bindings menu.

binding

Here, we can bind values using Handlebars or JavaScript expressions. The categories of data we can use are presented on the right. We’re going to click into Date.

date

And then, choose Server Date, populating the appropriate Handlebars expression for our needs.

engineering change management system

Setting up relationships

If you looked closely at our database schema earlier, you’ll have noticed that this includes primary and foreign keys for handling relationships between our tables.

We need to define these in Budibase so that we can utilize data across related tables within the builder.

Specifically, we want to set up two relationships:

  1. A one-to-many relationship between projects and change_requests.
  2. A one-to-many relationship between change_requests and change_status_tracker.

First, from the projects table, hit define relationship.

define relationships

In the modal that opens, we’ll set up our many-to-one relationship, using id on the projects table as our Primary Key and project_id on the change_requests table as our Foreign Key.

relationships

We’ll then repeat this process for our other relationship, this time using id on change_requests as our Primary Key and change_request_id on status_change_tracker as our Foreign Key.

relationships

We can now view related rows across each of our tables.

related rows

Adding user columns

Our workflow also requires database tables to be linked to specific users. These are handled using Budibase’s internal Users table, so setting up relationships works a little bit differently.

There are special column types for this, called Single User and Multi User. We’re going to add a Single User column to each of our tables. This includes a setting called Default to Current User, which we’ll enable for change_requests and change_status_tracker.

We’ll call our new columns on these tables requestor and approver, respectively.

Here’s how our requestor column on change_requests should look.

user column

Repeat this exact process to add a Single User column to status_change_tracker, calling it approver and defaulting to the current user.

approver

We’ll also add a Single User column to the projects table. This time we’ll call it owner, but we won’t default to the current user.

owner

Lastly, we’ll assign ourselves to a few rows on the projects and change_requests tables for testing purposes later.

assisgn users

Creating custom roles

Now that we have the ability to link rows across our tables to specific tables, we can start thinking about how we want to manage access. As we said earlier, our app will be built around two user roles called Requestor and Project Owner.

Requestors will be able to submit change_requests against projects. Project Owners can respond to change_requests or carry out CRUD actions on their own projects.

All users will be able to view the status_change_tracker rows that are relevant to them.

To start, we need to create our custom roles. By default, Budibase apps ship with two roles. App User encompasses any authenticated user. App Admin can carry out admin actions or make changes to our app.

We can hit Manage Roles to open Budibase’s visual RBAC editor.

RBAC

We’ll then hit Add Role to create our roles, giving them a name and description.

engineering change management system

Custom roles can be hierarchical or entirely independent of one another. We want ours to be hierarchical, so we’ll place Requestor to the left of Project Owner, and draw a line between them to denote inheritance.

RBAC

Now, users with the Project Owner role will inherit all access that we grant to Requestors as well as any additional permission we give them.

Building role-specific views

The last thing we need to do before we can start generating our UIs is define the permissions that we want to grant to users with each role across each of our three tables.

In Budibase, we can achieve this by creating Views for specific user roles defining the read and write access they’ll have for different rows and columns. We can then generate UIs and automations from these, which will inherit the access rules of their underlying data source.

We’ll start by updating the minimum role required to access each of our existing tables from App User to App Admin.

Access

Now, only App Admins have full CRUD access across all tables.

Let’s start building our Views. From the change_requests table, hit Create a View. We’ll leave this set to Table and call it Requestor change_requests.

View

On our new View, we’ll hit Filter and add a rule to only return rows where requestor equals {{ Current User._id }}.

Filter

Now, users can only access the rows that they’ve submitted. We’ll retain full write access to enable them to create new rows.

View

We’ll also set our Access role to Requestor.

Access

We’re going to repeat the same process as above to create a second View, which we’ll call Project Owner change_requests. This time our filter only includes rows where projects.owner equals {{ Current User.globalId }}.

Remember to set Access to Project Owner.

Filter

Next, we’ll create role-specific Views for our projects table.

First, we’ll create one called Requestor projects, setting its Access to Requestor. This time we don’t need a filter since we want all rows to be readable. However, we don’t want them to be writable. We can configure this by hitting Columns and setting them all to read-only.

Columns

We’ll then add a View called Project Owner projects, with its Access set to Project Owner. This will keep all columns writable, but we’ll add a filter for rows where owner equals {{ Current User._id }}.

Filter

Lastly, we’ll add a single View for the status_change_tracker table called Read status_change_tracker. Both sets of users will need to use this, so we’re setting the Access to Requestor. We’ll then set all columns to read-only.

Engineering Change Management System

And that’s our data layer ready to go.

2. Automated status change tracking

Before we start building our app screens, we want to create some automation logic. Specifically, we need to create a rule that will add a row to our change_status_tracker table when the status is changed.

So, we’ll head to the Automation section, where we’ll hit Create Automation.

New Automation

When we do so, we’ll see this dialog, where we’re going to enter statusTracker as our automation’s name. We’ll choose Row Updated as the trigger.

Create automation

Here’s what this looks like. We’ve used the Table dropdown to point our trigger at change_requests.

Trigger

Any actions that we chain after this will now be carried out anytime a row on our change_requests table is updated.

The first thing we want to do is add some logic to determine if the status attribute has been updated. To do this, we’ll add a Condition block. This accepts a Reference Value, Condition, and Comparison Value.

The automation rule will only continue if our Condition evaluates to true. We’ve set our Reference Value to {{ trigger.oldRow.status }}, our Condition to Not Equals, and our Comparison Value to {{ trigger.row.status }}.

Condition

When a status has changed, we want to add an entry to our status_change_tracker table. So, we’ve added a Create Row action and pointed this at status_change_tracker.

Create Row

We’ll then add the following fields and values using Add Fields:

  • approver - {{ Current User._id }}
  • change_requests - {{ trigger.id }}
  • new_status - {{ trigger.row.status }}
  • old_status - {{ trigger.oldRow.status }}
  • status_change_date - {{ date now "" }}

Bindings

We’ll then copy the _id from a change_request row and hit Run Test to confirm that this works, making sure to select different status values for our Updated Row and Old Row data.

Test

Note that we’ll have to publish our app for our automation rule to work outside of the Automation section.

3. Project owner UIs

Now that our status tracker works, we can begin building our UIs. As we said in Budibase, we can generate these from the Data section, and they’ll inherit any rules and policies that are applied to the underlying database table or view.

We’re going to start by building screens for our Project Owners.

Projects

First up, we’ll build a simple CRUD screen for our Project Owner projects View. This will allow users to create, edit, or delete any rows where they’re assigned as the owner.

So, from our View, we’ll generate a Table screen.

Generate

We’ll then choose to include our forms in Modals.

Modal

Here’s how this will look.

Engineering Change Management System

The first thing we’ll do is tidy up our table by deselecting the id and change_requests columns.

Columns

We also want to make a few adjustments to our forms. Here’s what our create row form will look like out of the box.

Form

We’ll start by giving our form a new Title. Under Styles, we’ve also set Button Position to Top.

New Project

We’ll remove the owner and change_requests fields, and use the Layout setting to arrange our two dates into columns.

Layout

Recall that our projects table doesn’t default the owner to the current user. Instead, we’ll populate this manually when a user hits Save. Start by opening the actions drawer for the button.

ACtions

Under the Save Row action, we’ll bind owner to {{ Current User._id }}.

Bindings

Next, we’ll head to our Update form and make the exact same layout changes as we did for our Create form.

New Form

Change requests

Next, we want to create a screen for Project Owners to manage change_requests relating to their projects.

We’ll start by generating a Table UI from the Project Owner change_requests View, exactly as we did for our previous screen.

Once again, we’ve tidied up our table by removing any extraneous columns.

Columns

Users on this screen won’t need to submit change requests, so we’ll delete our Create Row button and corresponding Modal.

Table

On our Update form, we’ve bound the Title to {{ Edit row form block.Project Owner change_requests.projects.0.primaryDisplay }}: {{ Edit row form block.Project Owner change_requests.change_title }}.

Engineering Change Management System

We’ll remove change_title, projects, and change_status_tracker from our form, arranging our remaining short-form fields into columns. We’ve also moved the buttons to the top again.

Columns

Lastly, we’ll select the Disabled option on all fields except status and impact_analysis.

disable

Beneath our form, we want to display the specific change_request’s status history in a small table. We’ll start by adding a container with its horizontal alignment set to the center.

container

Inside this, we’ll nest a table with its Data set to Read change_status_tracker.

table

We’ll add a filter to return rows where change_request.id equals {{ Edit row form block.Project Owner change_requests.id }}.

Filter

Under Styles, we’ve set our table’s Wdith to 600 and its Height to 300.

table

4. Requestor UIs

Next, we’ll start building our screens for Requestors.

Remember, we want to enable them to submit change_requests against projects or track the progress of their own previous submissions.

Submitting change requests

First, we’ll create a screen for submitting change_requests. We’re going to generate this based on the Requestor projects View.

Again, we’ll start by tidying up our table and removing any components related to adding a new row.

requestor

We’ll then set our remaining form to View and make some of the same design tweaks as we did earlier, including binding its Title to {{ Edit row form block.Requestor projects.project_name }} and arranging our dates into columns.

project

From this modal, we want users to access a separate form for submitting change requests. We’ll start by adding a Button to our form, with its Text set to Request Change.

button

We’re going to add an action called Update State, which will store a key/value pair that we can use elsewhere in the builder. We’re setting our Key to projectId and our Value to {{ Edit row form block.Requestor projects._id }}.

engineering change management system

We’re going to host our change_request form in a Side Panel component, so users can still view the project details while they make a submission.

We’ll add this beneath our existing Modal in the component tree.

side panel

Within this, we’ll nest a Form Block with its Data set to Requestor change_requests and its Type set to Create.

change request form

We’ll give this a Title and deselect everything except projects, change_title, change_type, and description. All of the other values we need to populate will be handled with default values from earlier.

change request form

Users shouldn’t have to populate a projects value since we’re already storing this as a state.

We’ll disable this and set its Default Value to {{ State.projectId }}.

default value

Lastly, we’ll head back to our other form, and add an Open Side Panel button action.

Open Side Panel

Here’s how this will look in our app preview.

App Preview

Viewing previous requests

The last screen we’ll build will enable Requestors to view the progress of their own previous change_requests.

We’ll generate this from the Requestor change_requests table. As before, we’ve tidied up our table and removed our Create Row components.

Table

For our remaining form, we’ll bind the Title to {{ Edit row form block.Requestor change_requests.change_title }}. We’ve also used the same fields layout as we did for the corresponding Project Owner screen, this time setting the form Type to View.

Form

We can simply copy and paste the status_change_tracker table and parent container from our other screen, remembering to update the binding for our filter so that this works with our new form.

status tracker

And that’s our app completed from a functional perspective.

5. Design tweaks, navigation & publishing

Before we push our engineering change management system live for users, we’re going to make a few key UX improvements.

Firstly, we want to consolidate the entries in the nav bar. Users can only see nav items for the screens to which they have access.

We’ll start by updating the Label for our two Requestors screens to Projects and Change Requests.

engineering change management system

These will be seen by both Requestors and Project Owners.

We’ll give Project Owners a single additional entry called Admin, selecting the option to Open sub-links.

sub nav

Here, we’ll add entries for their additional screens.

links

Here’s how this will look in the field.

app preview

App theming

To adjust the color scheme of our app, we can head to Screen and Theme. Here, we’ll choose Midnight.

We’ve also set our button colors to better reflect Budibase’s internal brand.

App Theme

Display texts

Lastly, all of our autogenerated screens use our database schema to populate display texts, including headlines, column names, and form fields.

We’ll start by updating the Text setting on the Headline components at the top of each screen. We can alter these for human-readability.

Heading

We’ll use the Label setting on each of our table columns to add capitalization.

display

We’ll also do the same on our form fields, using the Label and Placeholder settings.

form fields

When we’re happy, we can hit Publish to push our app live.

publish

Here’s a reminder of what our final engineering change management system should look like.

Turn data into action with Budibase

Budibase is the fast, easy way for IT teams to ship secure workflow tools. With extensive data support, autogenerated UIs, custom RBAC, low-code automations, optional self-hosting, and more, there’s never been a better way to ship mission-critical apps at pace.

Check out our features overview to learn more.