Create public, embeddable forms for any Directus collection with prefill support, iframe communication, and auto-resize.
A Directus module extension that transforms any collection into a public submission form that can be embedded in external websites, with full support for conditions, validations, and custom interfaces. Perfect for contact forms, newsletter signups, event registrations, and more.
- Features
- Installation
- Usage
- How It Works
- Permissions
- Security Considerations
- Development
- Demo & Examples
- Troubleshooting
- License
- Public Access: Forms are accessible without authentication
- Dynamic Form Generation: Automatically generates form fields based on collection schema
- Smart Field Mapping: Maps Directus field types to appropriate input interfaces
- Validation: Respects required fields and validation rules
- Success Feedback: Shows success message after submission
- Reset Functionality: Allows users to submit multiple entries
- Prefill Support: Populate form fields via query parameters or postMessage API
- Iframe Communication: Bidirectional messaging between form and parent page
- Auto-resize: Forms automatically resize to fit content when embedded
npm install directus-extension-embeddable-formsThen restart your Directus instance.
-
Copy this extension to your Directus extensions folder:
cp -r directus-extension-embeddable-forms /path/to/directus/extensions/
-
Install dependencies:
cd /path/to/directus/extensions/directus-extension-embeddable-forms npm install -
Build the extension:
npm run build
-
Restart your Directus instance
Access any collection form using the following URL pattern:
https://your-directus-instance.com/admin/forms/COLLECTION_NAME
For a contact_requests collection:
<iframe src="https://your-directus-instance.com/admin/forms/contact_requests"></iframe>The extension supports two methods for prefilling form fields: query parameters and postMessage API.
Prefill fields by adding query parameters to the iframe URL. Supports two formats:
Bracket Notation:
<iframe src="https://your-directus.com/admin/forms/newsletter_signups?prefill[email]=user@example.com&prefill[source]=homepage"></iframe>Dot Notation:
<iframe src="https://your-directus.com/admin/forms/newsletter_signups?prefill.email=user@example.com&prefill.source=homepage"></iframe>Use Cases:
- Simple prefills (email, name, referral codes)
- Shareable URLs with prefilled data
- Static embeds without JavaScript
- Marketing attribution (UTM parameters)
Limitations:
- URL length limits (~2000 characters typically)
- All data visible in URL
- Not ideal for sensitive information
Send prefill data from the parent page after the form is ready:
// Listen for the form ready event
window.addEventListener('message', (event) => {
if (event.data.type === 'directus-form-ready') {
const iframe = document.getElementById('my-form');
// Send prefill data to the iframe
iframe.contentWindow.postMessage({
type: 'directus-form-prefill',
data: {
email: 'user@example.com',
name: 'John Doe',
source: 'referral',
utm_campaign: 'spring_2024'
}
}, 'https://your-directus-instance.com');
}
});Use Cases:
- Complex nested objects
- Dynamic data based on user session
- Sensitive information (not in URL)
- Marketing attribution with analytics
- Authenticated user context
Combine both methods for maximum flexibility. PostMessage values override query parameters:
<!-- Static data via query params -->
<iframe
id="event-form"
src="https://your-directus.com/admin/forms/event_registrations?prefill[event_id]=workshop-2024&prefill[ticket_type]=standard">
</iframe>
<script>
// Dynamic data via postMessage
window.addEventListener('message', (event) => {
if (event.data.type === 'directus-form-ready') {
const iframe = document.getElementById('event-form');
iframe.contentWindow.postMessage({
type: 'directus-form-prefill',
data: {
attendee_name: 'Jane Smith',
attendee_email: 'jane@example.com',
dietary_restrictions: 'Vegetarian'
}
}, 'https://your-directus-instance.com');
}
});
</script>Query parameter strings are automatically converted to appropriate field types:
- Integers/Floats:
"5"→5,"3.14"→3.14 - Booleans:
"true","1","yes"→true - JSON:
"{\"key\":\"value\"}"→{key: "value"} - CSV:
"tag1,tag2,tag3"→["tag1", "tag2", "tag3"]
PostMessage data is used as-is with proper types already applied.
The form communicates with the parent page through postMessage events:
| Event | When | Data |
|---|---|---|
directus-form-ready |
Form is loaded and ready | { collection } |
directus-form-resize |
Form height changes | { height } |
directus-form-submitted |
Form successfully submitted | { data, collection } |
directus-form-error |
Form submission failed | { error, collection } |
directus-form-prefill-applied |
Prefill data was applied | { source, fields, collection } |
| Event | Purpose | Data |
|---|---|---|
directus-form-prefill |
Prefill form fields | { field1: value1, field2: value2, ... } |
Complete Example:
const iframe = document.getElementById('my-form');
// Listen for all form events
window.addEventListener('message', (event) => {
// Verify origin for security
if (event.origin !== 'https://your-directus-instance.com') return;
switch (event.data.type) {
case 'directus-form-ready':
console.log('Form ready for collection:', event.data.data.collection);
// Send prefill data
iframe.contentWindow.postMessage({
type: 'directus-form-prefill',
data: { email: 'user@example.com' }
}, event.origin);
break;
case 'directus-form-resize':
// Auto-resize iframe
iframe.style.height = event.data.data.height + 'px';
break;
case 'directus-form-submitted':
console.log('Form submitted successfully:', event.data.data.data);
// Show thank you message, redirect, etc.
break;
case 'directus-form-error':
console.error('Form error:', event.data.data.error);
// Show error message to user
break;
case 'directus-form-prefill-applied':
console.log('Prefilled fields:', event.data.data.fields);
break;
}
});- Route Registration: The extension registers a public route at
/forms/:collection - Schema Fetching: When accessed, it fetches the collection's field schema
- Form Rendering: Dynamically renders form fields based on the schema
- Prefill Processing: Applies query parameters and listens for postMessage prefill data
- Field Filtering: Automatically excludes system fields (id, dates, user tracking)
- Field Validation: Only accepts prefill data for fields that exist in the collection
- Type Conversion: Converts string values to appropriate field types
- Submission: Creates a new item in the specified collection via the API
- Feedback: Shows success or error messages and notifies parent page
- Auto-resize: Continuously monitors and communicates height changes to parent
The following fields are automatically excluded from forms:
iduser_createduser_updateddate_createddate_updatedsort- Any field marked as
hiddenorreadonly
The extension automatically maps Directus field types to appropriate interfaces:
- string → input
- text → input-multiline (or input-rich-text-md if specified)
- integer/float/decimal → input (numeric)
- boolean → boolean toggle
- datetime/date/time → datetime picker
- json → code editor
- csv → tags
The extension validates all prefill data to ensure security:
- Field Existence: Only fields that exist in the collection schema are accepted
- Type Safety: Values are converted to appropriate types based on field definitions
- No System Fields: System fields (id, user_created, etc.) cannot be prefilled
- Read-only Fields: Fields marked as readonly are excluded from prefill
When implementing postMessage communication, always verify the origin:
window.addEventListener('message', (event) => {
// Verify the origin matches your Directus instance
if (event.origin !== 'https://your-directus-instance.com') {
console.warn('Ignoring message from unknown origin:', event.origin);
return;
}
// Process the message
});- Query parameters are sanitized and type-checked before being applied
- Invalid field names are logged and ignored
- Malformed JSON in query parameters is caught and the original string is used
Since forms are public, ensure your Directus permissions allow public role to:
- Create items in the target collection
- Read field definitions (required for schema fetching)
Important: The public role should NOT have read access to items in the collection unless you want users to see existing submissions.
For a typical contact form, configure these permissions for the Public role:
-
System Collections (Read):
directus_collections- All Fieldsdirectus_fields- All Fieldsdirectus_relations- All Fields
-
Target Collection (e.g.,
contact_requests):- Create - All Fields
- Read - Selected fields only, with impossible filter:
{ "_and": [{ "id": { "_null": true }}]}
This allows the form to access schema information while preventing data exposure.
npm run devnpm run buildnpm run link- Directus ^11.0.0
- Node.js >=18.0.0
Access the live demo at /dirserve/index.html for:
- Live working examples of all prefill methods
- Multiple real-world use cases
- Complete implementation patterns
- Marketing attribution examples
- Copy-paste ready code snippets
- Check field names: Ensure field names in prefill data match exactly with collection schema
- Check browser console: Look for validation warnings about invalid fields
- Verify collection hydration: Wait for
directus-form-readyevent before sending postMessage - Check permissions: Ensure public role can create items in the target collection
The form automatically resizes, but ensure your iframe styling allows height changes:
iframe {
width: 100%;
border: none;
/* Don't set a fixed height */
}- Verify origin: Check that the origin in postMessage matches your Directus URL
- Wait for ready event: Only send prefill data after receiving
directus-form-ready - Check for errors: Look in browser console for blocked messages
MIT