# ADR-035: Custom Slugs in Micropub ## Status Proposed ## Context Currently, StarPunk auto-generates slugs from note content (first 5 words). While this works well for most cases, users may want to specify custom slugs for: - SEO-friendly URLs - Memorable short links - Maintaining URL structure from migrated content - Creating hierarchical paths (e.g., `2024/11/my-note`) - Personal preference and control The Micropub specification supports custom slugs via the `mp-slug` property, which we should honor. ## Decision Implement custom slug support through the Micropub endpoint: 1. **Accept mp-slug**: Process the `mp-slug` property in Micropub requests 2. **Validation**: Ensure slugs are URL-safe and unique 3. **Fallback**: Auto-generate if no slug provided or if invalid 4. **Conflict Resolution**: Handle duplicate slugs gracefully 5. **Character Restrictions**: Allow only URL-safe characters Implementation approach: ```python def process_micropub_request(request_data): # Extract custom slug if provided custom_slug = request_data.get('properties', {}).get('mp-slug', [None])[0] if custom_slug: # Validate and sanitize slug = sanitize_slug(custom_slug) # Ensure uniqueness if slug_exists(slug): # Add suffix or reject based on configuration slug = make_unique(slug) else: # Fall back to auto-generation slug = generate_slug(content) return create_note(content, slug=slug) ``` ## Rationale Supporting custom slugs provides: 1. **User Control**: Authors can define meaningful URLs 2. **Standards Compliance**: Follows Micropub specification 3. **Migration Support**: Easier to preserve URLs when migrating 4. **SEO Benefits**: Human-readable URLs improve discoverability 5. **Flexibility**: Accommodates different URL strategies 6. **Backward Compatible**: Existing auto-generation continues working Validation rules: - Maximum length: 200 characters - Allowed characters: `a-z0-9-_/` - No consecutive slashes or dashes - No leading/trailing special characters - Case-insensitive uniqueness check ## Consequences ### Positive - Full Micropub compliance for slug handling - Better user experience and control - SEO-friendly URLs when desired - Easier content migration from other platforms - Maintains backward compatibility ### Negative - Additional validation complexity - Potential for user confusion with conflicts - Must handle edge cases (empty, invalid, duplicate) - Slightly more complex note creation logic ### Security Considerations 1. **Path Traversal**: Reject slugs containing `..` or absolute paths 2. **Reserved Names**: Block system routes (`api`, `admin`, `feed`, etc.) 3. **Length Limits**: Enforce maximum slug length 4. **Character Filtering**: Strip or reject dangerous characters 5. **Case Sensitivity**: Normalize to lowercase for consistency ## Alternatives Considered ### Alternative 1: No Custom Slugs - **Pros**: Simpler, no validation needed - **Cons**: Poor user experience, non-compliant with Micropub - **Rejected because**: Users expect URL control in modern CMS ### Alternative 2: Separate Slug Field in UI - **Pros**: More discoverable for web users - **Cons**: Doesn't help API users, not Micropub standard - **Rejected because**: Should follow established standards ### Alternative 3: Slugs Only via Direct API - **Pros**: Advanced feature for power users only - **Cons**: Inconsistent experience, limits adoption - **Rejected because**: Micropub clients expect this feature ### Alternative 4: Hierarchical Slugs (`/2024/11/25/my-note`) - **Pros**: Organized structure, date-based archives - **Cons**: Complex routing, harder to implement - **Rejected because**: Can add later if needed, start simple ## Implementation Plan ### Phase 1: Core Logic (2 hours) 1. Modify note creation to accept optional slug parameter 2. Implement slug validation and sanitization 3. Add uniqueness checking with conflict resolution 4. Update database schema if needed (no changes expected) ### Phase 2: Micropub Integration (1 hour) 1. Extract `mp-slug` from Micropub requests 2. Pass to note creation function 3. Handle validation errors appropriately 4. Return proper Micropub responses ### Phase 3: Testing (1 hour) 1. Test valid custom slugs 2. Test invalid characters and patterns 3. Test duplicate slug handling 4. Test with Micropub clients 5. Test auto-generation fallback ## Validation Specification ### Allowed Slug Format ```regex ^[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*$ ``` Examples: - ✅ `my-awesome-post` - ✅ `2024/11/25/daily-note` - ✅ `projects/starpunk/update-1` - ❌ `My-Post` (uppercase) - ❌ `my--post` (consecutive dashes) - ❌ `-my-post` (leading dash) - ❌ `my_post` (underscore not allowed) - ❌ `../../../etc/passwd` (path traversal) ### Reserved Slugs The following slugs are reserved and cannot be used: - System routes: `api`, `admin`, `auth`, `feed`, `static` - Special pages: `login`, `logout`, `settings` - File extensions: Slugs ending in `.xml`, `.json`, `.html` ### Conflict Resolution Strategy When a duplicate slug is detected: 1. Append `-2`, `-3`, etc. to make unique 2. Check up to `-99` before failing 3. Return error if no unique slug found in 99 attempts Example: - Request: `mp-slug=my-note` - Exists: `my-note` - Created: `my-note-2` ## API Examples ### Micropub Request with Custom Slug ```http POST /micropub Content-Type: application/json Authorization: Bearer {token} { "type": ["h-entry"], "properties": { "content": ["My awesome post content"], "mp-slug": ["my-awesome-post"] } } ``` ### Response ```http HTTP/1.1 201 Created Location: https://example.com/note/my-awesome-post ``` ### Invalid Slug Handling ```http HTTP/1.1 400 Bad Request Content-Type: application/json { "error": "invalid_request", "error_description": "Invalid slug format: 'my/../../etc/passwd'" } ``` ## Migration Notes 1. Existing notes keep their auto-generated slugs 2. No database migration required (slug field exists) 3. No breaking changes to API 4. Existing clients continue working without modification ## References - Micropub Specification: https://www.w3.org/TR/micropub/#mp-slug - URL Slug Best Practices: https://stackoverflow.com/questions/695438/safe-characters-for-friendly-url - IndieWeb Slug Examples: https://indieweb.org/slug