UP (Unified Properties)

A Human-Friendly Data Serialization Format

View the Project on GitHub uplang/spec

Quick reference for UP syntax rules and common patterns.

Core Syntax Rules

Key-Value Pairs

UP is whitespace-delimited. The basic unit is:

key value

Multi-Word Values: Two Options

Option 1: Use quotes (traditional)

name "John Doe"
description "This is a description"

Option 2: Use : suffix (line-oriented)

name: John Doe
description: This is a complete sentence with spaces.

The : suffix after a key enables line-oriented value mode, capturing everything after the colon as the value.

WRONG (without quotes or colon):

name John Doe
description This is a description

This parses as:

name: "John"
Doe: (new key with no value)
description: "This"
is: "a"
description: (duplicate key)

Single-Word Values Don’t Need Quotes

name Alice
status active
environment production

Type Annotations

Type annotations work with all value syntaxes:

# With whitespace-delimited values
port!int 8080
enabled!bool true
timeout!dur 30s
version "1.2.3"

# With line-oriented values (: suffix)
count!int: 42
url!uri: https://example.com/path?query=value
name!string: John Doe

# Special annotation: !quoted preserves or adds literal quotes
title!quoted: "Senior Engineer"  # value is: "Senior Engineer" (with quotes)
name!quoted: Alice               # value is: "Alice" (quotes added)

Note: The !quoted annotation is used with line-oriented values (: suffix) to preserve or add literal quote characters in the value, since quotes are normally stripped in line-oriented mode.

URLs and Emails (No Spaces = No Quotes Needed)

email alice@example.com
repository https://github.com/user/repo
website https://example.com/path/to/page

The : in URLs is treated as a literal character, not a key suffix, because it’s not at the end of the key token.

Line-Oriented Values with : Suffix

The : suffix enables capturing the entire rest of the line as a value:

message: Hello, World!
description: This is a complete sentence.
url: https://example.com/path?query=value
note: Values can contain any characters, including "quotes" and :colons

Important: In line-oriented mode, surrounding quotes are automatically stripped by default:

# Line-oriented: surrounding quotes are stripped
title: "Senior Engineer"        # value is: Senior Engineer (no quotes)

# Traditional: quotes are delimiters (same result)
title "Senior Engineer"          # value is: Senior Engineer (no quotes)

# To preserve literal quotes, use !quoted annotation
title!quoted: "Senior Engineer"  # value is: "Senior Engineer" (with quotes)
title!quoted: Engineer           # value is: "Engineer" (quotes added)

Comments and Line-Oriented Values:

In line-oriented mode, # starts a comment, ending the value:

# The value ends at #, comment begins
message: Hello, World!  # This is a comment
# Result: value is "Hello, World!" (comment not included)

# To include literal # in value, use traditional quoted syntax
tag "Use #hashtags freely"      # value is: Use #hashtags freely
tag: Use #hashtags freely        # value is: Use (comment starts at #)

# Or escape within multiline strings
description ```
Use #hashtags and # symbols freely
in multiline strings

**Rule:** Use traditional quoted syntax when you need literal `#` characters in values without triggering comments.

This works with **any type annotation**:

```up
name!string: John Doe
count!int: 42
enabled!bool: true
url!uri: https://api.example.com/v1/endpoint

# With !quoted to preserve literal quotes
note!quoted: "Important"         # value is: "Important" (with quotes)

With dedented multiline strings:

description!4:
    This is a dedented
    multiline value
    with consistent indentation

Blocks

server {
  host localhost
  port!int 8080
  name "My Server"
}

Lists

# Multiline
tags [
  production
  web
  api
]

# Inline
tags [production, web, api]

Multiline Strings (No Quotes Needed)

description ```
This is a multiline string.
Whitespace is preserved.
No quotes needed inside.

notes ``` Multi-line notes can contain anything including “quotes”

Multiline Strings with Dedent (Best Practice)

For structured documents, use dedent to maintain indentation:

BAD (ruins indentation):

metadata {
  notes ```
This breaks the visual structure
because it's not indented

}


✅ **GOOD (maintains structure):**
```up
metadata {
  notes!2 ```
    This maintains the visual structure
    by using dedent to remove leading spaces
    ```
}

Dedent removes N spaces from each line:

# !4 removes 4 spaces from each line
description!4 ```
    Line 1 has 4 leading spaces
    Line 2 also has 4 leading spaces
    Result: both lines flush left in output
    ```

# Choose dedent based on your indentation level
block {
  field!2 ```
    Content indented 2 spaces
    ```

  nested {
    field!4 ```
      Content indented 4 spaces
      ```
  }
}

Colon Parsing Rules

The : character has different meanings depending on its context:

: as Key Suffix → Line-Oriented Value Mode

When : appears immediately after a key (with optional type annotation), it enables line-oriented value capture:

message: Hello, World!
name!string: John Doe
url!uri: https://example.com/path

The entire rest of the line becomes the value.

: Within Values → Literal Character

When : appears within a value (not as a key suffix), it’s a literal character:

website https://example.com       # value is "https://example.com"
time 10:30:45                     # value is "10:30:45"
ratio 3:4                         # value is "3:4"

URLs work naturally without quotes because the : is part of the value, not a key suffix.

: Within Keys (Not Suffix) → Literal Character

Keys can contain : as long as it’s not the final character:

http://example.com some-value     # key="http://example.com", value="some-value"
my:key:with:colons value          # key="my:key:with:colons", value="value"

Keys Ending with Literal : → Must Be Quoted

To use a key that ends with a literal : character, quote the key:

"key:" full line value            # key="key:", value="full line value"
"note:" This is the value         # key="note:", value="This is the value"
"http://example.com:" value       # key="http://example.com:", value="value"

Line-Oriented Values and Quotes

When using : suffix (line-oriented mode), surrounding quotes are automatically stripped by default:

# Line-oriented mode: surrounding quotes are stripped
title: "Senior Software Engineer"   # value is: Senior Software Engineer (no quotes)
note: This has "embedded" quotes    # value is: This has "embedded" quotes

# Traditional mode: quotes are delimiters (same result)
title "Senior Software Engineer"    # value is: Senior Software Engineer (no quotes)

# To preserve literal quotes, use !quoted annotation
title!quoted: "Senior Engineer"     # value is: "Senior Engineer" (with quotes)
title!quoted: Engineer              # value is: "Engineer" (quotes added)
note!quoted: Message                # value is: "Message" (quotes added)

Why this design?

Line-Oriented Values and Comments

In line-oriented mode, # starts a comment, which ends the value capture:

# Comment starts at #
message: Hello, World!  # This is a comment
# Result: value is "Hello, World!" (comment excluded)

line: Text before # text after
# Result: value is "Text before " (everything after # is a comment)

# Quotes are stripped before comment processing
title: "Senior Engineer"  # This is a comment
# Result: value is "Senior Engineer" (quotes stripped, comment excluded)

# To include literal # in values, use traditional quoted syntax
hashtag "Use #hashtags"             # value is: Use #hashtags
hashtag: Use #hashtags              # value is: Use (comment starts at #)

# Alternative: use multiline strings for content with # symbols
content ```
This can contain #hashtags
and # symbols freely

**When to use each approach:**

| Need | Solution | Example |
|------|----------|---------|
| Multi-word value, no `#` | Line-oriented | `title: Senior Engineer` |
| Multi-word value with `#` | Quoted string | `tag "Use #hashtags"` |
| Multi-word with `#` and `"` | Multiline | See above |

**Why this design?**
- Line-oriented mode captures the **raw text** after the colon
- Quotes lose their special meaning in line-oriented mode
- This is consistent with "entire rest of line becomes the value"
- If you want quotes as delimiters, use traditional syntax without `:`

**When to use each:**

| Syntax | Use Case | Result |
|--------|----------|--------|
| `key: value with spaces` | Natural text without quotes | `value with spaces` |
| `key "value with spaces"` | Traditional quoted string | `value with spaces` |
| `key: "value"` | Literal quotes in value | `"value"` (quotes included) |

### Summary Table

| Context | Example | Meaning |
|---------|---------|---------|
| After key (suffix) | `name: John Doe` | Line-oriented value mode |
| Within value | `https://example.com` | Literal `:` character |
| Within key (not suffix) | `my:key value` | Literal `:` character |
| Key ending in `:` | `"key:" value` | Must quote key |

## Common Patterns

### Schema Definitions

✅ **CORRECT:**
```up
namespace greeting
version 1.2.3
description "Simple greeting generator"

functions {
  hello {
    description "Generate a greeting"

    params {
      name!string {
        description "Name to greet"
        default World
        required!bool false
      }
    }

    returns!string {
      description "Greeting message"
      example "Hello, Alice!"
    }
  }
}

metadata {
  author "UP Team"
  license MIT
  safe!bool true
}

Security Policies

CORRECT:

policy {
  verify_hashes!bool true
  require_signatures!bool true
}

trusted_signers {
  up_core {
    name "UP Core Team"
    email security@uplang.org
    trust_level official
  }
}

Lock Files

CORRECT:

namespaces {
  greeting {
    version 1.2.3

    files {
      executable {
        path ./up-namespaces/greeting
        hash sha256:abc123...
      }
    }

    verified!bool true
    source local
  }
}

Example Data

CORRECT:

users [
  {
    name "Alice Johnson"
    email alice@example.com
    role admin
  }
  {
    name "Bob Smith"
    email bob@example.com
    role user
  }
]

Comprehensive Key and Value Examples

Valid Key Formats

Simple identifiers:

name Alice
port 8080
enabled true

Hyphenated:

app-name MyApp
max-connections 100
user-id 12345

Underscored:

app_name MyApp
max_connections 100
user_id 12345

URLs as keys (unquoted):

https://example.com some-value
http://api.example.com/endpoint config-data

Quoted keys (special characters or ending with :):

"key with spaces" value
"key!with!bangs" value
"key:" full line value after colon
"note:" This is captured as the value

Valid Value Formats

Single-word (no quotes needed):

name Alice
status active
environment production

Quoted multi-word (traditional):

name "John Doe"
description "This is a description"
message "Hello, World!"

Line-oriented with : suffix (modern):

name: John Doe
description: This is a complete sentence with multiple words.
message: Hello, World!
note: Values can contain "quotes" and special chars (quotes stripped)

With !quoted to preserve literal quotes:

title!quoted: "Senior Engineer"  # value is: "Senior Engineer" (with quotes)
name!quoted: Alice               # value is: "Alice" (quotes added)
note!quoted: Important           # value is: "Important" (quotes added)

URLs (no quotes needed):

website https://example.com
api https://api.example.com/v1/endpoint?key=value
repository https://github.com/user/repo

With type annotations (whitespace-delimited):

port!int 8080
enabled!bool true
timeout!dur 30s
score!float 98.6

With type annotations and : suffix:

name!string: John Doe
count!int: 42
url!uri: https://example.com/path
enabled!bool: true
message!quoted: "Important"      # combines type annotation with !quoted

With type annotations and multiline/blocks:

description!4 ```
    This is a dedented
    multiline value
    ```

config!json {
  host localhost
  port!int 8080
}

items!list [
  one
  two
  three
]

When To Quote

Values: Always Quote

Values: Never Quote

Important: Line-Oriented Mode and Quotes

In line-oriented mode (with : suffix), surrounding quotes are automatically stripped:

# These produce the SAME result:
title "Senior Engineer"      # value: Senior Engineer (no quotes)
title: Senior Engineer       # value: Senior Engineer (no quotes)
title: "Senior Engineer"     # value: Senior Engineer (quotes stripped)

# To preserve literal quotes, use !quoted annotation:
title!quoted: "Senior Engineer"  # value: "Senior Engineer" (with quotes)
title!quoted: Engineer           # value: "Engineer" (quotes added)

Rule: Use !quoted annotation when you need literal quote characters in the value.

Important: Line-Oriented Mode and Comments

In line-oriented mode, # starts a comment, ending the value:

# Comment starts at #
message: Hello, World!  # This is a comment
# Result: value is "Hello, World!"

# Quotes are stripped before comment processing
title: "Value"  # comment
# Result: value is "Value" (quotes stripped, comment excluded)

# To include # in values, use quoted strings
tag "Use #hashtags"      # value: Use #hashtags
tag: Use #hashtags       # value: Use (# starts comment)

Rule: Use traditional quoted syntax when values contain # characters.

Keys: Must Quote

Keys: Never Need Quotes

Common Mistakes

Mistake 1: Unquoted Multi-Word Values (Without Colon)

WRONG:

description This is wrong
name John Doe
message Hello World

CORRECT (with quotes):

description "This is correct"
name "John Doe"
message "Hello World"

CORRECT (with colon suffix):

description: This is correct
name: John Doe
message: Hello World

Mistake 2: Quoting Single Words Unnecessarily

⚠️ Works but unnecessary:

environment "production"
status "active"

Better (simpler):

environment production
status active

Mistake 3: Forgetting Type Annotations for Non-Strings

WRONG (parsed as strings):

port 8080
enabled true

CORRECT:

port!int 8080
enabled!bool true

Mistake 4: Quoting Multiline Strings

WRONG:

description "Line 1
Line 2
Line 3"

CORRECT:

description ```
Line 1
Line 2
Line 3

## Quick Reference Table

| Value Type | Needs Quotes? | Example |
|------------|---------------|---------|
| Single word | No | `status active` |
| Multi-word (quoted) | **YES** | `name "John Doe"` |
| Multi-word (colon) | No | `name: John Doe` |
| With `#` symbol | **YES** | `tag "Use #hashtags"` |
| Colon + quotes | No (quotes stripped) | `name: "John"` → value is `John` |
| Colon + `#` | Ends at `#` | `msg: Hi # comment` → value is `Hi ` |
| With !quoted | Preserves/adds quotes | `name!quoted: John` → value is `"John"` |
| URL/Email | No | `email user@example.com` |
| Number | No (+ type) | `port!int 8080` |
| Boolean | No (+ type) | `enabled!bool true` |
| Version | Optional | `version "1.2.3"` or `version 1.2.3` |
| Path (no spaces) | No | `path ./file.txt` |
| Path (with spaces) | **YES** | `path "./my file.txt"` |
| Multiline | Use ` ``` ` | See multiline syntax |
| Empty string | **YES** | `value ""` |
| With `:` suffix | No | `message: Any text here` |

## Document Lint Directives

UP supports document-level lint directives to enforce coding standards and catch potential errors. These directives are specified at the top of the document using the `!lint` annotation.

### Syntax

```up
!lint {
  rule-name!level error
  another-rule!level warning
}

# Your document content follows
name: John Doe

Available Lint Rules

Rule Default Description
require-line-oriented false All multi-word values must use : suffix syntax
require-quoted false All multi-word values must use quoted syntax
no-empty-values false Keys must have non-empty values
no-ambiguous-keys true Warn on keys that could be values (e.g., name John Doe)
require-type-annotations false Non-string values must have explicit type annotations
no-trailing-whitespace false Lines must not have trailing whitespace
consistent-indentation false Blocks must use consistent indentation (2 or 4 spaces)
prefer-line-oriented false Suggest : suffix for multi-word values instead of quotes
no-literal-quotes false Warn on key: "value" (suggests removing quotes or using !quoted)

Examples

Enforce line-oriented values:

!lint {
  require-line-oriented!level error
}

# ✅ Valid
title: Senior Software Engineer
description: This is a complete sentence.

# ❌ Invalid - must use : suffix
name "John Doe"

Prevent empty values:

!lint {
  no-empty-values!level error
}

# ✅ Valid
status active

# ❌ Invalid - empty value
status

Require type annotations:

!lint {
  require-type-annotations!level warning
}

# ✅ Valid
port!int 8080
enabled!bool true

# ❌ Invalid - missing type annotation
timeout 30

Catch ambiguous syntax:

!lint {
  no-ambiguous-keys!level error
}

# ❌ Warning - "Doe" appears to be a key with no value
# Suggestion: use `name "John Doe"` or `name: John Doe`
name John Doe

Prevent literal quotes in line-oriented mode:

!lint {
  no-literal-quotes!level warning
}

# ❌ Warning - quotes will be stripped, use !quoted if intentional
title: "Senior Engineer"

# ✅ Valid - explicitly using !quoted
title!quoted: "Senior Engineer"

# ✅ Valid - no quotes
title: Senior Engineer

Multiple Rules

!lint {
  no-empty-values!level error
  no-ambiguous-keys!level error
  require-type-annotations!level warning
  consistent-indentation!level error
}

# Document content
server {
  host!string: localhost
  port!int: 8080
  enabled!bool: true
}

Enforcement Levels

Lint rules can have different enforcement levels using the !level annotation (scoped within !lint blocks):

!lint {
  # Error: stops processing
  no-empty-values!level error
  
  # Warning: shows warning but continues
  prefer-line-oriented!level warning
  
  # Info: informational message
  no-literal-quotes!level info
}

Available levels:

Note: Within a !lint block, the !level annotation is automatically scoped to lint.level, providing cleaner syntax while maintaining explicit semantics.

CLI Usage

# Validate with lint rules
up validate -i yourfile.up --lint

# Show lint warnings
up lint -i yourfile.up

# Fix auto-fixable lint issues
up lint -i yourfile.up --fix

Grammar Support

Lint directives use the existing !annotation syntax and block structure, so no grammar changes are needed. The !lint annotation is semantically interpreted by the validator/linter.

Validation

To check if your UP is valid:

# Parse and validate
up validate -i yourfile.up

# Parse and validate with linting
up validate -i yourfile.up --lint

# Parse and output as JSON (will fail if invalid)
up parse -i yourfile.up --pretty

Summary

The Golden Rule:

For values containing whitespace, you have two options:

  1. Quote it: name "John Doe"
  2. Use : suffix: name: John Doe

Type Safety: Remember that strings are default, so only use quotes when needed for parsing, and use type annotations (!int, !bool, etc.) when you want non-string types.

Colon Rules: