---
title: slug
---

## Variables

### EPISODIC\_PREFIX\_LENGTH

```ts
const EPISODIC_PREFIX_LENGTH: 9 = 9;
```

The `YYYYMMDD-` prefix an episodic filename carries. Time is part of an episodic entry's
identity and it never receives a correction in place, so the date belongs in the name;
every other type is timeless and correctable, so it gets a bare slug.

***

### SLUG\_FALLBACK

```ts
const SLUG_FALLBACK: "untitled" = "untitled";
```

The stem used when a title reduces to nothing sluggable, such as an all-punctuation or
non-Latin title. A placeholder beats an empty filename, and `memhtml doctor` can find
these by name.

***

### SLUG\_MAX\_LENGTH

```ts
const SLUG_MAX_LENGTH: 80 = 80;
```

Maximum slug length in characters, before any collision suffix.

## Functions

### datePrefix()

```ts
function datePrefix(at): string;
```

Format an instant as the `YYYYMMDD` stamp of an episodic filename, in UTC.

#### Parameters

##### at

`Date`

#### Returns

`string`

***

### filenameFor()

```ts
function filenameFor(input): string;
```

The filename for a memory: `20260802-slug.html` for episodic, `slug.html` otherwise.
The date prefix sits outside the slug's length budget, because it is identity, not title.

#### Parameters

##### input

###### at

`Date`

###### episodic

`boolean`

###### slug

`string`

#### Returns

`string`

***

### hasDatePrefix()

```ts
function hasDatePrefix(filename): boolean;
```

True when a filename carries the `YYYYMMDD-` episodic prefix.

#### Parameters

##### filename

`string`

#### Returns

`boolean`

***

### isSlug()

```ts
function isSlug(value): boolean;
```

True when a string is already a valid slug, the fixed point of [slugify](/api/contracts/slug/#slugify).

#### Parameters

##### value

`string`

#### Returns

`boolean`

***

### slugify()

```ts
function slugify(title): string;
```

Kebab-case a title into `[a-z0-9-]`, at most [SLUG\_MAX\_LENGTH](/api/contracts/slug/#slug_max_length) characters.

Diacritics are folded to their base letters (`déployé` ⇒ `deploye`) rather than dropped,
so a title stays recognizable. Runs of separators collapse to one hyphen and the result
carries no leading or trailing hyphen, which makes the function idempotent: a slug fed
back in comes out unchanged.

Truncation cuts at [SLUG\_MAX\_LENGTH](/api/contracts/slug/#slug_max_length) and then trims any hyphen the cut exposed, so
a truncated slug is still a valid slug rather than one ending mid-separator.

#### Parameters

##### title

`string`

#### Returns

`string`

***

### withCollisionOrdinal()

```ts
function withCollisionOrdinal(slug, ordinal): string;
```

Append a collision suffix. `ordinal` is a 1-based ordinal for display in the filename;
ordinal 1 is the unsuffixed slug, 2 becomes `-2`, and so on, matching the `-2`/`-3`
convention. The suffix is added inside the length budget, so a maximum-length slug is
shortened rather than overflowed.

**The result never equals the input, at any slug length.** That is what makes the store's
collision loop (`packages/store/src/store.ts`, `pathFor`) terminate rather than re-propose
the name that collided. It is not free near the length cap: for a slug whose own tail IS the
suffix, cutting to make room and appending the suffix can rebuild the slug — and because the
cut also trims any hyphen it exposes, the rebuild can recur at MORE than one cut width. The
stem is therefore re-cut from its own post-trim length until appending the suffix no longer
reproduces the input; each re-cut strictly shortens the stem, so the loop terminates and the
result stays inside the budget.

#### Parameters

##### slug

`string`

##### ordinal

`number`

#### Returns

`string`