Using Pkl Language to Generate Configurations
How Pkl Language Improved Our UI Builder.
Feb 23, 2024
We built a config-based UI builder for internal dashboards. The idea was simple: define the screen in YAML or JSON, and let the builder render the UI.
The config controlled network calls, storage, and UI components. That made new screens quick to create, but the config files became the product surface. When they were hard to read, the builder was hard to use.
That was the problem. As capabilities grew, the configuration became unreadable and difficult to maintain. Bugs became harder to reproduce because the same screen could be expressed in multiple ways, but only a few of those ways were actually safe.
Config-based tools are fast at the start. They become risky when users have to guess the one correct shape.
Challenges with v0 Config
Here's what it looks like.
{
"endPoints":{
// all endpoints config
},
"pages":{
// all page level configs
},
"sidebar": [
// all sidebar options...this is an Array<SidebarItems>
],
"storage":{
// storage config
}
}
Initially, we built the product to consume configs in JSON.
- On average, it required 500 lines of valid JSON to render a screen with static text, a few CTAs and a table section that would depend on the responses from services.
- The code reuse was 0. For similar and repetitive definitions, the end users were forced to copy-paste previously used configs. This led to bugs.
- Early adopters of the tool are the teams who wanted to spin up an Admin console for their services rapidly, This copy-pasting approach increased the risk of errors, especially if dependencies/parameters were not updated to match the current context.
We added YAML support, which made the files easier to read, but it did not solve reuse or validation. In config v0, one file still contained everything the tool needed.
Improvements in v1 Config
We tweaked a few properties and split configs by purpose: sidebar.yaml,
network.yaml, pages.yaml, and so on.
This improved readability but reusability and validating the configs were still a problem.
We built static validators that ran every time a user changed config. These validators caught some broken files before the config reached the parser.
That helped, but only partially. Reuse was still weak, and validation was still something we were bolting on from the outside.
The v2 Config with Pkl Lang
On February 1, 2024, Apple announced Pkl Lang. A few days later, we found the GitHub repo.
Pkl was interesting because it attacked the exact pain we were working around: types, validation, reuse, and configuration generation.
Pkl's inherent support for modularity & reusability filled the void for us!
- Pkl's type and validation system allows for catching configuration errors before deploying an application. This is particularly useful in builder context, where detecting and resolving configuration errors early saves significant time and resources
- Pkl's support for modules allows for better organization of configuration code. Common UI components or configurations can be modularized and reused across different parts.
Here's a simplified example. In reality, we keep templates and generated config in separate files.
// endpoints.config.pkl
class Endpoint {
origin: String
endPoint: String
method: "GET"|"PUT"|"POST"|"DELETE"
}
endPoints = new Mapping {
["getCountries"] = new Endpoint {
origin = "api.example.com"
endPoint = "/countries"
method = "GET"
}
["getInfoForACountry"] = new Endpoint {
origin = "api.example.com"
endPoint = "/country/:countryID"
method = "GET"
}
}
this gets converted to
// endpoints.config.yaml
endPoints:
getCountries:
origin: api.example.com
endPoint: /countries
method: GET
getInfoForACountry:
origin: api.example.com
endPoint: /country/:countryID
method: GET
Since we adopted Pkl within days of the announcement, the official docs and community threads were the main resources we had.
What worked was not "Pkl is better than YAML." YAML was fine as an output. The useful shift was moving authoring to a typed, reusable configuration language and generating the simpler format from there.