{"id":32266829,"url":"https://github.com/permalinks/permalinks","last_synced_at":"2025-10-22T21:51:54.789Z","repository":{"id":16536157,"uuid":"19289512","full_name":"permalinks/permalinks","owner":"permalinks","description":"Add permalinks functionality to any node.js project.  Can be used in static site generators, build systems, web applications or anywhere you need to replace placeholders in paths.","archived":false,"fork":false,"pushed_at":"2018-01-11T06:15:32.000Z","size":91,"stargazers_count":33,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-30T04:25:37.749Z","etag":null,"topics":["javascript","permalink","permalinks","placeholder","rewrite","rewrite-urls","semantic","seo","url"],"latest_commit_sha":null,"homepage":"https://github.com/jonschlinkert","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/permalinks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-04-29T19:49:24.000Z","updated_at":"2025-03-03T12:26:48.000Z","dependencies_parsed_at":"2022-09-11T04:51:02.714Z","dependency_job_id":null,"html_url":"https://github.com/permalinks/permalinks","commit_stats":null,"previous_names":["jonschlinkert/permalinks"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/permalinks/permalinks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permalinks%2Fpermalinks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permalinks%2Fpermalinks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permalinks%2Fpermalinks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permalinks%2Fpermalinks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/permalinks","download_url":"https://codeload.github.com/permalinks/permalinks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permalinks%2Fpermalinks/sbom","scorecard":{"id":727995,"data":{"date":"2025-08-11","repo":{"name":"github.com/permalinks/permalinks","commit":"6caaffeda160c04f849919842c7f3a2ea31dae60"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/25 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T13:27:33.909Z","repository_id":16536157,"created_at":"2025-08-22T13:27:33.910Z","updated_at":"2025-08-22T13:27:33.910Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280520817,"owners_count":26344438,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-22T02:00:06.515Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","permalink","permalinks","placeholder","rewrite","rewrite-urls","semantic","seo","url"],"created_at":"2025-10-22T21:51:53.734Z","updated_at":"2025-10-22T21:51:54.783Z","avatar_url":"https://github.com/permalinks.png","language":"JavaScript","readme":"# permalinks [![NPM version](https://img.shields.io/npm/v/permalinks.svg?style=flat)](https://www.npmjs.com/package/permalinks) [![NPM monthly downloads](https://img.shields.io/npm/dm/permalinks.svg?style=flat)](https://npmjs.org/package/permalinks) [![NPM total downloads](https://img.shields.io/npm/dt/permalinks.svg?style=flat)](https://npmjs.org/package/permalinks) [![Linux Build Status](https://img.shields.io/travis/permalinks/permalinks.svg?style=flat\u0026label=Travis)](https://travis-ci.org/permalinks/permalinks)\n\n\u003e Easily add powerful permalink or URL routing/URL rewriting capablities to any node.js project. Can be used in static site generators, build systems, web applications or anywhere you need to do path or URL transformation.\n\nPlease consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.\n\n- [Install](#install)\n- [Quickstart](#quickstart)\n- [API](#api)\n- [Docs](#docs)\n  * [Structure](#structure)\n    + [Alternative syntax](#alternative-syntax)\n    + [file](#file)\n    + [locals](#locals)\n  * [Context](#context)\n    + [locals](#locals-1)\n    + [file.data](#filedata)\n    + [options.data](#optionsdata)\n    + [File path properties](#file-path-properties)\n  * [Presets](#presets)\n  * [Helpers](#helpers)\n    + [file helper](#file-helper)\n  * [Additional resources](#additional-resources)\n- [About](#about)\n\n_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_\n\n## Install\n\nInstall with [npm](https://www.npmjs.com/):\n\n```sh\n$ npm install --save permalinks\n```\n\n## Quickstart\n\nAdd permalinks support to any JavaScript project using node's `require()` system with the following line of code:\n\n```js\nconst permalink = require('permalinks');\n```\n\n_(The main export is a function that automatically creates an instance of `Permalinks` and calls the `permalinks.formt()` method. As an alternative, if you need to access any internal methods, the [API documentation](#api) shows how to create an instance of `Permalinks`.)_\n\n**Usage**\n\n```js\npermalink(structure, file[, options]);\n```\n\n**Params**\n\n* [structure](https://structure.js.org/): `{string}` (required)  A kind of template that determines what a permalink will look like when it's rendered.\n* [file](https://github.com/aconbere/node-file-utils): `{string|object}` (required) Locals, or a file object or path (the file associated with the permalink).\n* [locals](https://github.com/active9/Locals): `{object}` (optional) Additional data to use for resolving placeholder values in the structure\n\n**Examples**\n\n```js\nconsole.log(permalink('/:area/:slug/index.html', {area: 'blog', slug: 'idiomatic-permalinks'}));\n//=\u003e '/blog/idiomatic-permalinks/index.html'\nconsole.log(permalinks('/blog/:stem/index.html', 'src/about.hbs'));\n//=\u003e '/blog/about/index.html'\nconsole.log(permalinks('/archives/:stem-:num.html', 'src/foo.hbs', {num: 21}));\n//=\u003e '/archives/foo-21.html'\n```\n\n***\n\n## API\n\n### [Permalinks](index.js#L36)\n\nCreate an instance of `Permalinks` with the given `options`\n\n**Params**\n\n* `options` **{Options|String}**\n\n**Example**\n\n```js\nconst Permalinks = require('permalinks').Permalinks;\nconst permalinks = new Permalinks();\nconsole.log(permalinks.format(':stem/index.html'), {path: 'src/about.hbs'});\n//=\u003e 'about/index.html'\n```\n\n### [.parse](index.js#L84)\n\nUses [parse-filepath](https://github.com/jonschlinkert/parse-filepath) to parse the `file.path` on the given file object. This method is called by the [format](#format) method, but you can use it directly and pass the results as `locals` (the last argument) to the `.format` method if you need to override or modify any path segments.\n\n**Params**\n\n* `file` **{Object}**\n* `returns` **{Object}**\n\n**Example**\n\n```js\nconsole.log(permalinks.parse({path: 'foo/bar/baz.md'}));\n// { root: '',\n//   dir: 'foo/bar',\n//   base: 'baz.md',\n//   ext: '.md',\n//   name: 'baz',\n//   extname: '.md',\n//   basename: 'baz.md',\n//   dirname: 'foo/bar',\n//   stem: 'baz',\n//   path: 'foo/bar/baz.md',\n//   absolute: [Getter/Setter],\n//   isAbsolute: [Getter/Setter] }\n```\n\n### [.format](index.js#L119)\n\nGenerate a permalink by replacing `:prop` placeholders in the specified `structure` with data from the given `file` and `locals`.\n\n**Params**\n\n* `structure` **{String}**: Permalink structure or the name of a registered [preset](#preset).\n* `file` **{Object|String}**: File object or file path string.\n* `locals` **{Object}**: Any additional data to use for resolving placeholders.\n* `returns` **{String}**\n\n**Example**\n\n```js\nconst fp = permalinks.format('blog/:stem/index.html', {path: 'src/about.hbs'});\nconsole.log(fp);\n//=\u003e 'blog/about/index.html'\n```\n\n### [.preset](index.js#L150)\n\nDefine a permalink `preset` with the given `name` and `structure`.\n\n**Params**\n\n* `name` **{String}**: If only the name is passed,\n* `structure` **{String}**\n* `returns` **{Object}**: Returns the `Permalinks` instance for chaining\n\n**Example**\n\n```js\npermalinks.preset('blog', 'blog/:stem/index.html');\nconst url = permalinks.format('blog', {path: 'src/about.hbs'});\nconsole.log(url);\n//=\u003e 'blog/about/index.html'\n```\n\n### [.helper](index.js#L194)\n\nDefine permalink helper `name` with the given `fn`. Helpers work like any other variable on the context, but they can optionally take any number of arguments and can be nested to build up the resulting string.\n\n**Params**\n\n* `name` **{String}**: Helper name\n* `fn` **{Function}**\n* `returns` **{Object}**: Returns the Permalink instance for chaining.\n\n**Example**\n\n```js\npermalinks.helper('date', function(file, format) {\n  return moment(file.data.date).format(format);\n});\n\nconst structure1 = ':date(file, \"YYYY/MM/DD\")/:stem/index.html';\nconst file1 = permalinks.format(structure1, {\n  data: {date: '2017-01-01'},\n  path: 'src/about.tmpl'\n});\n\nconst structure2 = ':name(upper(stem))/index.html';\nconst file2 = permalinks.format(structure2, {\n  data: {date: '2017-01-01'},\n  path: 'src/about.tmpl'\n});\n\nconsole.log(file1);\n//=\u003e '2017/01/01/about/index.html'\n\nconsole.log(file2);\n//=\u003e '2017/01/01/about/index.html'\n```\n\n### [.context](index.js#L219)\n\nAdd a function for calculating the context at render time. Any number of context functions may be used, and they are called in the order in which they are defined.\n\n**Params**\n\n* `fn` **{Function}**: Function that takes the `file` being rendered and the `context` as arguments. The permalinks instance is exposed as `this` inside the function.\n* `returns` **{Object}**: Returns the instance for chaining.\n\n**Example**\n\n```js\npermalinks.context(function(file, context) {\n  context.site = { title: 'My Blog' };\n});\n\npermalinks.helper('title', function() {\n  return this.file.data.title || this.context.site.title;\n});\n```\n\n### [.normalizeFile](index.js#L331)\n\nNormalize the given `file` to be a [vinyl](https://github.com/gulpjs/vinyl) file object.\n\n**Params**\n\n* `file` **{String|Object}**: If `file` is a string, it will be converted to the `file.path` on a file object.\n* `file` **{Object}**\n* `options` **{Object}**\n* `returns` **{Object}**: Returns the normalize [vinyl](https://github.com/gulpjs/vinyl) file.\n\n**Example**\n\n```js\nconst file = permalinks.normalizeFile('foo.hbs');\nconsole.log(file);\n//=\u003e '\u003cFile \"foo.hbs\"\u003e'\n```\n\n## Docs\n\n**What is a permalink?**\n\nThe word \"permalink\" is a portmanteau for \"permanent link\". A permalink is a URL to a page, post or resource on your site that is intended to stay the same as long as the site exists.\n\nMost blogging platforms and static site generators offer some level of support or plugins for generating permalinks. To users surfing your site, a permalink represents an entire \"absolute\" URL, such as `https://my-site.com/foo/index.html`. However, you will probably only need to generate the relative path portion of the URL: `/foo/index.html`.\n\n**How are permalinks created?**\n\nA permalink is created by replacing placeholder values in a [permalink structure][] (like `:slug` in `/blog/:slug/index.html`) with actual data. This data is provided by the user in the form of [locals](https://github.com/active9/Locals), and/or created by parsing the [file path][] (of the file for which we are generating a permalink):\n\n```js\n// given this structure\n'/blog/:slug/index.html'\n\n// and this data\n{ slug: 'my-first-blog-post' }\n\n// the resulting (relative part of the) permalink would be\n'/blog/my-first-blog-post/index.html'\n```\n\nThis is covered in greater detail in the following documentation.\n\n### Structure\n\nA permalink structure is a template that determines how your permalink should look after it's rendered.\n\nStructures may contain literal strings, and/or placeholder strings like `:name`, which will be replaced with actual data when the [format](#format) method is called.\n\n**Examples**\n\nGiven a file named `10-powerful-seo-tips.md`, we can change the aesthetics or semantics of the resulting permalink simply by changing the structure. For example:\n\n```js\n'/blog/:name/'\n//=\u003e blog/10-powerful-seo-tips/\n'/blog/:name.html'\n//=\u003e blog/10-powerful-seo-tips.html\n'/blog/:name/index.html'\n//=\u003e blog/10-powerful-seo-tips/index.html\n```\n\nWith a bit more information, provided as [locals](https://github.com/active9/Locals) or from the [file](https://github.com/aconbere/node-file-utils) itself (such as `file.data`, which commonly holds parsed front-matter data), we can get much more creative:\n\nFor example, if `file.data.slug` was defined as `more-powerful-seo-tips`, we might use it like this:\n\n```js\n'/blog/:data.slug/index.html'\n//=\u003e blog/more-powerful-seo-tips/index.html\n```\n\nWe can get even more creative using [helpers](https://github.com/fshost/helpers). We might, for example:\n\n* create a helper that parses a date from front-matter, such as `2017-01-02`, into year, month and day\n* slugifies a `title`, like \"Foo Bar Baz\", to be dash-separated and all lower-case\n* adds the index of a file from an array of files\n\n```js\n'/blog/:date(data.date, \"YYYY/MM/DD\")/index.html'\n//=\u003e blog/:slugify(data.title)/index.html\n'blog/:slugify(data.title)/index.html' \n//=\u003e blog/:slugify(data.title)/index.html\n'/archives/:num.html'\n//=\u003e archives/23.html\n```\n\nYour creativity is the only limitation!\n\n#### Alternative syntax\n\nPermalinks uses [handlebars](http://www.handlebarsjs.com/) to resolve templates, which means that you can also/alternatively use handlebar's native mustache syntax for defining permalink structures:\n\n```handlebars\n/blog/{{name}}/index.html\n/blog/{{foo.bar.baz}}/index.html\n/blog/{{year}}/{{month}}/{{day}}/{{slug}}.html\n```\n\n**Why handlebars?**\n\nThere are a few reasons we decided to use Handlebars over parsing/rendering the `:params` internally:\n\n* Excellent context handling and resolution of variables. We have a lot of experience with templating, parsing and rendering. Most libraries choose the \"minimalist\" route for resolving `:prop` strings because of all the edge cases that crop up with more advanced features, and it's fast. With Handlebars, we compromise very slightly on speed (although it's still very fast) in exchange for power and reliability.\n* Helpers! You can [use helpers](#helpers) to modify any of the variables in your permalink structure. You can even register helpers from great libraries like [template-helpers](https://github.com/jonschlinkert/template-helpers) or [handlebars-helpers](https://github.com/helpers/handlebars-helpers)\n* Error handling: handlebars provides great error handling, but we especially like that handlebars allows us to choose what should happen when a missing helper is identified. We use that feature to detect dates and other similar patterns that might need to be parsed before calling other helpers (for example, let's say you define `:month/:year/:day`, but none of those variables exist. handlebars will call the `helperMissing` helper for each param, which gives you the opportunity to, for example, parse `file.data.date` into an object with those properties, and return the values instead of throwing an error)\n* Other advanced handlebars features, like subexpressions and object paths (`foo.bar.baz`)\n\n**Example using subexpressions**\n\n```js\n'/blog/{{lower (slugify data.title)}}/index.html'\n// or \n'/blog/:lower((slugify data.title))/index.html'\n```\n\nNote that `:lower(` is effectively converted to `{{lower`, thus only the outermost subexpression will result in double parentheses. This is easier to see in the following example, which has two nested subexpressions:\n\n```js\n'/blog/:foo((bar (baz \"qux\")))/index.html'\n```\n\nIf the `:param` syntax seems confusing, feel free to stick with the handlebars syntax.\n\n#### file\n\n**Type**: `{String|Object}` (optional if `locals` is passed)\n\nIf a file object or string is passed, it will be parsed using node's `path.parse()` method, and merged with locals to create the context for resolving `:props` in the structure.\n\n```js\npermalinks(structure, file, locals);\n//                     ↑\n```\n\n**File handling**\n\nFiles may be defined as a string or an object. If defined as a string, the filepath will be converted to an object and set on the `file.path` property.\n\nIn other words `'a/b/c.md'` becomes `{ path: 'a/b/c.md' }`.\n\n#### locals\n\n**Type**: (optional if `file` is passed)\n\nAdditional data to use for resolving `:props` in the structure\n\n```js\npermalinks(structure, file, locals);\n//                            ↑\n```\n\n### Context\n\nThe \"context\" is an in-memory object that is used to resolve placeholders in permalink [structures](#structures).\n\nThe context object is created dynamically before rendering each permalinks, by merging the following objects:\n\n* [file path properties](#file-path-properties):\n* [options.data](#options.data):\n* [file.data](#file.data):\n* [locals](#locals):\n\n#### locals\n\nIf a `locals` object is passed as the last argument, it will be merged onto the context to be used for resolving placeholders in the permalink [structure](#structure).\n\n```js\nconsole.log(permalinks('/blog/:name/index.:ext', 'src/about.hbs', {ext: '.html'}));\n//=\u003e '/blog/about/index.html'\n\nconsole.log(permalinks(':category/:name/index.html', 'src/about.hbs', {category: 'blog'}));\n//=\u003e 'blog/about/index.html'\n```\n\n#### file.data\n\n**Type**: `object`\n\n**Default**: `undefined`\n\nPopulate the `file.data` object with additional data to use for a specific file when rendering a permalink structure.\n\n#### options.data\n\n**Type**: `object`\n\n**Default**: `undefined`\n\nProvide additional data to use when rendering permalinks for all files.\n\n#### File path properties\n\nValues on the provided `file` object are merged onto the root of the context and are used to resolve placeholders in the permalink structure. File values can be overridden by [locals](#locals) or [helpers](#helpers).\n\n_A file does not need to be passed_, but if a file is provided with at least a `file.path` property, the path will be parsed to provide as many of the following variables on the context as possible.\n\n| **variable** | **description** | \n| --- | --- |\n| `file.base` | Gets and sets base directory. Used for created relative paths. When `null` or `undefined`, it simply proxies the `file.cwd` property. Will always be normalized and have trailing separators removed. Throws when set to any value other than non-empty strings or `null`/`undefined`. |\n| `file.path` | Gets and sets the absolute pathname string or `undefined`. This value is always normalized and trailing separators are removed. Throws when set to any value other than a string. |\n| `file.relative` | Gets the result of `path.relative(file.base, file.path)`. This is a getter and will throw if set or when `file.path` has not already been set. |\n| `file.dirname` | Gets and sets the dirname of `file.path`. Will always be normalized and have trailing separators removed. Throws when `file.dirname` is not exlicitly defined and/or `file.path` is not set. |\n| `file.basename` | Gets and sets the basename of `file.path`. Throws when `file.basename` is not exlicitly defined and/or `file.path` is not set. |\n| `file.stem` | Gets and sets stem (filename without suffix) of `file.path`. Throws when `file.stem` is not exlicitly defined and/or  `file.path` is not set. |\n| `file.name` | Alias for `file.stem`. |\n| `file.extname` | Gets and sets extname property of `file.path`. |\n| `file.ext` | Alias for `file.extname`. |\n\n**Example**\n\n```\n┌──────────────────────────────────────────────┐\n│                     file.path                    │\n┌─────────────────────┬────────────────────────┐\n│      file.dirname     │        file.basename     │\n│                       ├──────────┬─────────────┤\n│                       │ file.name │              │\n│                       │ file.stem │ file.extname │\n\" /home/user/foo/src    /   about        .tmpl      \"\n└─────────────────────┴──────────┴─────────────┘\n```\n\nA `file.relative` value can only be calculated if both `file.base` and `file.path` exist:\n\n```\n┌──────────────────────────────────────────────┐\n│                     file.path                    │\n┌─────────────────────┬────────────────────────┐\n│      file.base        │        file.relative     │\n└─────────────────────┴────────────────────────┘\n```\n\n### Presets\n\nEasily store and re-use permalink structures.\n\n_(If you're familiar with the popular blogging platform, WordPress, you might also be familiar with the built-in \"Permalinks Settings\" that WordPress offers. This feature attempts to replicate and improve on that functionality.)_\n\n**Example**\n\nCreate a `pretty` preset for automatically formatting URLs, where the [file.stem](#file-path-properties) of a blog post is used as the folder name, followed by `/index.html`:\n\n```js\nconst permalinks = new Permalinks();\npermalinks.preset('pretty', 'blog/:slugify(title)/index.html');\n\nconsole.log(permalinks.format(':pretty', 'foo/bar/baz.hbs', {title: 'Post One'}));\n//=\u003e 'blog/post-one/index.html'\nconsole.log(permalinks.format(':pretty', 'foo/bar/qux.hbs', {title: 'Post Two'}));\n//=\u003e 'blog/post-two/index.html'\n```\n\n### Helpers\n\nHelper functions can be used to resolve placeholders in permalink structures. For example:\n\n```js\n// register a helper function\npermalinks.helper('foo', function() {\n  // return the value to use to replace \"foo\"\n  return this.file.stem;\n});\n\nconst url = permalinks.format('/:foo', {path: 'about.hbs'});\nconsole.log(url);\n//=\u003e '/about'\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eHelper example\u003c/strong\u003e\u003c/summary\u003e\n\nUse a `date` helper to dynamically generate paths based on the date defined in YAML front matter of a file.\n\n```js\nvar moment = require('moment');\nvar Permalinks = require('permalinks');\nvar permalinks = new Permalinks();\n\nvar file = {\n  path: 'src/about.hbs',\n  data: {\n    date: '2017-02-14'\n  }\n};\n\n// \"file.data\" is merged onto \"this.context\" \npermalinks.helper('date', function(format) {\n  return moment(this.context.date).format(format || 'YYYY/MM/DD');\n});\n\nconsole.log(permalinks.format(':date/:stem/index.html', file));\n//=\u003e '2017/02/14/about/index.html'\n```\n\nHelpers can also take arguments:\n\n```js\nconsole.log(permalinks.format(':date(\"YYYY\")/:stem/index.html', file));\n//=\u003e '2017/about/index.html'\n```\n\u003c/details\u003e\n\nSee the [helper unit tests](test) for more examples.\n\n#### file helper\n\nA special built-in `file` helper is called on every file and then removed from the context before rendering.\n\n```js\npermalinks.helper('file', function(file, data, locals) {\n  // do stuff with file, data and locals\n});\n```\n\nYou can override this helper to modify the context or set properties on files before generating permalinks.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e`file` helper example\u003c/strong\u003e\u003c/summary\u003e\n\nUse the `file` helper to increment a value for pagination or something similar:\n\n```js\nvar file = new File({path: 'foo/bar/baz.hbs'});\nvar permalinks = new Permalinks();\nvar count = 0;\n\npermalinks.helper('file', function(file, data, locals) {\n  data.num = ++count;\n});\n\nconsole.log(permalinks.format(':num-:basename', file));\n//=\u003e '1-baz.hbs'\nconsole.log(permalinks.format(':num-:basename', file));\n//=\u003e '2-baz.hbs'\nconsole.log(permalinks.format(':num-:basename', file));\n//=\u003e '3-baz.hbs'\nconsole.log(count);\n//=\u003e 3\n```\n\n\u003c/details\u003e\n\n### Additional resources\n\nHere is some reading material if you're interested in learning more about permalinks.\n\n* [The ideal WordPress SEO URL structure](https://yoast.com/wordpress-seo-url-permalink/)\n* [A Guide To WordPress Permalinks, And Why You Should Never Use The Default Settings](https://www.elegantthemes.com/blog/tips-tricks/wordpress-permalinks)\n\n## About\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eContributing\u003c/strong\u003e\u003c/summary\u003e\n\nPull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eRunning Tests\u003c/strong\u003e\u003c/summary\u003e\n\nRunning and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:\n\n```sh\n$ npm install \u0026\u0026 npm test\n```\n\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eBuilding docs\u003c/strong\u003e\u003c/summary\u003e\n\n_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_\n\nTo generate the readme, run the following command:\n\n```sh\n$ npm install -g verbose/verb#dev verb-generate-readme \u0026\u0026 verb\n```\n\n\u003c/details\u003e\n\n### Related projects\n\nYou might also be interested in these projects:\n\n* [handlebars](https://www.npmjs.com/package/handlebars): Handlebars provides the power necessary to let you build semantic templates effectively with no frustration | [homepage](http://www.handlebarsjs.com/ \"Handlebars provides the power necessary to let you build semantic templates effectively with no frustration\")\n* [parse-filepath](https://www.npmjs.com/package/parse-filepath): Pollyfill for node.js `path.parse`, parses a filepath into an object. | [homepage](https://github.com/jonschlinkert/parse-filepath \"Pollyfill for node.js `path.parse`, parses a filepath into an object.\")\n* [vinyl](https://www.npmjs.com/package/vinyl): Virtual file format. | [homepage](https://github.com/gulpjs/vinyl#readme \"Virtual file format.\")\n\n### Author\n\n**Jon Schlinkert**\n\n* [linkedin/in/jonschlinkert](https://linkedin.com/in/jonschlinkert)\n* [github/jonschlinkert](https://github.com/jonschlinkert)\n* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)\n\n### License\n\nCopyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert).\nReleased under the [MIT License](LICENSE).\n\n***\n\n_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on January 11, 2018._","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpermalinks%2Fpermalinks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpermalinks%2Fpermalinks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpermalinks%2Fpermalinks/lists"}