Every open-source project needs a LICENSE file. Yet most developers either forget to add one, copy-paste from another repo, or click through GitHub's template picker without thinking twice. The problem? A project without a license isn't "free to use" — it's actually all rights reserved by default.
Let's fix that by building a CLI tool that generates LICENSE files automatically using GitHub's public Licenses API.
Why Every Project Needs a LICENSE
Here's something that surprises many developers: publishing code on GitHub does not make it open source. Without an explicit license, copyright law applies by default. That means:
- No one can legally copy your code. Even if your repo is public, others cannot fork it, modify it, or include it in their projects without your permission.
- No one can distribute your code. Companies that audit their dependencies will flag unlicensed packages and remove them.
- Contributors have no protection. Without a license, there's no contributor agreement. If someone submits a PR to your unlicensed repo, the legal ownership of that contribution is ambiguous.
- Your project can't be used in production. Any organization with a legal team will reject dependencies that lack a license. This means your brilliant library might never get adopted.
The MIT License is just 21 lines. The Apache 2.0 License covers patent grants. The GPL ensures derivative works stay open. Each serves a different purpose, but any license is better than no license.
GitHub even warns you about this. When you create a repo without a license, the "About" sidebar conspicuously omits the license badge. That missing badge is a red flag for anyone evaluating your project.
GitHub's Licenses API
Most developers don't know this, but GitHub provides a free, public API for license templates. No authentication required.
List all available licenses
curl https://api.github.com/licenses
This returns a JSON array with license keys, names, and URLs:
[
{
"key": "mit",
"name": "MIT License",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
{
"key": "apache-2.0",
"name": "Apache License 2.0",
"spdx_id": "Apache-2.0",
"url": "https://api.github.com/licenses/apache-2.0"
}
]
Get a specific license template
curl https://api.github.com/licenses/mit
The response includes a body field with the full license text, complete with placeholder tokens like [year] and [fullname] that you can replace programmatically.
{
"key": "mit",
"name": "MIT License",
"body": "MIT License\n\nCopyright (c) [year] [fullname]\n\nPermission is hereby granted..."
}
This is the foundation of our CLI. Instead of maintaining a local library of license templates, we can fetch them on-demand from GitHub and fill in the blanks.
Building the CLI
Let's build licensegen — a Node.js CLI that fetches license templates, populates them with your details, and writes a LICENSE file to your project root.
Project setup
mkdir licensegen && cd licensegen
npm init -y
The core logic
Create index.js:
#!/usr/bin/env node
const https = require('https');
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const API_BASE = 'https://api.github.com/licenses';
function fetch(url) {
return new Promise((resolve, reject) => {
https.get(url, { headers: { 'User-Agent': 'licensegen-cli' } }, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => resolve(JSON.parse(data)));
res.on('error', reject);
});
});
}
async function listLicenses() {
const licenses = await fetch(API_BASE);
console.log('\nAvailable licenses:\n');
licenses.forEach((l) => {
console.log(` ${l.key.padEnd(20)} ${l.name}`);
});
console.log('');
}
async function generateLicense(key, fullname, year) {
const license = await fetch(`${API_BASE}/${key}`);
if (license.message === 'Not Found') {
console.error(`License "${key}" not found. Run "licensegen --list" to see options.`);
process.exit(1);
}
let body = license.body;
body = body.replace(/\[year\]/g, year || new Date().getFullYear().toString());
body = body.replace(/\[fullname\]/g, fullname);
const outputPath = path.join(process.cwd(), 'LICENSE');
fs.writeFileSync(outputPath, body);
console.log(`\n${license.name} written to ${outputPath}`);
}
Parsing arguments
Add argument handling at the bottom of index.js:
const args = process.argv.slice(2);
if (args.includes('--list') || args.includes('-l')) {
listLicenses();
} else if (args.includes('--help') || args.includes('-h')) {
console.log(`
Usage: licensegen [options]
Options:
-l, --list List available licenses
-k, --key <key> License key (e.g., mit, apache-2.0)
-n, --name <name> Copyright holder name
-y, --year <year> Copyright year (defaults to current year)
-a, --auto Auto-detect from package.json
-h, --help Show help
`);
} else {
const keyIdx = args.indexOf('--key') !== -1 ? args.indexOf('--key') : args.indexOf('-k');
const nameIdx = args.indexOf('--name') !== -1 ? args.indexOf('--name') : args.indexOf('-n');
const yearIdx = args.indexOf('--year') !== -1 ? args.indexOf('--year') : args.indexOf('-y');
const key = keyIdx !== -1 ? args[keyIdx + 1] : 'mit';
const name = nameIdx !== -1 ? args[nameIdx + 1] : null;
const year = yearIdx !== -1 ? args[yearIdx + 1] : null;
if (!name && !args.includes('--auto') && !args.includes('-a')) {
console.error('Please provide --name "Your Name" or use --auto to read from package.json');
process.exit(1);
}
if (args.includes('--auto') || args.includes('-a')) {
autoGenerate(key, year);
} else {
generateLicense(key, name, year);
}
}
Auto-Detecting from package.json
This is where the CLI gets smart. Most Node.js projects already declare a license type and author in package.json. Why make the user type it again?
async function autoGenerate(keyOverride, yearOverride) {
const pkgPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(pkgPath)) {
console.error('No package.json found. Use --key and --name instead.');
process.exit(1);
}
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
// Extract license key from package.json
const key = keyOverride || (pkg.license ? pkg.license.toLowerCase() : 'mit');
// Extract author name — handle both string and object formats
let authorName = 'Unknown';
if (typeof pkg.author === 'string') {
// "John Doe <john@example.com>" → "John Doe"
authorName = pkg.author.replace(/<.*>/, '').trim();
} else if (pkg.author && pkg.author.name) {
authorName = pkg.author.name;
}
const year = yearOverride || new Date().getFullYear().toString();
console.log(`Detected: license=${key}, author=${authorName}, year=${year}`);
await generateLicense(key, authorName, year);
}
Now running licensegen --auto inside any Node.js project will:
- Read the
licensefield frompackage.json(e.g.,"MIT"becomes the API key"mit") - Parse the
authorfield (handling both"Jane Doe"and{ "name": "Jane Doe" }formats) - Use the current year as default
- Fetch the template and write the file
Supporting Multiple License Types
The GitHub API supports over a dozen licenses. Here are the most common ones your CLI can generate:
| Key | License | Best For |
|---|---|---|
mit |
MIT License | Maximum permissiveness, minimal restrictions |
apache-2.0 |
Apache License 2.0 | When you need explicit patent grants |
gpl-3.0 |
GNU GPL v3 | Ensuring derivative works stay open source |
bsd-2-clause |
BSD 2-Clause | Similar to MIT but with different heritage |
mpl-2.0 |
Mozilla Public License 2.0 | File-level copyleft (a middle ground) |
lgpl-2.1 |
GNU LGPL v2.1 | Libraries that can be used in proprietary software |
unlicense |
The Unlicense | Dedicating work to the public domain |
To support multi-license projects (some repos include both MIT and Apache-2.0), you can extend the CLI:
async function generateMultiple(keys, fullname, year) {
for (const key of keys) {
const license = await fetch(`${API_BASE}/${key}`);
let body = license.body;
body = body.replace(/\[year\]/g, year || new Date().getFullYear().toString());
body = body.replace(/\[fullname\]/g, fullname);
const filename = keys.length > 1 ? `LICENSE-${key.toUpperCase()}` : 'LICENSE';
const outputPath = path.join(process.cwd(), filename);
fs.writeFileSync(outputPath, body);
console.log(`${license.name} written to ${outputPath}`);
}
}
// Usage: licensegen --key mit --key apache-2.0 --name "Your Name"
This creates LICENSE-MIT and LICENSE-APACHE-2.0 files — a pattern used by many Rust ecosystem projects.
Making It Global
Add the bin field to your package.json:
{
"name": "licensegen",
"version": "1.0.0",
"bin": {
"licensegen": "./index.js"
}
}
Then install globally:
npm link
# or publish
npm publish
Now you can run licensegen --auto in any project directory.
What We Built
In under 100 lines of code, we built a CLI that:
- Fetches real license templates from GitHub's API (always up-to-date)
- Auto-detects license type and author from
package.json - Supports every license GitHub offers
- Handles multi-license projects
- Requires zero configuration for most use cases
The broader takeaway: GitHub's API is full of useful endpoints that most developers never explore. The Licenses API is just one example. Before building something from scratch, check if there's an API that does the heavy lifting for you.
Next time you npm init a project, don't forget the LICENSE file. Better yet, automate it so you never have to think about it again.
If you found this useful, check out licensegen on npm — a production-ready version of what we built here.
Top comments (0)