Originally created by @jjuliano on GitHub (Jul 25, 2025).
Hello,
I have a pkl template file that have many imports defined. For example, main.pkl is my template, and foo.pkl is a supporting util library, and bar.pkl is my configuration file.
The idea is that, as a user-facing configuration file, for UX reasons, bar.pkl shouldn't have 100 import lines, where the user scrolls to the bottom of the file to enter their configurations.
I assume that since the imports are already in the non user-facing pkl files, then by doing 'amends' or 'extends' the config files can just access those imports.
At the moment, I did several test for this functionality as seen below. Is there another way to do this without writing a chaining function on the template?
main.pkl
open module main
import "foo.pkl" as foo
bar: String?
baz: String?
function foo1(): String = foo.foo()
foo.pkl
function foo(): String = "foo bar baz"
bar.pkl
amends "main.pkl"
bar = foo1()
baz = "\(foo.foo())"
$ pkl eval bar.pkl
–– Pkl Error ––
Cannot find property `foo`.
4|baz="\(foo.foo())"
^^^
at bar#baz (file:///private/tmp/inherited-imports/bar.pkl, line 4)
Did you mean any of the following?
foo1()106|text= renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
baz.pkl
extends "main.pkl"
bar = foo1()
baz = "\(foo.foo())"
$ pkl eval baz.pkl
–– Pkl Error ––
Cannot find property `foo`.
4|baz="\(foo.foo())"
^^^
at baz#baz (file:///private/tmp/inherited-imports/baz.pkl, line 4)
Did you mean any of the following?
foo1()106|text= renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
qux.pkl
amends "main.pkl"
bar = foo1()
$ pkl eval qux.pkl
bar="foo bar baz"baz= null
Originally created by @jjuliano on GitHub (Jul 25, 2025).
Hello,
I have a pkl template file that have many imports defined. For example, main.pkl is my template, and foo.pkl is a supporting util library, and bar.pkl is my configuration file.
The idea is that, as a user-facing configuration file, for UX reasons, bar.pkl shouldn't have 100 import lines, where the user scrolls to the bottom of the file to enter their configurations.
I assume that since the imports are already in the non user-facing pkl files, then by doing 'amends' or 'extends' the config files can just access those imports.
At the moment, I did several test for this functionality as seen below. Is there another way to do this without writing a chaining function on the template?
*main.pkl*
```pkl
open module main
import "foo.pkl" as foo
bar: String?
baz: String?
function foo1(): String = foo.foo()
```
*foo.pkl*
```pkl
function foo(): String = "foo bar baz"
```
*bar.pkl*
```pkl
amends "main.pkl"
bar = foo1()
baz = "\(foo.foo())"
```
```sh
$ pkl eval bar.pkl
–– Pkl Error ––
Cannot find property `foo`.
4 | baz = "\(foo.foo())"
^^^
at bar#baz (file:///private/tmp/inherited-imports/bar.pkl, line 4)
Did you mean any of the following?
foo1()
106 | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
*baz.pkl*
```pkl
extends "main.pkl"
bar = foo1()
baz = "\(foo.foo())"
```
```sh
$ pkl eval baz.pkl
–– Pkl Error ––
Cannot find property `foo`.
4 | baz = "\(foo.foo())"
^^^
at baz#baz (file:///private/tmp/inherited-imports/baz.pkl, line 4)
Did you mean any of the following?
foo1()
106 | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
*qux.pkl*
```pkl
amends "main.pkl"
bar = foo1()
```
```sh
$ pkl eval qux.pkl
bar = "foo bar baz"
baz = null
```
Note: an import gives you both a value and a type. If you want to expose imported types to a child module, you can create a typealias:
// bar.pkl
import "MyType.pkl" as _MyType
typealias MyType = _MyType
amends "bar.pkl"
local t: MyType = new {}
@bioball commented on GitHub (Jul 25, 2025):
You can expose the import as a property. This makes makes it a `hidden const`, which means:
1. The property does not get rendered tot JSON/YAML/etc, nor exported to Go/Java/Swift
2. The property cannot be assigned to in an amending/extending module
```pkl
// bar.pkl
hidden const foo = import("foo.pkl")
```
Now, you can reference `foo` plainly in the amending/extending module. If you're in a nested scope, you need to use the `module` keyword:
```pkl
// baz.pkl
amends "bar.pkl"
baz = foo.foo()
nested {
qux = module.foo.foo()
}
```
Note: an import gives you both a value and a type. If you want to expose imported _types_ to a child module, you can create a typealias:
```pkl
// bar.pkl
import "MyType.pkl" as _MyType
typealias MyType = _MyType
```
```pkl
amends "bar.pkl"
local t: MyType = new {}
```
You can expose the import as a property. This makes makes it a hidden const, which means:
The property does not get rendered tot JSON/YAML/etc, nor exported to Go/Java/Swift
The property cannot be assigned to in an amending/extending module
// bar.pkl
hidden const foo = import("foo.pkl")
Now, you can reference foo plainly in the amending/extending module. If you're in a nested scope, you need to use the module keyword:
// baz.pkl
amends "bar.pkl"
baz = foo.foo()
nested {
qux = module.foo.foo()
}
Note: an import gives you both a value and a type. If you want to expose imported types to a child module, you can create a typealias:
// bar.pkl
import "MyType.pkl" as _MyType
typealias MyType = _MyType
amends "bar.pkl"
local t: MyType = new {}
Thanks @bioball, this works, however, for the nested, is it possible to not have to call module to access the function?
@jjuliano commented on GitHub (Jul 26, 2025):
> You can expose the import as a property. This makes makes it a `hidden const`, which means:
>
> 1. The property does not get rendered tot JSON/YAML/etc, nor exported to Go/Java/Swift
> 2. The property cannot be assigned to in an amending/extending module
>
> // bar.pkl
> hidden const foo = import("foo.pkl")
> Now, you can reference `foo` plainly in the amending/extending module. If you're in a nested scope, you need to use the `module` keyword:
>
> // baz.pkl
> amends "bar.pkl"
>
> baz = foo.foo()
>
> nested {
> qux = module.foo.foo()
> }
> Note: an import gives you both a value and a type. If you want to expose imported _types_ to a child module, you can create a typealias:
>
> // bar.pkl
> import "MyType.pkl" as _MyType
>
> typealias MyType = _MyType
> amends "bar.pkl"
>
> local t: MyType = new {}
Thanks @bioball, this works, however, for the nested, is it possible to not have to call `module` to access the function?
This is an intentional rule; it guarantees that adding/removing names in the base module will not change the meaning of a name lookup in an amending/extending module.
@bioball commented on GitHub (Jul 26, 2025):
This is an intentional rule; it guarantees that adding/removing names in the base module will not change the meaning of a name lookup in an amending/extending module.
You can read more about this rationale here: https://github.com/apple/pkl/discussions/865#discussioncomment-11640312
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @jjuliano on GitHub (Jul 25, 2025).
Hello,
I have a pkl template file that have many imports defined. For example, main.pkl is my template, and foo.pkl is a supporting util library, and bar.pkl is my configuration file.
The idea is that, as a user-facing configuration file, for UX reasons, bar.pkl shouldn't have 100 import lines, where the user scrolls to the bottom of the file to enter their configurations.
I assume that since the imports are already in the non user-facing pkl files, then by doing 'amends' or 'extends' the config files can just access those imports.
At the moment, I did several test for this functionality as seen below. Is there another way to do this without writing a chaining function on the template?
main.pkl
foo.pkl
bar.pkl
baz.pkl
qux.pkl
@bioball commented on GitHub (Jul 25, 2025):
You can expose the import as a property. This makes makes it a
hidden const, which means:Now, you can reference
fooplainly in the amending/extending module. If you're in a nested scope, you need to use themodulekeyword:Note: an import gives you both a value and a type. If you want to expose imported types to a child module, you can create a typealias:
@jjuliano commented on GitHub (Jul 26, 2025):
Thanks @bioball, this works, however, for the nested, is it possible to not have to call
moduleto access the function?@bioball commented on GitHub (Jul 26, 2025):
This is an intentional rule; it guarantees that adding/removing names in the base module will not change the meaning of a name lookup in an amending/extending module.
You can read more about this rationale here: https://github.com/apple/pkl/discussions/865#discussioncomment-11640312