Template
9155 lines
229 KiB
Plaintext
9155 lines
229 KiB
Plaintext
# Project config
|
||
|
||
|
||
To define a project, create a `likec4.config.json` file in the folder.
|
||
All files in the folder (and subfolders) will be part of this project:
|
||
|
||
<FileTree>
|
||
- externals
|
||
- amazon.c4
|
||
- ...
|
||
- services
|
||
- service1.c4
|
||
- service2.c4
|
||
- ...
|
||
- specification.c4
|
||
- **likec4.config.json**
|
||
</FileTree>
|
||
|
||
## Configuration
|
||
|
||
The `likec4.config.json` file must have the **name** of the project.
|
||
|
||
|
||
```jsonc
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name", // required
|
||
"title": "Project Title", // optional
|
||
"metadata": { // optional
|
||
"owner": "platform-team",
|
||
"domain": "payments"
|
||
}
|
||
}
|
||
```
|
||
|
||
The name must be unique if you use [multiple projects](/dsl/config/multi-projects).
|
||
|
||
Use `metadata` to store arbitrary project-level key-value data that can be consumed by tooling.
|
||
|
||
:::note
|
||
You can use these names for the config file:
|
||
- `.likec4rc`
|
||
- `.likec4.config.json`
|
||
- `likec4.config.json`
|
||
|
||
LikeC4 interprets any file as JSON5
|
||
|
||
You can also use these names:
|
||
- `likec4.config.js`
|
||
- `likec4.config.mjs`
|
||
- `likec4.config.ts`
|
||
- `likec4.config.mts`
|
||
|
||
See [Programmatic config](/dsl/config/programmatic) for more information.
|
||
:::
|
||
|
||
## Extend styles
|
||
|
||
If you want to share styles across multiple projects, you can use `extends` in **JSON configs**.
|
||
`extends` can be a string or an array of strings.
|
||
Each path is resolved relative to the config file that declares it, and `extends` can be recursive.
|
||
LikeC4 merges only the `styles` section (in order); all other fields come from the root config.
|
||
|
||
```jsonc
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"extends": [
|
||
"../shared/base-config.json",
|
||
"../shared/theme-config.json"
|
||
],
|
||
"styles": {
|
||
"defaults": {
|
||
"relationship": {
|
||
"arrow": "vee"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Include additional directories
|
||
|
||
You can include LikeC4 source files from directories outside the project folder by adding an `include` configuration to the config file.
|
||
This is useful for sharing specifications, common styles, or other model files across multiple projects.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"include": {
|
||
"paths": [
|
||
"../shared",
|
||
"../common/specs"
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
<FileTree>
|
||
- shared
|
||
- specs.c4
|
||
- common-styles.c4
|
||
- common
|
||
- specs
|
||
- base-elements.c4
|
||
- my-project
|
||
- **likec4.config.json**
|
||
- model.c4
|
||
- ...
|
||
</FileTree>
|
||
|
||
Paths are relative to the project folder (the folder containing the config file).
|
||
LikeC4 recursively scans the included directories for `.c4` files.
|
||
|
||
You can also configure the scanning behavior:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"include": {
|
||
"paths": ["../shared"],
|
||
"maxDepth": 5,
|
||
"fileThreshold": 50
|
||
}
|
||
}
|
||
```
|
||
|
||
- **`paths`** - Array of relative directory paths (required)
|
||
- **`maxDepth`** - Maximum directory depth to scan (default: 3, range: 1-20)
|
||
- **`fileThreshold`** - Warn if more than this many files are loaded (default: 30)
|
||
|
||
:::note
|
||
Include paths must be relative paths. Absolute paths (like `/usr/share`), drive letters (like `C:\`), and URLs are not allowed.
|
||
:::
|
||
|
||
For more details on sharing files between projects, see [Multi-projects](/dsl/config/multi-projects#share-specification).
|
||
|
||
## Exclude files
|
||
|
||
By default, LikeC4 recursively scans in the project folder.
|
||
You can exclude files by adding an `exclude` array to the config file.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"exclude": [
|
||
"**/node_modules/**"
|
||
]
|
||
}
|
||
```
|
||
|
||
If no exclude pattern is provided, LikeC4 uses `["**/node_modules/**"]` as default.
|
||
The exclude pattern is the same as the one used by [picomatch](https://github.com/micromatch/picomatch).
|
||
|
||
## Infer technology from icon
|
||
|
||
By default, LikeC4 automatically derives a human-readable `technology` label from bundled icon names (`aws:`, `azure:`, `gcp:`, `tech:`) when technology is not explicitly set on the element or its kind.
|
||
|
||
For example, an element with `icon tech:apache-flink` gets technology **"Apache Flink"**, and `icon aws:simple-storage-service` gets **"Simple Storage Service"**. `bootstrap:` icons are excluded.
|
||
|
||
To disable this behavior:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"inferTechnologyFromIcon": false
|
||
}
|
||
```
|
||
|
||
## Implicit views
|
||
|
||
LikeC4 can automatically generate scoped views for elements that don't have explicitly defined views, enabling drill-down navigation — clicking on an element navigates to its auto-generated scoped view (equivalent to `view of element { include * }`). This feature is disabled by default.
|
||
|
||
To enable this behavior:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"implicitViews": true
|
||
}
|
||
```
|
||
|
||
## Landing page
|
||
|
||
Configure what is shown when visiting the root URL of a generated static site.
|
||
|
||
### Redirect to the index view
|
||
|
||
Set `redirect` to `true` to go straight to the index view instead of showing the view grid:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"landingPage": {
|
||
"redirect": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### Filter views by inclusion
|
||
|
||
Show only specific views on the landing page grid, by view ID or tag (`#tagName`):
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"landingPage": {
|
||
"include": ["cloud", "#overview"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Filter views by exclusion
|
||
|
||
Hide specific views from the landing page grid:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"landingPage": {
|
||
"exclude": ["#internal", "legacy-backend"]
|
||
}
|
||
}
|
||
```
|
||
|
||
## Image Aliases
|
||
|
||
When using local images in your LikeC4 model, you can create aliases for the folder your images are in to make them more readable and the files more transportable.
|
||
|
||
Use the `likec4.config.json` to add an `imageAliases` field:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"imageAliases": {
|
||
"@": "./images",
|
||
"@root": "../../some-more-images"
|
||
}
|
||
}
|
||
```
|
||
|
||
You can then use the alias in your model:
|
||
|
||
```likec4 title="example"
|
||
// ./amazon.c4
|
||
model {
|
||
serviceA = service {
|
||
icon: @/service-a.png
|
||
}
|
||
|
||
serviceB = service {
|
||
icon: @root/service-b.png
|
||
}
|
||
}
|
||
|
||
// ./externals/externals.c4
|
||
model {
|
||
serviceC = service {
|
||
icon: @/service-c.png
|
||
}
|
||
}
|
||
```
|
||
|
||
<FileTree>
|
||
- some-more-images
|
||
- service-b.png
|
||
- ...
|
||
- docs
|
||
- project
|
||
- images
|
||
- service-a.png
|
||
- service-c.png
|
||
- ...
|
||
- externals
|
||
- externals.c4
|
||
- likec4.config.json
|
||
- amazon.c4
|
||
- ...
|
||
</FileTree>
|
||
|
||
:::note
|
||
In the example above the `externals.c4` is a nested file, but the aliasing works based on the project root.
|
||
:::
|
||
|
||
### Naming Rules
|
||
|
||
When using image aliases, keep the following rules in mind:
|
||
|
||
- Aliases must start with `@` and can include letters, numbers, and underscores.
|
||
- The alias must be unique within the project.
|
||
- The alias can point to a relative or absolute path.
|
||
|
||
### Defaults
|
||
|
||
When no LikeC4 configuration file is found, or when no `imageAliases` field is found, LikeC4 uses the following defaults:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"imageAliases": {
|
||
"@": "./images",
|
||
}
|
||
}
|
||
```
|
||
|
||
Simply override the `@` to change the default location.
|
||
|
||
```json
|
||
{
|
||
"imageAliases": {
|
||
"@": "./my-images",
|
||
}
|
||
}
|
||
```
|
||
|
||
## Styles customization
|
||
|
||
LikeC4 provides advanced style customization capabilities.
|
||
|
||
### Theme overrides
|
||
|
||
You can override default theme colors and sizes by adding a `styles.theme` section to the config file.
|
||
Each definition can be either a CSS value or a detailed object specifying color for specific parts.
|
||
|
||
Example of simple color definition:
|
||
|
||
```jsonc
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"styles": {
|
||
"theme": {
|
||
"colors": {
|
||
"primary": "#FF6B6B",
|
||
"secondary": "rgba(37,99,235,1)",
|
||
}
|
||
},
|
||
}
|
||
}
|
||
```
|
||
|
||
Example of detailed color definition:
|
||
|
||
```jsonc
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"styles": {
|
||
"theme": {
|
||
"colors": {
|
||
"muted": {
|
||
"elements": {
|
||
"fill": "#2563eb", // Background color
|
||
"stroke": "#1d4ed8", // Border color
|
||
"hiContrast": "#ffffff", // Title text color
|
||
"loContrast": "#e2e8f0" // Description text color
|
||
},
|
||
"relationships": {
|
||
"line": "#1d4ed8", // Line color
|
||
"label": "#ffffff", // Label text color
|
||
"labelBg": "rgba(37,99,235,0.1)" // Label background color
|
||
}
|
||
},
|
||
// You can give detailed definitions for a custom color in your specification
|
||
"custom-color-from-your-spec": {
|
||
// ...
|
||
}
|
||
}
|
||
},
|
||
}
|
||
}
|
||
```
|
||
|
||
You can override sizes used in LikeC4:
|
||
|
||
```jsonc
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"styles": {
|
||
"theme": {
|
||
"sizes": {
|
||
"md": {
|
||
"width": 200,
|
||
"height": 200
|
||
},
|
||
"lg": {
|
||
"width": 300,
|
||
"height": 300
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Default styles
|
||
|
||
You can override default values for style properties by adding a `styles.defaults` section to the config file.
|
||
These values will be applied to all elements and relationships, unless properties are explicitly defined in the specification.
|
||
|
||
```jsonc
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "project-name",
|
||
"styles": {
|
||
"defaults": {
|
||
// Defaults for all elements
|
||
"border": "dashed",
|
||
"opacity": 100,
|
||
"size": "md",
|
||
"color": "primary", // theme color name
|
||
|
||
// Defaults for groups
|
||
"group": {
|
||
"color": "primary",
|
||
"opacity": 10,
|
||
"border": "dashed"
|
||
},
|
||
|
||
// Defaults for relationships
|
||
"relationship": {
|
||
"color": "gray",
|
||
"line": "dashed",
|
||
"arrow": "normal"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
<AdvancedCustomizationTip />
|
||
|
||
# Multi-projects
|
||
|
||
|
||
Sometimes you may want to split your LikeC4 model into multiple ones, based on domains, teams, or any other criteria.
|
||
|
||
You can do this by creating multiple projects in your workspace and linking them together.
|
||
You can also use this feature to share your model with other teams or projects.
|
||
|
||
Create `likec4.config.json` files in the folders you want to be projects:
|
||
|
||
<FileTree>
|
||
- cloud
|
||
- **likec4.config.json**
|
||
- service1.c4
|
||
- service2.c4
|
||
- ...
|
||
- externals
|
||
- **likec4.config.json**
|
||
- amazon.c4
|
||
- ...
|
||
</FileTree>
|
||
|
||
Projects can be nested.
|
||
In this case, files from the nested project are not part of the parent project.
|
||
|
||
<FileTree>
|
||
- cloud
|
||
- likec4.config.json
|
||
- service1.c4
|
||
- service2.c4
|
||
- nested
|
||
- likec4.config.json
|
||
- service3.c4 // this will be part of the 'nested' project, not the 'cloud' project
|
||
</FileTree>
|
||
|
||
|
||
## Import elements
|
||
|
||
You can import elements from other projects by using the `import` keyword.
|
||
|
||
```likec4
|
||
import { serviceA } from 'projectA'
|
||
|
||
model {
|
||
serviceB = service {
|
||
-> serviceA.api 'calls serviceA'
|
||
}
|
||
}
|
||
```
|
||
|
||
<Aside type='caution' title="Limitations">
|
||
At the moment, the following limitations apply:
|
||
- Referenced projects must be loaded in the same workspace
|
||
- Only top-level **model** elements can be imported
|
||
</Aside>
|
||
|
||
## Share specification
|
||
|
||
You can share specification files (or any other LikeC4 sources) across multiple projects using the `include` configuration option.
|
||
|
||
### Using include paths
|
||
|
||
Add an `include` configuration to your project to include files from directories outside the project folder:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "cloud",
|
||
"include": {
|
||
"paths": ["../shared"]
|
||
}
|
||
}
|
||
```
|
||
|
||
<FileTree>
|
||
- shared
|
||
- specs.c4
|
||
- common-styles.c4
|
||
- cloud
|
||
- likec4.config.json
|
||
- services.c4
|
||
- ...
|
||
- externals
|
||
- likec4.config.json
|
||
- amazon.c4
|
||
- ...
|
||
</FileTree>
|
||
|
||
Both `cloud` and `externals` projects can include the shared specification:
|
||
|
||
```json title="cloud/likec4.config.json"
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "cloud",
|
||
"include": {
|
||
"paths": ["../shared"]
|
||
}
|
||
}
|
||
```
|
||
|
||
```json title="externals/likec4.config.json"
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "externals",
|
||
"include": {
|
||
"paths": ["../shared"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Include configuration
|
||
|
||
The `include` configuration accepts an object with the following properties:
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://likec4.dev/schemas/config.json",
|
||
"name": "my-project",
|
||
"include": {
|
||
"paths": ["../shared", "../common/specs"],
|
||
"maxDepth": 5,
|
||
"fileThreshold": 50
|
||
}
|
||
}
|
||
```
|
||
|
||
- **`paths`** - Array of relative directory paths (required)
|
||
- **`maxDepth`** - Maximum directory depth to scan (default: 3, range: 1-20)
|
||
- **`fileThreshold`** - Warn if more than this many files are loaded (default: 30)
|
||
|
||
<Aside type='tip' title="Performance optimization">
|
||
Use `maxDepth` to limit deep directory traversal, and `fileThreshold` to detect when you're accidentally including large directories.
|
||
If you see warnings about slow loading, consider reducing `maxDepth` or being more specific with your include paths.
|
||
</Aside>
|
||
|
||
<Aside type='tip' title="Path resolution">
|
||
Include paths are relative to the project folder (the folder containing the config file).
|
||
LikeC4 recursively scans the included directories for `.c4` files.
|
||
</Aside>
|
||
|
||
<Aside type='caution' title="Path requirements">
|
||
Include paths must be relative paths. Absolute paths, drive letters (like `C:\`), and URLs are not allowed.
|
||
</Aside>
|
||
|
||
### Using symlinks (alternative)
|
||
|
||
Alternatively, you can use symlinks to share files:
|
||
|
||
<FileTree>
|
||
- shared
|
||
- specs.c4
|
||
- cloud
|
||
- specs.c4 // -> ../shared/specs.c4
|
||
- likec4.config.json
|
||
- ...
|
||
- externals
|
||
- specs.c4 // -> ../shared/specs.c4
|
||
- likec4.config.json
|
||
- ...
|
||
</FileTree>
|
||
|
||
<Aside type='note'>
|
||
The `include` configuration is generally preferred over symlinks as it's more portable and easier to manage.
|
||
</Aside>
|
||
|
||
# TypeScript/JavaScript Config
|
||
|
||
import { PackageManagers } from 'starlight-package-managers'
|
||
|
||
:::caution
|
||
This feature is experimental and not well tested with IDEs.
|
||
|
||
If you find any issues, please report them to [GitHub](https://github.com/likec4/likec4/issues).
|
||
:::
|
||
|
||
You can configure your projects programmatically with TypeScript or JavaScript.
|
||
The config filename can be any of the following:
|
||
|
||
- `likec4.config.js`
|
||
- `likec4.config.mjs`
|
||
- `likec4.config.ts`
|
||
- `likec4.config.mts`
|
||
|
||
Example:
|
||
|
||
```ts
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
exclude: ["**/node_modules/**", "**/.cache/**"],
|
||
include: ["../shared", "../common/specs"],
|
||
imageAliases: {
|
||
"@": "./images",
|
||
"@root": "../../some-more-images"
|
||
}
|
||
})
|
||
```
|
||
|
||
:::tip
|
||
You can also use smaller package `@likec4/config`:
|
||
|
||
```ts
|
||
import { defineConfig } from '@likec4/config'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project'
|
||
})
|
||
```
|
||
|
||
:::
|
||
|
||
|
||
## Custom Generators
|
||
|
||
LikeC4 CLI has a [`generate`](/tooling/cli/#generate-mermaid-dot-d2-plantuml) command to generate files from your model.
|
||
You can define custom generators in your project config:
|
||
|
||
```ts
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
generators: {
|
||
'hello': async ({ likec4model, ctx }) => {
|
||
await ctx.write({
|
||
path: 'hello.txt', // relative to the project root
|
||
content: `Project: ${likec4model.project.id}`,
|
||
})
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
Now you can run your generator with [CLI](/tooling/cli):
|
||
|
||
```bash
|
||
likec4 gen hello
|
||
```
|
||
|
||
In [multi-project](/dsl/config/multi-projects) workspace use:
|
||
|
||
```bash
|
||
likec4 gen hello --project my-project
|
||
# Other options
|
||
likec4 gen hello --project my-project --use-dot
|
||
```
|
||
|
||
### Reusable Generators
|
||
|
||
There is also helper function `defineGenerators` to define reusable generators:
|
||
|
||
```ts title="example"
|
||
// shared_generators.ts
|
||
import { defineGenerators } from 'likec4/config'
|
||
|
||
export default defineGenerators({
|
||
'hello': async ({ likec4model, ctx }) => {
|
||
await ctx.write({
|
||
path: 'hello.txt', // relative to the project root
|
||
content: `Project: ${likec4model.project.id}`,
|
||
})
|
||
},
|
||
})
|
||
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
import generators from './shared_generators'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
generators,
|
||
})
|
||
```
|
||
|
||
## Styles Customization
|
||
|
||
Same as in [JSON-config](/dsl/config/#styles-customization), you can define styles in TypeScript config:
|
||
|
||
```ts
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
styles: {
|
||
// Theme section allows you to override default colors and sizes
|
||
theme: {
|
||
colors: {
|
||
// Simple color definition - automatically generates element/relationship colors
|
||
primary: "#FF6B6B",
|
||
secondary: "#4ECDC4",
|
||
// Detailed color definition with specific values
|
||
muted: {
|
||
elements: {
|
||
fill: "#2563eb", // Background color
|
||
stroke: "#1d4ed8", // Border color
|
||
hiContrast: "#ffffff", // Title text color
|
||
loContrast: "#e2e8f0" // Description text color
|
||
},
|
||
relationships: {
|
||
line: "#1d4ed8", // Line color
|
||
label: "#ffffff", // Label text color
|
||
labelBg: "rgba(37,99,235,0.1)" // Label background color
|
||
}
|
||
},
|
||
// Give detailed definitions for a color from your specification
|
||
"custom-color-from-your-spec": {
|
||
// ...
|
||
}
|
||
},
|
||
sizes: { // override dimensions
|
||
md: {
|
||
width: 200,
|
||
height: 200
|
||
}
|
||
}
|
||
},
|
||
// Override default values for style properties,
|
||
// These values will be used if such property is not defined
|
||
defaults: {
|
||
border: "dotted",
|
||
opacity: 100,
|
||
size: "md",
|
||
color: "slate",
|
||
group: {
|
||
color: "green",
|
||
opacity: 10,
|
||
border: "solid"
|
||
},
|
||
relationship: {
|
||
color: "indigo",
|
||
line: "solid",
|
||
arrow: "diamond"
|
||
}
|
||
}
|
||
}
|
||
})
|
||
```
|
||
|
||
### Reusable Styles
|
||
|
||
There is also helper functions `defineStyle`, `defineTheme` and `defineThemeColor`.
|
||
You can export them from a separate file (or publish as a package, to share with others) and import them in your config.
|
||
|
||
```ts title="@your-org/likec4-theme"
|
||
import { defineStyle, defineTheme, defineThemeColor } from 'likec4/config'
|
||
|
||
const theme1 = defineTheme({
|
||
colors: {
|
||
primary: "#FF6B6B",
|
||
},
|
||
})
|
||
|
||
const theme2 = defineTheme({
|
||
colors: {
|
||
primary: "#4ECDC4",
|
||
},
|
||
})
|
||
|
||
export default defineStyle({
|
||
// Even use environment variable to switch between themes
|
||
theme: process.env['THEME'] === 'theme2' ? theme2 : theme1,
|
||
defaults: {
|
||
// ...
|
||
}
|
||
})
|
||
```
|
||
|
||
And in your config:
|
||
|
||
```ts
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
import styles from '@your-org/likec4-theme'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
styles,
|
||
})
|
||
```
|
||
|
||
<br/>
|
||
|
||
|
||
<AdvancedCustomizationTip/>
|
||
|
||
# Deployment Model
|
||
|
||
|
||
Deployment Model represents another layer, _physical model_ with its own structure and elements (deployment nodes).
|
||
It references the [logical model](/dsl/model) and inherits its relationships.
|
||
|
||
## Specification
|
||
|
||
First, following the [same approach](/dsl/specification/#element-kind), deployment node kinds have to defined within the specification:
|
||
|
||
```likec4
|
||
specification {
|
||
deploymentNode environment
|
||
deploymentNode zone
|
||
deploymentNode kubernetes {
|
||
// Nodes have same styling options
|
||
style {
|
||
color blue
|
||
icon tech:kubernetes
|
||
multiple true
|
||
}
|
||
}
|
||
deploymentNode vm {
|
||
// Common properties for the kind
|
||
notation 'Virtual Machine'
|
||
technology 'VMware'
|
||
}
|
||
}
|
||
```
|
||
|
||
You define whatever you need to represent your deployment model and your ubiquitous language.
|
||
|
||
## Deployment nodes
|
||
|
||
The deployment model is a set of nodes, organized in a hierarchical structure:
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod {
|
||
zone eu {
|
||
zone zone1 {
|
||
vm vm1
|
||
vm vm2
|
||
}
|
||
// You can also use '=' with the name coming first
|
||
zone2 = zone {
|
||
vm1 = vm
|
||
vm2 = vm
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Node names must be unique within its container (parent node); same rules as for element names in the logical model.
|
||
|
||
Nodes may have properties just like [logical elements](/dsl/model/#element-properties).
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod 'Production' {
|
||
#live #sla-customer
|
||
technology 'OpenTofu'
|
||
summary 'Production environment'
|
||
description '''
|
||
## Detailed description
|
||
|
||
With **Markdown** support
|
||
|
||
'''
|
||
|
||
link https://likec4.dev
|
||
|
||
zone eu {
|
||
title 'EU Region'
|
||
// ...
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Extend nodes
|
||
|
||
As in the logical model, you can [`extend`](/dsl/extend/#extend-element) deployment nodes:
|
||
|
||
```likec4
|
||
// File: 'deployments/prod.c4'
|
||
deployment {
|
||
environment prod
|
||
}
|
||
|
||
// File: 'deployments/prod/zone-eu.c4'
|
||
deployment {
|
||
extend prod {
|
||
zone eu
|
||
}
|
||
}
|
||
```
|
||
|
||
Same [rules](/dsl/extend/#extend-element) apply for extending nodes:
|
||
- extended node must be referenced by a fully qualified name.
|
||
- define [additional properties](/dsl/extend/#additional-properties).
|
||
|
||
## Deployed instances
|
||
|
||
Operator `instanceOf` _“deploys”_ elements from the logical model to deployment nodes:
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod {
|
||
zone eu {
|
||
zone zone1 {
|
||
// 'frontend.ui' is a logical element
|
||
// by default, instance has same name,
|
||
// i.e. it becomes 'prod.eu.zone1.ui'
|
||
instanceOf frontend.ui
|
||
// this becomes 'prod.eu.zone1.api'
|
||
instanceOf backend.api
|
||
}
|
||
|
||
zone zone2 {
|
||
// or use '=' with the name coming first
|
||
ui = instanceOf frontend.ui
|
||
|
||
// two instances of same element
|
||
api1 = instanceOf backend.api
|
||
api2 = instanceOf backend.api
|
||
}
|
||
|
||
// Deploy to any level, not only leaf nodes
|
||
// Assume database shared between zones
|
||
db = instanceOf database
|
||
}
|
||
}
|
||
}
|
||
```
|
||
Deployed instance inherits properties and styling from the element.
|
||
It is possible to override:
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod {
|
||
zone eu {
|
||
db = instanceOf database {
|
||
title 'Primary DB'
|
||
technology 'PostgreSQL with streaming replication'
|
||
icon tech:postgresql
|
||
style {
|
||
color red
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
## Deployment relationships
|
||
|
||
Deployment model inherits relationships from the logical model.
|
||
But also allows to define specific ones:
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod {
|
||
vm vm1 {
|
||
db = instanceOf database 'Primary DB'
|
||
}
|
||
vm vm2 {
|
||
db = instanceOf database 'Standby DB'
|
||
}
|
||
vm2.db -> vm1.db 'replicates'
|
||
}
|
||
}
|
||
```
|
||
As you see, relationship is between same logical element, but different instances.
|
||
Assume, we don't need this relationship in our logical model, but it makes sense for deployment.
|
||
|
||
Deployment relationships can be "kinded", and have [same properties](/dsl/relationships/#relationship-properties) as logical ones:
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod {
|
||
vm2.db -[streaming]-> vm1.db {
|
||
#next, #live
|
||
title 'replicates'
|
||
description 'Streaming replication'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
Relationships can be defined for nested elements of deployed instances:
|
||
|
||
```likec4
|
||
model {
|
||
component database {
|
||
component repl_log
|
||
}
|
||
}
|
||
deployment {
|
||
vm vm1 {
|
||
db = instanceOf database 'Primary DB'
|
||
}
|
||
vm vm2 {
|
||
db = instanceOf database 'Standby DB'
|
||
}
|
||
|
||
// 'repl_log' is a nested element of deployed instance
|
||
vm2.db -> vm1.db.repl_log 'replicates'
|
||
}
|
||
```
|
||
:::
|
||
|
||
:::note
|
||
Check this <a href="https://github.com/likec4/likec4/discussions/1269" target='_blank'>GitHub discussion</a> for further development.
|
||
Feel free to share your ideas.
|
||
:::
|
||
|
||
# Deployment views
|
||
|
||
|
||
Deployment views allow you to visualize the deployment model, using same approach as [model views](/dsl/views/predicates) — predicates.
|
||
|
||
## View definition
|
||
|
||
```likec4 {17-23}
|
||
deployment {
|
||
environment prod {
|
||
zone eu {
|
||
zone zone1 {
|
||
instanceOf frontend.ui
|
||
instanceOf backend.api
|
||
}
|
||
zone zone2 {
|
||
instanceOf frontend.ui
|
||
instanceOf backend.api
|
||
}
|
||
instanceOf database
|
||
}
|
||
}
|
||
}
|
||
views {
|
||
deployment view index {
|
||
title 'Production Deployment'
|
||
link https://likec4.dev
|
||
|
||
include prod.**
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
## View predicates
|
||
|
||
Deployment views are based on same [predicates](/dsl/views/predicates) as model views.
|
||
But they refer to deployment nodes and instances.
|
||
|
||
<Aside type='caution' title='In development'>
|
||
The following features are not supported yet or do not work as expected:
|
||
|
||
- `with` expressions
|
||
- Shared styles and predicates
|
||
- Relationships browser, Element and Relationship Details popups (work with logical model)
|
||
|
||
```likec4
|
||
deployment view prod {
|
||
include * // works
|
||
include * where tag is #next // works (see details below)
|
||
include * with { color: red } // does not work
|
||
|
||
include * -> * // works
|
||
include * -> * where tag is #next // works
|
||
include * -> * where source.tag is #next // works (see details below)
|
||
include * -> * with { color: red } // does not work
|
||
|
||
global style applications // does not work
|
||
}
|
||
```
|
||
</Aside>
|
||
|
||
### Filtering
|
||
Filtering in deployment views use the same principles as in normal views but takes into account deployment nodes, relations, tags and metadata defined in deployment model.
|
||
When condition on element is checked the following rules are applied:
|
||
- For deployment instances, tags are combined from tags defined in model and tags defined in deployment model
|
||
- For a child of deployment instance the tags defined on child in model are used
|
||
- For a deployment node the tags defined in deployment model are used
|
||
- For deployment instances, the kind of the model element is used
|
||
- For a child of deployment instance the kind of this child is used
|
||
- For a deployment node the kind of this deployment node is used
|
||
- For deployment instances, metadata defined in deployment model replaces metadata from the model element
|
||
- For relationship endpoints, `source.metadata.*` and `target.metadata.*` use the same deployed instance metadata rules
|
||
- For deployment nodes, metadata defined in deployment model is used
|
||
- Tags are not inherited from parent nodes/elements
|
||
|
||
```likec4
|
||
model {
|
||
element cloud {
|
||
element frontend {
|
||
#next
|
||
-> backend "rel1"
|
||
}
|
||
element backend {
|
||
#next
|
||
-> db "rel2"
|
||
}
|
||
element db
|
||
}
|
||
}
|
||
deployment {
|
||
environment prod { // Resulting tags: #alpha
|
||
#alpha
|
||
zone eu { // Resulting tags: #beta
|
||
#beta
|
||
instanceOf frontend { // Resulting tags: #next, #gamma
|
||
#gamma
|
||
}
|
||
instanceOf backend { // Resulting tags: #next, #sigma
|
||
#sigma
|
||
}
|
||
eu -> prod.db "rel3"
|
||
}
|
||
instanceOf db { // Resulting tags: #delta
|
||
#delta
|
||
}
|
||
}
|
||
}
|
||
views {
|
||
deployment view some {
|
||
include prod.eu.frontend -> prod.eu.backend
|
||
where source.tag is #next // includes relation "rel1"
|
||
include prod.eu.frontend -> prod.eu.backend
|
||
where source.tag is #gamma // includes relation "rel1"
|
||
include prod.eu -> prod.db
|
||
where source.tag is #beta // includes relation "rel3"
|
||
include prod.eu -> prod.db
|
||
where source.tag is #sigma // does not include any relations
|
||
include eu.* -> prod.db
|
||
where source.tag is #sigma // includes relations "rel2"
|
||
}
|
||
}
|
||
```
|
||
|
||
Metadata predicates can be used with `include` and `exclude` as well:
|
||
|
||
```likec4
|
||
deployment {
|
||
environment prod {
|
||
instanceOf backend {
|
||
metadata {
|
||
zone 'public'
|
||
}
|
||
}
|
||
instanceOf database {
|
||
metadata {
|
||
zone 'restricted'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
views {
|
||
deployment view prod {
|
||
include * -> *
|
||
exclude * -> *
|
||
where target.metadata.zone is "restricted"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Include ancestors
|
||
|
||
Sometimes, it's useful to visualize exactly how elements are deployed. In this case, you can use
|
||
the `includeAncestors` attribute on a deployment view.
|
||
|
||
The purpose of this attribute is to force all ancestors of already visible elements to show in the final diagram.
|
||
It will not affect the visible relationships. It's just a representation preference allowing to view how each relevant elements are deployed.
|
||
|
||
```likec4
|
||
specification {
|
||
element service
|
||
|
||
deploymentNode hypervisor {
|
||
style {
|
||
color green
|
||
}
|
||
}
|
||
deploymentNode vm {
|
||
style {
|
||
color red
|
||
}
|
||
}
|
||
deploymentNode app_server {
|
||
style {
|
||
color sky
|
||
}
|
||
}
|
||
}
|
||
|
||
model {
|
||
svc1 = service
|
||
svc2 = service
|
||
|
||
svc1 -> svc2
|
||
}
|
||
|
||
deployment {
|
||
hyp1 = hypervisor {
|
||
vm = vm {
|
||
tomcat1 = app_server "tomcat" {
|
||
instanceOf svc1
|
||
}
|
||
}
|
||
}
|
||
hyp2 = hypervisor {
|
||
vm = vm {
|
||
tomcat2 = app_server "tomcat" {
|
||
instanceOf svc2
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
views {
|
||
view index {
|
||
title 'Landscape'
|
||
include *
|
||
}
|
||
|
||
deployment view ancestors_test {
|
||
includeAncestors: true
|
||
|
||
include hyp1.tomcat1.svc1
|
||
include hyp2.tomcat2.svc2
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
<br/>
|
||
<br/>
|
||
<LinkCard
|
||
title="Try it online"
|
||
description="Open deployment example in LikeC4 playground"
|
||
href="https://playground.likec4.dev/w/deployment/index/"
|
||
target="_blank"
|
||
/>
|
||
|
||
# Extending model
|
||
|
||
|
||
You can extend the model by creating new files and folders.
|
||
When LikeC4 source files are parsed, they are _merged_ into a single architecture model.
|
||
|
||
You are free to organize the workspace as you want.
|
||
|
||
|
||
## Example
|
||
|
||
Assume we have the following workspace:
|
||
|
||
<FileTree>
|
||
- cloud
|
||
- service1.c4
|
||
- service2.c4
|
||
- ...
|
||
- externals
|
||
- amazon.c4
|
||
- landscape.c4
|
||
- specs.c4
|
||
</FileTree>
|
||
|
||
|
||
<Tabs>
|
||
<TabItem label="specs.c4">
|
||
This file defines the specification:
|
||
|
||
```likec4
|
||
specification {
|
||
element actor {
|
||
style {
|
||
shape person
|
||
}
|
||
}
|
||
element system
|
||
element service
|
||
}
|
||
```
|
||
</TabItem>
|
||
<TabItem label="landscape.c4">
|
||
This file defines the top-level elements and landscape view:
|
||
|
||
```likec4
|
||
model {
|
||
customer = actor 'Customer'
|
||
cloud = system 'Cloud System'
|
||
}
|
||
views {
|
||
view index of cloud {
|
||
title "Cloud System - Landscape"
|
||
include *
|
||
}
|
||
}
|
||
```
|
||
</TabItem>
|
||
<TabItem label="externals/amazon.c4">
|
||
We keep definitions of external systems separately, inside the `externals/` folder:
|
||
|
||
```likec4
|
||
model {
|
||
amazon = system 'Amazon Web Services' {
|
||
rds = service 'Database'
|
||
}
|
||
}
|
||
```
|
||
</TabItem>
|
||
</Tabs>
|
||
|
||
## Extend element
|
||
|
||
`extend` is a way to enrich the model and define nested elements in a separate file.
|
||
|
||
|
||
We don't want to mess up the _landscape.c4_ file with the internals of the `cloud`.
|
||
In a separate file we extend `cloud` and define `cloud.service1`:
|
||
|
||
```likec4
|
||
// cloud/service1.c4
|
||
model {
|
||
// cloud is defined in landscape.c4
|
||
extend cloud {
|
||
// extend and define cloud.service1
|
||
service1 = service 'Service 1'
|
||
}
|
||
}
|
||
```
|
||
|
||
The element extension inherits the scope of the target (or, more accurately, the _**parent**_).
|
||
For example:
|
||
|
||
```likec4
|
||
// cloud/service2.c4
|
||
model {
|
||
// cloud is defined in landscape.c4
|
||
extend cloud {
|
||
// extend and define cloud.service2
|
||
service2 = service 'Some Service 2'
|
||
|
||
service2 -> service1 // ✅ service1 is known inside 'cloud'
|
||
}
|
||
}
|
||
```
|
||
|
||
<Aside type='caution'>
|
||
An extended element must be referenced by its fully qualified name.
|
||
|
||
Example:
|
||
|
||
```likec4
|
||
model {
|
||
extend service2 // ⛔️ Error: service2 not found in the global scope
|
||
extend cloud.service2 // ✅ Resolved by fully qualified name
|
||
}
|
||
```
|
||
</Aside>
|
||
|
||
### Additional properties
|
||
|
||
You can extend element with additional tags, links and metadata:
|
||
|
||
```likec4
|
||
model {
|
||
extend cloud {
|
||
// Add tags
|
||
#additional-tag, #another-tag
|
||
|
||
// Add metadata
|
||
metadata {
|
||
prop1 'value1'
|
||
}
|
||
|
||
// Add links
|
||
link ../src/index.ts#L1-L10
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Metadata Merging
|
||
|
||
When extending elements with metadata, duplicate keys from both the original element and the extension are merged into arrays:
|
||
|
||
```likec4
|
||
model {
|
||
component api {
|
||
metadata {
|
||
version '1.0.0'
|
||
tags 'backend'
|
||
regions 'us-east-1'
|
||
}
|
||
}
|
||
}
|
||
|
||
// In another file
|
||
model {
|
||
extend api {
|
||
metadata {
|
||
tags 'microservice' // Merges with existing 'backend'
|
||
regions ['eu-west-1'] // Merges with existing 'us-east-1'
|
||
owner 'platform-team' // New key
|
||
}
|
||
}
|
||
}
|
||
|
||
// Result:
|
||
{
|
||
version: '1.0.0',
|
||
tags: ['backend', 'microservice'], // Merged and kept as array
|
||
regions: ['us-east-1', 'eu-west-1'], // Merged and kept as array
|
||
owner: 'platform-team'
|
||
}
|
||
```
|
||
|
||
**Merging behavior:**
|
||
- Duplicate values are automatically de-duplicated
|
||
- If after de-duplication there's only one unique value, it's stored as a string (not an array)
|
||
- Arrays from both sides are merged and de-duplicated
|
||
|
||
```likec4
|
||
model {
|
||
component api {
|
||
metadata {
|
||
environment 'production'
|
||
tags ['backend', 'api']
|
||
}
|
||
}
|
||
}
|
||
|
||
model {
|
||
extend api {
|
||
metadata {
|
||
environment 'production' // Duplicate value
|
||
tags ['api', 'critical'] // 'api' is duplicate
|
||
}
|
||
}
|
||
}
|
||
|
||
// Result:
|
||
{
|
||
environment: 'production', // Single value (de-duplicated)
|
||
tags: ['backend', 'api', 'critical'] // Merged and de-duplicated
|
||
}
|
||
```
|
||
|
||
You can extend the same element multiple times across different files, and all metadata will be properly merged:
|
||
|
||
```likec4
|
||
// file1.c4
|
||
model {
|
||
component api {
|
||
metadata {
|
||
version '2.0.0'
|
||
tags 'backend'
|
||
}
|
||
}
|
||
}
|
||
|
||
// file2.c4
|
||
model {
|
||
extend api {
|
||
metadata {
|
||
tags 'rest'
|
||
owner 'team-a'
|
||
}
|
||
}
|
||
}
|
||
|
||
// file3.c4
|
||
model {
|
||
extend api {
|
||
metadata {
|
||
tags 'microservice'
|
||
regions ['us', 'eu']
|
||
}
|
||
}
|
||
}
|
||
|
||
// Result:
|
||
{
|
||
version: '2.0.0',
|
||
tags: ['backend', 'rest', 'microservice'],
|
||
owner: 'team-a',
|
||
regions: ['us', 'eu']
|
||
}
|
||
```
|
||
|
||
## Extend Relationship
|
||
|
||
Similar to extending elements, you can extend relationships to add metadata, tags, and links from separate files.
|
||
|
||
<Aside type='caution'>
|
||
+**Deployment model limitation**: Extending relationships is currently only supported for the logical model.
|
||
Deployment model relationships cannot be extended at this time.
|
||
</Aside>
|
||
|
||
Relations are uniquely identified by:
|
||
- Source element
|
||
- Target element
|
||
- Relationship kind (if specified)
|
||
- Title (if specified)
|
||
|
||
```likec4
|
||
// base.c4
|
||
specification {
|
||
element component
|
||
relationship sync
|
||
}
|
||
|
||
model {
|
||
component frontend
|
||
component api
|
||
|
||
// Untyped relation with title
|
||
frontend -> api "Makes requests"
|
||
|
||
// Typed relation with same title (different relation!)
|
||
frontend -[sync]-> api "Makes requests"
|
||
}
|
||
|
||
// extend-ops.c4
|
||
model {
|
||
// Extend the untyped relation
|
||
extend frontend -> api "Makes requests" {
|
||
metadata {
|
||
latency_p95 '150ms'
|
||
rate_limit '1000req/s'
|
||
}
|
||
}
|
||
|
||
// Extend the sync relation (different from above!)
|
||
extend frontend -[sync]-> api "Makes requests" {
|
||
metadata {
|
||
latency_p95 '80ms'
|
||
cache_enabled 'true'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
<Aside type='note'>
|
||
**Title normalization**: Relations without a title and relations with an empty string title (`""`) are treated identically. Both are normalized to an empty string internally. This means `extend a -> b` will match both `a -> b` and `a -> b ""`.
|
||
</Aside>
|
||
|
||
### Metadata Merging
|
||
|
||
Relationship extends follow the same metadata merging rules as element extends:
|
||
- New keys are added
|
||
- Duplicate keys with different values become arrays
|
||
- Duplicate keys with the same value are de-duplicated
|
||
|
||
```likec4
|
||
// base.c4
|
||
model {
|
||
api -> database "Queries data" {
|
||
metadata {
|
||
protocol 'TCP'
|
||
}
|
||
}
|
||
}
|
||
|
||
// extend-1.c4
|
||
model {
|
||
extend api -> database "Queries data" {
|
||
metadata {
|
||
protocol 'SSL' // Creates array
|
||
timeout '5s' // New key
|
||
}
|
||
}
|
||
}
|
||
|
||
// extend-2.c4
|
||
model {
|
||
extend api -> database "Queries data" {
|
||
metadata {
|
||
protocol 'TLS' // Adds to array
|
||
retry_policy 'exponential' // New key
|
||
}
|
||
}
|
||
}
|
||
|
||
// Result:
|
||
{
|
||
protocol: ['TCP', 'SSL', 'TLS'],
|
||
timeout: '5s',
|
||
retry_policy: 'exponential'
|
||
}
|
||
```
|
||
|
||
# Introduction
|
||
|
||
|
||
LikeC4 is a DSL for describing software architecture.
|
||
|
||
Source files must have `.likec4` or `.c4` extensions.
|
||
All sources are merged into _a single model_ (explained later in [extending the model](/dsl/extend)).
|
||
|
||
A project may look like this:
|
||
|
||
<FileTree>
|
||
- backend
|
||
- service1
|
||
- model.c4
|
||
- views.c4
|
||
- service2
|
||
- model.c4
|
||
- ...
|
||
- externals
|
||
- amazon.c4
|
||
- ...
|
||
- landscape.c4
|
||
- specs.c4
|
||
</FileTree>
|
||
|
||
## Top-level statements
|
||
|
||
A source file should have at least one of these statements:
|
||
|
||
- `specification` - defines element kinds to be used in the model, like **system**, **app**, **microservice**...
|
||
- `model` - architecture elements, hierarchies, compositions and relationships
|
||
- `views` - visualizations
|
||
- `global` - globally shared predicates (explained later in [Views](/dsl/views/predicates/#shared-global-styles))
|
||
|
||
```likec4
|
||
// example.c4
|
||
specification {
|
||
//...
|
||
}
|
||
|
||
global {
|
||
//...
|
||
}
|
||
|
||
model {
|
||
//...
|
||
}
|
||
|
||
views {
|
||
//...
|
||
}
|
||
```
|
||
|
||
You can have multiple statements of the same type:
|
||
|
||
```likec4
|
||
// Views group 1
|
||
views {
|
||
}
|
||
|
||
// Views group 2
|
||
views {
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
For example, the `views` block allows _"local styles"_ that apply to views in the same block.
|
||
This way you can group views that have same styles, and avoid boilerplate.
|
||
Explained later in [Views](/dsl/views/predicates/#shared-local-styles).
|
||
:::
|
||
|
||
# Model
|
||
|
||
|
||
The `model` describes the architecture as a set of hierarchical elements and relationships among them.
|
||
|
||
## Element
|
||
|
||
An element is a basic building block. It represents a logical part of the architecture.
|
||
Any element must have a [`kind`](/dsl/specification#element-kind) and a `name` (_identifier_):
|
||
|
||
```likec4
|
||
specification {
|
||
element actor
|
||
element service
|
||
}
|
||
|
||
model {
|
||
// element of kind 'actor' with the name 'customer'
|
||
actor customer
|
||
// element of kind 'service' named as 'cloud'
|
||
service cloud
|
||
|
||
// also possible with '=' and the name goes first
|
||
cloud = service
|
||
}
|
||
```
|
||
|
||
An element name is required for references.
|
||
It can contain letters, digits, hyphens, and underscores, but cannot start with a digit or contain `.`
|
||
|
||
| name | valid |
|
||
| :--------- | :-- |
|
||
| api | ✅ |
|
||
| Api2 | ✅ |
|
||
| \_api | ✅ |
|
||
| \__Api-1 | ✅ |
|
||
| 1api | ⛔️ |
|
||
| a.pi | ⛔️ |
|
||
|
||
|
||
## Element Properties
|
||
|
||
### Title
|
||
|
||
```likec4 'SaaS' copy
|
||
specification {
|
||
element softwareSystem
|
||
}
|
||
model {
|
||
// Title can be inlined
|
||
saas = softwareSystem 'SaaS'
|
||
|
||
// or nested
|
||
saas = softwareSystem {
|
||
title 'SaaS'
|
||
|
||
// You can use `:` (optional)
|
||
title: 'SaaS'
|
||
}
|
||
|
||
// If title is not specified, name will be used by default
|
||
saas = softwareSystem
|
||
}
|
||
```
|
||
|
||
:::note
|
||
You can use single or double quotes:
|
||
```likec4
|
||
model {
|
||
service cloud 'Cloud Service'
|
||
// Or
|
||
service cloud "Cloud Service"
|
||
}
|
||
```
|
||
If you need quotes inside, escape with backslash:
|
||
```likec4
|
||
model {
|
||
service cloud 'Cloud\'s Service'
|
||
service cloud "Cloud\"s Service"
|
||
}
|
||
```
|
||
:::
|
||
|
||
### Description
|
||
|
||
```likec4 'Provides services to customers' copy
|
||
model {
|
||
// Can be inlined
|
||
saas = softwareSystem 'SaaS' 'Provides services to customers'
|
||
|
||
// or nested
|
||
saas = softwareSystem {
|
||
title 'SaaS'
|
||
description 'Provides services to customers'
|
||
}
|
||
}
|
||
```
|
||
|
||
An element may have a short `summary` (optional; falls back to `description`):
|
||
|
||
```likec4 copy
|
||
model {
|
||
saas = softwareSystem {
|
||
title 'SaaS'
|
||
summary 'Provides services to customers'
|
||
description '
|
||
Detailed description
|
||
...
|
||
'
|
||
}
|
||
}
|
||
```
|
||
|
||
If `summary` is provided, it will be shown on the diagram, and the `description` will be shown in the details dialog.
|
||
If you don't provide `description`, summary will be used.
|
||
|
||
For inlined definition:
|
||
```likec4 copy
|
||
model {
|
||
// [title] [summary]
|
||
saas = softwareSystem 'SaaS' 'Provides services to customers' {
|
||
description '
|
||
Detailed description
|
||
...
|
||
'
|
||
}
|
||
}
|
||
```
|
||
|
||
### Technology
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service {
|
||
technology 'REST'
|
||
}
|
||
|
||
// Structurizr DSL style:
|
||
// <name> = softwareSystem [title] [summary] [technology]
|
||
saas = softwareSystem 'SaaS' 'Provides services to customers' 'SaaS'
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
You can define element properties in the [specification](/dsl/specification#element-kind) if they are common to all elements of that kind:
|
||
|
||
```likec4
|
||
specification {
|
||
element mobileApp {
|
||
title 'Mobile App'
|
||
description 'Universal mobile application'
|
||
technology 'React Native'
|
||
}
|
||
}
|
||
```
|
||
|
||
This allows you to define properties once and reuse them across the model.
|
||
Properties from the element definition override the ones from the specification.
|
||
:::
|
||
|
||
:::note[Auto-derived technology]
|
||
If an element has a bundled icon (`aws:`, `azure:`, `gcp:`, or `tech:`) but no explicit technology,
|
||
LikeC4 automatically derives a human-readable technology label from the icon name.
|
||
For example, `icon tech:apache-flink` produces technology **"Apache Flink"**, and `icon aws:simple-storage-service` produces **"Simple Storage Service"**.
|
||
|
||
`bootstrap:` icons are excluded because they are generic UI icons.
|
||
|
||
This behavior is enabled by default. To disable it, set `inferTechnologyFromIcon` to `false` in the [project config](/dsl/config).
|
||
:::
|
||
|
||
### Tags
|
||
|
||
Element [tags](/dsl/specification/#tag) are defined in a nested block and must come first, before any other properties:
|
||
|
||
```likec4 copy
|
||
model {
|
||
appV1 = application 'App v1' {
|
||
#deprecated
|
||
description 'Old version of the application'
|
||
}
|
||
|
||
// multiple tags
|
||
appV2 = application {
|
||
#next, #serverless
|
||
#team2
|
||
title 'App v2'
|
||
}
|
||
|
||
appV3 = application {
|
||
title 'App v3'
|
||
#team3 // ⛔️ Error: tags must be defined first
|
||
}
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
|
||
You can add tags in the [specification](/dsl/specification/#tag), if it is common for all of the kind:
|
||
|
||
```likec4
|
||
specification {
|
||
element lambda {
|
||
#serverless
|
||
}
|
||
}
|
||
```
|
||
:::
|
||
|
||
### Links
|
||
|
||
An element may have multiple links:
|
||
|
||
```likec4 copy
|
||
model {
|
||
bastion = application 'Bastion' {
|
||
// External link
|
||
link https://any-external-link.com
|
||
|
||
// With label
|
||
link https://github.com/likec4/likec4 'Repository'
|
||
|
||
// or any URI
|
||
link ssh://bastion.internal 'SSH'
|
||
|
||
// or relative link to navigate to sources
|
||
link ../src/index.ts#L1-L10
|
||
}
|
||
}
|
||
```
|
||
|
||
### Metadata
|
||
|
||
Element metadata is a set of key-value pairs defined in a nested block:
|
||
|
||
```likec4 copy
|
||
model {
|
||
app = application 'App' {
|
||
metadata {
|
||
prop1 'value1'
|
||
prop2 '
|
||
apiVersion: apps/v1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: app-statefulset
|
||
spec: {}
|
||
'
|
||
prop3 '{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string"
|
||
},
|
||
"age": {
|
||
"type": "integer"
|
||
}
|
||
}
|
||
}'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Only string values are allowed, but you can use JSON or YAML format for complex data.
|
||
|
||
### Array Values
|
||
|
||
You can also use arrays for metadata values using array literal syntax:
|
||
|
||
```likec4 copy
|
||
model {
|
||
app = application 'App' {
|
||
metadata {
|
||
tags ['frontend', 'react', 'typescript']
|
||
environments ['dev', 'staging', 'prod']
|
||
version '2.1.0'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Mixed single and array values are supported in the same metadata block:
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API Gateway' {
|
||
metadata {
|
||
version '3.2.1'
|
||
maintainer 'Platform Team'
|
||
tags ['backend', 'gateway', 'microservice']
|
||
regions ['us-east-1', 'eu-west-1']
|
||
critical true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Here are more examples showing various mixed metadata patterns:
|
||
|
||
```likec4 copy
|
||
model {
|
||
// E-commerce application with mixed metadata types
|
||
frontend = application 'Frontend App' {
|
||
metadata {
|
||
framework 'React'
|
||
version '18.2.0'
|
||
features ['shopping-cart', 'user-auth', 'payment', 'search']
|
||
deployment_targets ['staging', 'production']
|
||
team_lead 'Alice Johnson'
|
||
developers ['Bob Smith', 'Carol Davis', 'David Wilson']
|
||
release_cycle 'weekly'
|
||
supported_browsers ['Chrome', 'Firefox', 'Safari', 'Edge']
|
||
accessibility_level 'WCAG 2.1 AA'
|
||
has_mobile_app true
|
||
}
|
||
}
|
||
|
||
// Database service with operational metadata
|
||
database = service 'PostgreSQL Cluster' {
|
||
metadata {
|
||
engine 'PostgreSQL'
|
||
version '15.3'
|
||
instances ['primary', 'replica-1', 'replica-2']
|
||
backup_schedule 'daily'
|
||
backup_retention_days '30'
|
||
monitoring_endpoints ['metrics', 'logs', 'traces']
|
||
alert_channels ['slack', 'email', 'pagerduty']
|
||
maintenance_window 'Sunday 2-4 AM UTC'
|
||
data_classification 'sensitive'
|
||
encryption_at_rest true
|
||
}
|
||
}
|
||
|
||
// Microservice with complex deployment metadata
|
||
payment = service 'Payment Service' {
|
||
metadata {
|
||
language 'Go'
|
||
version '2.1.4'
|
||
port '8080'
|
||
health_check_path '/health'
|
||
dependencies ['database', 'redis', 'external-payment-api']
|
||
environments ['dev', 'test', 'stage', 'prod']
|
||
scaling_policy 'auto'
|
||
min_replicas '2'
|
||
max_replicas '10'
|
||
circuit_breaker_enabled true
|
||
rate_limits ['1000/minute', '100/second']
|
||
compliance_standards ['PCI-DSS', 'SOC2']
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Metadata Properties Behavior
|
||
|
||
**Alphabetic Ordering**: Metadata properties are automatically sorted alphabetically when displayed, regardless of the order in which they are defined in the DSL. This ensures consistent presentation across all elements.
|
||
|
||
**Property Duplication**: When the same property name is defined multiple times, all values are collected into an array, preserving their definition order:
|
||
|
||
```likec4 copy
|
||
model {
|
||
service = component 'Payment Service' {
|
||
metadata {
|
||
version '1.0.0' // First value
|
||
version '2.0.0' // Second value
|
||
// Result: version: ['1.0.0', '2.0.0']
|
||
|
||
owner ['team-a', 'team-b'] // First: array values
|
||
owner 'team-c' // Second: single value
|
||
// Result: owner: ['team-a', 'team-b', 'team-c']
|
||
|
||
tags 'primary' // First: single value
|
||
tags ['backend', 'critical'] // Second: array values
|
||
// Result: tags: ['primary', 'backend', 'critical']
|
||
|
||
ports ['8080', '9090'] // First: array values
|
||
ports ['3000', '4000'] // Second: array values
|
||
// Result: ports: ['8080', '9090', '3000', '4000']
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
This behavior applies to **all duplicate keys**.
|
||
|
||
All metadata properties are displayed alphabetically, regardless of definition order.
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API Gateway' {
|
||
metadata {
|
||
version '3.2.1'
|
||
maintainer 'Platform Team'
|
||
tags ['backend', 'gateway', 'microservice']
|
||
regions ['us-east-1', 'eu-west-1']
|
||
critical true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Here are more examples showing various mixed metadata patterns:
|
||
|
||
```likec4 copy
|
||
model {
|
||
// E-commerce application with mixed metadata types
|
||
frontend = application 'Frontend App' {
|
||
metadata {
|
||
framework 'React'
|
||
version '18.2.0'
|
||
features ['shopping-cart', 'user-auth', 'payment', 'search']
|
||
deployment_targets ['staging', 'production']
|
||
team_lead 'Alice Johnson'
|
||
developers ['Bob Smith', 'Carol Davis', 'David Wilson']
|
||
release_cycle 'weekly'
|
||
supported_browsers ['Chrome', 'Firefox', 'Safari', 'Edge']
|
||
accessibility_level 'WCAG 2.1 AA'
|
||
has_mobile_app true
|
||
}
|
||
}
|
||
|
||
// Database service with operational metadata
|
||
database = service 'PostgreSQL Cluster' {
|
||
metadata {
|
||
engine 'PostgreSQL'
|
||
version '15.3'
|
||
instances ['primary', 'replica-1', 'replica-2']
|
||
backup_schedule 'daily'
|
||
backup_retention_days '30'
|
||
monitoring_endpoints ['metrics', 'logs', 'traces']
|
||
alert_channels ['slack', 'email', 'pagerduty']
|
||
maintenance_window 'Sunday 2-4 AM UTC'
|
||
data_classification 'sensitive'
|
||
encryption_at_rest true
|
||
}
|
||
}
|
||
|
||
// Microservice with complex deployment metadata
|
||
payment = service 'Payment Service' {
|
||
metadata {
|
||
language 'Go'
|
||
version '2.1.4'
|
||
port '8080'
|
||
health_check_path '/health'
|
||
dependencies ['database', 'redis', 'external-payment-api']
|
||
environments ['dev', 'test', 'stage', 'prod']
|
||
scaling_policy 'auto'
|
||
min_replicas '2'
|
||
max_replicas '10'
|
||
circuit_breaker_enabled true
|
||
rate_limits ['1000/minute', '100/second']
|
||
compliance_standards ['PCI-DSS', 'SOC2']
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API Gateway' {
|
||
metadata {
|
||
version '3.2.1'
|
||
maintainer 'Platform Team'
|
||
tags ['backend', 'gateway', 'microservice']
|
||
regions ['us-east-1', 'eu-west-1']
|
||
critical true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API Gateway' {
|
||
metadata {
|
||
version '3.2.1'
|
||
maintainer 'Platform Team'
|
||
tags ['backend', 'gateway', 'microservice']
|
||
regions ['us-east-1', 'eu-west-1']
|
||
critical true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Using Markdown
|
||
|
||
You can use markdown in `description` (and `summary`) with triple quotes:
|
||
|
||
```likec4 copy
|
||
model {
|
||
mobile = application {
|
||
title 'Mobile Application'
|
||
description '''
|
||
### Multi-platform application
|
||
|
||
[React Native](https://reactnative.dev)
|
||
'''
|
||
}
|
||
|
||
web = application {
|
||
description """
|
||
### Web Application
|
||
|
||
> Provides services to customers through
|
||
> the web interface.
|
||
|
||
| checks | |
|
||
| :--------- | :-- |
|
||
| check 1 | ✅ |
|
||
| check 2 | ⛔️ |
|
||
| check 3 | ✅ |
|
||
"""
|
||
}
|
||
}
|
||
```
|
||
|
||
## Structuring Model
|
||
|
||
Any element can act as a container and include other elements.
|
||
This way you define the structure and internals of the element.
|
||
|
||
```likec4 filename="nested-elements.c4"
|
||
model {
|
||
// service1 has backend and frontend
|
||
service service1 {
|
||
component backend {
|
||
// backend has api
|
||
component api
|
||
}
|
||
component frontend
|
||
}
|
||
|
||
// or use '='
|
||
service2 = service {
|
||
backend = component {
|
||
api = component
|
||
}
|
||
frontend = component
|
||
}
|
||
}
|
||
```
|
||
|
||
Nested elements are _"namespaced"_: the parent name is used as a prefix.
|
||
So, the model above has the elements with these fully qualified names:
|
||
|
||
- `service1`
|
||
- `service1.backend`
|
||
- `service1.backend.api`
|
||
- `service1.frontend`
|
||
|
||
and:
|
||
|
||
- `service2`
|
||
- `service2.backend`
|
||
- `service2.backend.api`
|
||
- `service2.frontend`
|
||
|
||
:::caution
|
||
You cannot have elements with the same name within the same parent.
|
||
It is explained in detail in [references](/dsl/references).
|
||
|
||
```likec4 filename="nested-elements.c4"
|
||
model {
|
||
|
||
service service1 'Service 1' {
|
||
component backend
|
||
|
||
component backend // ⛔️ Error: 'service1.backend' already defined
|
||
}
|
||
|
||
service service2 'Service 2' {
|
||
component backend // ✅ This is OK - 'service2.backend'
|
||
|
||
component legacy {
|
||
component backend // ✅ This is OK - 'service2.legacy.backend'
|
||
}
|
||
}
|
||
|
||
component backend // ✅ This is OK - 'backend'
|
||
}
|
||
```
|
||
:::
|
||
|
||
# View Notations
|
||
|
||
|
||
<Aside type='caution' title="Experimental" >
|
||
The implementation is experimental and may change in the future.
|
||
The main purpose is to gather feedback, suggestions and ideas.
|
||
</Aside>
|
||
|
||
<Aside type='caution' title="In progress" >
|
||
Relationship notations are in progress.
|
||
</Aside>
|
||
|
||
View notations (or key/legend) provide information about the meanings of shapes and styles.
|
||
It's important to provide a key explaining the difference in colors and shapes.
|
||
|
||
|
||
It is possible to define global notations (per element kind) and local (per view).
|
||
|
||
#### Global notations
|
||
|
||
As comes from the name, global notations apply to all views, and defined in the `specification` block:
|
||
|
||
```likec4 copy {4, 12}
|
||
specification {
|
||
|
||
element customer {
|
||
notation "Person, Customer"
|
||
style {
|
||
shape person
|
||
color green
|
||
}
|
||
}
|
||
|
||
element staff {
|
||
notation "Person, Staff"
|
||
style {
|
||
shape person
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Notations will be applied to all views with these elements and displayed as:
|
||
|
||
<div style="max-width:400px;margin: 1rem auto">
|
||

|
||
</div>
|
||
|
||
Live example.
|
||
Expand the following view and click on help icon in bottom right:
|
||
|
||
<LikeC4ThemeView viewId="notations_example"/>
|
||
|
||
#### Local notations
|
||
|
||
Local notations override global ones and apply to a specific view.
|
||
|
||
##### With style predicate
|
||
|
||
When you change the style, you can add a notation to explain the meaning:
|
||
|
||
```likec4
|
||
view {
|
||
|
||
style webApp1, webApp2 {
|
||
notation "Application under development"
|
||
color amber
|
||
}
|
||
|
||
style element.tag = #deprecated {
|
||
notation "Deprecated"
|
||
color muted
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
Notations are not merged or grouped, the last one will be applied.
|
||
|
||
:::tip
|
||
|
||
You may have same notation for different styles:
|
||
|
||
```likec4
|
||
view {
|
||
|
||
style webApp1 {
|
||
notation "Web Application"
|
||
color amber
|
||
}
|
||
|
||
style webApp2 {
|
||
notation "Web Application"
|
||
color green
|
||
}
|
||
}
|
||
```
|
||
:::
|
||
|
||
|
||
##### With overrides
|
||
|
||
Define notation when include elements:
|
||
|
||
```likec4
|
||
view {
|
||
|
||
include *
|
||
where kind is microservice
|
||
and tag is #deprecated
|
||
with {
|
||
notation "Deprecated microservice"
|
||
shape rectangle
|
||
color muted
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
This override has the highest priority, then defined within style predicates, and finally global notations.
|
||
|
||
:::note
|
||
Feel free to share your ideas in <a href="https://github.com/likec4/likec4/discussions/" target='_blank'>GitHub discussions</a> how to improve notations, make reusable or more flexible.
|
||
:::
|
||
|
||
# References
|
||
|
||
|
||
LikeC4 uses the lexical scope with hoisting, almost like in JavaScript.
|
||
|
||
## Scope
|
||
|
||
To understand references, we need to understand scopes first.
|
||
Example:
|
||
|
||
```likec4
|
||
model {
|
||
service service1 {
|
||
component api
|
||
component frontend
|
||
}
|
||
}
|
||
```
|
||
|
||
Every element is unique in the model, so we can add a relationship referencing them, like:
|
||
|
||
```likec4 'frontend -> api'
|
||
model {
|
||
service service1 {
|
||
component api
|
||
component frontend
|
||
}
|
||
frontend -> api
|
||
}
|
||
```
|
||
|
||
But if we add `service2` with another `api`:
|
||
|
||
```diff lang="likec4"
|
||
model {
|
||
service service1 {
|
||
component api
|
||
component frontend
|
||
}
|
||
+ service service2 {
|
||
+ component api
|
||
+ }
|
||
|
||
frontend -> api // ⛔️ Error: 'api' not found
|
||
}
|
||
```
|
||
|
||
The reference is ambiguous, as there are two `api` components in the model.
|
||
|
||
Every element creates a new scope inside `{...}`, so `api` is unique inside `service1` and `service2`,
|
||
but not in the scope of `model`.
|
||
|
||
We can resolve by moving the relationship to the scope of `service2`:
|
||
|
||
```likec4 {9-11}
|
||
model {
|
||
service service1 {
|
||
component api
|
||
component frontend
|
||
}
|
||
service service2 {
|
||
component api
|
||
|
||
frontend -> api // ✅ This is OK,
|
||
// 'api' is unique in 'service2'
|
||
// 'frontend' is unique in 'model'
|
||
}
|
||
}
|
||
```
|
||
|
||
## Hoisting
|
||
|
||
<Aside>
|
||
**Hoisting** is a mechanism that moves the reference to the top of the scope.
|
||
</Aside>
|
||
|
||
In LikeC4, the element, besides being hoisted in its scope, also _"bubbles"_ to the upper scopes, if it stays unique.
|
||
|
||
We may reference something that is not yet declared but will be hoisted later.
|
||
The relationship on line 8 references `graphql` defined below on line 15:
|
||
|
||
```likec4 showLineNumbers copy
|
||
model {
|
||
|
||
service service1 {
|
||
component api
|
||
component frontend
|
||
|
||
frontend -> api // ✅ This is OK, references to 'api' from 'service1'
|
||
frontend -> graphql // ✅ This is OK, references to unique 'graphql'
|
||
}
|
||
|
||
frontend -> api // ⛔️ Error: 'api' is ambiguous
|
||
|
||
service service2 {
|
||
component api
|
||
component graphql
|
||
|
||
frontend -> api // ✅ This is OK, references to 'api' from 'service2'
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
<Aside>
|
||
Lines 7 and 17 are the same: `frontend -> api`
|
||
But they reference different elements:
|
||
{'-'} Line 7 references `service1.api`
|
||
{'-'} Line 17 references `service2.api`
|
||
</Aside>
|
||
|
||
## Fully qualified names
|
||
|
||
Top-level elements (placed directly in the `model` block) are available globally.
|
||
You can use fully qualified names (FQN) to reference nested elements.
|
||
|
||
Example:
|
||
|
||
```likec4 {11,12}
|
||
model {
|
||
service service1 {
|
||
component api
|
||
component frontend
|
||
}
|
||
service service2 {
|
||
component api
|
||
}
|
||
|
||
frontend -> api // ⛔️ Error: 'api' not found
|
||
frontend -> service1.api // ✅ This is OK
|
||
frontend -> service2.api // ✅ This is OK
|
||
}
|
||
```
|
||
|
||
Or even:
|
||
|
||
```likec4 {6}
|
||
model {
|
||
service service1 {
|
||
component api
|
||
component frontend {
|
||
-> api
|
||
-> service2.api // references to outer scope
|
||
}
|
||
}
|
||
service service2 {
|
||
component api
|
||
}
|
||
}
|
||
```
|
||
|
||
Some parts may be omitted, if FQN stays unique:
|
||
|
||
```likec4
|
||
model {
|
||
service service {
|
||
component backend1 {
|
||
component api
|
||
}
|
||
component backend2 {
|
||
component api
|
||
component graphql
|
||
}
|
||
}
|
||
|
||
frontend -> service.backend1.api // ✅ Non-ambiguous fully qualified name
|
||
|
||
frontend -> backend1.api // ✅ This is OK, 'api' is unique in 'backend1',
|
||
// and 'backend1' is unique in the model
|
||
// We may omit 'service'
|
||
|
||
frontend -> backend2.api // ✅ This is also OK
|
||
|
||
frontend -> service.api // ⛔️ Error: 'api' is ambiguous in 'service'
|
||
|
||
frontend -> service.graphql // ✅ This is also OK, we omit 'backend2'
|
||
// as 'graphql' is unique in 'service'
|
||
}
|
||
```
|
||
|
||
<Aside type='caution'>
|
||
While omitting FQN-parts makes code better looking and references shorter,
|
||
it may be error-prone when you refactor the model
|
||
</Aside>
|
||
|
||
# Relationships
|
||
|
||
|
||
Relationships describe the connections, data flows and interactions within your model.
|
||
|
||
## Relationship definition
|
||
|
||
Relationships are defined with the **`->`** operator:
|
||
|
||
```likec4
|
||
model {
|
||
customer = actor 'Customer'
|
||
cloud = service 'Cloud'
|
||
|
||
customer -> cloud
|
||
}
|
||
```
|
||
|
||
Relationships may be nested
|
||
|
||
```likec4
|
||
model {
|
||
service cloud {
|
||
component backend
|
||
component frontend
|
||
|
||
frontend -> backend
|
||
customer -> frontend
|
||
}
|
||
}
|
||
```
|
||
|
||
In nested relationships you can use `it` or `this` to refer parent:
|
||
|
||
```likec4
|
||
model {
|
||
customer = actor {
|
||
// as a source
|
||
it -> frontend
|
||
// as a target
|
||
frontend -> this
|
||
}
|
||
}
|
||
```
|
||
|
||
Nested relationships may be _"sourceless"_, then the source is the parent element
|
||
|
||
```likec4
|
||
model {
|
||
actor customer {
|
||
// same as customer -> frontend
|
||
-> frontend
|
||
}
|
||
service cloud {
|
||
component backend
|
||
component frontend {
|
||
// same as frontend -> backend
|
||
-> backend
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
:::caution
|
||
|
||
_"sourceless"_ relationships must be nested:
|
||
|
||
```likec4
|
||
model {
|
||
-> backend // ⛔️ Error: model can't be a source
|
||
}
|
||
```
|
||
:::
|
||
|
||
## Relationship kinds
|
||
|
||
Relationships can be "kinded":
|
||
|
||
```likec4
|
||
specification {
|
||
element system
|
||
// Define relationship kind
|
||
relationship async
|
||
relationship uses
|
||
}
|
||
|
||
model {
|
||
system1 = system 'System 1'
|
||
system2 = system 'System 2'
|
||
|
||
system1 -[async]-> system2
|
||
|
||
// Or prefix with '.' to use the kind
|
||
system1 .uses system2
|
||
}
|
||
```
|
||
|
||
This makes it possible to add richer semantics to the interactions between elements, for example,
|
||
from a technology perspective (REST, gRPC, GraphQL, Sync/Async, etc.)
|
||
or from a business perspective (delegation, informing, accountability, etc.).
|
||
|
||
You can define whichever relationship types best fit your context.
|
||
|
||
A relationship kind can also define [tags](#tags) and default properties — `title`, `description`, `technology`, `notation` and [links](#links) — inherited by every relationship of that kind (a relationship may override any of the properties; tags are merged):
|
||
|
||
```likec4
|
||
specification {
|
||
relationship async {
|
||
#tcp
|
||
title 'Asynchronous'
|
||
technology 'Kafka'
|
||
}
|
||
tag tcp
|
||
}
|
||
model {
|
||
// inherits tag #tcp, title 'Asynchronous' and technology 'Kafka'
|
||
system1 .async system2
|
||
|
||
// overrides the title, keeps the inherited tag and technology
|
||
system1 .async system3 'publishes events'
|
||
}
|
||
```
|
||
|
||
<Aside type='tip'>
|
||
With kinds you can customize the styling of the relationships, see [styling](/dsl/styling#relationship)
|
||
</Aside>
|
||
|
||
## Relationship Properties
|
||
|
||
### Title
|
||
|
||
Relationships may have a title (and it's better to have one):
|
||
|
||
```likec4 'opens in browser'
|
||
model {
|
||
customer -> frontend 'opens in browser'
|
||
// or nested
|
||
customer -> frontend {
|
||
title 'opens in browser'
|
||
}
|
||
}
|
||
```
|
||
|
||
### Description
|
||
|
||
```likec4 'Customer opens...'
|
||
model {
|
||
customer -> frontend 'opens in browser' {
|
||
description 'Customer opens...'
|
||
}
|
||
|
||
// Or in a shorter way
|
||
customer -> frontend 'opens in browser' 'Customer opens...'
|
||
}
|
||
```
|
||
|
||
As with elements, you can [use markdown](/dsl/model#using-markdown) in the `description` with triple quotes:
|
||
|
||
```likec4
|
||
model {
|
||
customer -> frontend 'opens in browser' {
|
||
description '''
|
||
**Customer** opens the frontend in the browser
|
||
to interact with the system
|
||
|
||
|
||
| checks | |
|
||
|:--------- |:-- |
|
||
| check 1 | ✅ |
|
||
| check 2 | ⛔️ |
|
||
| check 3 | ✅ |
|
||
'''
|
||
}
|
||
}
|
||
```
|
||
|
||
### Technology
|
||
|
||
```likec4
|
||
model {
|
||
customer -> frontend 'opens in browser' {
|
||
technology 'HTTPS'
|
||
}
|
||
|
||
// Or in a shorter way
|
||
// order is [title] [description] [technology]
|
||
customer -> frontend 'opens in browser' 'Customer opens...' 'HTTPS'
|
||
}
|
||
```
|
||
|
||
|
||
### Tags
|
||
|
||
Relationships can have tags:
|
||
|
||
```likec4
|
||
model {
|
||
// inlined
|
||
frontend -> backend 'requests data' #graphql #team1
|
||
|
||
// or nested
|
||
customer -> frontend 'opens in browser' {
|
||
#graphql #team1
|
||
}
|
||
}
|
||
```
|
||
|
||
### Links
|
||
|
||
Relationships can have multiple links:
|
||
|
||
```likec4 copy
|
||
model {
|
||
customer -> frontend 'opens in browser' {
|
||
// External link
|
||
link https://any-external-link.com
|
||
|
||
// With label
|
||
link https://github.com/likec4/likec4 'Repository'
|
||
|
||
// or any URI
|
||
link ssh://bastion.internal 'SSH'
|
||
|
||
// or relative link to navigate to sources
|
||
link ../src/index.ts#L1-L10
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
### Navigate To
|
||
|
||
A relationship may have a `navigateTo` property, which links to a [dynamic view](/dsl/views/dynamic).
|
||
This allows to _"zoom-in"_ and see more details about this relationship.
|
||
|
||
```likec4 copy
|
||
model {
|
||
webApp -> backend.api {
|
||
title 'requests data for the dashboard'
|
||
navigateTo dashboard-request-flow
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
## Relationships Metadata
|
||
|
||
Same as [element metadata](/dsl/model/#metadata):
|
||
|
||
```likec4
|
||
model {
|
||
customer -> frontend 'opens in browser' {
|
||
metadata {
|
||
prop1 'value1'
|
||
prop2 '{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"name": {
|
||
"type": "string"
|
||
},
|
||
"age": {
|
||
"type": "integer"
|
||
}
|
||
}
|
||
}'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
# Specification
|
||
|
||
In the `specification`, you define your notation.
|
||
|
||
## Element kind
|
||
|
||
Defines element kinds that are used in the model:
|
||
|
||
```likec4 copy
|
||
specification {
|
||
// Define whatever you want
|
||
element user
|
||
element cloud
|
||
element system
|
||
element application
|
||
element component
|
||
element controller
|
||
element microservice
|
||
element queue
|
||
element restapi
|
||
element graphqlMutation
|
||
element repository
|
||
element database
|
||
element pgTable
|
||
}
|
||
```
|
||
|
||
You'll learn later that element kinds may have properties and define styles:
|
||
|
||
```likec4
|
||
specification {
|
||
element queue {
|
||
title 'Kafka'
|
||
description 'Kafka queue'
|
||
technology 'kafka topic'
|
||
notation 'Kafka Topic'
|
||
style {
|
||
shape queue
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Relationship
|
||
|
||
Relationship kinds may define tags, properties (`title`, `description`, `technology`, `notation`, links) and visual styles:
|
||
|
||
```likec4
|
||
specification {
|
||
relationship async {
|
||
#tcp
|
||
title 'Asynchronous'
|
||
description 'Communication over a message broker'
|
||
technology 'Kafka'
|
||
notation 'Async'
|
||
link https://example.com/async
|
||
multiple true
|
||
line dotted
|
||
color amber
|
||
}
|
||
relationship subscribes
|
||
relationship is-downstream-of
|
||
|
||
tag tcp
|
||
}
|
||
```
|
||
|
||
These properties are inherited by every relationship of that kind, unless the relationship overrides them (tags are merged):
|
||
|
||
```likec4
|
||
model {
|
||
// inherits the title and style defined for the 'async' kind
|
||
ui .async backend
|
||
|
||
// overrides the title, keeps the inherited style
|
||
ui .async db 'reads from'
|
||
}
|
||
```
|
||
|
||
More in the [relationships](/dsl/relationships/#relationship-kinds)
|
||
|
||
## Tag
|
||
|
||
Tags can be used to mark, group, or filter elements/relationships/views, or to add additional semantics, like `#deprecated`, `#epic-123`, or `#team2`.
|
||
|
||
```likec4
|
||
specification {
|
||
tag deprecated
|
||
tag epic-123
|
||
tag team2
|
||
}
|
||
```
|
||
|
||
You can assign colors:
|
||
|
||
```likec4
|
||
specification {
|
||
tag deprecated {
|
||
color #FF0000 // or `rgb(255 0 0)` see below
|
||
}
|
||
}
|
||
```
|
||
|
||
You can add tags to element kinds:
|
||
|
||
```likec4
|
||
specification {
|
||
// Now every kafka-topic will be marked with the infra and data-lake tags
|
||
element kafka-topic {
|
||
#infra #data-lake
|
||
}
|
||
tag infra
|
||
tag data-lake
|
||
}
|
||
```
|
||
|
||
## Color
|
||
|
||
Custom colors can be defined to extend built-in themes. Once defined in the specification, they can be used alongside the theme colors.
|
||
|
||
```likec4
|
||
specification {
|
||
color custom-color1 #F00
|
||
color custom-color2 #AABBCC
|
||
color custom-color3 rgb(255, 0, 0)
|
||
color custom-color4 rgb(100 150 200)
|
||
color custom-color5 rgba(44, 8, 128, 0.9)
|
||
color custom-color6 rgba(255, 200, 100, 50%)
|
||
|
||
element person {
|
||
style {
|
||
color custom-color1
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
:::note
|
||
Only base colors with 3, 6, or 8-character hex codes, and `rgb()`/`rgba()` formats are supported. The reason is that to draw a node, we need not only a fill color but also a border color and text color. The same applies to the color of edges, where a line color, label background color, and text color are required. Therefore, LikeC4 generates a color palette used to build a color scheme from the provided base color.
|
||
:::
|
||
|
||
# Styling
|
||
|
||
|
||
LikeC4 provides advanced customization capabilities. You can change colors, shapes, sizes, and icons of elements and relationships.
|
||
|
||
## Element
|
||
|
||
There are multiple ways to style elements:
|
||
- Style all elements of a kind in `specification`
|
||
- Specific for an element in `model` or `deployment`
|
||
- Override styles in `view` (more on this in [next section](/dsl/views/predicates/#style-predicates))
|
||
|
||
### Elements of a kind
|
||
|
||
To style all elements of a kind, use the `style` block in the `specification`:
|
||
|
||
```likec4 copy
|
||
specification {
|
||
element user {
|
||
style {
|
||
// every element of 'user' kind
|
||
shape person // has 'person' shape
|
||
color amber // and amber color
|
||
}
|
||
}
|
||
|
||
element frontend {
|
||
style {
|
||
// every 'frontend' displayed as browser
|
||
shape: browser // ':' is optional, but if you prefer
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Single element
|
||
|
||
To style a specific element, use the nested `style` block.
|
||
Element styles override ones from the kind:
|
||
|
||
```likec4 copy
|
||
specification {
|
||
element actor {
|
||
style {
|
||
shape person
|
||
color red
|
||
}
|
||
}
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer' {
|
||
style {
|
||
// inherits shape and overrides color
|
||
color green
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Per view
|
||
|
||
The [next section](/dsl/views/predicates/#style-predicates) explains how to customize elements per view.
|
||
|
||
## Style properties
|
||
|
||
### Shape
|
||
|
||
```likec4 "shape person" copy
|
||
specification {
|
||
element actor {
|
||
style {
|
||
shape person
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Available shapes: `rectangle` (default), `component`, `storage`, `cylinder`, `browser`, `mobile`, `person`, `queue`, `bucket`, and `document`.
|
||
|
||
<LikeC4ThemeView viewId="allshapes"/>
|
||
|
||
### Color
|
||
|
||
|
||
```likec4 "color red" copy
|
||
specification {
|
||
element actor {
|
||
style {
|
||
color red
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Available colors: `primary` (default), `secondary`, `muted`, `amber`, `gray`, `green`, `indigo`, `red`.
|
||
|
||
<LikeC4ThemeView viewId="index"/>
|
||
|
||
:::tip
|
||
It's also possible to use custom colors defined in [specification](/dsl/specification/#color).
|
||
:::
|
||
|
||
### Size
|
||
|
||
Size of an element is controlled by following properties:
|
||
|
||
| property | explanation |
|
||
| :-------- | :----- |
|
||
| size | size of the shape |
|
||
| padding | space around element's title |
|
||
| textSize | font size of element's title |
|
||
|
||
Each property accepts: `xsmall`, `small`, `medium`, `large`, or `xlarge`
|
||
(or short `xs`, `sm`, `md`, `lg`, `xl`).
|
||
Default size is `medium`.
|
||
|
||
When shape size is `xsmall`, only element's title is displayed.
|
||
|
||
```likec4 "size large" "textSize xl"
|
||
specification {
|
||
element element {
|
||
style {
|
||
size large
|
||
textSize xl
|
||
}
|
||
}
|
||
}
|
||
```
|
||
<LikeC4ThemeView viewId="sizes1_example" interactive={false}/>
|
||
<LikeC4ThemeView viewId="sizes2_example" interactive={false}/>
|
||
|
||
### Opacity
|
||
|
||
If an element is displayed as a group (like a container), you can set its opacity:
|
||
|
||
```likec4 "opacity 10%"
|
||
specification {
|
||
element element {
|
||
style {
|
||
opacity 10%
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
<LikeC4ThemeView viewId="opacity_example"/>
|
||
|
||
### Border
|
||
|
||
If an element is displayed as a group (like a container), you can change its border style:
|
||
|
||
```likec4 "border dotted"
|
||
specification {
|
||
element element {
|
||
style {
|
||
opacity 10%
|
||
border dotted
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Supported values: `dashed` (default), `dotted`, `solid`, `none`
|
||
|
||
<LikeC4ThemeView viewId="border_example"/>
|
||
|
||
### Multiple
|
||
|
||
To display element as multiple instances, set `multiple` to `true`:
|
||
|
||
```likec4
|
||
specification {
|
||
element element {
|
||
style {
|
||
multiple true
|
||
}
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
<LikeC4ThemeView viewId="multiple_example" interactive={false} fitViewPadding={'16px'}/>
|
||
|
||
### Icon
|
||
|
||
Elements may have an icon - any browser-supported image format (png, svg, webp, etc.):
|
||
|
||
```likec4 copy
|
||
model {
|
||
pg = service 'PostgreSQL' {
|
||
style {
|
||
// Publicly available with `https://`
|
||
icon https://icons.terrastruct.com/dev%2Fpostgresql.svg
|
||
|
||
// or local image, relative to current file
|
||
icon ../postgresql.svg
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
`icon` can be defined as a property and skip `style` block
|
||
|
||
```likec4 copy
|
||
model {
|
||
pg = service 'PostgreSQL' {
|
||
icon https://icons.terrastruct.com/dev%2Fpostgresql.svg
|
||
}
|
||
}
|
||
```
|
||
:::
|
||
|
||
:::tip
|
||
Use `none` to unset `icon`
|
||
|
||
```likec4 copy
|
||
pg = service 'PostgreSQL' {
|
||
icon none
|
||
}
|
||
```
|
||
:::
|
||
|
||
:::tip[Light and Dark mode SVG support]
|
||
When using SVGs, you can bake the light and dark modes into a single file and use a `prefers-color-scheme` media query to switch between them.
|
||
|
||
LikeC4 will render the correct version based on the current theme.
|
||
|
||
```xml
|
||
<style>
|
||
.svg-dark { display: none; }
|
||
@media (prefers-color-scheme: dark) {
|
||
.svg-light { display: none; }
|
||
.svg-dark { display: inline; }
|
||
}
|
||
</style>
|
||
<g class="svg-light">
|
||
<!-- Light mode icon version here -->
|
||
</g>
|
||
<g class="svg-dark">
|
||
<!-- Dark mode icon version here -->
|
||
</g>
|
||
```
|
||
|
||
Try changing the theme to see it in action!
|
||
|
||

|
||
:::
|
||
|
||
### Bundled icons
|
||
|
||
LikeC4 includes icons (over 5,000 in total) from these packs:
|
||
- `aws:` from <a href="https://aws-icons.com" target='_blank'>aws-icons.com</a>
|
||
- `azure:` from <a href="https://learn.microsoft.com/en-us/azure/architecture/icons/" target='_blank'>microsoft.com</a>
|
||
- `bootstrap:` from <a href="https://icons.getbootstrap.com/" target='_blank'>Bootstrap Icons</a> (2,000+ icons)
|
||
- `gcp:` from <a href="https://gcpicons.com" target='_blank'>gcpicons.com</a>
|
||
- `tech:` from <a href="https://techicons.dev" target='_blank'>techicons.dev</a> and <a href="https://svglogos.dev/" target='_blank'>svglogos.dev</a>
|
||
|
||
Example:
|
||
|
||
```likec4 copy
|
||
model {
|
||
fn = service 'Lambda Function' {
|
||
icon aws:lambda
|
||
}
|
||
k8s = service 'K8s Service' {
|
||
icon gcp:google-kubernetes-engine
|
||
}
|
||
pg = storage 'PostgreSQL' {
|
||
icon tech:postgresql
|
||
}
|
||
ui = component 'User Interface' {
|
||
icon bootstrap:house
|
||
}
|
||
}
|
||
```
|
||
|
||
<br/>
|
||
|
||
<LikeC4ThemeView viewId="icons_example"/>
|
||
|
||
<br/>
|
||
|
||
:::note[Auto-derived technology]
|
||
Bundled icons (except `bootstrap:`) automatically set the element's `technology` if not already defined.
|
||
For example, `icon tech:docker` sets technology to **"Docker"**. See [Technology](/dsl/model#technology) for details.
|
||
:::
|
||
|
||
:::tip
|
||
Use VS Code's code completion to explore available icons.
|
||
:::
|
||
|
||
|
||
### Icon color
|
||
|
||
Use `iconColor` to override the icon color independently of the element fill.
|
||
Applies to bundled `bootstrap:*` icons.
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API' {
|
||
style {
|
||
icon tech:nodejs // ⛔️ Not applied
|
||
icon bootstrap:gear // ✅ Applies to `bootstrap:*` icons.
|
||
iconColor indigo
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`iconColor` accepts theme colors or custom colors defined in the [specification](/dsl/specification/#color).
|
||
|
||
|
||
|
||
### Icon size
|
||
|
||
Use `iconSize` to resize the icon without changing the element size:
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API' {
|
||
style {
|
||
icon tech:nodejs
|
||
iconSize sm
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`iconSize` uses the same values as [size](/dsl/styling/#size) and defaults to the element `size` (i.e. if not specified, but size is 'large', icon will be 'large').
|
||
|
||
### Icon position
|
||
|
||
Use `iconPosition` to place the icon around the element text:
|
||
|
||
```likec4 copy
|
||
model {
|
||
api = service 'API' {
|
||
style {
|
||
icon tech:nodejs
|
||
iconPosition top
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Supported values: `left` (default), `right`, `top`, `bottom`.
|
||
|
||
|
||
<LikeC4ThemeView viewId="icons_position_example" keepAspectRatio={false} style={{ '--likec4-view-max-height': '250px', minHeight: '250px' }}/>
|
||
|
||
### Aliased icons
|
||
|
||
With `@` prefix, you can use aliased folders (learn more in [configuration](/dsl/config/#image-aliases)):
|
||
|
||
```likec4 copy
|
||
model {
|
||
pg = service 'PostgreSQL' {
|
||
style {
|
||
// local images, based on aliased folders
|
||
icon @/postgresql.svg
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
## Relationship
|
||
|
||
There are multiple ways to style relationships:
|
||
- Style all relationships of a kind in `specification`
|
||
- Specific relationship in `model`
|
||
- Customize per `view` ([explained here](/dsl/views/predicates/#relationship-customization))
|
||
|
||
### Relationships of a kind
|
||
|
||
Relationships can be styled in [specification](/dsl/relationships/#relationship-kinds):
|
||
|
||
```likec4
|
||
specification {
|
||
relationship async {
|
||
color amber
|
||
line dotted
|
||
head diamond
|
||
tail vee
|
||
}
|
||
}
|
||
```
|
||
|
||
### Specific Relationship
|
||
|
||
```likec4
|
||
model {
|
||
customer -> ui 'opens in browser' {
|
||
style {
|
||
line solid
|
||
color amber
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Relationship per view
|
||
|
||
The [next section](/dsl/views/predicates/#relationship-customization) explains how to customize relationships per view.
|
||
|
||
## Relationship properties
|
||
|
||
Besides `color`, relationships may have the following properties:
|
||
|
||
### Line
|
||
|
||
| line | example |
|
||
| :----- | :-----: |
|
||
| dashed | .. |
|
||
| solid | .. |
|
||
| dotted | .. |
|
||
|
||
By default, the line is `dashed`.
|
||
|
||
### Arrow type
|
||
|
||
The arrow type can be set for the head and the tail of the relationship:
|
||
|
||
| type | example |
|
||
| :-------- | :-----: |
|
||
| normal | .. |
|
||
| onormal | .. |
|
||
| diamond | .. |
|
||
| odiamond | .. |
|
||
| crow | .. |
|
||
| vee | .. |
|
||
| open | .. |
|
||
| none | .. |
|
||
|
||
> `onormal` means "outlined normal", i.e. no fill
|
||
> `odiamond` - "outlined diamond"
|
||
|
||
By default, the head is `normal` and the tail is `none`.
|
||
|
||
```likec4
|
||
model {
|
||
customer -> ui 'opens in browser' {
|
||
style {
|
||
head diamond
|
||
tail crow
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Multiple
|
||
|
||
By default, multiple relationships between the same two elements are merged into a single edge.
|
||
If the titles differ, the merged label becomes `[...]`.
|
||
|
||
Set `multiple` to `true` to render each relationship as its own separate edge with its own label:
|
||
|
||
```likec4
|
||
specification {
|
||
relationship async {
|
||
multiple true
|
||
line dotted
|
||
color amber
|
||
}
|
||
}
|
||
```
|
||
|
||
You can also enable it per-view on individual relationships:
|
||
|
||
```likec4
|
||
views {
|
||
view flows {
|
||
include *
|
||
include customer -> api with {
|
||
multiple true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
:::note
|
||
- `multiple true` splits matching relationships into individual edges; non-matching
|
||
relationships in the same connection remain merged.
|
||
- Per-view rules (`with { ... }`) take precedence over specification-level settings.
|
||
Set `multiple false` to disable expansion for specific relationships in a view.
|
||
- Expanded edges are never merged into bidirectional edges; only non-expanded
|
||
(merged) edges participate in bidirectional merging.
|
||
- Expansion from nested elements is supported — each relationship becomes its own edge
|
||
at the correct element level.
|
||
:::
|
||
|
||
## Styles customization
|
||
|
||
How to override default styles is explained in project configuration
|
||
|
||
<LinkCard
|
||
title="Project configuration"
|
||
description="Learn how to customize styles in your project"
|
||
href="/dsl/config/#styles-customization"
|
||
/>
|
||
|
||
# Views
|
||
|
||
|
||
LikeC4 is model-based. Views are projections of the model from various perspectives, scopes, and levels of detail, such as:
|
||
|
||
- System/service overviews
|
||
- Component interactions in specific use cases
|
||
- Data flows and sequence diagrams
|
||
|
||
LikeC4 does not enforce specific rules, such as a strict number of levels or what should be included — it's entirely up to you and your context.
|
||
|
||
## View definition
|
||
|
||
Views are defined in `views` section.
|
||
They can be named (must be unique) or unnamed (can't be referenced, but still can be exported):
|
||
|
||
```likec4
|
||
views {
|
||
// with name
|
||
view index {
|
||
}
|
||
// unnamed
|
||
view {
|
||
}
|
||
}
|
||
```
|
||
|
||
The view name is used as the image filename during export and as part of the URL when sharing, so it's advisable to define one.
|
||
|
||
<Aside title="Default view">
|
||
`index` is a special view, and is rendered by default if no view name is specified.
|
||
If it is not defined - will be generated and include top-level elements
|
||
</Aside>
|
||
|
||
### View properties
|
||
|
||
Views can have a `title`, `description`, `tags` and `links`:
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view epic12 {
|
||
#next, #epic-12
|
||
title "Cloud System - Changes in Epic-12"
|
||
|
||
// Description can be Markdown with triple quotes
|
||
description """
|
||
This diagram shows the **high-level**
|
||
components and interactions.
|
||
"""
|
||
|
||
link https://my.jira/epic/12 'Epic-12'
|
||
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
Properties must be defined before any [predicates](/dsl/views/predicates/).
|
||
|
||
## Scoped views
|
||
|
||
A view can be defined for a specific element (`view of ...`).
|
||
The view will then inherit the [scope](/dsl/references#scope) of that element:
|
||
|
||
:::note
|
||
You will learn about `include` and `exclude` in the [predicates](/dsl/views/predicates/) section.
|
||
:::
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view {
|
||
include api // ⛔️ Error: 'api' is not found
|
||
}
|
||
|
||
view of cloud.backend {
|
||
include api // ✅ This is OK, resolves to 'cloud.backend.api'
|
||
}
|
||
|
||
view of legacy {
|
||
include api // ✅ This is OK, resolves to 'legacy.api'
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
Additionally, a scoped view becomes the default for the element:
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view {
|
||
// on click navigates to 'view1',
|
||
// because it is default for 'cloud.backend'
|
||
include cloud.backend
|
||
}
|
||
|
||
view view1 of cloud.backend {
|
||
include *
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
You can define multiple views for the same element, with the default determined by their order.
|
||
|
||
## Extend views
|
||
|
||
Views can be extended to avoid duplication, to create a "baseline" or, for example, "slides" for a presentation:
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view view1 {
|
||
include *
|
||
}
|
||
|
||
view view2 extends view1 {
|
||
title 'Same as View1, but with more details'
|
||
|
||
style * {
|
||
color muted
|
||
}
|
||
|
||
include some.backend
|
||
}
|
||
|
||
// cascade inheritance
|
||
view view3 extends view2 {
|
||
title 'Same as View2, but with more details'
|
||
|
||
include * -> some.backend
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
The predicates and style rules of extended views are applied after the ones from ancestors.
|
||
|
||
Extended view also inherits the scope:
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view view1 of cloud.backend {
|
||
title 'Backend components'
|
||
}
|
||
|
||
view view2 extends view1 {
|
||
include api // ✅ This is OK, references 'cloud.backend.api'
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
# Generated Views
|
||
|
||
|
||
### Relationships browser
|
||
|
||
### Relationship decomposition
|
||
|
||
# Dynamic views
|
||
|
||
|
||
|
||
Dynamic view describes a particular use-case or scenario, with specific elements and interactions, defined only in the view (without polluting the model).
|
||
|
||
## Dynamic view definition
|
||
|
||
```likec4 showLineNumbers copy collapse={1-54}
|
||
//dynamic-view.c4
|
||
specification {
|
||
element actor {
|
||
style {
|
||
shape person
|
||
}
|
||
}
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer' {
|
||
description 'Customer of Cloud System'
|
||
}
|
||
|
||
cloud = system 'Cloud System' {
|
||
backend = component 'Backend' {
|
||
description 'Backend services and API'
|
||
|
||
auth = component 'Authentication'
|
||
|
||
api = component 'Backend API' {
|
||
description 'RESTful API'
|
||
}
|
||
|
||
api -> auth 'validates bearer token'
|
||
}
|
||
|
||
ui = component 'Frontend' {
|
||
description '
|
||
All the frontend applications
|
||
of Cloud System
|
||
'
|
||
style {
|
||
shape browser
|
||
}
|
||
|
||
web = component 'Customer Dashboard' {
|
||
description 'React Application'
|
||
style {
|
||
shape browser
|
||
}
|
||
}
|
||
|
||
web -> auth
|
||
web -> api 'requests'
|
||
}
|
||
}
|
||
|
||
customer -> web 'opens in browser'
|
||
|
||
}
|
||
|
||
views {
|
||
dynamic view example {
|
||
title 'Dynamic View Example'
|
||
customer -> web 'opens in browser'
|
||
web -> auth 'updates bearer token if needed'
|
||
web -> api 'POST request'
|
||
api -> auth // title is derived from the model
|
||
api -> api 'process request' // allow self-call
|
||
|
||
// reverse direction, as a response to line 59
|
||
web <- api 'returns JSON'
|
||
|
||
// Include elements, that are not participating
|
||
include cloud, ui, backend
|
||
|
||
style cloud {
|
||
color muted
|
||
opacity 0%
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Continuous steps
|
||
|
||
Alternative syntax for describing continuous steps: `A -> B -> C`
|
||
|
||
```likec4 copy
|
||
dynamic view example {
|
||
customer
|
||
-> web
|
||
-> api // same as web -> api
|
||
-> web // same as web <- api
|
||
}
|
||
```
|
||
|
||
It identifies the backward direction of the step, i.e. `A -> B -> A` is the same as `A -> B; A <- B`.
|
||
Nested steps are processed as well, i.e.
|
||
```likec4
|
||
A -> B -> C -> D -> B -> A
|
||
```
|
||
is the same as
|
||
```likec4
|
||
A -> B
|
||
B -> C
|
||
C -> D
|
||
D -> B
|
||
A <- B // is backward
|
||
```
|
||
|
||
|
||
### Flow control
|
||
|
||
Steps can be grouped into flow-control blocks to express parallelism, loops, optional paths, alternatives, and error handling. Every block accepts an optional title and can be nested.
|
||
|
||
:::caution[Experimental]
|
||
Flow control blocks are experimental — syntax and rendering may still change.
|
||
We are looking for your feedback in <a href="https://github.com/likec4/likec4/discussions" target='_blank'>discussions</a>.
|
||
:::
|
||
|
||
In the `sequence` variant these blocks are rendered as nested frames (`par`, `opt`, `loop`, …), mirroring classic sequence-diagram fragments.
|
||
|
||
#### Parallel — `parallel` / `par`
|
||
|
||
Steps inside run concurrently:
|
||
|
||
```likec4 copy
|
||
dynamic view parallelexample {
|
||
title 'Dynamic View Parallel Example'
|
||
ui -> api
|
||
parallel {
|
||
api -> cache
|
||
api -> db
|
||
}
|
||
// or
|
||
par {
|
||
api -> cache
|
||
api -> db
|
||
}
|
||
}
|
||
```
|
||
|
||
Nested parallel blocks are not possible - <a href="https://github.com/likec4/likec4/discussions/816#discussioncomment-10015146" target='_blank'>see this discussion</a>
|
||
|
||
#### Optional — `opt`
|
||
|
||
A block of steps that may be skipped:
|
||
|
||
```likec4 copy
|
||
opt 'if not cached' {
|
||
api -> db 'load and cache'
|
||
}
|
||
```
|
||
|
||
#### Loop — `loop`
|
||
|
||
A block of steps that repeats:
|
||
|
||
```likec4 copy
|
||
loop 'until success' {
|
||
api -> auth 'retry authentication'
|
||
}
|
||
```
|
||
|
||
#### Break — `break`
|
||
|
||
Interrupts the enclosing flow (e.g. exits a loop):
|
||
|
||
```likec4 copy
|
||
loop 'poll for result' {
|
||
api -> db 'check status'
|
||
break 'when ready' {
|
||
api -> web 'return result'
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Alternatives — `alt`
|
||
|
||
Groups mutually exclusive branches. Each branch is a `when` / `if` / `else` block with an optional title:
|
||
|
||
```likec4 copy
|
||
alt {
|
||
when 'authorized' {
|
||
web -> api 'requests data'
|
||
}
|
||
else 'not authorized' {
|
||
web -> customer 'shows login'
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Try / catch / finally — `try`
|
||
|
||
Models the happy path together with error handling. `catch` and `finally` are optional:
|
||
|
||
```likec4 copy
|
||
try {
|
||
api -> db 'query'
|
||
} catch 'on failure' {
|
||
api -> web 'shows error'
|
||
} finally {
|
||
api -> api 'release resources'
|
||
}
|
||
```
|
||
|
||
#### Nesting
|
||
|
||
Blocks can be combined and nested to any depth:
|
||
|
||
```likec4 copy
|
||
alt {
|
||
when 'online' {
|
||
loop 'until synced' {
|
||
web -> api 'sync changes'
|
||
}
|
||
}
|
||
else 'offline' {
|
||
web -> web 'queue locally'
|
||
}
|
||
}
|
||
```
|
||
|
||
The `sequence` variant below combines `alt` / `when` / `else`, `loop`, `try` / `catch` / `finally`, `parallel` and `opt`:
|
||
|
||
<DynamicLikeC4View viewId="flowControl" variant="sequence"/>
|
||
|
||
### Navigation
|
||
|
||
Steps can navigate to other dynamic views:
|
||
|
||
```likec4 copy
|
||
dynamic view level1 {
|
||
title 'Highlevel'
|
||
|
||
ui -> api {
|
||
navigateTo moreDetails
|
||
}
|
||
}
|
||
|
||
dynamic view moreDetails {
|
||
title 'Some details'
|
||
}
|
||
```
|
||
|
||
### Notes
|
||
|
||
`notes` can be used to add additional information to the step. It supports Markdown:
|
||
|
||
```likec4 copy
|
||
dynamic view stepnotes {
|
||
title 'Dynamic View Parallel Example'
|
||
|
||
ui -> api {
|
||
notes '
|
||
🏛️ - Requests data using predefined GraphQL queries
|
||
🤖 - Queries regression on CI
|
||
'
|
||
}
|
||
|
||
parallel {
|
||
api -> cache {
|
||
// Supports Markdown
|
||
notes '''
|
||
**What it does**:
|
||
- requests session-scoped data
|
||
- updates TTL
|
||
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Variants
|
||
|
||
Dynamic views support two variants: `diagram` and `sequence`.
|
||
By default, dynamic views are displayed as diagrams.
|
||
|
||
### Diagram
|
||
|
||

|
||
|
||
### Sequence
|
||
|
||
Classic sequence diagram:
|
||
|
||

|
||
|
||
:::note
|
||
The sequence variant supports only connections with _leaf_ elements, i.e. elements that do not have any child elements.
|
||
:::
|
||
|
||
## Order of actors
|
||
|
||
The sequence variant allows to set the order of actors with `include` predicate:
|
||
|
||
**Default variant**:
|
||
|
||
```likec4 copy
|
||
dynamic view order1 {
|
||
customer
|
||
-> web
|
||
-> auth
|
||
-> web
|
||
-> api
|
||
}
|
||
```
|
||
Steps define the order of actors.
|
||
|
||

|
||
|
||
**Ordered variant:**
|
||
|
||
```likec4 copy {9-12}
|
||
dynamic view order2 {
|
||
customer
|
||
-> web
|
||
-> auth
|
||
-> web
|
||
-> api
|
||
|
||
// Strict order
|
||
include
|
||
auth,
|
||
web,
|
||
api
|
||
}
|
||
```
|
||
|
||
`include` predicate may define order partially, for the rest order will be derived based on the steps.
|
||
|
||

|
||
|
||
## Example
|
||
|
||
Browse this example:
|
||
|
||
<DynamicLikeC4View viewId="index" variant="sequence"/>
|
||
|
||
<br/>
|
||
<CardGrid>
|
||
<LinkCard
|
||
title="Try it online"
|
||
description="Open this example in LikeC4 playground"
|
||
href="https://playground.likec4.dev/w/dynamic/"
|
||
target="_blank"
|
||
/>
|
||
</CardGrid>
|
||
|
||
# Organize views
|
||
|
||
|
||
You can organize views into folders to keep the workspace clean and easy to navigate.
|
||
|
||
## Create folders
|
||
|
||
To create a folder use `/` in the title.
|
||
For example, the following view will have the title `Production`, and will be nested under `Deployment` folder:
|
||
|
||
```likec4
|
||
views {
|
||
view {
|
||
title 'Deployment / Production'
|
||
}
|
||
}
|
||
```
|
||
|
||
On the UI, this view will be displayed as:
|
||
|
||

|
||
|
||
Views can be defined in the same file, but placed in different folders:
|
||
|
||
```likec4
|
||
views {
|
||
dynamic view {
|
||
title 'Use Cases / 16.2 Checkout / Checkout flow'
|
||
}
|
||
|
||
deployment view {
|
||
title 'Deployments / Staging / Checkout microservice'
|
||
}
|
||
}
|
||
```
|
||
|
||
## Common folder
|
||
|
||
You can specify a common folder for the `views` block.
|
||
Every view defined inside will be placed under that folder:
|
||
|
||
```likec4 "'Domain 1 / Subdomain'"
|
||
// Common folder for all views in the block
|
||
views 'Domain 1 / Subdomain' {
|
||
|
||
view {
|
||
// Will be displayed as 'Domain 1 / Subdomain / Landscape'
|
||
title 'Landscape'
|
||
}
|
||
|
||
dynamic view {
|
||
// You can add nested folder
|
||
// Will be displayed as 'Domain 1 / Subdomain / Use Cases / 16.2 Checkout'
|
||
title 'Use Cases / 16.2 Checkout'
|
||
}
|
||
}
|
||
```
|
||
|
||
# View Predicates
|
||
|
||
|
||
Views are not static, they are generated from the model. Any changes in the model are applied immediately and update views.
|
||
Two types of predicates define what is visible: element and relationship predicates.
|
||
|
||
<Aside>
|
||
Views contain elements and their connections (relationships).
|
||
Connections represent merged relationships - direct between elements and/or those derived from their nested elements.
|
||
</Aside>
|
||
|
||
## Element predicates
|
||
|
||
Element predicates explicitly define which elements are visible. Each included element brings in its relationships with already visible elements.
|
||
|
||
```likec4
|
||
view {
|
||
// Only backend is visible
|
||
include backend
|
||
|
||
// Add frontend to the view
|
||
// and its relationships with backend
|
||
include frontend
|
||
|
||
// Add authService to the view
|
||
// and its relationships with visible (backend and frontend)
|
||
include authService
|
||
|
||
// Add children of messageBroker,
|
||
// and their relationships among themselves and visible (backend, frontend and authService)
|
||
include messageBroker.*
|
||
|
||
// Add all descendants of messageBroker,
|
||
// and their relationships among themselves and visible (backend, frontend and authService)
|
||
include messageBroker.**
|
||
|
||
// Exclude emailsQueue and its relationships
|
||
exclude messageBroker.emailsQueue
|
||
}
|
||
```
|
||
|
||
<Aside>
|
||
Order is significant; predicates are applied as defined within the view.
|
||
Excludes apply only to elements/relationships included earlier.
|
||
</Aside>
|
||
|
||
### Combining
|
||
|
||
Predicates can be combined. The following is the same as example above:
|
||
|
||
```likec4
|
||
view {
|
||
include
|
||
backend,
|
||
frontend,
|
||
authService,
|
||
messageBroker.**
|
||
|
||
exclude messageBroker.emailsQueue
|
||
}
|
||
```
|
||
|
||
### Wildcard
|
||
|
||
Wildcard predicates can be used to reference "everything" (but it differs for scoped/unscoped views).
|
||
Consider the following model:
|
||
|
||
```likec4
|
||
model {
|
||
actor customer {
|
||
-> webApp 'uses in browser via HTTPS'
|
||
}
|
||
system cloud {
|
||
container backend {
|
||
component api
|
||
}
|
||
container ui {
|
||
component webApp {
|
||
-> api 'requests data'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
views {
|
||
|
||
// Unscoped view - wildcard refers to top-level elements
|
||
view {
|
||
include *
|
||
// Visible top-level elements: customer, cloud
|
||
// and derived relationship customer -> cloud
|
||
}
|
||
|
||
// Scoped view - wildcard refers to element and its children
|
||
view of cloud.ui {
|
||
include *
|
||
// Visible:
|
||
// - cloud.ui
|
||
// - cloud.ui.webApp
|
||
// - customer
|
||
// - relationship customer -> cloud.ui.webApp
|
||
// - cloud.backend
|
||
// - cloud.ui.webApp -> cloud.backend, derived from cloud.ui.webApp -> cloud.backend.api
|
||
}
|
||
}
|
||
```
|
||
|
||
### With overrides
|
||
|
||
You can modify element properties specifically for the view:
|
||
|
||
```likec4
|
||
// Include the element and override its properties
|
||
include cloud.backend with {
|
||
title 'Backend components'
|
||
description '...'
|
||
technology 'Java, Spring'
|
||
icon tech:java
|
||
color amber
|
||
shape browser
|
||
multiple true
|
||
}
|
||
// Include all nested elements, change color and textSize
|
||
include cloud.* with {
|
||
color amber
|
||
textSize small
|
||
}
|
||
```
|
||
|
||
`with` may be used only within `include`.
|
||
|
||
### With custom navigation
|
||
|
||
You can define custom navigation and links between views:
|
||
|
||
```likec4 title="example.c4"
|
||
|
||
view view2 {
|
||
include *
|
||
include cloud.backend with {
|
||
// navigate to 'view3' on click
|
||
navigateTo view3
|
||
}
|
||
}
|
||
|
||
view view3 {
|
||
include *
|
||
include cloud.backend with {
|
||
// navigate back to 'view2'
|
||
navigateTo view2
|
||
}
|
||
}
|
||
```
|
||
|
||
### By element kind or tag
|
||
|
||
```likec4
|
||
// elements by kind
|
||
include element.kind != system
|
||
exclude element.kind = container
|
||
|
||
// elements by tag
|
||
include element.tag != #V2
|
||
exclude element.tag = #next
|
||
```
|
||
|
||
:::caution
|
||
These predicates may be deprecated in the future, please consider [`where`](#filter) operator
|
||
:::
|
||
|
||
### Element Selectors
|
||
|
||
#### Children `.*`
|
||
|
||
The children selector includes element's children and their relationships with visible elements.
|
||
|
||
```likec4
|
||
include cloud.*
|
||
|
||
// Same as
|
||
include cloud.backend
|
||
include cloud.ui
|
||
```
|
||
|
||
#### Descendants `.**`
|
||
|
||
The descendants selector includes element's descendants **IF** they have a relationship with visible elements.
|
||
|
||
```likec4
|
||
include cloud.**
|
||
|
||
// Same as
|
||
include cloud.backend
|
||
include cloud.ui
|
||
include cloud.ui.webApp
|
||
```
|
||
|
||
#### Expand `._`
|
||
|
||
The expand selector includes element's children **IF** they have a relationship with visible elements.
|
||
All other children are omitted.
|
||
|
||
```likec4
|
||
include cloud._
|
||
|
||
// Same as
|
||
include cloud
|
||
include -> cloud.* ->
|
||
```
|
||
|
||
## Relationship predicates
|
||
|
||
Relationship predicates include elements only if they have relationships that meet the specified predicate conditions.
|
||
|
||
### Directed relationships
|
||
|
||
Include elements if they have **directed** relationships (or their nested elements):
|
||
|
||
```likec4
|
||
// Include customer and cloud:
|
||
include customer -> cloud
|
||
|
||
// Include customer and nested elements of cloud (that have relationships):
|
||
include customer -> cloud.*
|
||
```
|
||
|
||
### Any relationship
|
||
|
||
Include elements if they have any relationships:
|
||
|
||
```likec4
|
||
include customer <-> cloud
|
||
```
|
||
|
||
### Incoming
|
||
|
||
Include elements if they have incoming relationships from already visible elements.
|
||
Here’s an example based on the model from the [wildcard example](#wildcard):
|
||
|
||
```likec4 wrap title="incoming predicate.c4"
|
||
view {
|
||
// visible element
|
||
include customer
|
||
|
||
// include nothing, customer has no relation to backend
|
||
include -> backend
|
||
|
||
// add ui,
|
||
// because customer has a relationship with nested ui.webApp
|
||
include -> ui
|
||
|
||
// add backend, because visible ui has a relationship to backend
|
||
// derived from ui.webApp -> backend.api
|
||
include -> backend
|
||
}
|
||
|
||
// This view includes customer and ui
|
||
view {
|
||
include
|
||
customer,
|
||
-> cloud.*
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
Relationship predicates are useful for refining your diagrams, allowing you to narrow the scope and focus on specific parts of the system.
|
||
:::
|
||
|
||
### Outgoing
|
||
|
||
Include elements if only they have outgoing relationships to already visible elements:
|
||
|
||
```likec4
|
||
include customer ->
|
||
include cloud.* ->
|
||
```
|
||
|
||
### In/Out
|
||
|
||
Include nested elements of `cloud`, that have any relationships with visible elements:
|
||
|
||
```likec4
|
||
include -> cloud.* ->
|
||
```
|
||
|
||
### Relationship customization
|
||
|
||
Relationships can be customized inside view:
|
||
|
||
```likec4
|
||
include
|
||
// Make lines red and solid
|
||
cloud.* <-> amazon.* with {
|
||
color red
|
||
line solid
|
||
},
|
||
// or only directed
|
||
customer -> cloud.* with {
|
||
// Override label
|
||
title 'Customer uses cloud'
|
||
navigateTo dynamicview1
|
||
},
|
||
```
|
||
:::tip
|
||
Sometimes, connections may have a title `[...]`. This indicates that the connection has been merged from multiple relationships with different titles, and it was impossible to derive a definitive one.
|
||
You can change the title, or set `multiple true` to render each relationship as a separate edge:
|
||
|
||
```likec4
|
||
include
|
||
customer -> cloud with {
|
||
// Render each relationship separately
|
||
multiple true
|
||
},
|
||
// or override the merged title
|
||
customer -> cloud with {
|
||
title 'Customer uses cloud'
|
||
},
|
||
```
|
||
:::
|
||
|
||
:::caution
|
||
It is possible to customize relationships with known endpoints only
|
||
(i.e. [directed](#directed-relationships) or [any between](#any-relationship))
|
||
|
||
For example, `* -> *` can be customized (in other words, all relationships on the view), but outgoing `cloud.* ->` can not.
|
||
:::
|
||
|
||
### Relationship navigation
|
||
|
||
To customize [navigation](/dsl/relationships/#navigate-to) from relationship:
|
||
|
||
```likec4
|
||
include
|
||
webApp -> backend.api with {
|
||
navigateTo dashboardRequestFlow
|
||
}
|
||
```
|
||
|
||
## Filter
|
||
|
||
`where` operator narrows down results by applying additional conditions:
|
||
|
||
```likec4
|
||
// include only microservices from nested
|
||
include cloud.*
|
||
where kind is microservice
|
||
|
||
// only microservices and not deprecated
|
||
include cloud.*
|
||
where
|
||
kind == microservice and // possible to use 'is' or '=='
|
||
tag != #deprecated // possible to use 'is not' or '!='
|
||
|
||
// Use logical operators
|
||
include cloud.*
|
||
where
|
||
not (kind is microservice or kind is webapp)
|
||
and tag is not #legacy
|
||
and (tag is #v1 or tag is #v2)
|
||
```
|
||
|
||
<br/>
|
||
|
||
**Relationship predicates**
|
||
|
||
When `where` is used with element predicates, it is applied to the elements.
|
||
When used with relationship predicates - to the relationships.
|
||
|
||
```likec4
|
||
include
|
||
// only relationships with tag #messaging
|
||
cloud.* <-> amazon.*
|
||
where tag is #messaging,
|
||
|
||
// only incoming http-requests
|
||
-> backend
|
||
where kind is http-request
|
||
-[http-request]-> backend
|
||
.http-request backend
|
||
```
|
||
|
||
It is also possible to filter relations by tag or kind of its endpoints.
|
||
```likec4
|
||
include
|
||
// only relationships outgoing from elements with with tag #next
|
||
cloud.* -> amazon.*
|
||
where source.tag is #next,
|
||
|
||
// only incoming relations of elements with kind microservice
|
||
-> *
|
||
where target.kind is microservice
|
||
```
|
||
|
||
<br/>
|
||
|
||
**Together with `with`**
|
||
|
||
It is possible to use `where` together with `with`, but `where` should be defined first:
|
||
|
||
```likec4
|
||
include *
|
||
where kind is microservice
|
||
with {
|
||
color amber
|
||
}
|
||
```
|
||
|
||
<br/>
|
||
|
||
:::tip
|
||
Less verbose and more satisfying results are achieved with `where` in `exclude` predicates.
|
||
For example:
|
||
|
||
```likec4
|
||
|
||
// only keep elements tagged with #v1
|
||
exclude * where tag is not #v1
|
||
|
||
// only keep relationships tagged with #commands
|
||
exclude * -> * where tag is not #commands
|
||
|
||
```
|
||
|
||
Together with [predicate groups](#global-predicate-groups) you may define a "baseline" (includes everything), and then filter out in inherited views.
|
||
|
||
:::
|
||
|
||
### Metadata filter
|
||
|
||
`where` can also filter by element or relationship metadata values:
|
||
|
||
```likec4
|
||
// include only elements with environment="production"
|
||
include cloud.*
|
||
where metadata.environment is "production"
|
||
|
||
// exclude elements without a version metadata key
|
||
exclude *
|
||
where not metadata.version
|
||
|
||
// combine with other filters
|
||
include cloud.*
|
||
where
|
||
metadata.environment is "production"
|
||
and kind is not database
|
||
```
|
||
|
||
Boolean metadata values can be matched with `true` or `false` directly (no quotes needed):
|
||
|
||
```likec4
|
||
// matches elements where critical is true
|
||
include *
|
||
where metadata.critical is true
|
||
```
|
||
|
||
When a metadata value is an array (e.g. `regions ['us-east-1', 'eu-west-1']`), `is` checks if the array **contains** the value:
|
||
|
||
```likec4
|
||
// matches if "us-east-1" is one of the regions
|
||
include *
|
||
where metadata.regions is "us-east-1"
|
||
```
|
||
|
||
For relationship predicates, filter by the relationship's own metadata or by its endpoints:
|
||
|
||
```likec4
|
||
include
|
||
// only relationships with protocol="grpc"
|
||
cloud.* -> amazon.*
|
||
where metadata.protocol is "grpc",
|
||
|
||
// only relations from elements with env="production"
|
||
cloud.* -> *
|
||
where source.metadata.environment is "production",
|
||
|
||
// only relations to staging targets
|
||
* -> *
|
||
where target.metadata.environment is "staging"
|
||
```
|
||
|
||
The same metadata filters work with `exclude` predicates:
|
||
|
||
```likec4
|
||
// remove relationships with protocol="http"
|
||
exclude * -> *
|
||
where metadata.protocol is "http"
|
||
|
||
// remove relationships targeting staging elements
|
||
exclude * -> *
|
||
where target.metadata.environment is "staging"
|
||
```
|
||
|
||
In deployment views, `source.metadata.*` and `target.metadata.*` follow the deployment metadata rules.
|
||
Metadata defined on a deployed instance replaces the metadata from its model element.
|
||
|
||
## Global predicate groups
|
||
|
||
If you find yourself repeating the same predicates in multiple views, you can define them as global group:
|
||
|
||
```likec4
|
||
global {
|
||
predicateGroup microservices {
|
||
include cloud.*
|
||
where kind is microservice
|
||
exclude *
|
||
where tag is #deprecated
|
||
}
|
||
}
|
||
|
||
views {
|
||
view of newServices {
|
||
include cloud.new.*
|
||
global predicate microservices
|
||
}
|
||
|
||
view of newBackendServices {
|
||
// Keep in mind that order is significant
|
||
global predicate microservices
|
||
include cloud.backend.*
|
||
}
|
||
}
|
||
```
|
||
|
||
## Groups
|
||
|
||
It is possible to group elements, and this is rendered as a boundary around them:
|
||
|
||
```likec4
|
||
view {
|
||
|
||
group {
|
||
include backend
|
||
}
|
||
|
||
// with title
|
||
group 'Frontend' {
|
||
include frontend.*
|
||
}
|
||
|
||
// with style
|
||
group 'Service Bus' {
|
||
color amber
|
||
opacity 20%
|
||
border solid
|
||
|
||
include messageBroker.*
|
||
}
|
||
}
|
||
```
|
||
|
||
Groups can be nested:
|
||
|
||
```likec4
|
||
view {
|
||
group 'Third-parties' {
|
||
group 'Integrations' {
|
||
group 'Analytics' {}
|
||
group 'Marketing' {}
|
||
}
|
||
group 'Monitoring' {}
|
||
}
|
||
}
|
||
```
|
||
:::note
|
||
Order of predicates is significant.
|
||
<details>
|
||
<summary>How element predicates are grouped?</summary>
|
||
|
||
For element predicates - element stays in first group it was included.
|
||
|
||
```likec4
|
||
group {
|
||
include backend //wins
|
||
group {
|
||
include backend //ignored
|
||
}
|
||
}
|
||
group {
|
||
group {
|
||
include api //wins
|
||
}
|
||
include api //ignored
|
||
}
|
||
```
|
||
It is possible to change:
|
||
|
||
```likec4
|
||
group {
|
||
include backend
|
||
group {
|
||
exclude backend
|
||
include backend //wins
|
||
}
|
||
}
|
||
```
|
||
</details>
|
||
<details>
|
||
<summary>How relationship predicates are grouped?</summary>
|
||
|
||
For relationship predicates - the last one "wins":
|
||
|
||
```likec4
|
||
group {
|
||
include -> backend
|
||
group {
|
||
include -> backend //wins
|
||
}
|
||
}
|
||
|
||
|
||
group {
|
||
group {
|
||
include -> backend
|
||
}
|
||
include -> backend //wins
|
||
}
|
||
```
|
||
</details>
|
||
:::
|
||
|
||
<Aside type="caution" title="Elements hierarchy and Groups">
|
||
Element is included in the group if only there is no parent in the view.
|
||
This might lead to unexpected results.
|
||
|
||
Example:
|
||
```likec4
|
||
group {
|
||
include cloud
|
||
group 'Backend' {
|
||
include cloud.backend.api // ⛔️ no, will be nested in 'cloud'
|
||
}
|
||
}
|
||
|
||
group 'Amazon' {
|
||
group 'Queues' {
|
||
include amazon.sqs.queue1 // ⛔️ no, will be nested in 'amazon' from below
|
||
}
|
||
include cloud -> amazon
|
||
}
|
||
```
|
||
</Aside>
|
||
|
||
## Style predicates
|
||
|
||
Style predicates define how elements are rendered, and applied in the order they are defined merging with previous ones:
|
||
|
||
```likec4
|
||
view apiApp of internetBankingSystem.apiApplication {
|
||
|
||
include *
|
||
|
||
// apply to all elements
|
||
style * {
|
||
color muted
|
||
opacity 10%
|
||
}
|
||
|
||
// apply only to these elements
|
||
style singlePageApplication, mobileApp {
|
||
color secondary
|
||
size xlarge
|
||
}
|
||
|
||
// apply only to nested of apiApplication
|
||
style apiApplication.* {
|
||
color primary
|
||
multiple true
|
||
}
|
||
|
||
// apply to apiApplication and nested
|
||
style apiApplication._ {
|
||
color primary
|
||
}
|
||
|
||
// apply only to elements with specific tag
|
||
style element.tag = #deprecated {
|
||
color muted
|
||
}
|
||
|
||
// apply to elements not tagged
|
||
style element.tag != #deprecated {
|
||
opacity 20%
|
||
}
|
||
}
|
||
```
|
||
|
||
:::caution
|
||
[`Group`](#groups) does not support nested `style` predicates (yet).
|
||
:::
|
||
|
||
### Shared local styles
|
||
|
||
Styles can be shared within `views` block (_"local styles"_):
|
||
|
||
```likec4
|
||
views {
|
||
// apply to all views in this block
|
||
style * {
|
||
color muted
|
||
opacity 10%
|
||
}
|
||
|
||
view of apiApp {
|
||
include *
|
||
style cloud.web.* {
|
||
color green
|
||
}
|
||
}
|
||
|
||
view of mobileApp {
|
||
include *
|
||
style cloud.ui.* {
|
||
color amber
|
||
}
|
||
}
|
||
}
|
||
|
||
views {
|
||
// Styles from previous block are not applied here
|
||
// ...
|
||
}
|
||
```
|
||
|
||
<Aside title="Order">
|
||
Styles are applied in the order they are defined.
|
||
First, local styles from `views` block and then from `view`
|
||
</Aside>
|
||
|
||
:::caution
|
||
[Overrides](#with-overrides) are always applied last, after all styles
|
||
:::
|
||
|
||
|
||
### Shared global styles
|
||
|
||
Styles can be shared globally.
|
||
Global styles must be named and defined in `global` block:
|
||
|
||
```likec4 {4,9,15,25,33-34}
|
||
global {
|
||
// Format:
|
||
// style <name> <targets> { ... }
|
||
style mute_all * {
|
||
color muted
|
||
opacity 10%
|
||
}
|
||
|
||
style applications
|
||
singlePageApplication._,
|
||
mobileApp._ {
|
||
color secondary
|
||
}
|
||
|
||
style mute_deprecated
|
||
element.tag = #deprecated {
|
||
color muted
|
||
}
|
||
}
|
||
|
||
views {
|
||
view of singlePageApplication {
|
||
// Styles are applied in the order they are defined
|
||
// 1. Apply global style
|
||
global style mute_all
|
||
|
||
// 2. Then this
|
||
style cloud.* {
|
||
color green
|
||
}
|
||
|
||
// 3. and 4.
|
||
global style applications
|
||
global style mute_deprecated
|
||
}
|
||
}
|
||
```
|
||
|
||
### Shared style groups
|
||
|
||
Global styles can be grouped:
|
||
|
||
```likec4 {3,18}
|
||
global {
|
||
// Define style group
|
||
styleGroup common_styles {
|
||
style singlePageApplication, mobileApp {
|
||
color secondary
|
||
}
|
||
style element.tag = #deprecated {
|
||
color muted
|
||
}
|
||
}
|
||
}
|
||
|
||
views {
|
||
view mobileApp of mobileApp {
|
||
include *
|
||
|
||
// Apply styles from group
|
||
global style common_styles
|
||
|
||
// Override
|
||
style mobileApp {
|
||
color primary
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
:::tip
|
||
Global styles and groups can be used as `views`-locals:
|
||
|
||
```likec4
|
||
global {
|
||
style mute_all * { color muted }
|
||
styleGroup theme1 { //...
|
||
styleGroup theme2 { //...
|
||
}
|
||
|
||
// All views within this block have styles from 'theme1'
|
||
views {
|
||
global style theme1
|
||
view view1 { //...
|
||
}
|
||
|
||
// All views have 'mute_all' style and all from 'theme2'
|
||
views {
|
||
global style mute_all
|
||
global style theme2
|
||
view view2 { //...
|
||
}
|
||
```
|
||
:::
|
||
|
||
|
||
## Auto-layout
|
||
|
||
```likec4
|
||
view {
|
||
include *
|
||
autoLayout LeftRight 120 110
|
||
}
|
||
```
|
||
|
||
Parameters are:
|
||
- direction: possible values are `TopBottom` (default), `BottomTop`, `LeftRight`, `RightLeft`.
|
||
- rank distance: optional, must be a positive number
|
||
- node distance. optional, must be a positive number
|
||
|
||
<Aside>
|
||
Manual changes are supported in VSCode extension, but functionality is limited.
|
||
Your <a href="https://github.com/likec4/likec4/discussions/343" target="_blank">feedback</a> is much appreciated.
|
||
</Aside>
|
||
|
||
|
||
## Extend views
|
||
|
||
Views can be extended to avoid duplication, to create a "baseline" or, for example, "slides" for a presentation:
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view view1 {
|
||
include *
|
||
}
|
||
|
||
view view2 extends view1 {
|
||
title 'Same as View1, but with more details'
|
||
|
||
style * {
|
||
color muted
|
||
}
|
||
|
||
include some.backend
|
||
}
|
||
|
||
// cascade inheritance
|
||
view view3 extends view2 {
|
||
title 'Same as View2, but with more details'
|
||
|
||
include * -> some.backend
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
The predicates and style rules of extended views applied after the ones from ancestors.
|
||
|
||
Extended view also inherits the scope:
|
||
|
||
```likec4
|
||
views {
|
||
|
||
view view1 of cloud.backend {
|
||
title 'Backend components'
|
||
}
|
||
|
||
view view2 extends view1 {
|
||
include api // ✅ This is OK, references 'cloud.backend.api'
|
||
}
|
||
|
||
}
|
||
```
|
||
## Rank constraints
|
||
|
||
You can keep specific elements on the same horizontal/vertical level (or push them to the beginning/end of the layout)
|
||
by adding an explicit `rank` block to the view.
|
||
These rank constraints are forwarded to the Graphviz layout engine to achieve the desired layout effects.
|
||
|
||
```likec4
|
||
view checkoutFlow {
|
||
include *
|
||
|
||
// keep the API nodes aligned
|
||
rank same {
|
||
cloud.backend.api,
|
||
cloud.backend.billingApi,
|
||
}
|
||
|
||
// make customers appear at the beginning of the diagram, exclusive of the elements
|
||
rank source {
|
||
customer
|
||
}
|
||
|
||
// render reporting systems at the end, exclusive of the elements
|
||
rank sink {
|
||
analytics,
|
||
dataWarehouse
|
||
}
|
||
}
|
||
```
|
||
|
||
- Allowed rank values: `same`, `min`, `max`, `source`, `sink`. If omitted, `same` is assumed.
|
||
- Targets are regular `FqnRef`s, so you can reference nested elements just like in other rules. Non-existent or duplicate targets are ignored.
|
||
- The constraint only affects elements that actually remain in the computed view. If a predicate later removes an element, it also disappears from the rank block.
|
||
- Rank rules participate in manual tiling of compound nodes as well, so authored constraints combine with the automatic layout instead of fighting it.
|
||
|
||
Use rank constraints sparingly—they are most helpful for anchoring critical columns/rows
|
||
(e.g., ingress vs. egress nodes, semantic grouping in lieu of container) to achieve better layout.
|
||
|
||
# Deploy to GitHub Pages
|
||
|
||
:::note
|
||
You can check <a href="https://github.com/likec4/template" target='_blank'>likec4/template</a> repository.
|
||
It builds and deploys static website to GitHub Pages
|
||
|
||
[](https://stackblitz.com/~/github.com/likec4/template)
|
||
:::
|
||
|
||
## Use LikeC4 CLI
|
||
|
||
Prefer to use [LikeC4 CLI](/tooling/cli) directly in your scripts, as it offers more control.
|
||
Example workflow (it uses `bun` for faster setup, but you can use your preferred runtime):
|
||
|
||
```yaml
|
||
# Sample workflow for building and deploying a website to GitHub Pages
|
||
name: Deploy Pages
|
||
|
||
on:
|
||
# Runs on pushes targeting the default branch
|
||
push:
|
||
branches: ["main"]
|
||
|
||
# Allows you to run this workflow manually from the Actions tab
|
||
workflow_call:
|
||
workflow_dispatch:
|
||
|
||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||
permissions:
|
||
contents: read
|
||
pages: write
|
||
id-token: write
|
||
|
||
# Allow only one concurrent deployment, skipping runs queued between the run in progress and the latest queued.
|
||
# However, do NOT cancel in-progress runs, as we want to allow these production deployments to be completed.
|
||
concurrency:
|
||
group: "pages"
|
||
cancel-in-progress: false
|
||
|
||
jobs:
|
||
# Build job
|
||
build-pages:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v5
|
||
- uses: oven-sh/setup-bun@v2
|
||
|
||
- uses: actions/configure-pages@v4
|
||
id: pages
|
||
|
||
- name: build
|
||
run: |
|
||
bunx likec4@latest build \
|
||
--base "${{ steps.pages.outputs.base_path || '/' }}" \
|
||
--output dist
|
||
|
||
- name: upload artifact
|
||
uses: actions/upload-pages-artifact@v3
|
||
with:
|
||
path: ./dist
|
||
|
||
# Deployment job
|
||
deploy-pages:
|
||
environment:
|
||
name: github-pages
|
||
url: ${{ steps.deployment.outputs.page_url }}
|
||
runs-on: ubuntu-latest
|
||
needs: build-pages
|
||
steps:
|
||
- name: Deploy to GitHub Pages
|
||
id: deployment
|
||
uses: actions/deploy-pages@v4
|
||
|
||
```
|
||
|
||
## GitHub Actions workflow
|
||
|
||
Use [LikeC4 GitHub Action](/tooling/github/) to build and deploy static website to GitHub Pages.
|
||
|
||
```yaml
|
||
# Sample workflow for building and deploying a website to GitHub Pages
|
||
name: Deploy Pages
|
||
|
||
on:
|
||
# Runs on pushes targeting the default branch and c4 files
|
||
push:
|
||
branches: ["main"]
|
||
|
||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||
permissions:
|
||
contents: read
|
||
pages: write
|
||
id-token: write
|
||
|
||
# Allow only one concurrent deployment, skipping runs queued between the run in progress and the latest queued.
|
||
# However, do NOT cancel in-progress runs, as we want to allow these production deployments to be completed.
|
||
concurrency:
|
||
group: "pages"
|
||
cancel-in-progress: false
|
||
|
||
jobs:
|
||
# Build job
|
||
build-pages:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v5
|
||
|
||
- name: Setup Pages
|
||
id: pages
|
||
uses: actions/configure-pages@v4
|
||
|
||
- name: Build
|
||
uses: likec4/actions@v1
|
||
with:
|
||
action: build
|
||
output: dist
|
||
# required if you don't set a custom domain for the repository
|
||
# https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#types-of-github-pages-sites
|
||
base: ${{ steps.pages.outputs.base_path }}
|
||
likec4-version: latest
|
||
|
||
- name: Upload artifact
|
||
uses: actions/upload-pages-artifact@v3
|
||
with:
|
||
path: ./dist
|
||
|
||
# Deployment job
|
||
deploy-pages:
|
||
environment:
|
||
name: github-pages
|
||
url: ${{ steps.deployment.outputs.page_url }}
|
||
runs-on: ubuntu-latest
|
||
needs: build-pages
|
||
steps:
|
||
- name: Deploy to GitHub Pages
|
||
id: deployment
|
||
uses: actions/deploy-pages@v4
|
||
```
|
||
|
||
# Embed to website
|
||
|
||
|
||
<Card title="Soon" icon="warning">
|
||
This page is under construction.
|
||
</Card>
|
||
|
||
# Preview changes in PR
|
||
|
||
|
||
|
||
# Deploy a static website
|
||
|
||
import { PackageManagers } from 'starlight-package-managers';
|
||
|
||
## Pre-requisites
|
||
|
||
You must have [Node.js](https://nodejs.org) installed.
|
||
Minimum supported version is 20.x, but using the latest stable version is recommended.
|
||
|
||
You can also use [LikeC4 Docker](/tooling/docker/).
|
||
|
||
## Build
|
||
|
||
Run `build` command, that prepares a folder with static files ready to be deployed to any host.
|
||
|
||
```sh
|
||
npx likec4 build -o ./dist
|
||
```
|
||
|
||
Available options:
|
||
|
||
| Option | Description |
|
||
| ----------------------- | ----------------------------------------------------------------------------------------------------- |
|
||
| `-o, --output` | Output directory |
|
||
| `--base` | Base URL from which the app is being served, e.g., "/", "/pages/", or "./" for a relocatable app |
|
||
| `--use-hash-history` | Hash-based navigation, e.g. "/#/view" instead of "/view" |
|
||
| `--use-dot` | Use local binaries of Graphviz ("dot") instead of bundled WASM |
|
||
| `--title` | Base title of the app pages (default is "LikeC4") |
|
||
| `--output-single-file` | Generates a single self-contained HTML file |
|
||
|
||
To see all available options run:
|
||
```sh
|
||
npx likec4 build -h
|
||
```
|
||
|
||
|
||
## Deploy
|
||
|
||
The LikeC4 CLI uses Vite to build the website.
|
||
|
||
All the examples from [Vite documentation](https://vitejs.dev/guide/static-deploy.html) apply to LikeC4, just replace `vite build` with `likec4 build`.
|
||
|
||
# Enforce and validate your model
|
||
|
||
import { PackageManagers } from 'starlight-package-managers';
|
||
|
||
Sometimes you may want to enforce custom rules on your model or validate its consistency.
|
||
Here's a simple recipe for how to do that.
|
||
|
||
:::note
|
||
Check <a href="https://github.com/likec4/template" target='_blank'>likec4/template</a> repository for a complete example.
|
||
|
||
[](https://stackblitz.com/~/github.com/likec4/template)
|
||
:::
|
||
|
||
|
||
## Test your model
|
||
|
||
In this example we'll use [Vitest](https://vitest.dev) together with the [LikeC4 API](/tooling/model-api/).
|
||
The LikeC4 API gives you methods to query and traverse the model - perfect for writing tests that enforce your rules.
|
||
|
||
<PackageManagers
|
||
pkg="likec4 vitest"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
### Example
|
||
|
||
Suppose we want to enforce that every element of kind `app` has a `technology` specified.
|
||
|
||
```ts
|
||
// test/metadata.spec.ts
|
||
import { LikeC4 } from './LikeC4'
|
||
import { test } from 'vitest'
|
||
|
||
// Initialize and compute LikeC4 Model
|
||
const likec4 = await LikeC4.fromWorkspace('..')
|
||
const model = likec4.computedModel()
|
||
|
||
// With `test.for` we generate tests for each element of kind `app`
|
||
// This improves the output, showing each test failure separately
|
||
test.for(
|
||
model
|
||
// Select elements of kind `app`
|
||
.elementsWhere({ kind: 'app' })
|
||
// Map to array of [id, element] tuples, we need it for test names
|
||
.map(e => [e.id, e] as const)
|
||
.toArray(),
|
||
)('app "%s" has technology', ([, e], { expect }) => {
|
||
expect(e.technology).toBeTruthy()
|
||
})
|
||
|
||
// Or we can use `expect.soft` to accumulate all errors
|
||
test('elements of kind `app` have technology', ({ expect }) => {
|
||
expect.hasAssertions()
|
||
for (const app of model.elements()) {
|
||
if (app.kind !== 'app') continue // Skip non-app elements
|
||
expect.soft(app.technology, `app ${app.id} has no technology`).toBeTruthy()
|
||
}
|
||
})
|
||
```
|
||
|
||
:::note
|
||
Calls to `LikeC4.fromWorkspace('..')` are memoized (by absolute path).
|
||
You can call it multiple times without performance impact - for example in `beforeEach`.
|
||
|
||
You can also use Vitest's [`provide`](https://vitest.dev/advanced/api/vitest.html#provide) function to reuse the same workspace path.
|
||
:::
|
||
|
||
|
||
## Pre-generate model
|
||
|
||
We can optimize our tests by pre-generating the model in [global setup](https://vitest.dev/config/#globalsetup):
|
||
|
||
```ts
|
||
// global-setup.ts
|
||
import { execSync } from 'node:child_process'
|
||
|
||
export default function() {
|
||
execSync('npx likec4 gen model -o ./test/likec4-model.ts', {
|
||
stdio: 'inherit'
|
||
})
|
||
}
|
||
```
|
||
|
||
The generated model is fully typed, giving us type checking and autocompletion in tests:
|
||
|
||
```ts
|
||
// test/metadata.spec.ts
|
||
import { likec4model } from './likec4-model'
|
||
import { test } from 'vitest'
|
||
|
||
test('Relationships should have metadata', ({ expect }) => {
|
||
expect.hasAssertions()
|
||
for (const r of likec4model.relationships()) {
|
||
expect.soft(
|
||
r.getMetadata('key'), // here we get type checking
|
||
`Relationship ${r.source.id} -> ${r.target.id} has no metadata`
|
||
).toBeDefined()
|
||
}
|
||
})
|
||
```
|
||
|
||
We can go further and use [test context](https://vitest.dev/api/#test-context) to improve our experience:
|
||
|
||
```ts
|
||
// test/likec4test.ts
|
||
import { likec4model } from './likec4-model'
|
||
import { test } from 'vitest'
|
||
|
||
interface LikeC4TestFixtures {
|
||
likec4: typeof likec4model
|
||
}
|
||
|
||
// This wil be our test function with the model in the context
|
||
export const likec4test = test.extend<LikeC4TestFixtures>({
|
||
likec4: async ({}, use) => {
|
||
await use(likec4model)
|
||
},
|
||
})
|
||
```
|
||
|
||
Now refactor tests to use it:
|
||
|
||
```ts
|
||
// test/metadata.spec.ts
|
||
import { likec4test } from './likec4test'
|
||
|
||
likec4test('Relationships should have metadata', ({ expect, likec4 }) => {
|
||
expect.hasAssertions()
|
||
for (const r of likec4.relationships()) {
|
||
expect.soft(
|
||
r.getMetadata('key'), // here we get type checking
|
||
`Relationship ${r.source.id} -> ${r.target.id} has no metadata`
|
||
).toBeDefined()
|
||
}
|
||
})
|
||
```
|
||
|
||
## Conclusion
|
||
|
||
This approach makes it easy to enforce custom constraints and validate your model consistency.
|
||
Running these checks in CI pipeline is fast and provides immediate feedback when the model breaks your rules.
|
||
|
||
# LikeC4
|
||
|
||
|
||
|
||
# AI Tools
|
||
|
||
|
||
LikeC4 provides two complementary tools for AI-powered development: **Agent Skills** for teaching AI assistants the DSL syntax, and an **MCP Server** for querying your architecture model.
|
||
|
||
## Agent Skills
|
||
|
||
Agent skills are DSL references that AI coding assistants load automatically when editing `.c4`/`.likec4` files.
|
||
They teach agents correct LikeC4 syntax, patterns, and validation workflows — so they can write DSL code without hallucinating.
|
||
|
||
### Install into any project
|
||
|
||
```sh
|
||
npx skills add https://likec4.dev/
|
||
```
|
||
|
||
This uses the <a href="https://www.skillsdk.dev/" target="_blank">Agent Skills Discovery</a> protocol. Available skills:
|
||
|
||
| Skill | Description |
|
||
|-------|-------------|
|
||
| `likec4-dsl` | Complete DSL reference — specification, model, views, deployment, predicates, examples |
|
||
|
||
Works with Claude Code, Cursor, Windsurf, and other agents that support the protocol.
|
||
|
||
## MCP Server
|
||
|
||
LikeC4 <a href="https://modelcontextprotocol.io" target="_blank">MCP Server</a> provides knowledge of your LikeC4 model to LLMs.
|
||
This enables you to query your model in natural language:
|
||
|
||
> _"Lookup LikeC4 model and list all incoming relationships of the backend api"_
|
||
|
||
> _"What nested elements of the 'Backend' have relations with the legacy api"_
|
||
|
||
> _"List all elements tagged legacy from team1 project"_
|
||
|
||
> _"What technologies are used for ui (consider all elements with browser shape)"_
|
||
|
||
> _"Export to CSV all relationships between Backend and Amazon SQS"_
|
||
|
||
### Usage
|
||
|
||
Three options are available:
|
||
- Use extension's built-in MCP Server
|
||
- Use `likec4 mcp` CLI
|
||
- Use `@likec4/mcp` package
|
||
|
||
#### Using extension
|
||
|
||
When [LikeC4 extension](/tooling/editors/) is installed in VSCode, the MCP Server is registered automatically via VSCode's native MCP support (stdio transport).
|
||
No additional configuration is needed — the server appears in your MCP server list once the extension is active.
|
||
|
||
:::caution
|
||
The MCP server is only available when the extension is active.
|
||
The extension activates automatically once you open any LikeC4 source file.
|
||
|
||
If it doesn't show up, try refreshing the list of available MCP servers in your editor.
|
||
:::
|
||
|
||
For other editors, start the MCP server via CLI and configure it manually:
|
||
|
||
<Tabs syncKey="label">
|
||
<Tab label='Cursor'>
|
||
|
||
Create `.cursor/mcp.json`:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"likec4": {
|
||
"url": "http://localhost:33335/mcp"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Then start the server: `likec4 mcp --http`
|
||
|
||
</Tab>
|
||
<Tab label='Windsurf'>
|
||
|
||
See [Windsurf documentation](https://docs.windsurf.com/windsurf/mcp) for details:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"likec4": {
|
||
"serverUrl": "http://localhost:33335/mcp"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Then start the server: `likec4 mcp --http`
|
||
|
||
</Tab>
|
||
<Tab label='Claude Code'>
|
||
|
||
Add the MCP server to Claude Code:
|
||
|
||
```sh
|
||
claude mcp add likec4 -- npx -y @likec4/mcp
|
||
```
|
||
|
||
Or add to `.mcp.json`:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"likec4": {
|
||
"command": "npx",
|
||
"args": ["-y", "@likec4/mcp"],
|
||
"env": {
|
||
"LIKEC4_WORKSPACE": "${workspaceFolder}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
#### Using CLI
|
||
|
||
If you have installed [`likec4`](/tooling/cli) CLI, you can start MCP server with `stdio` transport:
|
||
|
||
```sh
|
||
likec4 mcp
|
||
# or
|
||
likec4 mcp --stdio
|
||
```
|
||
|
||
Start MCP server with `http` transport on port `33335` (default) at `./src` folder:
|
||
|
||
```sh
|
||
likec4 mcp --http ./src
|
||
```
|
||
|
||
Start MCP server with `http` transport on port `1234`:
|
||
|
||
```sh
|
||
likec4 mcp -p 1234
|
||
```
|
||
|
||
#### Using `@likec4/mcp` package
|
||
|
||
Example configuration:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"likec4": {
|
||
"command": "npx",
|
||
"args": [
|
||
"-y",
|
||
"@likec4/mcp"
|
||
],
|
||
"env": {
|
||
"LIKEC4_WORKSPACE": "${workspaceFolder}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
This package starts MCP server using [`stdio`](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio) transport.
|
||
|
||
If `LIKEC4_WORKSPACE` environment variable is not set, the current directory will be used as workspace.
|
||
|
||
:::tip
|
||
You can also use `@likec4/mcp` package as CLI (this package is smaller than `likec4`):
|
||
|
||
```sh
|
||
npm install -g @likec4/mcp
|
||
|
||
# Start MCP server with streamable http transport
|
||
likec4-mcp --http --port 1234 /path/to/workspace
|
||
|
||
# See available options
|
||
likec4-mcp -h
|
||
```
|
||
|
||
Disable watch mode with `--no-watch` to consume less resources, if you have static workspace.
|
||
:::
|
||
|
||
### Available tools
|
||
|
||
| Tool | Description |
|
||
|------|-------------|
|
||
| `list-projects` | List all LikeC4 projects in the workspace |
|
||
| `read-project-summary` | Project specification, configuration, all elements, deployment nodes and views |
|
||
| `search-element` | Search elements and deployment nodes by id/title/kind/shape/tags/metadata |
|
||
| `read-element` | Full element details including relationships, views, deployments, metadata |
|
||
| `read-deployment` | Details of a deployment node or deployed instance |
|
||
| `read-view` | Full view details (nodes/edges) and source location |
|
||
| `find-relationships` | Direct and indirect relationships between two elements |
|
||
| `query-graph` | Query element hierarchy (ancestors, descendants, siblings, children, parent) and single-hop relationships (incomers, outgoers) |
|
||
| `query-incomers-graph` | Recursive BFS graph of all upstream dependencies/producers |
|
||
| `query-outgoers-graph` | Recursive BFS graph of all downstream consumers/dependents |
|
||
| `query-by-metadata` | Search elements by metadata key-value pairs (exact/contains/exists) |
|
||
| `query-by-tags` | Tag filtering with boolean logic (allOf, anyOf, noneOf) |
|
||
| `query-by-tag-pattern` | Search elements by tag patterns using prefix, contains, or suffix matching |
|
||
| `find-relationship-paths` | Discover all multi-hop relationship chains between two elements via BFS |
|
||
| `batch-read-elements` | Read full details for multiple elements in a single request |
|
||
| `subgraph-summary` | Summarize descendants of an element with depth, metadata, and relationship counts |
|
||
| `element-diff` | Compare two elements and show differences in properties, tags, metadata, and relationships |
|
||
| `open-view` | Opens the LikeC4 view panel (editor only) |
|
||
|
||
Check out [README](https://github.com/likec4/likec4/blob/main/packages/mcp/README.md) for more details.
|
||
|
||
# LikeC4 CLI
|
||
|
||
import { PackageManagers } from 'starlight-package-managers';
|
||
|
||
<p style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
</p>
|
||
|
||
The `likec4` CLI is a tool for various operations and automation tasks, such as:
|
||
|
||
- Start development server to preview diagrams (with hot-reload)
|
||
- Build a static website for sharing and embedding diagrams
|
||
- Export to PNG, JPEG, JSON, Mermaid, Dot, D2, DrawIO
|
||
- Generate [source code artifacts](/tooling/code-generation/react/):
|
||
- React components
|
||
- Web Components
|
||
- Typed data
|
||
- Format source files (with CI-friendly check mode)
|
||
|
||
## Install
|
||
|
||
### Local installation
|
||
|
||
If you're using it in an npm project, install it as a development dependency:
|
||
|
||
<PackageManagers
|
||
pkg="likec4"
|
||
dev
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
You can reference it directly in the `package.json#scripts` object:
|
||
|
||
```json5
|
||
{
|
||
scripts: {
|
||
dev: 'likec4 dev ...',
|
||
build: 'likec4 build ...'
|
||
}
|
||
}
|
||
```
|
||
|
||
### Global installation
|
||
|
||
To fetch and execute a package binary, without installing it as a dependency:
|
||
|
||
<PackageManagers
|
||
type="dlx"
|
||
pkg="likec4"
|
||
args="start"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
|
||
To use it in any project without [`npx`](https://docs.npmjs.com/cli/v11/commands/npx), install it globally:
|
||
|
||
```sh
|
||
npm install --global likec4
|
||
|
||
# Then, you can call `likec4` directly:
|
||
likec4 [command]
|
||
```
|
||
|
||
## Usage
|
||
|
||
Almost all commands have a `--help` option and provide usage examples.
|
||
|
||
```sh
|
||
likec4 build -h
|
||
likec4 gen react -h
|
||
```
|
||
|
||
|
||
### Preview diagrams
|
||
|
||
In a folder with LikeC4 sources:
|
||
|
||
```sh
|
||
likec4 serve
|
||
# Aliases:
|
||
likec4 start
|
||
likec4 dev
|
||
```
|
||
|
||
This recursively searches for `*.c4` and `*.likec4` files in the current folder, parses them, and serves diagrams via a local web server.
|
||
Any change in the source files triggers a hot update in the browser immediately.
|
||
|
||
| Option | Description |
|
||
| ------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||
| `--port` | Port for the dev server (default: 5173, or `PORT` env var) |
|
||
| `--hmr-port` | Port for the HMR WebSocket (default: auto-discovered from 24678–24690, or `HMR_PORT` env var) |
|
||
| `--listen, -l` | IP address to listen on (default: `127.0.0.1`, or `0.0.0.0` inside a container) |
|
||
| `--no-react-hmr` | Disable React HMR (useful for CI or static previews) |
|
||
| `--use-dot` | Use local Graphviz (`dot`) binaries instead of bundled WASM |
|
||
| `--public, --public-dir` | Directory whose files are served as-is (Vite [`publicDir`](https://vite.dev/guide/assets#the-public-directory)) |
|
||
| `--allowed-host` | Hostname allowed to access the dev server (Vite [`server.allowedHosts`](https://vite.dev/config/server-options#server-allowedhosts)); repeatable. Defaults to allowing all hosts. |
|
||
|
||
:::tip
|
||
You can start the process in a separate terminal window and keep it running while you're editing the model in your editor, or even serve multiple projects at once.
|
||
:::
|
||
|
||
<Aside type="caution">
|
||
By default, the web server listens on localhost (127.0.0.1). If you want it to listen on all network interfaces, add `--listen 0.0.0.0` to the serve command.
|
||
</Aside>
|
||
|
||
### Build static website
|
||
|
||
CLI can build a website with the diagrams, ready to be deployed or embedded.
|
||
When you deployed the website, you can use "Share" button and get a link to a specific diagram.
|
||
|
||
The resulting website is strictly bound to the given base path (`/` by default).
|
||
A relocatable app can be built with `--base "./"`.
|
||
|
||
```sh
|
||
likec4 build -o ./dist
|
||
```
|
||
|
||
| Option | Description |
|
||
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||
| `--output` | Output directory |
|
||
| `--base, --base-url` | Base URL from which the app is being served, e.g., "/", "/pages/", or "./" for a relocatable app |
|
||
| `--use-hash-history` | Hash-based navigation, e.g. "/#/view" instead of "/view" |
|
||
| `--use-dot` | Use local binaries of Graphviz ("dot") instead of bundled WASM |
|
||
| `--webcomponent-prefix` | Prefix for web components, e.g. "c4" generates `<c4-view ../>` |
|
||
| `--title` | Base title of the app pages (default is "LikeC4") |
|
||
| `--output-single-file` | Generates a single self-contained HTML file |
|
||
| `--public, --public-dir` | Directory whose files are copied into the output as-is (Vite [`publicDir`](https://vite.dev/guide/assets#the-public-directory)); preserved with `--output-single-file` |
|
||
|
||
:::note
|
||
Internally, CLI uses Vite to build the website, and `likec4 build` calls `vite build`.
|
||
Vite [deploy documentation](https://vitejs.dev/guide/static-deploy.html) may also help you.
|
||
|
||
Repository [likec4/template](https://github.com/likec4/template) demonstrates how to deploy likec4 website to github pages.
|
||
:::
|
||
|
||
|
||
There is also a supplementary command to preview the build:
|
||
|
||
```sh
|
||
likec4 preview -o ./dist
|
||
```
|
||
|
||
For example, this command can be used in CI to compare diagrams with ones from the previous or main build.
|
||
|
||
### Export to PNG
|
||
|
||
```sh
|
||
likec4 export png -o ./assets
|
||
likec4 export jpg -o ./assets --quality 90
|
||
```
|
||
|
||
This command starts local web server and uses Playwright to take screenshots.
|
||
If you plan to use it in CI, refer to the [Playwright documentation](https://playwright.dev/docs/ci) for details
|
||
or consider [LikeC4 GitHub Actions](/tooling/github)
|
||
|
||
Use `likec4 export jpg` to export JPEG files. It supports the same screenshot options, plus `--quality`.
|
||
|
||
:::note
|
||
Exporting to PNG or JPEG requires Playwright.
|
||
You will be prompted with a command to install if it's not found.
|
||
:::
|
||
|
||
| Option | Description |
|
||
| ----------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||
| `--outdir, -o` | Output directory for PNG/JPEG files; if not specified, images are saved next to sources. `--output` is accepted as alias for compatibility. |
|
||
| `--project` | select LikeC4 project to export from by name (e.g. "my-project") or by path<br/>If not specified, exports all |
|
||
| `--theme` | color-scheme to use, "light" (default), "dark" |
|
||
| `--dark` | use dark theme, shortcut for --theme=dark |
|
||
| `--light` | use light theme, shortcut for --theme=light |
|
||
| `--use-dot` | Use local binaries of Graphviz ("dot") instead of bundled WASM |
|
||
| `--seq, --sequence` | use sequence layout for dynamic views |
|
||
| `--flat, --flatten` | Flatten all images in outdir, ignoring source structure |
|
||
| `-f, --filter` | include views with ids matching given patterns<br/>(multiple patterns are combined with OR) |
|
||
| `--quality, -q` | JPEG quality from 1 to 100 (default is 80); only used by `likec4 export jpg` |
|
||
| `-i, --ignore` | continue if export fails for some views |
|
||
| `-t, --timeout` | timeout for playwright in seconds, default is 15 |
|
||
| `--max-attempts` | max attempts to export failing view, 1 means no retry (default is 3) |
|
||
| `--server-url` | use this url instead of starting new likec4 server |
|
||
| `--chromium-sandbox` | enable chromium sandbox (see Playwright docs) |
|
||
| `--notation` | include view notation in exported PNG/JPEG files |
|
||
| `--description` | include the view title and Markdown description in exported PNG/JPEG files |
|
||
|
||
### Export to JSON
|
||
|
||
```sh
|
||
likec4 export json -o dump.json
|
||
```
|
||
|
||
### Export to DrawIO
|
||
|
||
Export views to DrawIO (`.drawio`) so you can edit diagrams in [draw.io](https://draw.io) or reuse them in tools that support the format. One `.drawio` file is generated per view.
|
||
|
||
```sh
|
||
likec4 export drawio
|
||
likec4 export drawio -o ./diagrams
|
||
likec4 export drawio -o ./diagrams src/
|
||
likec4 export drawio --profile leanix -o ./diagrams
|
||
```
|
||
|
||
| Option | Description |
|
||
| ---------------- | --------------------------------------------------------------------------- |
|
||
| `path` | Path to project (default: current directory) |
|
||
| `--outdir, -o` | Output directory for `.drawio` files (default: current directory or `path`) |
|
||
| `--all-in-one` | Write one `.drawio` file with all views as tabs |
|
||
| `--roundtrip` | Apply layout, stroke colors/widths, and edge waypoints from DrawIO round-trip comment blocks in `.c4` source (e.g. after import) |
|
||
| `--uncompressed` | Write diagram XML uncompressed (larger file; use if draw.io fails to open the default compressed export) |
|
||
| `--profile` | Export profile: `default` (round-trip) or `leanix` (bridge-managed metadata for LeanIX interoperability). Default: `default` |
|
||
| `--project` | Select LikeC4 project by name or path; if not set, exports all |
|
||
| `--use-dot` | Use local Graphviz (`dot`) for layout instead of bundled WASM |
|
||
|
||
:::note
|
||
Export to DrawIO requires layout. If Graphviz is not installed, the CLI uses bundled WASM; for large diagrams, install [Graphviz](https://graphviz.org/) and use `--use-dot` for better performance. Use `--roundtrip` when re-exporting after an import so the generated DrawIO matches the previously imported layout and waypoints. Use `--profile leanix` when exporting for LeanIX interoperability; see [Draw.io — Export profiles](/tooling/drawio/#export-profiles).
|
||
:::
|
||
|
||
### Generate Mermaid, Dot, D2, PlantUml
|
||
|
||
Via codegen:
|
||
|
||
```sh
|
||
likec4 gen mmd
|
||
likec4 gen mermaid
|
||
likec4 gen dot
|
||
likec4 gen d2
|
||
likec4 gen plantuml
|
||
```
|
||
|
||
Together with [Export to DrawIO](#export-to-drawio), this allows teams to reuse diagrams in Mermaid, Dot, D2, PlantUML, or DrawIO.
|
||
|
||
<LinkCard
|
||
title="Generate components"
|
||
description="Learn how to generate React and Web Components"
|
||
href="/tooling/code-generation/react/"
|
||
/>
|
||
|
||
<LinkCard
|
||
title="Custom generators"
|
||
description="Learn how to define and use custom generators"
|
||
href="/tooling/code-generation/custom/"
|
||
/>
|
||
|
||
### Validate
|
||
|
||
```sh
|
||
likec4 validate
|
||
```
|
||
|
||
This command checks for:
|
||
- Syntax errors
|
||
- Layout drift (outdated manual layout)
|
||
|
||
If any errors are found, the command exits with a non-zero return code.
|
||
|
||
### Format
|
||
|
||
Format `.c4` source files in place:
|
||
|
||
```sh
|
||
# Format all files in current workspace
|
||
likec4 format
|
||
# Alias
|
||
likec4 fmt
|
||
|
||
# Format a specific workspace directory
|
||
likec4 format ./my-project
|
||
|
||
# Format only specific project(s) in a multi-project workspace
|
||
likec4 format --project alpha --project beta
|
||
|
||
# Format specific files
|
||
likec4 format --files src/model.c4 --files src/views.c4
|
||
|
||
# Check mode (CI-friendly, exits with code 1 if any file needs formatting)
|
||
likec4 format --check
|
||
```
|
||
|
||
| Option | Description |
|
||
| ------------------- | --------------------------------------------------------------- |
|
||
| `path` | Path to workspace (default: current directory; falls back to `LIKEC4_WORKSPACE` env) |
|
||
| `--project, -p` | Format only specific project(s) by name (repeatable) |
|
||
| `--files` | Format only specific files (repeatable) |
|
||
| `--check` | Check if files are formatted without writing changes |
|
||
|
||
### Language Server (LSP)
|
||
|
||
Start the LikeC4 language server for use with editors that support the Language Server Protocol:
|
||
|
||
```sh
|
||
likec4 lsp --stdio
|
||
likec4 lsp --node-ipc
|
||
likec4 lsp --socket 3000
|
||
likec4 lsp --pipe /tmp/likec4.pipe
|
||
```
|
||
|
||
One of the transport options (`--stdio`, `--node-ipc`, `--socket`, `--pipe`) is required.
|
||
|
||
| Option | Description |
|
||
| ------------------- | ----------------------------------------------------------- |
|
||
| `--stdio` | Use stdio transport |
|
||
| `--node-ipc` | Use node-ipc transport |
|
||
| `--socket <port>` | Use socket transport on specified port |
|
||
| `--pipe <name>` | Use pipe transport with specified pipe name |
|
||
| `--watch, -w` | Enable built-in watcher (disabled by default) |
|
||
| `--no-manual-layouts` | Disable manual layouts (enabled by default) |
|
||
| `--use-dot` | Use local Graphviz (`dot`) for layout instead of bundled WASM |
|
||
|
||
# Custom Generators
|
||
|
||
import { PackageManagers } from 'starlight-package-managers'
|
||
|
||
Ensure you have [`likec4`](https://www.npmjs.com/package/likec4) in your dependencies:
|
||
|
||
<PackageManagers
|
||
pkg="likec4"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
|
||
You can define custom generators in your project config:
|
||
|
||
```ts
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
generators: {
|
||
'hello': async ({ likec4model, ctx }) => {
|
||
await ctx.write({
|
||
path: 'hello.txt', // relative to the project root
|
||
content: `Project: ${likec4model.project.id}`,
|
||
})
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
Now you can run your generator with [CLI](/tooling/cli):
|
||
|
||
```bash
|
||
likec4 gen hello
|
||
```
|
||
|
||
In [multi-project](/dsl/config/multi-projects) workspace use:
|
||
|
||
```bash
|
||
likec4 gen hello --project my-project
|
||
# Other options
|
||
likec4 gen hello --project my-project --use-dot
|
||
```
|
||
|
||
## Reusable Generators
|
||
|
||
There is also helper function `defineGenerators` to define reusable generators:
|
||
|
||
```ts
|
||
// shared_generators.ts
|
||
import { defineGenerators } from 'likec4/config'
|
||
|
||
export default defineGenerators({
|
||
'hello': async ({ likec4model, ctx }) => {
|
||
await ctx.write({
|
||
path: 'hello.txt', // relative to the project root
|
||
content: `Project: ${likec4model.project.id}`,
|
||
})
|
||
},
|
||
})
|
||
```
|
||
Now you can use it in your configs:
|
||
|
||
```ts
|
||
// likec4.config.ts
|
||
import { defineConfig } from 'likec4/config'
|
||
import generators from './shared_generators'
|
||
|
||
export default defineConfig({
|
||
name: 'my-project',
|
||
title: 'My Project',
|
||
generators,
|
||
})
|
||
```
|
||
|
||
:::tip
|
||
You can also use smaller package `@likec4/config`:
|
||
|
||
```ts
|
||
import { defineGenerators } from '@likec4/config'
|
||
|
||
export default defineGenerators({
|
||
'hello': async ({ likec4model, ctx }) => {
|
||
await ctx.write({
|
||
path: 'hello.txt', // relative to the project root
|
||
content: `Project: ${likec4model.project.id}`,
|
||
})
|
||
},
|
||
})
|
||
```
|
||
|
||
:::
|
||
|
||
# Generate LikeC4Model
|
||
|
||
import { PackageManagers } from 'starlight-package-managers';
|
||
|
||
<p style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
</p>
|
||
|
||
Generate source code artifacts from architecture model.
|
||
|
||
## Typed Model
|
||
|
||
<PackageManagers
|
||
type="dlx"
|
||
pkg="likec4"
|
||
args="codegen model --outfile ./likec4-model.ts"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
<Aside type='caution' title="In progress" >
|
||
Documentation in progress
|
||
|
||
Meanwhile, check [`@likec4/core/model`](https://github.com/likec4/likec4/blob/main/packages/core/README.md).
|
||
</Aside>
|
||
|
||
# Generate React
|
||
|
||
import { PackageManagers } from 'starlight-package-managers'
|
||
|
||
<p style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
</p>
|
||
|
||
Generate React components with views from your architecture model.
|
||
|
||
## Install
|
||
|
||
Ensure you have [`likec4`](https://www.npmjs.com/package/likec4) in your dependencies:
|
||
|
||
<PackageManagers
|
||
pkg="likec4"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
|
||
## React
|
||
|
||
The following command generates a JavaScript bundle with React Component (and `.d.ts`):
|
||
|
||
<PackageManagers
|
||
type="dlx"
|
||
pkg="likec4"
|
||
args="codegen react --outfile ./src/likec4.generated.js"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
<br />
|
||
|
||
```sh frame="none"
|
||
# Aliases
|
||
npx likec4 generate react -o ./src/likec4.generated.js
|
||
npx likec4 gen react -o ./src/likec4.generated.js
|
||
|
||
```
|
||
|
||
:::note
|
||
Check `likec4 codegen react --help` for available options.
|
||
:::
|
||
|
||
<Aside type='tip' title="Vite plugin" >
|
||
There is also an option to use Vite plugin in library mode and get auto-updates, check this [example](/tooling/vite-plugin/#with-nextjs).
|
||
</Aside >
|
||
|
||
|
||
To use the component:
|
||
|
||
```tsx
|
||
import { LikeC4View } from './likec4.generated'
|
||
|
||
const App = () => {
|
||
return (
|
||
<div>
|
||
<LikeC4View viewId="index" />
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
| Property | Description |
|
||
| ----------------- | --------------------------------------------------------------------------------------------------- |
|
||
| `viewId` | Typed enumeration of your views |
|
||
| `where` | Optional, see [filter](#filter) |
|
||
| `injectFontCss` | Injects CSS with <a href='https://fontsource.org/fonts/ibm-plex-sans' target='_blank'>IBM Plex Sans Variable</a> font from CDN.<br/>Default is `true` |
|
||
|
||
:::tip
|
||
Check <a href="https://github.com/likec4/likec4/blob/main/packages/diagram/src/bundle/LikeC4View.props.ts#L5-L156" target="_blank">source code</a> for all properties.
|
||
:::
|
||
|
||
:::caution
|
||
`LikeC4View` does not rehydrate correctly if rendered on the server, prefer client-side.
|
||
:::
|
||
|
||
### Filter
|
||
|
||
`where` is same [view predicate](/dsl/views/predicates/#filter), but applies dynamically and enables to show/hide elements based on the context. For example:
|
||
|
||
```tsx
|
||
import { LikeC4View } from './likec4.generated'
|
||
|
||
// Keeps elements and relationships where:
|
||
// - tag is not 'legacy'
|
||
// - and
|
||
// - tag is 'v1' or 'v2'
|
||
const App = () => {
|
||
return (
|
||
<div>
|
||
<LikeC4View
|
||
viewId="index"
|
||
where={{
|
||
and: [
|
||
{ tag: { neq: 'legacy' } },
|
||
{
|
||
or: [
|
||
{ tag: { eq: 'v1' } },
|
||
{ tag: { eq: 'v2' } }
|
||
]
|
||
}
|
||
]
|
||
}}/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
Layout stays the same, i.e. elements are not rearranged.
|
||
Be aware, `where` applies both to elements and relationships.
|
||
|
||
## ReactLikeC4
|
||
|
||
`LikeC4View` renders views from your model, and allows exploring in the popup browser.
|
||
Component works in most usecases, but if you need more - use `ReactLikeC4`:
|
||
|
||
```tsx
|
||
import { ReactLikeC4, type LikeC4ViewId } from './likec4.generated'
|
||
|
||
const App = () => {
|
||
const [viewId, setViewId] = useState<LikeC4ViewId>('index')
|
||
return (
|
||
<ReactLikeC4
|
||
viewId={viewId}
|
||
pannable
|
||
zoomable={false}
|
||
keepAspectRatio
|
||
showNavigationButtons
|
||
enableDynamicViewWalkthrough={false}
|
||
enableElementDetails
|
||
enableRelationshipDetails
|
||
showDiagramTitle={false}
|
||
onNavigateTo={setViewId}
|
||
onNodeClick={...}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
`ReactLikeC4` is a low-level component, giving you more control and allowing react to the events.
|
||
Check <a href="https://github.com/likec4/likec4/blob/main/packages/diagram/src/LikeC4Diagram.props.ts" target="_blank">source code</a> for available options.
|
||
|
||
Feel free to share your ideas or ask questions in <a href="https://github.com/likec4/likec4/discussions/" target='_blank'>GitHub discussions</a>.
|
||
|
||
<Aside type='tip' title="Generic version" >
|
||
Code generation prepares component, which is already "bound" to your model.
|
||
But it is possible to use a generic from the library:
|
||
|
||
```tsx
|
||
import { ReactLikeC4, LikeC4ModelProvider } from 'likec4/react'
|
||
import { RenderIcon, likeC4Model } from './likec4.generated'
|
||
|
||
const App = () => {
|
||
return (
|
||
<LikeC4ModelProvider likec4model={likeC4Model}>
|
||
<ReactLikeC4
|
||
viewId={"index"}
|
||
renderIcon={RenderIcon} // Optional, used for bundled icons
|
||
onEdgeClick={...}
|
||
/>
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
|
||
</Aside>
|
||
|
||
## Styling
|
||
|
||
<Aside type='caution' title="In progress" >
|
||
TODO: Document styling and theming with PandaCSS
|
||
</Aside>
|
||
|
||
# Generate Web Components
|
||
|
||
import { PackageManagers } from 'starlight-package-managers'
|
||
|
||
<p style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
</p>
|
||
|
||
## Install
|
||
|
||
Ensure you have [`likec4`](https://www.npmjs.com/package/likec4) in your dependencies:
|
||
|
||
<PackageManagers
|
||
pkg="likec4"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
|
||
|
||
## Web Component
|
||
|
||
Generate javascript bundle with web component:
|
||
|
||
<PackageManagers
|
||
type="dlx"
|
||
pkg="likec4"
|
||
args="codegen webcomponent -o ./src/likec4-webcomponent.js"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
<br />
|
||
|
||
Use it:
|
||
|
||
```html
|
||
<script src="./src/likec4-webcomponent.js"></script>
|
||
<likec4-view view-id="index"></likec4-view>
|
||
```
|
||
|
||
By default, cli generates a `likec4-view` web component.
|
||
You can change the `likec4` prefix by `-w, --webcomponent-prefix`.
|
||
|
||
For example:
|
||
|
||
<PackageManagers
|
||
type="dlx"
|
||
pkg="likec4"
|
||
args="codegen webcomponent -w custom-c4 -o ./src/likec4-webcomponent.js"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
And in HTML:
|
||
|
||
```html
|
||
<custom-c4-view view-id="index" browser="true" dynamic-variant="sequence"></custom-c4-view>
|
||
```
|
||
|
||
| Property | Description |
|
||
| ----------------- | ----------------------------------------------------------------------------------------------------- |
|
||
| `view-id` | Your view id |
|
||
| `browser` | Whether to show views browser popup (default `true`) |
|
||
| `dynamic-variant` | How dynamic view should be rendered<br />Possible values: `diagram` or `sequence` (default `diagram`) |
|
||
| `color-scheme` | Force light or dark color scheme<br />Possible values: `light` or `dark` (default: follows system) |
|
||
|
||
:::note
|
||
CLI command [build](/tooling/cli/#build-static-website) always generates javascript with web components.
|
||
Check `Share` button on the top at <a href="https://template.likec4.dev/view/index" target="_blank">this example</a>
|
||
:::
|
||
|
||
# AspireC4
|
||
|
||
|
||
:::caution[Community Project]
|
||
AspireC4 is a community-maintained project, not an official LikeC4 package. APIs and behavior may change.
|
||
:::
|
||
|
||
[AspireC4](https://github.com/kjldev/aspirec4) is a .NET Aspire extension library that auto-generates live LikeC4 architecture diagrams from the Aspire resource graph. Diagrams update in real-time as resources start, stop, or produce errors — and each element links back to the corresponding Aspire dashboard page.
|
||
|
||
<CardGrid>
|
||
<LinkCard title="GitHub" href="https://github.com/kjldev/aspirec4" />
|
||
<LinkCard title="NuGet Package" href="https://www.nuget.org/packages/AspireC4.Hosting/" />
|
||
<LinkCard title="Project" href="https://kjl.dev/projects/aspirec4" />
|
||
</CardGrid>
|
||
|
||
## Prerequisites
|
||
|
||
- **Docker** (required by default) — the LikeC4 server runs as a `ghcr.io/likec4/likec4` container sidecar.
|
||
- Alternatively, use `.WithLocalCLI()` to run via a locally-installed Node.js CLI (`npx`, `pnpm`, `yarn`, `bun`, or `deno`).
|
||
|
||
## Quick Start
|
||
|
||
Add `AspireC4.Hosting` to your AppHost project, then call `AddAspireC4()`:
|
||
|
||
```csharp
|
||
// AppHost/Program.cs
|
||
var builder = DistributedApplication.CreateBuilder(args);
|
||
|
||
// Add your services ...
|
||
var api = builder.AddProject<Projects.MyApi>("my-api");
|
||
|
||
// Register the LikeC4 visualization sidecar.
|
||
builder.AddAspireC4();
|
||
|
||
builder.Build().Run();
|
||
```
|
||
|
||
This will:
|
||
|
||
1. Write a `./likec4/model.gen.c4` file from the Aspire resource graph.
|
||
2. Start a `ghcr.io/likec4/likec4` Docker container serving the live diagram.
|
||
3. Watch for resource state changes and regenerate the file automatically.
|
||
|
||
<Tabs>
|
||
<TabItem label="Basic Setup">
|
||
This simply adds the AspireC4 hosting extension to the AppHost builder.
|
||
|
||
```csharp
|
||
// AppHost/Program.cs
|
||
var builder = DistributedApplication.CreateBuilder(args);
|
||
builder.AddAspireC4();
|
||
|
||
// ...add other projects/resources such as databases, services, etc.
|
||
|
||
builder
|
||
.AddProject<Projects.Web>("web")
|
||
.WithReference(db);
|
||
```
|
||
|
||

|
||
</TabItem>
|
||
<TabItem label="Enhanced Setup">
|
||
This simply adds the AspireC4 hosting extension to the AppHost builder.
|
||
|
||
```csharp
|
||
// AppHost/Program.cs
|
||
var builder = DistributedApplication.CreateBuilder(args);
|
||
builder.AddAspireC4(opts => {
|
||
opts.Title = "MakerStack Architecture";
|
||
});
|
||
|
||
// ...add other projects/resources such as databases, services, etc.
|
||
|
||
builder
|
||
.AddProject<Projects.Web>("web")
|
||
.WithLikeC4Details(opts => {
|
||
opts
|
||
.WithLabel("Web Host")
|
||
.WithSummary(".NET 10/ ASP.NET Minimal API web app. Hosting APIs, and the Astro-based landing site and the MarkerStack SPA");
|
||
})
|
||
# Note the reference to the database resource (db) here, to save you from calling .WithReference(db) as well...
|
||
.WithLikeC4Reference(db, opts => {
|
||
opts
|
||
.WithLabel("Tabular Data Stream (TDS)")
|
||
... etc
|
||
});
|
||
```
|
||
|
||

|
||
</TabItem>
|
||
</Tabs>
|
||
|
||
|
||
## How It Works
|
||
|
||
### Data Flow
|
||
|
||
```
|
||
Aspire resources
|
||
│
|
||
▼
|
||
LikeC4ModelBuilder.Build() ← resource states, dashboard URL
|
||
│
|
||
▼
|
||
LikeC4DSLGenerator.Generate()
|
||
│
|
||
▼
|
||
./likec4/model.gen.c4 ← written to disk (and Docker volume)
|
||
│
|
||
▼
|
||
ghcr.io/likec4/likec4 ← serves the diagram, hot-reloads on file change
|
||
```
|
||
|
||
### Resource State Colors
|
||
|
||
Each Aspire resource is represented as a LikeC4 element. Its color reflects the live runtime state:
|
||
|
||
| State | Color | Description |
|
||
|-------|-------|-------------|
|
||
| Unknown | default | Not yet started |
|
||
| Starting | sky | Resource is initializing |
|
||
| Running | green | Healthy and running |
|
||
| Stopping | slate | Winding down (60% opacity) |
|
||
| Exited | muted | Stopped cleanly (30% opacity) |
|
||
| Failed | amber | Exited with a non-zero code |
|
||
| Error | red | Reported an error state |
|
||
|
||
{/* Screenshot placeholder: LikeC4 diagram showing resources in various states */}
|
||
|
||
## Dashboard Deep-Links
|
||
|
||
When `IncludeAspireDashboardLinks` is enabled (the default), each LikeC4 element receives two links:
|
||
|
||
- **Dashboard: Console Logs** → `/consolelogs/resource/{name}`
|
||
- **Dashboard: Structured Logs** → `/structuredlogs/resource/{name}`
|
||
|
||
Links are built at runtime once the Aspire dashboard reaches the `Running` state. With a browser token (default Aspire setup), the links embed the token for seamless authentication.
|
||
|
||
To disable dashboard links:
|
||
|
||
```csharp
|
||
builder.AddAspireC4(options =>
|
||
{
|
||
options.IncludeAspireDashboardLinks = false;
|
||
});
|
||
```
|
||
|
||
|
||
|
||
## Alternative Modes
|
||
|
||
### Local CLI (`WithLocalCLI`)
|
||
|
||
Uses a locally-installed `likec4` CLI (via `npx`/`pnpm`/`yarn`/`bun`/`deno`) instead of the Docker container:
|
||
|
||
```csharp
|
||
builder.AddAspireC4().WithLocalCLI();
|
||
```
|
||
|
||
### Hide from Dashboard (`WithHideFromDashboard`)
|
||
|
||
Removes the LikeC4 sidecar from the Aspire dashboard resource list and surfaces the diagram URL as a link and command on each `ProjectResource`:
|
||
|
||
```csharp
|
||
builder.AddAspireC4().WithHideFromDashboard();
|
||
```
|
||
|
||
## Excluding Resources
|
||
|
||
A resource is excluded from the diagram if:
|
||
|
||
- It carries an `ExcludeFromLikeC4Annotation`, or
|
||
- Its `ResourceSnapshotAnnotation.InitialSnapshot.IsHidden == true` (Aspire internal resources).
|
||
|
||
The LikeC4 sidecar resource itself is always excluded.
|
||
|
||
## Limitations
|
||
|
||
- **Static diagram tool**: LikeC4 renders a file-based diagram. State updates require an HMR refresh.
|
||
- **Dashboard URL discovery**: If the Aspire dashboard hasn't started when the first diagram is generated, dashboard links will be absent until it reaches `Running` and triggers a regeneration.
|
||
- **Browser token in generated file**: The Aspire browser token appears in the `.c4` file embedded in dashboard link URLs. Do not commit the generated file to source control.
|
||
- **Windows HMR relay**: On Windows, a TCP relay bridges the fixed host port `24678` to the dynamically-allocated Docker port for Hot Module Replacement.
|
||
|
||
# Erode
|
||
|
||
|
||
:::caution[Experimental]
|
||
Erode is experimental and in active development. APIs and behavior may change.
|
||
:::
|
||
|
||
[Erode](https://erode.dev) analyzes code changes against your LikeC4 architecture model using AI, making undeclared dependencies and structural drift visible during code review and while coding.
|
||
|
||
<LinkButton
|
||
href="https://erode.dev"
|
||
variant="minimal"
|
||
icon="external"
|
||
iconPlacement="start"
|
||
>
|
||
{'https://erode.dev'}
|
||
</LinkButton>
|
||
|
||
<CardGrid>
|
||
<LinkCard title="Getting Started" href="https://erode.dev/docs/getting-started/" />
|
||
<LinkCard title="GitHub" href="https://github.com/erode-app/erode" />
|
||
</CardGrid>
|
||
|
||
## Quick Start
|
||
|
||
Erode can be integrated in three ways. See the [Erode docs](https://erode.dev/docs/getting-started/) for detailed setup instructions.
|
||
|
||
### GitHub Actions
|
||
|
||
```yaml
|
||
- uses: erode-app/erode@0
|
||
```
|
||
|
||
### CLI
|
||
|
||
```shell
|
||
npx @erode-app/cli check
|
||
```
|
||
|
||
### Claude Code Skill
|
||
|
||
Erode is also available as a Claude Code skill — see the [docs](https://erode.dev/docs/getting-started/) for details.
|
||
|
||
# MkDocs plugin
|
||
|
||
|
||
Colleagues (and our sponsors) from [doubleslash.de](https://doubleslash.de/) made the MkDocs plugin that allows to integrate LikeC4 diagrams
|
||
into MkDocs documentation.
|
||
|
||
<LinkButton
|
||
href="https://github.com/doubleSlashde/mkdocs-likec4"
|
||
variant="minimal"
|
||
icon="external"
|
||
iconPlacement="start"
|
||
>
|
||
{'https://github.com/doubleSlashde/mkdocs-likec4'}
|
||
</LinkButton>
|
||
|
||
Check the project's [repository](https://github.com/doubleSlashde/mkdocs-likec4) and documentation website for examples and more details.
|
||
|
||
<CardGrid>
|
||
<LinkCard title="Documentation" href="https://doubleslashde.github.io/mkdocs-likec4/" />
|
||
</CardGrid>
|
||
|
||
|
||
## Quick Start
|
||
|
||
1. Ensure `likec4` and `graphviz` are available on the build system
|
||
|
||
2. Install the `mkdocs-likec4` plugin via `pip`:
|
||
```shell
|
||
pip install mkdocs-likec4
|
||
```
|
||
|
||
3. Add the plugin to your `mkdocs.yml`:
|
||
```yaml
|
||
plugins:
|
||
- search
|
||
- mkdocs-likec4
|
||
```
|
||
|
||
4. Start embedding views in your markdown:
|
||
````markdown
|
||
```likec4-view
|
||
<your-view-id>
|
||
```
|
||
````
|
||
|
||
# LikeC4 Docker
|
||
|
||
import { PackageManagers } from 'starlight-package-managers';
|
||
|
||
LikeC4 Docker image is a self-contained environment for running LikeC4 commands and can be used as a drop-in replacement for `likec4` CLI.
|
||
It is hosted in [GitHub Container Registry](https://github.com/likec4/likec4/pkgs/container/likec4) and [Docker Hub](https://hub.docker.com/r/likec4/likec4) and includes:
|
||
|
||
- Node.js (22.x)
|
||
- Graphviz (built from sources of the [latest release](https://gitlab.com/graphviz/graphviz/-/releases))
|
||
- Playwright (latest)
|
||
- LikeC4 CLI (latest)
|
||
|
||
You can find the [Dockerfile](https://github.com/likec4/likec4/blob/main/Dockerfile) in the repository.
|
||
|
||
|
||
<Tabs>
|
||
<Tab label='Docker HUB'>
|
||
```sh copy title="Run any CLI command"
|
||
# Example: Help for export command
|
||
docker run --rm -t likec4/likec4 export png -h
|
||
```
|
||
</Tab>
|
||
<Tab label='GitHub Container Registry'>
|
||
```sh copy title="Run any CLI command"
|
||
# Example: Help for export command
|
||
docker run --rm -t ghcr.io/likec4/likec4 export png -h
|
||
```
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
## Usage
|
||
|
||
To work with the container you need to mount the folder with LikeC4 sources to `/data` directory
|
||
(it is the default working directory, but you can change it and use any other you prefer).
|
||
|
||
### Start local web server
|
||
|
||
```sh copy title="Start local web server"
|
||
# mount LikeC4 sources to /data: -v $(pwd):/data
|
||
# publish ports: -p 5173:5173
|
||
# (optional) for realtime updates: -p 24678:24678
|
||
# (optional) use init process to correctly handle signals (eg Ctrl+C): --init
|
||
# (optional) enable color output: -t
|
||
docker run --rm \
|
||
-v $PWD:/data \
|
||
--init \
|
||
-t \
|
||
-p 5173:5173 \
|
||
-p 24678:24678 \
|
||
-e CHOKIDAR_USEPOLLING=1 \
|
||
-e CHOKIDAR_INTERVAL=200 \
|
||
likec4/likec4 \
|
||
start
|
||
```
|
||
|
||
The HMR WebSocket port is auto-discovered from the range 24678–24690. To pin it to a specific port, use `--hmr-port` (or the `HMR_PORT` env var) and publish that exact port:
|
||
|
||
```sh copy title="Pin the HMR port"
|
||
docker run --rm \
|
||
-v $PWD:/data \
|
||
--init \
|
||
-t \
|
||
-p 5173:5173 \
|
||
-p 24700:24700 \
|
||
-e CHOKIDAR_USEPOLLING=1 \
|
||
-e CHOKIDAR_INTERVAL=200 \
|
||
likec4/likec4 \
|
||
start --hmr-port 24700
|
||
# Or equivalently via env var:
|
||
# -e HMR_PORT=24700
|
||
```
|
||
|
||
:::note
|
||
By default LikeC4 Docker sets `--use-dot` flag and uses local Graphviz binaries instead of bundled WASM (as it has [memory issues](https://github.com/likec4/likec4/issues?q=Memory%20type:Bug)).
|
||
|
||
You can override it with `--no-use-dot` flag.
|
||
:::
|
||
|
||
:::caution[Running the Local Web Server on Windows]
|
||
When using Windows with the Docker-based preview, updates to the diagrams/ file system are not reflected in the running container when files are hosted within a Windows-based file system (i.e. on a drive such as `C:\`).
|
||
|
||
This is a [known issue](https://github.com/microsoft/WSL/issues/4739) with file system notifications for Linux applications using WSL.
|
||
|
||
**Workaround**:
|
||
|
||
Enable polling for file changes by adding the following environment variables to the `docker run` command:
|
||
|
||
```diff lang="sh" copy title="Start local web server with WSL workaround"
|
||
docker run --rm \
|
||
-v $PWD:/data \
|
||
--init \
|
||
-t \
|
||
-p 5173:5173 \
|
||
-p 24678:24678 \
|
||
+ -e CHOKIDAR_USEPOLLING=1 \ # Enable polling support
|
||
+ -e CHOKIDAR_INTERVAL=200 \ # Adjust timing to your needs
|
||
likec4/likec4 \
|
||
start
|
||
```
|
||
|
||
**Note**: If your diagrams are hosted within a WSL-based filesystem (e.g. /home/user/...), then file system notifications should work as expected.
|
||
:::
|
||
|
||
### Build static website
|
||
|
||
```sh copy title="Build static website"
|
||
docker run -v $PWD:/data likec4/likec4 build -o dist
|
||
```
|
||
|
||
### Export to PNG
|
||
|
||
```sh copy title="Export to PNG"
|
||
docker run -v $PWD:/data likec4/likec4 export png --outdir assets --theme dark
|
||
```
|
||
|
||
# Draw.io integration
|
||
|
||
LikeC4 can **export** views to [Draw.io](https://draw.io) (`.drawio`) so you can edit diagrams in Draw.io or reuse them in tools that support the format.
|
||
|
||
**CLI:** `likec4 export drawio` — see [CLI — Export to DrawIO](/tooling/cli/#export-to-drawio).
|
||
|
||
This page describes what is mapped on export, limitations, and multi-diagram behavior.
|
||
|
||
## Export (LikeC4 → Draw.io)
|
||
|
||
By default, one `.drawio` file is produced per view (each view becomes one diagram tab in that file). Use the CLI option `--all-in-one` to combine all views into a single multi-tab `.drawio` file.
|
||
|
||
### Export profiles
|
||
|
||
Export can use two profiles:
|
||
|
||
| Profile | Description |
|
||
| --------- | ----------- |
|
||
| `default` | Standard round-trip; layout, stroke, waypoints, and existing style attributes (see [Mapped to Draw.io](#mapped-to-drawio) below). |
|
||
| `leanix` | Adds **bridge-managed metadata** on vertices, edges, and diagram root so diagrams can be used with the [LeanIX bridge](https://github.com/likec4/likec4/tree/main/packages/leanix-bridge) or LeanIX tooling. LikeC4 remains the source of truth; Draw.io is an interchange format. |
|
||
|
||
**When using `leanix` profile:**
|
||
|
||
- **Vertices and root cell:** `bridgeManaged=true`, `likec4Id`, `likec4Kind`, `likec4ViewId`, and (if provided) `likec4ProjectId`. Optionally `leanixFactSheetType` per element kind when `leanixFactSheetTypeByKind` is set (e.g. via custom generator).
|
||
- **Edges:** `likec4RelationId`, `bridgeManaged`.
|
||
|
||
**CLI:** `likec4 export drawio --profile leanix -o ./diagrams`
|
||
|
||
**Programmatic:** Pass `profile: 'leanix'` (and optionally `projectId`, `leanixFactSheetTypeByKind`) in `GenerateDrawioOptions` when calling `generateDrawio` / `generateDrawioMulti`. See [@likec4/leanix-bridge](https://github.com/likec4/likec4/tree/main/packages/leanix-bridge) for sync and round-trip mapping.
|
||
|
||
### Mapped to Draw.io
|
||
|
||
| LikeC4 | Draw.io |
|
||
|--------|---------|
|
||
| View (view id) | Diagram tab `name` and `id`; view title/description in root cell as `likec4ViewTitle`, `likec4ViewDescription` |
|
||
| Element id | mxCell `id` (internal); hierarchy via `parent` |
|
||
| Element title, description | mxCell `value` (title; description in HTML or in style `likec4Description`) |
|
||
| Element: technology, notes, tags, **navigateTo**, icon | Style `likec4Technology`, `likec4Notes`, `likec4Tags`, `likec4NavigateTo`, `likec4Icon` (URI-encoded). **navigateTo** is also written as a Draw.io cell **link** so the element opens the view tab when clicked (see below). |
|
||
| Element: summary, links | Style `likec4Summary`, `likec4Links` (JSON array) |
|
||
| Element: notation | Style `likec4Notation` |
|
||
| Element: shape | Style `shape` (rectangle, umlActor, cylinder3, document, etc.) |
|
||
| Element: color | Style `fillColor`, `strokeColor`, `fontColor` (hex); theme/custom name in `likec4ColorName`; stroke in `likec4StrokeColor` for round-trip |
|
||
| Element: border, opacity | Style `strokeWidth`, `likec4Border`, `likec4StrokeWidth` (round-trip); `opacity` |
|
||
| Element: size, padding, textSize, iconPosition | Style `likec4Size`, `likec4Padding`, `likec4TextSize`, `likec4IconPosition` |
|
||
| View notation | Root cell `likec4ViewNotation` (round-trip) |
|
||
| Element position/size | mxGeometry `x`, `y`, `width`, `height` (from layout; overridable via options) |
|
||
| Element/edge custom data | mxUserObject `<data key="k">v</data>` (round-trip) |
|
||
| Edge waypoints | mxGeometry `relative="0"` with `<Array>` of `<mxPoint>` (when viewmodel has `points`) |
|
||
| Relationship: source, target, label | mxCell edge `source`, `target`, `value` |
|
||
| Relationship: description, technology, notes, navigateTo | Style `likec4Description`, `likec4Technology`, `likec4Notes`, `likec4NavigateTo` |
|
||
| Relationship: kind, notation | Style `likec4RelationshipKind`, `likec4Notation` |
|
||
| Relationship: links, metadata | Style `likec4Links` (JSON array), `likec4Metadata` (JSON object) |
|
||
| Relationship: head/tail, line style, color | Style `endArrow`, `startArrow`, `dashed`/`dashPattern`, `strokeColor`; label uses theme `relationships.label` for font color |
|
||
|
||
**Navigability (elements with their own view):** If an element has **navigateTo** set (in the model) to a view id, the exported Draw.io cell gets a **link** that opens the diagram tab with id `likec4-<viewId>`. So when you export all views (`--all-in-one`), clicking that element in the first diagram jumps to the tab of the view that shows the element’s internals. Set `navigateTo <viewId>` on the element in your LikeC4 model so the viewmodel provides it; the exporter then adds the link automatically.
|
||
|
||
Export uses the **project theme** (or LikeC4 default) for element and relationship colors; containers (bounded contexts) are drawn **behind** their children as a wrapping rectangle; edge **connection points** are derived from layout so arrows attach on the correct sides of nodes.
|
||
|
||
The generator accepts optional **options** for round-trip or tooling: `layoutOverride` (per-node bbox: `{ x, y, width, height }`, same shape as `@likec4/core` BBox), `strokeColorByNodeId`, `strokeWidthByNodeId`, `compressed`, and `profile` (default or leanix; when leanix, optionally `projectId`, `leanixFactSheetTypeByKind`). Set **`compressed: false`** to write diagram XML uncompressed inside the `.drawio` file (larger file; use when draw.io desktop or Open file / Import fails to open the default compressed export). The **Playground** exports uncompressed by default for maximum compatibility; the **CLI** uses compressed by default and supports `--uncompressed`.
|
||
|
||
### Not preserved on export
|
||
|
||
- View rules (include/exclude, style rules) — only the resulting nodes and edges are exported.
|
||
- Layout drifts / fine position tweaks — only final node position is written (or the provided `layoutOverride`).
|
||
- Theme color names — stored as hex in Draw.io; `likec4ColorName` keeps the name for round-trip.
|
||
|
||
## Multi-diagram (multiple tabs)
|
||
|
||
When **exporting**, each LikeC4 view is written to a **separate** `.drawio` file (one file per view). With `--all-in-one`, all views are written as tabs in a single file; combined with `--roundtrip`, layout, stroke colors/widths, and edge waypoints from the round-trip comment blocks in `.c4` source are applied per view in that file.
|
||
|
||
## Troubleshooting
|
||
|
||
**The exported .drawio file does not open in draw.io** (e.g. "Could not add object Array", blank diagram, or draw.io desktop / Open file / Import does nothing):
|
||
|
||
- **Playground:** Exports are now **uncompressed** by default so the file opens in draw.io desktop and via Open file / Import. Refresh the app and export again.
|
||
- **CLI:** Use **`--uncompressed`** so the diagram XML is written raw (no compression): `likec4 export drawio --uncompressed -o ./out`. This produces a larger file that opens reliably in draw.io desktop.
|
||
- If you still use compressed export, **update to the latest release** (older versions used an invalid XML structure for edge waypoints): `pnpm update @likec4/likec4` or `npm update @likec4/likec4`, then export again.
|
||
|
||
**The diagram shows fewer elements than expected:**
|
||
|
||
- Each view exports **exactly what you see** in that view. A top-level view (e.g. `view index { include * }`) typically shows only **top-level elements** (e.g. Customer and Our SaaS). Nested elements (e.g. Frontend, Backend inside the system) appear in the **element view** of that system (e.g. `view saas of saas { include * }`). To get all elements in Draw.io in one file, use **Export all views** (CLI: `--all-in-one`) and open the tab for the nested view, or switch to that view in the Playground before exporting.
|
||
|
||
## Re-export using comment blocks
|
||
|
||
When you export to Draw.io from the **Playground**, if the current workspace source contains Draw.io round-trip comment blocks (e.g. `// <likec4.layout.drawio>` … `// </likec4.layout.drawio>`), the exporter can apply layout, vertex stroke color/width, and edge waypoints from those comments so the generated `.drawio` file matches a previously edited diagram. The CLI supports `--roundtrip` for the same behavior. The generators API exposes `parseDrawioRoundtripComments(source)`, `generateDrawio(viewmodel, options)`, `generateDrawioMulti(viewmodels, optionsByViewId)`, and `GenerateDrawioOptions` (including `edgeWaypoints`, `layoutOverride`, `compressed`) for custom tooling.
|
||
|
||
# Editors
|
||
|
||
|
||
## VSCode
|
||
|
||
<div style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://marketplace.visualstudio.com/items?itemName=likec4.likec4-vscode" target="_blank"></a>
|
||
<a href="https://open-vsx.org/extension/likec4/likec4-vscode" target="_blank"></a>
|
||
</div>
|
||
|
||
LikeC4 has official [extension for VSCode](https://marketplace.visualstudio.com/items?itemName=likec4.likec4-vscode) - open-source and available on [GitHub](https://github.com/likec4/likec4/tree/main/packages/vscode).
|
||
The extension provides:
|
||
|
||
- Validation and error reporting
|
||
- Semantic syntax highlighting
|
||
- Live Previews (and editing)
|
||
- Code completion and navigation
|
||
- Resolve references (like `find all references`, `go to definition` .. )
|
||
- "Safe" renames
|
||
- Hover information
|
||
- [MCP Server](/tooling/ai-tools/#mcp-server)
|
||
|
||
Extension is universal and can run in the browser.
|
||
|
||
Try [example-cloud-system](https://github.dev/likec4/example-cloud-system) with:
|
||
|
||
<CardGrid>
|
||
<LinkCard
|
||
title="github.dev"
|
||
description="Open example project in the browser using github.dev"
|
||
href="https://github.dev/likec4/example-cloud-system/blob/main/model.c4"
|
||
target="_blank"
|
||
/>
|
||
|
||
<LinkCard
|
||
title="vscode.dev"
|
||
description="Open example project in the browser using vscode.dev"
|
||
href="https://vscode.dev/github/likec4/example-cloud-system/blob/main/model.c4"
|
||
target="_blank"
|
||
/>
|
||
|
||
<LinkCard
|
||
title="Stackblitz"
|
||
description="Open example project in the browser using Stackblitz"
|
||
href="https://stackblitz.com/~/github/likec4/example-cloud-system/"
|
||
target="_blank"
|
||
/>
|
||
</CardGrid>
|
||
|
||
## Standalone Language Server
|
||
|
||
For editors other than VSCode, install the standalone language server package:
|
||
|
||
```sh
|
||
npm install -g @likec4/lsp
|
||
```
|
||
|
||
This installs the `likec4-lsp` binary — a self-contained, fully-bundled language server with zero dependencies.
|
||
It auto-detects transport from command-line arguments: `--stdio`, `--node-ipc`, `--socket=<port>`, `--pipe=<name>`.
|
||
|
||
## Neovim
|
||
|
||
LikeC4 has a Neovim plugin for syntax highlighting and LSP integration with code navigation and completion.
|
||
|
||
The plugin supports:
|
||
- Auto start of the LikeC4 language server for files with .c4 extension
|
||
- Validation and error reporting
|
||
- Semantic syntax highlighting
|
||
- Code completion and navigation
|
||
- Resolve references (like `find all references`, `go to definition` .. )
|
||
- "Safe" renames (do not to forget to write your buffers)
|
||
- Hover information
|
||
- Live Previews (and editing)
|
||
|
||
likec4.nvim is available in a separate repository on GitHub - [likec4/likec4.nvim](https://github.com/likec4/likec4.nvim).
|
||
Installation:
|
||
|
||
```lua
|
||
{
|
||
'likec4/likec4.nvim',
|
||
build = 'npm install -g @likec4/lsp'
|
||
}
|
||
```
|
||
|
||
## Emacs
|
||
|
||
There's no MELPA package yet, but you can wire up the [`@likec4/lsp`](#standalone-language-server) standalone language server in a few lines.
|
||
|
||
First, define a major wrapper mode (derived from `prog-mode`) so the LSP client knows which buffers to attach to:
|
||
|
||
```elisp
|
||
;; Major mode for .c4 / .likec4 files
|
||
(define-derived-mode likec4-mode prog-mode "LikeC4"
|
||
"Major mode for LikeC4 architecture diagram files."
|
||
(setq-local comment-start "// ")
|
||
(setq-local comment-start-skip "//+ *")
|
||
(setq-local comment-end ""))
|
||
|
||
(add-to-list 'auto-mode-alist '("\\.c4\\'" . likec4-mode))
|
||
(add-to-list 'auto-mode-alist '("\\.likec4\\'" . likec4-mode))
|
||
```
|
||
|
||
Then pick one of the two LSP clients:
|
||
|
||
### Eglot (built-in since Emacs 29)
|
||
|
||
```elisp
|
||
(use-package eglot
|
||
:ensure t
|
||
:hook ((likec4-mode . eglot-ensure)))
|
||
|
||
(with-eval-after-load 'eglot
|
||
(add-to-list 'eglot-server-programs
|
||
'(likec4-mode . ("likec4-lsp" "--stdio"))))
|
||
```
|
||
|
||
### lsp-mode
|
||
|
||
```elisp
|
||
(use-package lsp-mode
|
||
:ensure t
|
||
:commands lsp
|
||
:hook ((likec4-mode . lsp))
|
||
:init (setq lsp-keymap-prefix "C-c l")
|
||
:config (setq lsp-enable-snippet t))
|
||
|
||
(with-eval-after-load 'lsp-mode
|
||
(add-to-list 'lsp-language-id-configuration
|
||
'(likec4-mode . "likec4"))
|
||
(lsp-register-client
|
||
(make-lsp-client
|
||
:new-connection (lsp-stdio-connection '("likec4-lsp" "--stdio"))
|
||
:activation-fn (lsp-activate-on "likec4")
|
||
:server-id 'likec4-lsp)))
|
||
```
|
||
|
||
Either approach gives you validation, semantic syntax highlighting, completion, go-to-definition, hover docs, and safe renames — same surface as the [Neovim plugin](#neovim).
|
||
|
||
Thanks to [@vincent067](https://github.com/vincent067) for the original setup in [#2268](https://github.com/likec4/likec4/issues/2268).
|
||
|
||
## Zed
|
||
|
||
LikeC4 has a community Zed extension: [zed-likec4](https://github.com/Lenivvenil/zed-likec4).
|
||
|
||
## JetBrains IDEs
|
||
|
||
LikeC4 has a JetBrains plugin for syntax highlighting and LSP integration with code navigation and completion.
|
||
|
||
The plugin is available in the [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/30490-likec4) and GitHub - [likec4/jetbrains-plugin](https://github.com/likec4/jetbrains-plugin).
|
||
|
||
# GitHub Actions
|
||
|
||
|
||

|
||
|
||
This action wraps [LikeC4 CLI](/tooling/cli) as a GitHub Action.
|
||
|
||
<Aside type='tip'>
|
||
Github repository [likec4/template](https://github.com/likec4/template) demonstrates how to deploy to github pages.
|
||
|
||
Also check [Deploying to GitHub Pages guide](/guides/deploy-github-pages/) for more details.
|
||
</Aside>
|
||
|
||
### Usage
|
||
|
||
#### Build website
|
||
|
||
```yaml
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: ⚙️ build
|
||
uses: likec4/actions@v1
|
||
with:
|
||
action: build
|
||
path: src/likec4
|
||
output: dist
|
||
base: /baseurl/
|
||
|
||
- name: upload artifacts
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: likec4
|
||
path: dist
|
||
```
|
||
|
||
|
||
#### Export diagrams to PNG
|
||
|
||
```yaml
|
||
steps:
|
||
- name: export diagrams
|
||
uses: likec4/actions@v1
|
||
with:
|
||
export: png
|
||
path: src/likec4
|
||
output: out/images
|
||
use-dot-bin: 'true'
|
||
```
|
||
|
||
#### Code generation
|
||
|
||
```yaml
|
||
steps:
|
||
- name: code generation
|
||
uses: likec4/actions@v1
|
||
with:
|
||
codegen: react
|
||
output: __generated__/likec4.jsx
|
||
```
|
||
|
||
### Inputs
|
||
|
||
| Name | Description |
|
||
| ------------- | ----------------------------------------------------------------------------------------------------- |
|
||
| `action` | Action to perform (`build` / `export` / `codegen`) |
|
||
| `export` | Can be used instead of `action: export` |
|
||
| `codegen` | Can be used instead of `action: codegen`, same values as in [cli](https://likec4.dev/docs/tools/cli/) |
|
||
| `path` | Path in repository to likec4 sources, root otherwise |
|
||
| `output` | Output directory/file |
|
||
| `base` | Custom baseUrl for website |
|
||
| `use-dot-bin` | if `'true'` will use `dot` binary of graphviz |
|
||
|
||
> All inputs are optional.
|
||
> By default CLI builds a website to `dist` directory.
|
||
|
||
# LikeC4 API
|
||
|
||
import { PackageManagers } from 'starlight-package-managers';
|
||
|
||
You can access and traverse your architecture model programmatically using the LikeC4 Model API.
|
||
|
||
<Aside type='note'>
|
||
API allows to query and traverse the model from DSL, but not modify or create a new one.
|
||
</Aside>
|
||
|
||
Ensure you have `likec4` in your dependencies:
|
||
|
||
<PackageManagers
|
||
pkg="likec4"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
## Usage
|
||
|
||
You can initiate LikeC4 API from a directory with source files or from a string with DSL source.
|
||
|
||
### From workspace
|
||
|
||
Recursively search and parse source files:
|
||
|
||
```ts
|
||
import { LikeC4 } from 'likec4'
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('/path/to/workspace')
|
||
```
|
||
|
||
Method also accepts options:
|
||
|
||
| Property | Description |
|
||
| -----------------| --------------------------------------------------------------------------------------------------- |
|
||
| `printErrors` | if model is invalid, errors are reported to the logger (default `true`) |
|
||
| <span style="text-wrap:nowrap">`throwIfInvalid`</span> | return rejected promise if model is invalid (default `false`) |
|
||
| `logger` | Whenever to use `default` (console), `vite` logger or your custom implementation <br/> Disable with `false` |
|
||
| `graphviz` | `wasm` (default) or `binary` - use local binaries of Graphviz ("dot") or bundled WASM |
|
||
| `watch` | Whether to watch for changes in the workspace. (default `false`) |
|
||
| `mcp` | Whether to start MCP server.<br/> - `false` - do not start MCP server (default) <br/> - `"stdio"` - use stdio transport,<br/> - `{"port": number}` - use http transport on specified port |
|
||
|
||
:::tip
|
||
Take a look at [Validate your model](/guides/validate-your-model) for examples of how to enforce custom rules on your model with the LikeC4 API
|
||
:::
|
||
|
||
### From source
|
||
|
||
Parse from the string:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromSource(`
|
||
specification {
|
||
element system
|
||
element user
|
||
}
|
||
model {
|
||
customer = user 'Customer'
|
||
cloud = system 'System'
|
||
}
|
||
views {
|
||
view index {
|
||
include *
|
||
}
|
||
}
|
||
`)
|
||
```
|
||
|
||
### Dispose
|
||
|
||
If you initialized LikeC4 with `watch` mode or enabled MCP server, you should dispose it:
|
||
|
||
```ts
|
||
import { LikeC4 } from 'likec4'
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('/path/to/workspace', {
|
||
watch: true,
|
||
mcp: { port: 33335 },
|
||
})
|
||
|
||
// Cleanup resources
|
||
await likec4.dispose()
|
||
```
|
||
|
||
LikeC4 is automatically disposed with `await using` declaration:
|
||
|
||
```ts
|
||
import { LikeC4 } from 'likec4'
|
||
|
||
async function() {
|
||
await using likec4 = await LikeC4.fromWorkspace('/path/to/workspace', {
|
||
watch: true,
|
||
mcp: { port: 33335 },
|
||
})
|
||
|
||
// ...
|
||
// likec4 is disposed automatically
|
||
}
|
||
```
|
||
|
||
## API
|
||
|
||
When the model is initialized, you can use the following methods to query and traverse it.
|
||
|
||
Two types of model (with similar API):
|
||
|
||
- **LikeC4Model.Computed** - includes computed views (from predicates), fast, synchronous, enough to traverse but not ready for rendering.
|
||
- **LikeC4Model.Layouted** - extends computed model with layout data (dimensions, positions), that is needed for rendering.
|
||
|
||
:::tip
|
||
Low-level API is available from [`@likec4/core`](https://github.com/likec4/likec4/blob/main/packages/core/README.md).
|
||
:::
|
||
|
||
### Example
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromSource(`....`)
|
||
|
||
// Validation errors
|
||
console.log(likec4.getErrors())
|
||
|
||
// Traverse the model
|
||
const model = likec4.computedModel()
|
||
|
||
// Get elements of some kind
|
||
const elements = model.elementsOfKind('kind1')
|
||
|
||
// Use where operator to filter elements:
|
||
// kind is 'kind1' and (tag is 'tag2' or tag is not 'tag3')
|
||
const elements = model.elementsWhere({
|
||
and: [
|
||
{ kind: 'kind1' },
|
||
{
|
||
or: [
|
||
{ tag: 'tag2' },
|
||
{
|
||
tag: {
|
||
neq: 'tag3',
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
})
|
||
|
||
// Get views that include the element
|
||
model
|
||
.element('cloud.backend.api')
|
||
.views()
|
||
|
||
// Get source elements of incoming relationships (filter by tags)
|
||
model
|
||
.element('cloud.backend.api')
|
||
.incoming() // relationships incoming to the element
|
||
.filter(r => r.isTagged('http')) // filter by tags
|
||
.map(r => r.source) // get source elements
|
||
|
||
```
|
||
|
||
To get layouted model:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromSource(`....`)
|
||
|
||
const model = await likec4.layoutedModel()
|
||
|
||
const diagram = model.view('index')
|
||
|
||
// Working with metadata (including array values)
|
||
const element = model.element('cloud.backend.api')
|
||
|
||
// Get all metadata
|
||
const allMetadata = element.getMetadata()
|
||
console.log(allMetadata)
|
||
// Output: { version: '3.2.1', tags: ['backend', 'gateway'], regions: ['us-east-1', 'eu-west-1'] }
|
||
|
||
// Get specific metadata field (could be string or string[])
|
||
const tags = element.getMetadata('tags')
|
||
if (Array.isArray(tags)) {
|
||
console.log(`Element has ${tags.length} tags: ${tags.join(', ')}`)
|
||
} else if (tags) {
|
||
console.log(`Element has single tag: ${tags}`)
|
||
}
|
||
|
||
// Filter elements by array metadata values
|
||
const elementsWithBackendTag = model.elements().filter(el => {
|
||
const tags = el.getMetadata('tags')
|
||
return Array.isArray(tags)
|
||
? tags.includes('backend')
|
||
: tags === 'backend'
|
||
})
|
||
|
||
```
|
||
|
||
### LikeC4Model
|
||
|
||
:::tip
|
||
It is possible to generate Typed API from your model, see [Code generation](/tooling/code-generation/model/)
|
||
:::
|
||
|
||
Model API provides methods to query and traverse the whole model.
|
||
|
||
```ts
|
||
interface LikeC4Model {
|
||
/**
|
||
* Returns the root elements of the model.
|
||
*/
|
||
roots(): Element[];
|
||
/**
|
||
* Returns all elements in the model.
|
||
*/
|
||
elements(): Element[];
|
||
/**
|
||
* Returns a specific element by its FQN.
|
||
*/
|
||
element(id: Fqn): Element;
|
||
/**
|
||
* Returns all relationships in the model.
|
||
*/
|
||
relationships(): Relationship[];
|
||
/**
|
||
* Returns a specific relationship by its ID.
|
||
*/
|
||
relationship(id: RelationID): Relationship;
|
||
/**
|
||
* Returns all views in the model.
|
||
*/
|
||
views(): ReadonlyArray<LikeC4ViewModel>;
|
||
/**
|
||
* Returns a specific view by its ID.
|
||
*/
|
||
view(viewId: ViewID): LikeC4ViewModel;
|
||
/**
|
||
* Returns the parent element of given element.
|
||
* @see ancestors
|
||
*/
|
||
parent(element: ElementOrFqn): Element | null;
|
||
/**
|
||
* Get all children of the element (only direct children),
|
||
* @see descendants
|
||
*/
|
||
children(element: ElementOrFqn): Element[];
|
||
/**
|
||
* Get all sibling (i.e. same parent)
|
||
*/
|
||
siblings(element: ElementOrFqn): Element[];
|
||
/**
|
||
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
||
* (from closest to root)
|
||
*/
|
||
ancestors(element: ElementOrFqn): Element[];
|
||
/**
|
||
* Get all descendant elements (i.e. children, children’s children, etc.)
|
||
*/
|
||
descendants(element: ElementOrFqn): Element[];
|
||
/**
|
||
* Incoming relationships to the element and its descendants
|
||
* @see incomers
|
||
*/
|
||
incoming(element: ElementOrFqn, filter?: 'all' | 'direct' | 'to-descendants'): Relationship[];
|
||
/**
|
||
* Source elements of incoming relationships
|
||
*/
|
||
incomers(element: ElementOrFqn, filter?: 'all' | 'direct' | 'to-descendants'): Element[];
|
||
/**
|
||
* Outgoing relationships from the element and its descendants
|
||
* @see outgoers
|
||
*/
|
||
outgoing(element: ElementOrFqn, filter?: 'all' | 'direct' | 'from-descendants'): Relationship[];
|
||
/**
|
||
* Target elements of outgoing relationships
|
||
*/
|
||
outgoers(element: ElementOrFqn, filter?: 'all' | 'direct' | 'from-descendants'): Element[];
|
||
}
|
||
```
|
||
|
||
Check sources for methods - [LikeC4Model](https://github.com/likec4/likec4/blob/main/packages/core/src/model/LikeC4Model.ts)
|
||
|
||
### Working with Element Metadata
|
||
|
||
Elements can have metadata with both single string values and string arrays. The API provides convenient methods to access and work with this metadata:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromSource(`
|
||
specification {
|
||
element service
|
||
element application
|
||
}
|
||
model {
|
||
api = service 'API Gateway' {
|
||
metadata {
|
||
version '3.2.1'
|
||
maintainer 'Platform Team'
|
||
tags ['backend', 'gateway', 'microservice']
|
||
regions ['us-east-1', 'eu-west-1']
|
||
critical true
|
||
}
|
||
}
|
||
|
||
frontend = application 'Frontend' {
|
||
metadata {
|
||
framework 'React'
|
||
features ['auth', 'dashboard', 'reports']
|
||
team_members ['alice', 'bob', 'carol']
|
||
release_branch 'main'
|
||
}
|
||
}
|
||
}
|
||
`)
|
||
|
||
const model = likec4.computedModel()
|
||
|
||
// Get element and access metadata
|
||
const api = model.element('api')
|
||
|
||
// Check if element has any metadata
|
||
if (api.hasMetadata()) {
|
||
console.log('API has metadata')
|
||
|
||
// Get all metadata as an object
|
||
const metadata = api.getMetadata()
|
||
console.log('All metadata:', metadata)
|
||
|
||
// Get specific metadata fields
|
||
const version = api.getMetadata('version') // string: '3.2.1'
|
||
const tags = api.getMetadata('tags') // string[]: ['backend', 'gateway', 'microservice']
|
||
const regions = api.getMetadata('regions') // string[]: ['us-east-1', 'eu-west-1']
|
||
|
||
// Handle array metadata values
|
||
if (Array.isArray(tags)) {
|
||
console.log(`API has ${tags.length} tags:`)
|
||
tags.forEach(tag => console.log(` - ${tag}`))
|
||
|
||
// Check if specific value exists in array
|
||
if (tags.includes('backend')) {
|
||
console.log('API is tagged as backend service')
|
||
}
|
||
}
|
||
|
||
// Handle mixed metadata types
|
||
const handleMetadataValue = (key: string, value: string | string[] | undefined) => {
|
||
if (Array.isArray(value)) {
|
||
return `${key}: [${value.join(', ')}]`
|
||
} else if (value) {
|
||
return `${key}: ${value}`
|
||
}
|
||
return `${key}: undefined`
|
||
}
|
||
|
||
console.log(handleMetadataValue('version', version))
|
||
console.log(handleMetadataValue('tags', tags))
|
||
console.log(handleMetadataValue('regions', regions))
|
||
}
|
||
|
||
// Advanced filtering using metadata
|
||
const backendServices = model.elements()
|
||
.filter(element => {
|
||
const tags = element.getMetadata('tags')
|
||
return Array.isArray(tags) ? tags.includes('backend') : tags === 'backend'
|
||
})
|
||
|
||
const multiRegionServices = model.elements()
|
||
.filter(element => {
|
||
const regions = element.getMetadata('regions')
|
||
return Array.isArray(regions) && regions.length > 1
|
||
})
|
||
|
||
// Group elements by metadata values
|
||
const elementsByFramework = new Map<string, typeof model.elements>()
|
||
for (const element of model.elements()) {
|
||
const framework = element.getMetadata('framework')
|
||
if (typeof framework === 'string') {
|
||
if (!elementsByFramework.has(framework)) {
|
||
elementsByFramework.set(framework, [])
|
||
}
|
||
elementsByFramework.get(framework)!.push(element)
|
||
}
|
||
}
|
||
|
||
// Collect all unique tags from all elements
|
||
const allTags = new Set<string>()
|
||
for (const element of model.elements()) {
|
||
const tags = element.getMetadata('tags')
|
||
if (Array.isArray(tags)) {
|
||
tags.forEach(tag => allTags.add(tag))
|
||
} else if (typeof tags === 'string') {
|
||
allTags.add(tags)
|
||
}
|
||
}
|
||
console.log('All unique tags:', Array.from(allTags).sort())
|
||
```
|
||
|
||
:::note[Metadata Merging with Extends]
|
||
When using [`extend`](/dsl/extend/) to add metadata to elements, duplicate metadata keys are automatically merged:
|
||
|
||
- String + String = Array (if different values)
|
||
- String + Array = Array (merged)
|
||
- Array + Array = Array (merged)
|
||
- Duplicate values are automatically de-duplicated
|
||
- Single-value arrays are converted back to strings
|
||
|
||
This allows you to progressively build up metadata across multiple files. See the [extend documentation](/dsl/extend/#metadata-merging) for more details.
|
||
:::
|
||
|
||
### LikeC4DeploymentModel
|
||
|
||
API provides methods to query and traverse deployment model.
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromSource(`....`)
|
||
const model = likec4.computedModel()
|
||
|
||
// Get deployment model
|
||
const deployment = model.deployment
|
||
|
||
// Get elements of some kind
|
||
for (const instance of deployment.instancesOf('cloud.backend.api')) {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### LikeC4ViewModel
|
||
|
||
View model API provides methods to query and traverse elements and relationships that are included in the view.
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromSource(`....`)
|
||
const model = likec4.computedModel()
|
||
|
||
for (const view of model.views()) {
|
||
if (view.isDynamicView()) {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
## Model Builder
|
||
|
||
Type-safe builder available from `@likec4/core/builder` (and `likec4/model/builder`).
|
||
Builder can be used to create model programmatically and supports two styles:
|
||
|
||
<Tabs>
|
||
<Tab label='Chain'>
|
||
```ts
|
||
import { Builder } from "@likec4/core/builder"
|
||
|
||
const m = Builder
|
||
.specification({
|
||
elements: {
|
||
actor: {
|
||
style: {
|
||
shape: 'person',
|
||
},
|
||
},
|
||
system: {},
|
||
component: {},
|
||
},
|
||
relationships: {
|
||
likes: {},
|
||
},
|
||
tags: ['tag1', 'tag2', 'tag1'],
|
||
})
|
||
.model(({ actor, system, component, relTo, rel }, _) =>
|
||
_(
|
||
actor('alice'),
|
||
actor('bob'),
|
||
rel('alice', 'bob', {
|
||
tags: ['tag1'], // you get code completion for tags
|
||
kind: 'likes', // code completion for kind
|
||
}),
|
||
system('cloud', { tags: ['tag1', 'tag2'] }).with(
|
||
component('backend').with(
|
||
component('api'),
|
||
component('db'),
|
||
// code completion for relationships
|
||
rel('cloud.backend.api', 'cloud.backend.db')
|
||
),
|
||
component('frontend').with(
|
||
relTo('cloud.backend.api')
|
||
),
|
||
),
|
||
)
|
||
)
|
||
.views(({ view, viewOf, $include, $style }, _) =>
|
||
_(
|
||
view('index', 'Index').with(
|
||
$include('cloud.*'),
|
||
),
|
||
viewOf('ui', 'cloud.ui').with(
|
||
// code completion for predicates
|
||
$include('* -> cloud.**'),
|
||
$style('cloud.ui', { color: 'red' }),
|
||
),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
```
|
||
</Tab>
|
||
|
||
<Tab label='Composition'>
|
||
|
||
```ts
|
||
import { Builder } from "@likec4/core/builder"
|
||
|
||
// Get composition functions for given specification
|
||
const {
|
||
model: {
|
||
model,
|
||
actor,
|
||
system,
|
||
component,
|
||
rel,
|
||
relTo,
|
||
},
|
||
views: {
|
||
view,
|
||
viewOf,
|
||
views,
|
||
$include,
|
||
$style,
|
||
},
|
||
builder,
|
||
} = Builder.forSpecification({
|
||
elements: {
|
||
actor: {
|
||
style: {
|
||
shape: 'person',
|
||
},
|
||
},
|
||
system: {},
|
||
component: {},
|
||
},
|
||
relationships: {
|
||
likes: {},
|
||
},
|
||
tags: ['tag1', 'tag2', 'tag1'],
|
||
})
|
||
|
||
const b1 = builder.with(
|
||
model(
|
||
actor('alice'),
|
||
actor('bob'),
|
||
rel('alice', 'bob', {
|
||
tags: ['tag1'],
|
||
kind: 'likes',
|
||
}),
|
||
system('cloud', { tags: ['tag1', 'tag2'] }).with(
|
||
component('backend').with(
|
||
component('api'),
|
||
component('db'),
|
||
rel('cloud.backend.api', 'cloud.backend.db')
|
||
),
|
||
component('frontend').with(
|
||
relTo('cloud.backend.api')
|
||
),
|
||
),
|
||
)
|
||
)
|
||
|
||
const b2 = b1.with(
|
||
views(
|
||
view('index', 'Index').with(
|
||
$include('cloud.*'),
|
||
),
|
||
viewOf('ui', 'cloud.ui').with(
|
||
$include('* -> cloud.**'),
|
||
$style('cloud.ui', { color: 'red' }),
|
||
),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
```
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
You can mix both styles, depending on your preference and use cases.
|
||
|
||
:::tip
|
||
Check unit tests in our repository for examples:
|
||
- <a href="https://github.com/likec4/likec4/blob/main/packages/core/src/builder/Builder-style1.spec.ts">Builder-style1</a>
|
||
- <a href="https://github.com/likec4/likec4/blob/main/packages/core/src/builder/Builder-style2.spec.ts">Builder-style2</a>
|
||
:::
|
||
|
||
## Enriching a loaded workspace
|
||
|
||
You can load an existing LikeC4 workspace, hand the parsed model back to the [Model Builder](#model-builder),
|
||
add elements/relations/views programmatically, and either keep working with the resulting model in memory
|
||
or emit DSL source back to disk.
|
||
|
||
Typical use cases:
|
||
|
||
- Overlay infrastructure pulled from Terraform state, AWS or Kubernetes onto a hand-written model.
|
||
- Merge multiple teams' models and add cross-team relationships.
|
||
- Generate views by convention (one view per team, per environment, etc.).
|
||
- Patch a model from an external service catalogue and re-emit `.c4` source files.
|
||
|
||
<Aside type='note'>
|
||
The DSL round-trip (`fromWorkspace` → `toDSL` → `fromSource`) is intentionally **lossy** —
|
||
comments, source positions and original formatting are not preserved. Treat `writeDSL` as
|
||
one-way generation, not a formatter.
|
||
</Aside>
|
||
|
||
### Reading the parsed model
|
||
|
||
`likec4.parsedModel(project?)` returns the parsed-stage model (elements, relations, views, deployments,
|
||
globals and imports) *before* view computation:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('./')
|
||
const parsed = await likec4.parsedModel()
|
||
|
||
// element/relation lookups work the same as on computed models
|
||
console.log(parsed.element('cloud.frontend').kind)
|
||
console.log(Object.keys(parsed.$data.views))
|
||
```
|
||
|
||
### `LikeC4.toBuilder()` — get a Builder seeded from disk
|
||
|
||
`likec4.toBuilder(mode?, project?)` returns a [Model Builder](#model-builder) pre-populated with the
|
||
loaded workspace. You can then chain `.model(...)`, `.deployment(...)` and `.views(...)` to add new
|
||
content, and call `.toLikeC4Model()` (or `.build()`) to get the enriched model.
|
||
|
||
The builder is **editable** by default — re-declaring an element that already exists (same FQN, same
|
||
kind) edits it in place rather than throwing, which is what you usually want when patching a loaded
|
||
model. Pass `'strict'` to get the strict builder where duplicate FQNs always throw:
|
||
|
||
```ts
|
||
const editable = await likec4.toBuilder() // editable (default)
|
||
const strict = await likec4.toBuilder('strict') // strict
|
||
```
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('./')
|
||
|
||
const enriched = (await likec4.toBuilder())
|
||
.model(({ system, component }, _) =>
|
||
_(
|
||
system('monitoring').with(
|
||
component('grafana'),
|
||
component('prometheus'),
|
||
),
|
||
)
|
||
)
|
||
.views(({ view, $include }, _) =>
|
||
_(
|
||
view('monitoring', 'Monitoring').with(
|
||
$include('monitoring.*'),
|
||
),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
```
|
||
|
||
:::note
|
||
The helper names (`system`, `component`, …) are the **element kinds from the loaded specification** — one
|
||
helper per `element <kind>` declared in your DSL. When seeding from disk these are only known at runtime,
|
||
so `toBuilder()` returns a loosely-typed builder. To get them statically (with autocomplete and a check
|
||
that they match the spec), use [`toTypedBuilder({ specification })`](#likec4totypedbuilder--get-a-typed-builder).
|
||
:::
|
||
|
||
The same seeding entry is also available directly on the Builder for when you already have the parsed
|
||
data in hand:
|
||
|
||
```ts
|
||
import { Builder } from "@likec4/core/builder"
|
||
|
||
const builder = Builder.fromParsed(parsedData)
|
||
```
|
||
|
||
### Editable vs strict — editing seeded elements
|
||
|
||
`toBuilder()` is editable by default, so you can *edit* seeded entries (for example, to update a title
|
||
or merge in new children) just by re-declaring them:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('./')
|
||
|
||
const updated = (await likec4.toBuilder())
|
||
.model(({ component }, _) =>
|
||
_(
|
||
component('cloud.api', { title: 'Updated' }),
|
||
component('cloud.ui').with(
|
||
component('react'),
|
||
),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
```
|
||
|
||
In editable mode, same-FQN + same-kind redeclaration replaces the element's properties. Redeclaring
|
||
with a *different kind* still throws (it's almost certainly a programmer error). If you want
|
||
re-declaring any existing FQN to throw, use the strict builder via `await likec4.toBuilder('strict')`
|
||
(or `Builder.fromParsed(data, 'strict')`).
|
||
|
||
:::caution
|
||
Editing **replaces**, it does not **merge**. Re-declaring an element overwrites it with exactly the
|
||
properties you pass — any property you omit falls back to its default, it is *not* carried over from the
|
||
loaded element. To patch one property while keeping the rest, read the existing element first and spread
|
||
the properties you want to preserve (see [Finding and patching existing elements](#finding-and-patching-existing-elements)).
|
||
:::
|
||
|
||
### Relating new elements to existing ones
|
||
|
||
A common task is adding a new element and connecting it to something already in the DSL. Existing FQNs
|
||
aren't in the static type set, but you can reference them as plain strings — the relationship is
|
||
validated against the seeded model when you build:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('./')
|
||
|
||
const enriched = (await likec4.toBuilder())
|
||
.model(({ system, relTo }, _) =>
|
||
_(
|
||
system('monitoring').with(
|
||
// `cloud.backend` is an existing element loaded from the DSL
|
||
relTo('cloud.backend', 'observes'),
|
||
),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
|
||
// or, equivalently, from the top level via `rel`:
|
||
// .model(({ system, rel }, _) =>
|
||
// _(
|
||
// system('monitoring'),
|
||
// rel('monitoring', 'cloud.backend', 'observes'),
|
||
// )
|
||
// )
|
||
```
|
||
|
||
Referencing an FQN that doesn't exist throws at build time, so typos surface immediately even though the
|
||
compiler can't catch them.
|
||
|
||
### Finding and patching existing elements
|
||
|
||
To discover what's already in the model, walk it through the [LikeC4Model](#likec4model) returned by
|
||
`computedModel()` (or inspect the parsed shape via `parsedModel()`):
|
||
|
||
```ts
|
||
const model = await likec4.computedModel()
|
||
for (const element of model.elements()) {
|
||
console.log(element.id, element.kind, element.title)
|
||
}
|
||
```
|
||
|
||
Because editing **replaces** rather than merges (see the note above), patching a single property means
|
||
reading the existing element and passing back the values you want to keep alongside your change:
|
||
|
||
```ts
|
||
const existing = (await likec4.parsedModel()).element('customer')
|
||
|
||
const updated = (await likec4.toBuilder())
|
||
.model(({ actor }, _) =>
|
||
_(
|
||
actor('customer', {
|
||
title: existing.title, // keep what you are not changing
|
||
metadata: { team: 'platform' }, // add / patch
|
||
}),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
```
|
||
|
||
### `Builder.specification()` — add new kinds and tags
|
||
|
||
The instance method `.specification(spec)` extends an existing builder with extra element / deployment /
|
||
relationship kinds, tags and metadata keys. Existing keys with the same name are overridden; new keys
|
||
are added. Useful when the workspace's specification is a starting point and you want to introduce
|
||
overlay-specific kinds:
|
||
|
||
```ts
|
||
import { LikeC4 } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('./')
|
||
|
||
const model = (await likec4.toBuilder())
|
||
.specification({
|
||
elements: {
|
||
database: { style: { shape: 'storage' } },
|
||
},
|
||
tags: {
|
||
experimental: { color: '#f00' },
|
||
},
|
||
})
|
||
.model(({ database }, _) =>
|
||
_(database('warehouse', { tags: ['experimental'] })),
|
||
)
|
||
.toLikeC4Model()
|
||
```
|
||
|
||
The returned builder's types are the **union** of the prior types and the newly-declared spec, so
|
||
element-kind helpers, tags and metadata keys accumulate across chained `.specification(...)` calls
|
||
instead of being narrowed.
|
||
|
||
### `LikeC4.toDSL()` and `writeDSL()` — render DSL back
|
||
|
||
`likec4.toDSL(project?)` renders the parsed model back to LikeC4 DSL source. To write that source to
|
||
disk, use the Node-only `writeDSL` helper exported from `likec4`:
|
||
|
||
```ts
|
||
import { LikeC4, writeDSL } from "likec4"
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('./')
|
||
|
||
// Render to string
|
||
const dsl = await likec4.toDSL()
|
||
|
||
// Or write to a file (default: ${targetDir}/model.c4)
|
||
const outPath = await writeDSL(likec4, './generated')
|
||
console.log(`Wrote DSL to ${outPath}`)
|
||
|
||
// Custom filename
|
||
await writeDSL(likec4, './generated', { fileName: 'snapshot.c4' })
|
||
```
|
||
|
||
You can combine the pieces above to load, enrich, and re-emit in a single pipeline:
|
||
|
||
```ts
|
||
const enriched = (await likec4.toBuilder())
|
||
.specification({ tags: { autogenerated: { color: '#999' } } })
|
||
.model(({ component }, _) =>
|
||
_(component('cloud.new-service', { tags: ['autogenerated'] })),
|
||
)
|
||
|
||
const data = enriched.build()
|
||
const dsl = generateLikeC4(data) // from @likec4/generators/likec4
|
||
await writeFile('./generated/model.c4', dsl) // or use writeDSL on the LikeC4 instance
|
||
```
|
||
|
||
### `LikeC4.toTypedBuilder()` — get a *typed* Builder
|
||
|
||
When the parsed model comes from a workspace, element kinds and FQNs are only known at runtime — so the
|
||
builder returned by `LikeC4.toBuilder()` has loose types (`Builder<AnyTypes>`). If your code authors the
|
||
model with the **same specification** as the DSL, pass that spec to `toTypedBuilder` and get a builder
|
||
typed by it — with autocomplete on element/deployment/relationship kinds, tags and metadata keys:
|
||
|
||
```ts
|
||
const builder = await likec4.toTypedBuilder({
|
||
specification: {
|
||
elements: ['actor', 'system', 'component'],
|
||
tags: ['external'],
|
||
},
|
||
})
|
||
|
||
// `system` / `component` are statically known — no cast needed.
|
||
builder.model(({ system, component }, _) =>
|
||
_(system('monitoring').with(component('grafana'))),
|
||
)
|
||
```
|
||
|
||
The given specification is **validated** against the loaded model (subset semantics): every kind / tag /
|
||
metadata key you declare must exist in the loaded model, otherwise the call throws — so the types can't
|
||
silently drift away from the DSL. The loaded model may contain *extra* kinds you didn't declare; you
|
||
just won't get typed helpers for them. `mode` defaults to `'editable'`, same as `toBuilder`.
|
||
|
||
:::caution
|
||
**Existing FQNs stay untyped.** `toTypedBuilder` types the *kinds*, not the FQNs — `findElement('cloud.api')`
|
||
and cross-references like `rel('cloud.api', 'cloud.ui')` are still only validated at builder-mutation
|
||
time, not by the compiler.
|
||
|
||
If you can't (or don't want to) pass a spec, the escape hatch is still an explicit cast on `toBuilder()`:
|
||
|
||
```ts
|
||
import type { Builder, Types } from "@likec4/core/builder"
|
||
|
||
type MySpec = { elements: ['actor', 'system', 'component'] }
|
||
const builder = (await likec4.toBuilder()) as unknown as Builder<Types.FromSpecification<MySpec>>
|
||
```
|
||
:::
|
||
|
||
:::tip
|
||
For working examples, see
|
||
[`Builder.fromParsed.spec.ts`](https://github.com/likec4/likec4/blob/main/packages/core/src/builder/Builder.fromParsed.spec.ts)
|
||
and
|
||
[`LikeC4.toBuilder.spec.ts`](https://github.com/likec4/likec4/blob/main/packages/language-services/src/__tests__/LikeC4.toBuilder.spec.ts).
|
||
:::
|
||
|
||
# React Components
|
||
|
||
import { PackageManagers } from 'starlight-package-managers'
|
||
|
||
<p style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://www.npmjs.com/package/%40likec4%2Fdiagram" target="_blank"></a>
|
||
</p>
|
||
|
||
The LikeC4 React library is available to embed diagrams into your applications.
|
||
Although you can use it directly, consider [Vite Plugin](/tooling/vite-plugin/)
|
||
or [CLI](/tooling/code-generation/react/) for smoother developer experience.
|
||
|
||
<AdvancedCustomizationTip />
|
||
|
||
## Usage
|
||
|
||
You must have `react` and `react-dom` installed.
|
||
Add [`@likec4/core`](https://www.npmjs.com/package/%40likec4%2Fcore) and [`@likec4/diagram`](https://www.npmjs.com/package/%40likec4%2Fdiagram):
|
||
|
||
<PackageManagers
|
||
pkg="@likec4/core @likec4/diagram"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
|
||
## Keyboard shortcuts
|
||
|
||
Focused diagrams support keyboard zoom shortcuts. Click a diagram or tab into it first; while the diagram is focused,
|
||
these shortcuts control diagram zoom instead of browser page zoom. Tab out of the diagram to use browser page zoom.
|
||
|
||
| Shortcut | Action |
|
||
| --- | --- |
|
||
| `Ctrl`/`Cmd` + `+` or `=` | Zoom in |
|
||
| `Ctrl`/`Cmd` + `-` or `_` | Zoom out |
|
||
| `Ctrl`/`Cmd` + `0` | Reset the viewport |
|
||
|
||
LikeC4 React library can be used in two ways.
|
||
|
||
|
||
### Bundled
|
||
|
||
This is the easiest way to use the library.
|
||
The diagram renders inside the shadow DOM, includes all dependencies, and handles styling.
|
||
|
||
#### LikeC4ModelProvider
|
||
|
||
The diagram requires an instance of `LikeC4Model.Layouted` to render.
|
||
You need to prepare it and wrap your diagram with the `LikeC4ModelProvider` component.
|
||
Below are examples of how to prepare the model:
|
||
- Using CLI codegen
|
||
- Using Source files
|
||
- Using Model Builder
|
||
|
||
<br />
|
||
|
||
|
||
<Tabs>
|
||
<Tab label='CLI Codegen'>
|
||
Prepare model with [code generation](/tooling/code-generation/model/):
|
||
|
||
```sh
|
||
likec4 codegen model --outfile ./likec4-model.ts
|
||
```
|
||
|
||
Then:
|
||
|
||
```tsx copy
|
||
import { LikeC4ModelProvider } from '@likec4/diagram/bundle'
|
||
// import model from generated file
|
||
import { likec4model } from './likec4-model.ts'
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
{/* ... */}
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
</Tab>
|
||
|
||
<Tab label='From Sources'>
|
||
It's possible to prepare the model from a string. See [API usage](/tooling/model-api/#usage):
|
||
```tsx copy
|
||
import { LikeC4 } from 'likec4'
|
||
import { LikeC4ModelProvider } from 'likec4/react'
|
||
|
||
const likec4 = await LikeC4.fromWorkspace('/path/to/workspace')
|
||
const likec4model = await likec4.layoutedModel()
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
{/* ... */}
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
</Tab>
|
||
|
||
<Tab label='Model Builder'>
|
||
You can prepare the model with the [Builder](/tooling/model-api/#model-builder), then lay it out with `layoutLikeC4Model`:
|
||
|
||
```tsx copy collapse={7-52}
|
||
import { LikeC4ModelProvider } from '@likec4/diagram/bundle'
|
||
import { Builder } from "@likec4/core/builder"
|
||
import { layoutLikeC4Model } from "@likec4/layouts"
|
||
|
||
const computedModel = Builder
|
||
.specification({
|
||
elements: {
|
||
actor: {
|
||
style: {
|
||
shape: 'person',
|
||
},
|
||
},
|
||
system: {},
|
||
component: {},
|
||
},
|
||
relationships: {
|
||
likes: {},
|
||
},
|
||
tags: ['tag1', 'tag2', 'tag1'],
|
||
})
|
||
.model(({ actor, system, component, relTo, rel }, _) =>
|
||
_(
|
||
actor('alice'),
|
||
actor('bob'),
|
||
rel('alice', 'bob', {
|
||
tags: ['tag1'], // you get code completion for tags
|
||
kind: 'likes', // code completion for kind
|
||
}),
|
||
system('cloud', { tags: ['tag1', 'tag2'] }).with(
|
||
component('backend').with(
|
||
component('api'),
|
||
component('db'),
|
||
// code completion for relationships
|
||
rel('cloud.backend.api', 'cloud.backend.db')
|
||
),
|
||
component('frontend').with(
|
||
relTo('cloud.backend.api')
|
||
),
|
||
),
|
||
)
|
||
)
|
||
.views(({ view, viewOf, $include, $style }, _) =>
|
||
_(
|
||
view('index', 'Index').with(
|
||
$include('cloud.*'),
|
||
),
|
||
viewOf('ui', 'cloud.ui').with(
|
||
$include('* -> cloud.**'),
|
||
$style('cloud.ui', { color: 'red' }),
|
||
),
|
||
)
|
||
)
|
||
.toLikeC4Model()
|
||
|
||
// The Builder returns a computed model. To render it, you need to lay it out
|
||
const likec4model = await layoutLikeC4Model(computedModel)
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
{/* ... */}
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
</Tab>
|
||
|
||
|
||
</Tabs>
|
||
|
||
:::tip
|
||
If you have `likec4` as a dependency, you can use:
|
||
- `likec4/react` instead of `@likec4/diagram/bundle`
|
||
- `likec4/model` instead of `@likec4/core/model` and `@likec4/core/types`
|
||
- `likec4/model/builder` instead of `@likec4/core/builder`
|
||
- `likec4/icons/all` instead of `@likec4/icons/all`
|
||
:::
|
||
|
||
|
||
#### LikeC4View
|
||
|
||
```tsx
|
||
import { LikeC4View, LikeC4ModelProvider } from '@likec4/diagram/bundle'
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
<LikeC4View
|
||
viewId="index1"
|
||
onNodeClick={(nodeId) => console.log(nodeId)}
|
||
/>
|
||
{/* Possible to have multiple views */}
|
||
<LikeC4View viewId="index2" />
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
|
||
See [LikeC4ViewProps](https://github.com/likec4/likec4/blob/main/packages/diagram/src/bundle/LikeC4View.props.ts) for available props.
|
||
|
||
#### ReactLikeC4
|
||
|
||
`LikeC4View` renders views from your model and allows exploration in the popup browser.
|
||
This component works in most use cases, but if you need more functionality, use `ReactLikeC4`:
|
||
|
||
```tsx
|
||
import { ReactLikeC4, LikeC4ModelProvider } from '@likec4/diagram/bundle'
|
||
|
||
function App() {
|
||
const [viewId, setViewId] = useState('index')
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
<ReactLikeC4
|
||
viewId={viewId}
|
||
pannable
|
||
zoomable={false}
|
||
keepAspectRatio
|
||
showNavigationButtons
|
||
enableDynamicViewWalkthrough={false}
|
||
enableElementDetails
|
||
enableRelationshipDetails
|
||
showDiagramTitle={false}
|
||
onNavigateTo={setViewId}
|
||
onNodeClick={...}
|
||
/>
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
#### Hooks
|
||
|
||
Available hooks inside `LikeC4View` or `ReactLikeC4`:
|
||
|
||
```tsx
|
||
import {
|
||
useLikeC4Model,
|
||
useLikeC4Specification,
|
||
useLikeC4ViewModel,
|
||
useEnabledFeatures,
|
||
useCurrentViewId,
|
||
|
||
// XYFlow hooks
|
||
useXYFlow,
|
||
useXYStore,
|
||
useXYStoreApi,
|
||
|
||
// Diagram API
|
||
useDiagram,
|
||
|
||
// Select from state
|
||
useDiagramContext
|
||
} from '@likec4/diagram/bundle'
|
||
```
|
||
|
||
#### Icons
|
||
|
||
If you use built-in icons, install [`@likec4/icons`](https://www.npmjs.com/package/%40likec4%2Ficons) (or use `likec4/icons`):
|
||
|
||
```tsx
|
||
import type { ElementIconRenderer } from '@likec4/diagram/bundle'
|
||
import { LikeC4ModelProvider, LikeC4View, ReactLikeC4 } from '@likec4/diagram/bundle'
|
||
import { lazy, Suspense } from 'react'
|
||
|
||
// Better to lazy load icons, bundle is quite large at the moment
|
||
const Icon = lazy(async () => {
|
||
const { IconRenderer } = await import('@likec4/icons/all')
|
||
return { default: IconRenderer }
|
||
})
|
||
|
||
const IconRenderer: ElementIconRenderer = (props) => (
|
||
<Suspense>
|
||
<Icon {...props} />
|
||
</Suspense>
|
||
)
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
<LikeC4View
|
||
viewId="index1"
|
||
renderIcon={IconRenderer}
|
||
/>
|
||
{/* Same for ReactLikeC4 */}
|
||
<ReactLikeC4
|
||
viewId="index2"
|
||
renderIcon={IconRenderer}
|
||
/>
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Library
|
||
|
||
If you want to use the package as a library with your bundler, you need to manage the CSS yourself.
|
||
|
||
The library uses [Mantine](https://mantine.dev). If you already use it and have `MantineProvider` in scope, the LikeC4 diagram will use it.
|
||
Otherwise, it will wrap itself with `MantineProvider`.
|
||
Even if you are not using Mantine in your app, its styles are required for the diagrams to work (don't worry, Mantine is tree-shakable).
|
||
|
||
Here are the options:
|
||
|
||
#### With bundled styles
|
||
|
||
1. Import all styles
|
||
|
||
```css
|
||
@import '@likec4/diagram/styles.css'
|
||
```
|
||
|
||
This includes all styles, including the [Mantine](https://mantine.dev) styles.
|
||
|
||
2. If you are using Mantine
|
||
|
||
```css
|
||
@layer reset, base, mantine, xyflow, tokens, recipes, utilities;
|
||
@import "@mantine/core/styles.layer.css";
|
||
@import "@likec4/diagram/styles-min.css";
|
||
```
|
||
|
||
:::caution
|
||
Order of layers is important, make sure `mantine` layer is before `xyflow`, and `xyflow` is before `tokens`.
|
||
:::
|
||
|
||
3. Font.\
|
||
LikeC4Diagram uses [`IBM Plex Sans Variable`](https://fontsource.org/fonts/ibm-plex-sans) by default.\
|
||
You can bundle it, import it from [fontsource](https://fontsource.org/fonts/ibm-plex-sans), from any other CDN, or use:
|
||
|
||
```css
|
||
@import '@likec4/diagram/styles-font.css'
|
||
```
|
||
|
||
You can override the font, this is explained later.
|
||
|
||
|
||
#### With PandaCSS
|
||
|
||
Check the [PandaCSS](https://panda-css.com) docs for full setup instructions.
|
||
LikeC4 provides a preset.
|
||
|
||
<PackageManagers
|
||
pkg="@likec4/styles"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
|
||
Configure your `panda.config.ts`:
|
||
|
||
```ts
|
||
import likec4preset from '@likec4/styles/preset'
|
||
import { defineConfig } from '@pandacss/dev'
|
||
|
||
export default defineConfig({
|
||
include: [
|
||
'src/**/*.{ts,tsx}',
|
||
// Include likec4 diagram source code to get the styles
|
||
'./node_modules/@likec4/diagram/panda.buildinfo.json',
|
||
],
|
||
importMap: [
|
||
'@likec4/styles',
|
||
],
|
||
presets: [
|
||
likec4preset,
|
||
],
|
||
theme: {
|
||
extend: {
|
||
// Here you can override/extend the theme
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
You global CSS should look like this:
|
||
|
||
```css
|
||
@layer reset, base, mantine, xyflow, tokens, recipes, utilities;
|
||
@import "@mantine/core/styles.layer.css";
|
||
@import "@likec4/diagram/styles-xyflow.css";
|
||
@import "@likec4/diagram/styles-font.css";
|
||
```
|
||
|
||
#### Usage
|
||
|
||
Same as [ReactLikeC4](#reactlikec4), but import from `@likec4/diagram`.
|
||
You must provide an instance of `DiagramView`:
|
||
|
||
```tsx
|
||
import { LikeC4Diagram, LikeC4ModelProvider, useLikeC4ViewModel } from '@likec4/diagram'
|
||
|
||
function LikeC4View({viewId}: {viewId: string}) {
|
||
const view = useLikeC4ViewModel(viewId)
|
||
if (!view) {
|
||
return <>View not found</>
|
||
}
|
||
return (
|
||
<LikeC4Diagram
|
||
view={view.$view}
|
||
readOnly
|
||
pannable
|
||
zoomable={false}
|
||
keepAspectRatio
|
||
showNavigationButtons
|
||
enableDynamicViewWalkthrough={false}
|
||
enableElementDetails
|
||
enableRelationshipDetails
|
||
showDiagramTitle={false}
|
||
/>
|
||
)
|
||
}
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4ModelProvider model={likec4model}>
|
||
<LikeC4View viewId="index" />
|
||
</LikeC4ModelProvider>
|
||
)
|
||
}
|
||
```
|
||
|
||
## Customization
|
||
|
||
You can render any component inside `LikeC4Diagram`\
|
||
(or `LikeC4View`/`ReactLikeC4` if you are using bundle):
|
||
|
||
```tsx
|
||
import { LikeC4Diagram, LikeC4ModelProvider } from '@likec4/diagram'
|
||
import { Panel, ViewportPortal } from '@xyflow/react'
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4Diagram>
|
||
<YourComponent />
|
||
|
||
{/* You can use components from xyflow */}
|
||
<Panel position="top">
|
||
<p>Your component as a panel</p>
|
||
<a href="https://reactflow.dev/examples">Check examples</a>
|
||
</Panel>
|
||
|
||
<ViewportPortal>
|
||
<div
|
||
style={{
|
||
transform: 'translate(100px, 100px)',
|
||
position: 'absolute',
|
||
}}>
|
||
This div is positioned at [100, 100] on the diagram canvas
|
||
</div>
|
||
</ViewportPortal>
|
||
</LikeC4Diagram>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Custom node renderers
|
||
|
||
LikeC4Diagram can use custom node renderers.\
|
||
Compose custom node renderers using primitives from `@likec4/diagram/custom`\
|
||
(or `@likec4/diagram/bundle/custom` for the bundled version).\
|
||
See [customNodes.tsx](https://github.com/likec4/likec4/blob/main/packages/diagram/src/custom/customNodes.tsx) for examples.
|
||
|
||
```tsx
|
||
import { LikeC4Diagram } from '@likec4/diagram'
|
||
import {
|
||
ElementActions,
|
||
ElementDetailsButtonWithHandler,
|
||
elementNode,
|
||
ElementNodeContainer,
|
||
ElementShape,
|
||
ElementTitle,
|
||
ElementToolbar,
|
||
IfNotReadOnly,
|
||
} from '@likec4/diagram/custom'
|
||
import { IconPlus } from '@tabler/icons-react'
|
||
|
||
const customNodes = {
|
||
element: elementNode(({ nodeProps, nodeModel }) => (
|
||
<ElementNodeContainer nodeProps={nodeProps}>
|
||
<ElementShape {...nodeProps} />
|
||
<ElementTitle {...nodeProps} />
|
||
{/* Add extra buttons */}
|
||
<ElementActions
|
||
{...nodeProps}
|
||
extraButtons={[
|
||
{
|
||
key: 'plus',
|
||
icon: <IconPlus />,
|
||
onClick: () => console.log('extra'),
|
||
},
|
||
]}
|
||
/>
|
||
{/* Add extra info */}
|
||
<div style={{ position: 'absolute', bottom: 0 }}>
|
||
{nodeModel.element.getMetadata('your-attr')}
|
||
</div>
|
||
</ElementNodeContainer>
|
||
)),
|
||
}
|
||
|
||
function App() {
|
||
return (
|
||
<LikeC4Diagram
|
||
view={view}
|
||
renderNodes={customNodes}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
You can also use [hooks](/tooling/react/#hooks) to access the model and diagram API.
|
||
|
||
### Custom styles
|
||
|
||
LikeC4Diagram uses [PandaCSS](https://panda-css.com) for styling. You can use it to customize the styles.
|
||
|
||
TODO: add example
|
||
|
||
# LikeC4 Vite Plugin
|
||
|
||
import { PackageManagers } from 'starlight-package-managers'
|
||
|
||
<p style={{display: 'flex', gap: '10px'}}>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
<a href="https://www.npmjs.com/package/likec4" target="_blank"></a>
|
||
</p>
|
||
|
||
LikeC4 Vite Plugin allows you to embed views directly, without any pre-build/generate steps.
|
||
The plugin automatically generates all the necessary code to render the views in your application, with Hot Module Replacement (HMR) supported.
|
||
|
||
This is useful for building documentation, tutorials, or any other application where you want to include diagrams.
|
||
|
||
## Guide
|
||
|
||
<br />
|
||
|
||
<Steps>
|
||
|
||
1. ### Create Vite project
|
||
|
||
To get started, we will need to create a new Vite project using react-ts template.
|
||
|
||
<PackageManagers
|
||
type="create"
|
||
pkg="vite@latest"
|
||
args="--template react-ts"
|
||
comment="create a new project with {PKG}"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
<br />
|
||
|
||
2. ### Install LikeC4
|
||
|
||
Add `likec4` dependency:
|
||
|
||
<PackageManagers
|
||
pkg="likec4"
|
||
dev
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
<br />
|
||
<br />
|
||
|
||
3. ### Configure Vite
|
||
|
||
Add LikeC4 plugin to vite config:
|
||
|
||
```diff lang="ts"
|
||
// vite.config.ts
|
||
import { defineConfig } from 'vite'
|
||
import react from '@vitejs/plugin-react'
|
||
+ import { LikeC4VitePlugin } from 'likec4/vite-plugin'
|
||
|
||
export default defineConfig({
|
||
plugins: [
|
||
react(),
|
||
+ LikeC4VitePlugin(),
|
||
],
|
||
})
|
||
```
|
||
<br />
|
||
<br />
|
||
|
||
4. ### Add type references
|
||
|
||
Add types reference to the `vite-env.d.ts` file
|
||
(or create new one, like `src/likec4.d.ts`)
|
||
|
||
```diff lang="ts" {3}
|
||
// src/vite-env.d.ts
|
||
/// <reference types="vite/client" />
|
||
/// <reference types="likec4/vite-plugin-modules" />
|
||
```
|
||
|
||
Another option is to add to the `tsconfig.json`:
|
||
|
||
```json
|
||
// tsconfig.json
|
||
{
|
||
"compilerOptions": {
|
||
"types": [
|
||
"likec4/vite-plugin-modules"
|
||
]
|
||
}
|
||
}
|
||
```
|
||
<br />
|
||
<br />
|
||
|
||
5. ### Add LikeC4 model
|
||
|
||
Create `src/tutorial.c4` and copy the following model from tutorial:
|
||
|
||
```likec4 showLineNumbers copy collapse={14-57}
|
||
//src/tutorial.c4
|
||
// Tutorial - https://likec4.dev/tutorial/
|
||
|
||
specification {
|
||
element actor
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer' {
|
||
description 'The regular customer of the system'
|
||
}
|
||
|
||
saas = system 'Our SaaS' {
|
||
component ui 'Frontend' {
|
||
description 'Nextjs application, hosted on Vercel'
|
||
style {
|
||
icon tech:nextjs
|
||
shape browser
|
||
}
|
||
}
|
||
component backend 'Backend Services' {
|
||
description '
|
||
Implements business logic
|
||
and exposes as REST API
|
||
'
|
||
}
|
||
|
||
// UI requests data from the Backend
|
||
ui -> backend 'fetches via HTTPS'
|
||
}
|
||
|
||
// Customer uses the UI
|
||
customer -> ui 'opens in browser'
|
||
customer -> saas 'enjoys our product'
|
||
}
|
||
|
||
views {
|
||
|
||
view index {
|
||
title 'Landscape view'
|
||
|
||
include *
|
||
}
|
||
|
||
view saas of saas {
|
||
include *
|
||
|
||
style * {
|
||
opacity 25%
|
||
}
|
||
style customer {
|
||
color muted
|
||
}
|
||
}
|
||
|
||
}
|
||
```
|
||
<br />
|
||
<br />
|
||
|
||
6. ### Use LikeC4 view in your app
|
||
|
||
Change the `src/main.tsx` file and import LikeC4 view from the `likec4:react` module:
|
||
|
||
```tsx
|
||
// src/main.tsx
|
||
import { createRoot } from 'react-dom/client'
|
||
import { LikeC4View } from 'likec4:react'
|
||
|
||
createRoot(document.getElementById('root')!).render(
|
||
<LikeC4View viewId='index' />
|
||
)
|
||
```
|
||
<br />
|
||
<br />
|
||
|
||
7. ### Start vite dev server
|
||
|
||
<PackageManagers
|
||
type="run"
|
||
args="dev"
|
||
pkgManagers={['npm', 'pnpm', 'yarn', 'bun']}
|
||
frame="none"
|
||
/>
|
||
|
||
Open the browser and navigate to `http://localhost:5173/`.
|
||
You should see the LikeC4 diagram rendered in your app.
|
||
|
||
</Steps>
|
||
|
||
<AdvancedCustomizationTip />
|
||
|
||
## Plugin
|
||
|
||
### Options
|
||
|
||
| Option | Description |
|
||
| -----------------| --------------------------------------------------------------------------------------------------- |
|
||
| `workspace` | directory with source files (defaults to vite root) |
|
||
| `printErrors` | if model is invalid, errors are reported to the logger (default `true`) |
|
||
| <span style="text-wrap:nowrap">`throwIfInvalid`</span> | fails with rejected promise if model is invalid (default `false`) |
|
||
| `graphviz` | `wasm` (default) or `binary` - use local binaries of Graphviz ("dot") or bundled WASM |
|
||
|
||
### Multi-project workspaces
|
||
|
||
If you have [multiple projects](/dsl/config/multi-projects/) in your workspace:
|
||
|
||
```tsx "project-a" "project-b"
|
||
// src/main.tsx
|
||
|
||
// where `project-a` and `project-b` are the names of your projects
|
||
import { LikeC4View as ProjectA_LikeC4View } from 'likec4:react/project-a'
|
||
import { LikeC4View as ProjectB_LikeC4View } from 'likec4:react/project-b'
|
||
|
||
const example = () => (
|
||
<>
|
||
<ProjectA_LikeC4View viewId='index' />
|
||
<ProjectB_LikeC4View viewId='index' />
|
||
</>
|
||
)
|
||
```
|
||
<br />
|
||
|
||
### Usage with API
|
||
|
||
It is also possible to initiate using [LikeC4 API](/tooling/model-api):
|
||
|
||
```ts
|
||
// vite.config.ts
|
||
import { defineConfig } from 'vite'
|
||
import react from '@vitejs/plugin-react'
|
||
import { LikeC4 } from 'likec4'
|
||
import { LikeC4VitePlugin } from 'likec4/vite-plugin'
|
||
|
||
const { languageServices } = await LikeC4.fromSource(`
|
||
specification {
|
||
element system
|
||
element user
|
||
}
|
||
model {
|
||
customer = user 'Customer'
|
||
cloud = system 'System'
|
||
}
|
||
views {
|
||
view index {
|
||
include *
|
||
}
|
||
}
|
||
`)
|
||
|
||
export default defineConfig({
|
||
plugins: [
|
||
react(),
|
||
LikeC4VitePlugin({
|
||
languageServices,
|
||
}),
|
||
],
|
||
})
|
||
```
|
||
<br />
|
||
|
||
### Virtual modules
|
||
|
||
Other modules are available to get access to the model:
|
||
|
||
```tsx
|
||
// For multi-project workspaces
|
||
import { projects } from 'likec4:projects'
|
||
|
||
// Pick first one (default)
|
||
import { useLikeC4Views, useLikeC4View } from 'likec4:single-project'
|
||
|
||
// Project by name
|
||
import { useLikeC4Views, useLikeC4View } from 'likec4:model/project-a'
|
||
|
||
// Other modules
|
||
import { loadDotSources } from 'likec4:dot'
|
||
import { mmdSource } from 'likec4:mmd/project-a'
|
||
```
|
||
|
||
Complete list - <a href="https://github.com/likec4/likec4/blob/main/packages/vite-plugin/src/modules.d.ts">vite-plugin/modules.d.ts</a>
|
||
|
||
|
||
## Usage
|
||
|
||
Here are some examples of how to use the plugin with different frameworks
|
||
|
||
### With Astro
|
||
|
||
You can use LikeC4VitePlugin with <a href="https://astro.build/" target='_blank'>Astro</a> and <a href="https://starlight.astro.build/" target='_blank'>Starlight</a> documentation tool as well.
|
||
Configure Astro:
|
||
|
||
```js
|
||
// astro.config.mjs
|
||
import { defineConfig } from 'astro/config';
|
||
import starlight from '@astrojs/starlight';
|
||
import { LikeC4VitePlugin } from 'likec4/vite-plugin'
|
||
|
||
export default defineConfig({
|
||
integrations: [
|
||
starlight({
|
||
title: 'Your architecture docs site',
|
||
}),
|
||
],
|
||
vite: {
|
||
plugins: [
|
||
LikeC4VitePlugin({}),
|
||
],
|
||
},
|
||
});
|
||
```
|
||
|
||
To use React components, first you need to wrap them in astro components:
|
||
|
||
```astro
|
||
// src/components/LikeC4View.astro
|
||
---
|
||
import { LikeC4View as ReactLikeC4View, type LikeC4ViewId } from 'likec4:react';
|
||
interface Props {
|
||
viewId: LikeC4ViewId;
|
||
}
|
||
const { viewId } = Astro.props
|
||
---
|
||
|
||
<ReactLikeC4View
|
||
viewId={viewId}
|
||
{/* Configure view */}
|
||
controls={false}
|
||
browser={{
|
||
// options for likec4 browser
|
||
enableFocusMode: false,
|
||
enableSearch: false,
|
||
}}
|
||
client:only="react">
|
||
</ReactLikeC4View>
|
||
```
|
||
|
||
Then you can use in markdown:
|
||
|
||
```mdx
|
||
// src/content/docs/example.mdx
|
||
---
|
||
title: Welcome to my docs
|
||
---
|
||
|
||
import LikeC4View from '../../components/LikeC4View.astro';
|
||
|
||
## Introduction
|
||
|
||
This is an example of using LikeC4 in your documentation
|
||
|
||
<LikeC4View viewId="index" />
|
||
|
||
```
|
||
<br />
|
||
|
||
:::tip
|
||
Check sources how LikeC4 <a href="https://github.com/likec4/likec4/tree/main/apps/docs/src/components" target='_blank'>multi-projects</a> are embedded in this website.
|
||
:::
|
||
|
||
:::caution
|
||
Don't forget to add type references as described [here](/tooling/vite-plugin/#add-type-references).
|
||
For Astro this is `src/env.d.ts` file.
|
||
:::
|
||
|
||
### With Next.js
|
||
|
||
For Next.js (since it does not use Vite), there is a workaround - [library mode](https://vite.dev/guide/build.html#library-mode):
|
||
Vite will generate a bundled library with likec4 diagrams, that you can import from your Next.js app.
|
||
|
||
Export everything from `likec4:react`:
|
||
|
||
```tsx
|
||
// src/likec4/index.tsx
|
||
export * from 'likec4:react'
|
||
```
|
||
<br />
|
||
|
||
Configure Vite:
|
||
|
||
```ts
|
||
// vite.config.ts
|
||
import { defineConfig } from 'vite'
|
||
import react from '@vitejs/plugin-react'
|
||
import { LikeC4VitePlugin } from 'likec4/vite-plugin'
|
||
|
||
export default defineConfig({
|
||
build: {
|
||
// Build views to 'lib' directory
|
||
outDir: 'lib',
|
||
lib: {
|
||
entry: 'src/likec4/index.tsx',
|
||
},
|
||
rollupOptions: {
|
||
// make sure to externalize deps that shouldn't be bundled
|
||
// to avoid code duplication
|
||
external: [
|
||
'react',
|
||
'react-dom',
|
||
'react/jsx-runtime',
|
||
'react/jsx-dev-runtime',
|
||
'react-dom/client',
|
||
],
|
||
},
|
||
},
|
||
plugins: [
|
||
react(),
|
||
LikeC4VitePlugin(),
|
||
],
|
||
})
|
||
```
|
||
|
||
Run `vite build` and import outputs from your Next.js app:
|
||
|
||
```tsx
|
||
// pages/index.tsx
|
||
import { LikeC4View } from '../lib'
|
||
|
||
export default function Home() {
|
||
return (
|
||
<LikeC4View viewId="index" />
|
||
)
|
||
}
|
||
```
|
||
|
||
You can run `vite build --watch` as a background process to watch for changes in likec4 source files.
|
||
|
||
<br />
|
||
<br />
|
||
|
||
:::tip
|
||
You can use LikeC4 with other frameworks as well:
|
||
- via [React code generation](/tooling/code-generation/react/)
|
||
- For non-react-based documentation tools like Docusaurus, see [Web Components](/tooling/code-generation/webcomponent/)
|
||
:::
|
||
|
||
# Getting Started
|
||
|
||
|
||
To start the tutorial, you have two options:
|
||
- Open <a href="https://playground.likec4.dev/w/blank/" target='_blank'>blank playground</a> in a new tab
|
||
- Install [vscode extension](https://marketplace.visualstudio.com/items?itemName=likec4.likec4-vscode) (or <a href="https://open-vsx.org/extension/likec4/likec4-vscode" target="_blank">open-vsx</a>) and create a new file with `.c4` extension
|
||
|
||
and follow the steps:
|
||
|
||
<Steps>
|
||
|
||
1. ##### Prepare the specification
|
||
|
||
We'll start by defining the kinds of elements in our architecture.
|
||
We only need two - `actor` and `system`:
|
||
|
||
```likec4 copy
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
}
|
||
```
|
||
|
||
2. ##### Create the model
|
||
|
||
Start with the top-level elements and define the model:
|
||
|
||
```diff lang="likec4"
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
}
|
||
|
||
+ model {
|
||
+ customer = actor 'Customer'
|
||
+ saas = system 'Our SaaS'
|
||
+ }
|
||
```
|
||
|
||
These are the first elements of our architecture model.
|
||
Let's add details.
|
||
|
||
3. ##### Add a hierarchy
|
||
|
||
Assume our system has two main components: `ui` and `backend`.
|
||
Let's add a new kind to the specification and update the model.
|
||
|
||
```diff lang="likec4" {5,11-12} copy
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
+ element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer'
|
||
saas = system 'Our SaaS' {
|
||
+ component ui
|
||
+ component backend
|
||
}
|
||
}
|
||
```
|
||
|
||
4. ##### Add relationships
|
||
|
||
**Any links** between elements (i.e. interactions, calls, delegations, dependencies, flows).
|
||
You are free to define them as you like.
|
||
|
||
In the model:
|
||
|
||
```diff lang="likec4" {14-15,18-19} copy
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer'
|
||
saas = system 'Our SaaS' {
|
||
component ui
|
||
component backend
|
||
|
||
+ // UI fetches data from the Backend
|
||
+ ui -> backend
|
||
}
|
||
|
||
+ // Customer uses the UI
|
||
+ customer -> ui 'opens in browser'
|
||
}
|
||
```
|
||
|
||
5. ##### Create first diagram
|
||
|
||
Diagrams are rendered from views. Views are projections of the model defined by predicates (what to include or exclude).
|
||
|
||
Start with a bird's eye view (_"Landscape"_):
|
||
|
||
```diff lang="likec4" {23-25} copy
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer'
|
||
saas = system 'Our SaaS' {
|
||
component ui
|
||
component backend
|
||
|
||
// UI fetches data from the Backend
|
||
ui -> backend
|
||
|
||
// Customer uses the UI
|
||
customer -> ui 'opens in browser'
|
||
}
|
||
}
|
||
|
||
views {
|
||
+ view index {
|
||
+ include *
|
||
+ }
|
||
}
|
||
```
|
||
|
||
We got this:
|
||
|
||

|
||
|
||
<Aside title='Wondering why there is a relationship?'>
|
||
The predicate `include *` includes only "top-level" elements and infers relationships between them from nested elements:
|
||
> `customer` has a _known relationship_ with nested `saas.ui` element
|
||
|
||
that implies:
|
||
> `customer` has _some relationship_ with `saas`.
|
||
|
||
</Aside>
|
||
|
||
|
||
6. ##### Add more views
|
||
|
||
```diff lang="likec4" {27-29}
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer'
|
||
saas = system 'Our SaaS' {
|
||
component ui
|
||
component backend
|
||
|
||
// UI requests data from the Backend
|
||
ui -> backend
|
||
|
||
// Customer uses the UI
|
||
customer -> ui 'opens in browser'
|
||
}
|
||
}
|
||
|
||
views {
|
||
view index {
|
||
include *
|
||
}
|
||
|
||
+ view of saas {
|
||
+ include *
|
||
+ }
|
||
}
|
||
```
|
||
|
||
Imagine we zoom in to the `saas` element and see its nested elements and their relationships:
|
||
|
||

|
||
|
||
7. ##### Enrich the model
|
||
|
||
Let's add descriptions, define the shape of the `ui` and add a label to the relationship `ui -> backend`
|
||
|
||
```diff lang="likec4" {10,15-19,22-25,29,47-49} ins="'fetches via HTTPS'" copy
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer' {
|
||
+ description 'The regular customer of the system'
|
||
}
|
||
|
||
saas = system 'Our SaaS' {
|
||
component ui 'Frontend' {
|
||
+ description 'Nextjs application, hosted on Vercel'
|
||
+ style {
|
||
+ icon tech:nextjs
|
||
+ shape browser
|
||
+ }
|
||
}
|
||
component backend 'Backend Services' {
|
||
+ description '
|
||
+ Implements business logic
|
||
+ and exposes as REST API
|
||
+ '
|
||
}
|
||
|
||
// UI fetches data from the Backend
|
||
ui -> backend 'fetches via HTTPS'
|
||
}
|
||
|
||
// Customer uses the UI
|
||
customer -> ui 'opens in browser'
|
||
}
|
||
|
||
views {
|
||
|
||
view index {
|
||
title 'Landscape view'
|
||
|
||
include *
|
||
}
|
||
|
||
view of saas {
|
||
include *
|
||
|
||
+ style customer {
|
||
+ color muted
|
||
+ }
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
The `saas` view after changes:
|
||
|
||

|
||
|
||
8. ##### Add changes
|
||
|
||
Let's change the description of the `customer` and the label of `customer -> ui`
|
||
|
||
```diff lang="likec4"
|
||
// tutorial.c4
|
||
specification {
|
||
element actor
|
||
element system
|
||
element component
|
||
}
|
||
|
||
model {
|
||
customer = actor 'Customer' {
|
||
- description 'The regular customer of the system'
|
||
+ description 'Our dear customer'
|
||
}
|
||
|
||
saas = system 'Our SaaS' {
|
||
component ui 'Frontend' {
|
||
description 'Nextjs application, hosted on Vercel'
|
||
style {
|
||
icon tech:nextjs
|
||
shape browser
|
||
}
|
||
}
|
||
component backend 'Backend Services' {
|
||
description '
|
||
Implements business logic
|
||
and exposes as REST API
|
||
'
|
||
}
|
||
|
||
// UI requests data from the Backend
|
||
ui -> backend 'fetches via HTTPS'
|
||
}
|
||
|
||
// Customer uses the UI
|
||
customer -> ui 'opens in browser'
|
||
+ customer -> saas 'enjoys our product'
|
||
}
|
||
|
||
views {
|
||
|
||
view index {
|
||
title 'Landscape view'
|
||
|
||
include *
|
||
}
|
||
|
||
view of saas {
|
||
include *
|
||
|
||
style customer {
|
||
color muted
|
||
}
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
View `index`:
|
||
|
||

|
||
|
||
View `saas`:
|
||
|
||

|
||
|
||
:::tip[Did you see?]
|
||
We changed elements in the model, and all views are updated accordingly.
|
||
:::
|
||
|
||
9. ##### Try it yourself
|
||
|
||
Play with [this tutorial in the playground](https://playground.likec4.dev/w/tutorial/) and try adding the following:
|
||
|
||
- change [shape](/dsl/styling/#single-element) of the `customer` element
|
||
- add a database (with `storage` shape) and tables like `customers` and `orders` (what relationships should be added?)
|
||
- add an external system, like Stripe, and show how the backend might interact with it
|
||
|
||
<LinkCard
|
||
title="Open playground"
|
||
description="Play with this tutorial in playground"
|
||
href="https://playground.likec4.dev/w/tutorial/"
|
||
target="_blank"
|
||
/>
|
||
|
||
</Steps>
|
||
|