Publish your own profile

Most organisations join the map the easy way — by adding your organisation through our form, with no code at all. This page is for the permaculture networks and developers who would rather publish and host their own Murmurations profiles, and keep their data entirely in their own hands.


This guide is for the regional permaculture networks and organisations who would like their data to appear on the International Permaculture CoLab map — and, because of the way it works, on any other map built on the Murmurations protocol at the same time.

The idea behind it is a simple one. You publish your data once, in an open format, and you keep hold of it. We (and anyone else) gather it onto our maps. When you change your data, every map that lists you changes too. You never end up filling in someone else’s database.


How it works, in three steps

1. Describe each organisation as a Murmurations PROFILE (a small JSON document)
2. HOST that profile at a stable URL you control
3. SUBMIT the profile's URL to the Murmurations INDEX
                        │
                        ▼
   We query the index for the tags we trust → fetch your profile → it appears on our map

There is no account to open with us, and no API key to look after. You stay the source of truth, and the index simply points back to you.

Our map gathers the profiles that carry a tag we have agreed with you. Ask us for your network’s tag — something along the lines of pc-network:permaculture-britain — and add it to your profile’s tags array. That tag is what tells our aggregator, “this profile belongs on the permaculture map.”

Do you hold data that no schema covers yet, such as soil type or average rainfall? Have a look at Extending with your own data further down. Our map renders any documented field automatically, and we are glad to help you standardise it so that other networks settle on the same approach.


Would you rather not hand-write JSON?

You do not have to write JSON or run a server to take part. There are several gentler ways in.

The simplest is our own Add your organisation form. Fill it in, and once one of our moderators has had a look, your organisation appears on the map. No code, no hosting, and nothing to maintain afterwards.

If you would rather use the wider Murmurations tooling (part of MurmurMaps), there are hosted options too:

  • One organisation — the Profile Generator. Fill in the web form at https://murmurmaps.murmurations.network/profile-generator, choose the schemas, and it both hosts the profile and submits it to the index for you. You get an editable profile you can return to and update, with no website or server of your own required.
  • A whole network at once — the Batch Importer. Upload a CSV at https://murmurmaps.murmurations.network/batch-importer and it validates, hosts and submits every row. A few rules apply:
    • Up to 1,000 rows per batch.
    • The first column must be oid — a stable, unique ID for each organisation that you never reassign, since it is how later updates are matched.
    • Column headers must match schema field names exactly. Nested fields use dot notation (geolocation.lat); arrays use bracket notation (tags[0], urls[0].url).
    • To update later, re-upload a revised CSV with the same oids.
  • Already have JSON hosted? — the Index Updater. Paste a profile URL at https://murmurmaps.murmurations.network/index-updater to submit it or refresh it.

These hosted tools are the easiest way to begin, though they do mean that someone else holds your data. Hosting the JSON on your own domain — which is what the rest of this guide covers — gives you the most control, and the strongest claim to your own organisation’s entry. Choose whichever fits your network’s capacity.


The schemas

A profile declares which schemas it follows in linked_schemas. A permaculture organisation should link both of these:

SchemaPurposeLibrary URL
organizations_schema-v1.0.0The base identity of any group, project or organisationhttps://library.murmurations.network/v2/schemas/organizations_schema-v1.0.0
permaculture_addon_schema-v1.0.0Permaculture-specific fieldshttps://library.murmurations.network/v2/schemas/permaculture_addon_schema-v1.0.0

organizations_schema-v1.0.0 fields

Required: linked_schemas, name.

FieldTypeNotes
linked_schemasarray<string>Required. List every schema you link, e.g. ["organizations_schema-v1.0.0","permaculture_addon_schema-v1.0.0"]
namestringRequired. The organisation’s name
nicknamestringShort or alternate name
primary_urlstringYour website. Strongly recommended — we use it to recognise the same organisation across networks (deduplication)
tagsarray<string>Keywords and types. Put your agreed network tag here so that we gather you
urlsarray<object>Other links: { "name": "Facebook", "url": "https://…" }
descriptionstringA paragraph about the organisation
missionstringMission or purpose
statusstring (enum)active · completed · cancelled · on_hold · in_planning
full_addressstringPostal address
country_iso_3166string (enum)Two-letter country code, e.g. GB, KE, IN
geolocationobject{ "lat": <number>, "lon": <number> }needed to appear on the map
imagestringLogo URL
header_imagestringBanner URL
imagesarray<object>More images
rssstringRSS feed URL
relationshipsarray<object>Links to other Murmurations profiles
starts_at / ends_atnumberUnix timestamps (for time-bound entities)
contact_detailsobject{ "email": "hi@org.example", "contact_form": "https://org.example/contact" }
telephonestringPhone number
geographic_scopestring (enum)local · regional · national · international
unique_idstringA stable ID you control. If present, we use it as the strongest deduplication key

permaculture_addon_schema-v1.0.0 fields

Required: linked_schemas (already covered above).

FieldTypeNotes / allowed values
org_typearray<string> (enum)Association · Network · Group · Project · Permaculture school or academy · Permaculture designers' organisation or cooperative · Other organisation
permaculture_domainsarray<string> (enum)Built Environment · Culture and Education · Health and Wellbeing · Finance and Economics · Land and Nature Stewardship · Land Tenure and Community Governance · Tools and Technology
ways_to_get_involvedarray<string> (enum)Join as a member · Volunteer · Donate · Visit us · Buy our products or services · Enrol in our activities · Other - see our website or social media for details
purposestringFree text
number_of_membersnumber
number_of_beneficiariesnumber
geographic_scope_descriptionstringFree text describing your reach
leadership_tagsarray<string>Free tags describing leadership
date_addednumberUnix timestamp

Enum values must match exactly, including the capitalisation and the apostrophe in designers'. Anything outside the list will fail validation.


A complete example profile

{
  "linked_schemas": [
    "organizations_schema-v1.0.0",
    "permaculture_addon_schema-v1.0.0"
  ],
  "name": "Permaculture Association Britain",
  "nickname": "PAB",
  "primary_url": "https://www.permaculture.org.uk",
  "description": "The membership charity supporting permaculture across Britain.",
  "tags": ["permaculture", "pc-network:permaculture-britain"],
  "urls": [
    { "name": "Forum", "url": "https://www.permaculture.org.uk/forums" }
  ],
  "country_iso_3166": "GB",
  "geolocation": { "lat": 51.5074, "lon": -0.1278 },
  "image": "https://www.permaculture.org.uk/logo.png",
  "contact_details": { "email": "hello@permaculture.org.uk" },
  "geographic_scope": "national",
  "status": "active",
  "unique_id": "pab-uk",
  "org_type": ["Association", "Network"],
  "permaculture_domains": ["Culture and Education", "Land and Nature Stewardship"],
  "ways_to_get_involved": ["Join as a member", "Volunteer", "Donate"]
}

The two linked schemas are merged into one flat object, which is why the organisation fields and the permaculture addon fields sit side by side.


Hosting the profile

Serve that JSON at a stable, publicly reachable URL that you control, with Content-Type: application/json. Any of these will do:

  • A static file on your site, such as https://your-org.example/murmurations.json
  • A CMS endpoint or serverless function that returns the JSON
  • A file generated in your CI/CD pipeline

If you publish many organisations — a network with dozens of members — give each one its own URL, for example https://your-network.example/murmurations/{slug}.json.


Submitting to the index

Tell the Murmurations Index where your profile lives. It will fetch it, validate it, and begin serving it to aggregators, ourselves included.

POST https://index.murmurations.network/v2/nodes
Content-Type: application/json
{ "profile_url": "https://your-org.example/murmurations.json" }

A successful response returns a node_id (the SHA-256 of your profile_url) and a status. You can check it at any time:

GET https://index.murmurations.network/v2/nodes/{node_id}
  • Validate first — it saves a lot of bother. You can check a profile before you host or submit anything by sending the full profile JSON (not a URL) to the validator:
  POST https://index.murmurations.network/v2/validate
  Content-Type: application/json
  { …your full profile JSON, including linked_schemas… }

It returns either success or a list of precise, field-level errors, each with a JSON Pointer such as /geolocation/lat. No hosting is required.

  • Updating. Edit your hosted JSON, then submit the same profile_url again — re-submitting is what makes the index re-fetch. This matters: the index does not poll healthy profiles on a schedule, so an edit to your file is not picked up until you re-submit. Re-submitting unchanged content does no harm, and will not bump your timestamp.
  • Removing. Take the profile offline so that its URL stops returning the JSON (a 404 is the usual way), then call DELETE https://index.murmurations.network/v2/nodes/{node_id}. The index checks the URL first and will refuse while the profile is still being served. Alternatively, set an expires field (a Unix timestamp) in the profile, and the index removes it automatically once that time passes. Either way, it then disappears from the maps that aggregate it, ours included.

You can rehearse all of this safely. There is a full test environment — https://test-index.murmurations.network/v2 for validating and submitting, and https://test-library.murmurations.network/v2 for schemas — so you can try things out without touching production. No account or API key is needed anywhere.


Code snippets

Each snippet (a) builds a profile object and (b) submits its hosted URL to the index. Adapt the profile to your own data, and host the serialised JSON at profileUrl.

TypeScript / Node

type Geo = { lat: number; lon: number };
interface PermacultureProfile {
  linked_schemas: string[];
  name: string;
  primary_url?: string;
  tags?: string[];
  country_iso_3166?: string;
  geolocation?: Geo;
  org_type?: string[];
  permaculture_domains?: string[];
  ways_to_get_involved?: string[];
  unique_id?: string;
}
const profile: PermacultureProfile = {
  linked_schemas: ["organizations_schema-v1.0.0", "permaculture_addon_schema-v1.0.0"],
  name: "Permaculture Association Britain",
  primary_url: "https://www.permaculture.org.uk",
  tags: ["permaculture", "pc-network:permaculture-britain"],
  country_iso_3166: "GB",
  geolocation: { lat: 51.5074, lon: -0.1278 },
  org_type: ["Association", "Network"],
  permaculture_domains: ["Culture and Education"],
  ways_to_get_involved: ["Join as a member", "Volunteer"],
  unique_id: "pab-uk",
};
// 1. Host JSON.stringify(profile) at this URL yourself, then:
const profileUrl = "https://www.permaculture.org.uk/murmurations.json";
// 2. Register it with the index.
const res = await fetch("https://index.murmurations.network/v2/nodes", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ profile_url: profileUrl }),
});
console.log(res.status, await res.json());

Python

import json
import requests
profile = {
    "linked_schemas": [
        "organizations_schema-v1.0.0",
        "permaculture_addon_schema-v1.0.0",
    ],
    "name": "Permaculture Association Britain",
    "primary_url": "https://www.permaculture.org.uk",
    "tags": ["permaculture", "pc-network:permaculture-britain"],
    "country_iso_3166": "GB",
    "geolocation": {"lat": 51.5074, "lon": -0.1278},
    "org_type": ["Association", "Network"],
    "permaculture_domains": ["Culture and Education"],
    "ways_to_get_involved": ["Join as a member", "Volunteer"],
    "unique_id": "pab-uk",
}
# 1. Host this JSON at a URL you control:
with open("murmurations.json", "w", encoding="utf-8") as f:
    json.dump(profile, f, ensure_ascii=False, indent=2)
profile_url = "https://www.permaculture.org.uk/murmurations.json"
# 2. Submit it to the index:
res = requests.post(
    "https://index.murmurations.network/v2/nodes",
    json={"profile_url": profile_url},
    timeout=15,
)
print(res.status_code, res.json())

PHP

<?php
$profile = [
    'linked_schemas' => [
        'organizations_schema-v1.0.0',
        'permaculture_addon_schema-v1.0.0',
    ],
    'name' => 'Permaculture Association Britain',
    'primary_url' => 'https://www.permaculture.org.uk',
    'tags' => ['permaculture', 'pc-network:permaculture-britain'],
    'country_iso_3166' => 'GB',
    'geolocation' => ['lat' => 51.5074, 'lon' => -0.1278],
    'org_type' => ['Association', 'Network'],
    'permaculture_domains' => ['Culture and Education'],
    'ways_to_get_involved' => ['Join as a member', 'Volunteer'],
    'unique_id' => 'pab-uk',
];
// 1. Host this JSON at a URL you control:
file_put_contents('murmurations.json', json_encode($profile, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$profileUrl = 'https://www.permaculture.org.uk/murmurations.json';
// 2. Submit it to the index:
$ch = curl_init('https://index.murmurations.network/v2/nodes');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode(['profile_url' => $profileUrl]),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE) . "n" . $response . "n";
curl_close($ch);

Deduplication and hand-overs

If two networks both list the same organisation, our map collapses them into a single entry and shows the most recently updated profile. It matches them on a shared primary_url (normalised) or a shared unique_id. A few things follow from that:

  • Always set primary_url, and ideally unique_id too. It is how we know two entries are the same organisation.
  • When a regional network takes over an organisation that the CoLab had been listing as a placeholder, the network simply publishes a fresher profile, and it becomes the canonical entry on its own.
  • If two networks disagree about who should list an organisation, there is no central authority to settle it. The CoLab can help you talk it through, but it cannot overrule a network. In practice the mechanism above tends to settle things: whoever the organisation itself recognises keeps its profile fresh, and the freshest profile is the one shown. If need be, an operator can also pin a particular profile as the canonical one on our own map.

Extending with your own data

Do you hold data that no existing schema covers — soil type, average rainfall, climate zone? You have three options, in order of preference.

A quick note: today’s permaculture_addon_schema-v1.0.0 has no environmental or quantitative fields at all — no soil, rainfall or climate. For data like that there is genuinely no field yet, so option 2 or 3 applies.

1. Reuse a shared schema if one already exists. Browse the library first. If the field is already standardised, just add that schema to your linked_schemas. Our map renders any documented field automatically, so nothing changes on our side.

2. Propose a new shared schema — the right long-term path. This is how average_annual_rainfall_mm becomes one agreed field rather than five incompatible ones. In short: talk to us first, since we steward the permaculture schemas and help the networks converge on shared names; then fork MurmurationsLibrary, add a draft-07 schema file under /schemas/, and open a pull request against the test branch. It goes live in the test environment when merged, and the maintainers then promote test → main for production. One thing to know: you cannot self-host a schema and point linked_schemas at your own URL. linked_schemas holds bare schema names, which the index resolves only against the central library, so a new shared field always goes through that pull request.

If you would like a worked example — taking a soil or rainfall field all the way from “no field exists” to “live on the map”, with the actual schema file and the steps to test it first — get in touch and we will walk you through it.

**3. Need a field on the map today, before any schema exists?** Because the schemas do not forbid extra properties, you can add your own fields to a profile (use lowercase, underscore-separated names, e.g. "average_annual_rainfall_mm": 850) and they will pass validation and be stored. Our detail pages already render any such field with a readable label. Treat this as a stop-gap only: these fields are not schema-validated, other aggregators that read only declared fields will ignore them, and it leans on current index behaviour that is not a guaranteed feature. Tell us which extra fields you are using and we will fast-track standardising them under option 2.

Convergence — everyone reaching for the same shared schema — is the whole point. Publish openly, link shared schemas, and your data becomes useful well beyond our one map.


Questions networks often ask

How quickly does my data show up, and how quickly do updates appear? There are two hops, and both are pull-based. First, the index only learns of a change when you re-submit your profile_url, since it does not poll healthy profiles. Second, each map re-queries the index on its own schedule — ours syncs hourly — so an update reaches our map within roughly an hour of you re-submitting. There is no global push; every aggregator pulls.

How do I remove someone, or honour a “delete my data” request? Take the profile offline and call DELETE …/v2/nodes/{node_id} (see Submitting to the index). Deletion stops new aggregation and clears the index, but it cannot retract copies a map has already pulled — those clear when each map next re-syncs. Bear that in mind before publishing people.

Can I publish people, projects or events, not just organisations? Yes. Murmurations has other schemas, such as people_schema, and our map is schema-agnostic: it will show any profile type, filterable. But personal data is sensitive. The index is fully public, so only publish a person’s name, contact details or address with their explicit consent, and publish the minimum needed. Organisation data is unrestricted; member and people data is the publishing network’s legal responsibility.

**How does the CoLab handle privacy and GDPR for the data it publishes?** We keep a GDPR policy and a consent tick-box. The CoLab only publishes the personal data of CoLab members who have actively opted in, and only the fields they agreed to. Withdrawing consent is the off-switch: when someone opts out we stop publishing them, take the profile offline, and — because the whole pipeline refreshes hourly — their data drops off the maps within about an hour. We can only speak for the slice we publish; every other network that publishes people has to run its own equivalent — a lawful basis to publish, a clear consent step, and a quick way to withdraw. Murmurations itself gives you none of that scaffolding, since the index is just public, so it has to live with the publishing network.

Will the CoLab publish my organisation without me asking? No. The CoLab only publishes an organisation’s profile when the organisation asks us to, and only when it is permaculture-related — the kind of thing this map is for. We do not scrape or list groups uninvited. (Your own network can of course publish its members under its own tag — that is between you and them.)

What of my data actually appears? On the map: a pin (which needs geolocation) with the name, locality and links. In the directory and on the organisation’s detail page: every field from your linked schemas, plus any extra fields, each with a readable label.

What if your map — or the index — disappears? Your data is portable by design. You host it, and you own it. Any Murmurations-based map can aggregate the same profiles, so you are never locked into us. That is the main reason to publish in this open format rather than fill in someone’s private database.

Who decides my network’s tag? Nobody owns the tag namespace — Murmurations tags are just free text, and we cannot (and would not want to) police them. What the CoLab does is recommend a convention, pc-network:<your-network> (for example pc-network:permaculture-britain), and keep an informal register so that two networks do not accidentally pick the same one. Ask us which tag to use before you publish. The more networks that adopt the convention, the better everyone’s data lines up. It works by collaboration, not by enforcement.

Do I need an account or an API key? No — not with us, and not with the index. Control is proven by your control of the profile’s URL.


Checklist

  • ☐ The profile links both schemas in linked_schemas
  • name is set; primary_url is set (for deduplication); geolocation is set (for the map)
  • ☐ Your network tag is in tags
  • ☐ Enum values match exactly (org_type, permaculture_domains, ways_to_get_involved, country_iso_3166, status, geographic_scope)
  • ☐ Validated via POST /v2/validate, to catch errors before going live
  • ☐ The profile is hosted at a stable public URL returning application/json
  • ☐ The profile_url is submitted to https://index.murmurations.network/v2/nodes
  • ☐ (Optional) verified via GET /v2/nodes/{node_id}